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 01/27] =?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 : "商品创建应该成功"; + } +} From 197afab5ca0a4b884d1d261e8c2e6aba69013329 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 3 Sep 2025 19:25:58 +0800 Subject: [PATCH 02/27] =?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 --- .../supermarket/service/impl/MallProductServiceImpl.java | 2 ++ 1 file changed, 2 insertions(+) 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 210395034..78cf5c2a1 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 @@ -2,6 +2,8 @@ package com.bsd.pjcloud.supermarket.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.supermarket.entity.MallProductEntity; import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; From d747ad5241ad74d202cb8c952295994a8e316ed2 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Thu, 4 Sep 2025 19:56:49 +0800 Subject: [PATCH 03/27] =?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 --- .../common/core/exception/ErrorCodes.java | 2 + .../supermarket/constant/MallConstants.java | 200 +++++++ .../controller/MallAppController.java | 534 ++++++++++++++++++ .../MallMerchantUserController.java | 261 +++++++++ .../controller/MallProductController.java | 60 +- .../controller/SelectOptionController.java | 1 + .../supermarket/dto/MallProductCreateDTO.java | 2 + .../dto/MallProductSkuCreateDTO.java | 1 + .../supermarket/dto/MerchantUserBindDTO.java | 29 + .../dto/MerchantUserSingleBindDTO.java | 34 ++ .../supermarket/dto/SelectOptionDTO.java | 1 + .../entity/MallMerchantUserEntity.java | 108 ++++ .../exception/BusinessException.java | 11 + .../mapper/MallMerchantUserMapper.java | 10 + .../mapper/MallProductSkuMapper.java | 11 +- .../service/MallMerchantUserService.java | 79 +++ .../service/MallProductSkuService.java | 14 + .../impl/MallMerchantUserServiceImpl.java | 147 +++++ .../service/impl/MallProductServiceImpl.java | 156 ++++- .../impl/MallProductSkuServiceImpl.java | 46 ++ .../vo/app/MallProductDetailAppVo.java | 127 +++++ .../vo/app/MallProductListAppVo.java | 107 ++++ .../vo/app/MallProductSkuAppVo.java | 83 +++ .../service/MallProductServiceTest.java | 1 + .../admin/api/feign/RemoteRoleService.java | 13 + .../admin/api/feign/RemoteUserService.java | 10 + .../pjcloud/admin/api/vo/UserSimpleVo.java | 12 + .../admin/controller/SysRoleController.java | 16 + .../admin/controller/SysUserController.java | 32 ++ 29 files changed, 2038 insertions(+), 70 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/exception/BusinessException.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java create mode 100644 pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/UserSimpleVo.java diff --git a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/exception/ErrorCodes.java b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/exception/ErrorCodes.java index c2603dd21..71a4a027f 100644 --- a/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/exception/ErrorCodes.java +++ b/pjcloud-common/pjcloud-common-core/src/main/java/com/bsd/pjcloud/common/core/exception/ErrorCodes.java @@ -110,4 +110,6 @@ public interface ErrorCodes { */ String APP_USER_USERINFO_EMPTY = "app.user.userInfo.empty"; + + } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java new file mode 100644 index 000000000..9dfda84fd --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java @@ -0,0 +1,200 @@ +package com.bsd.pjcloud.supermarket.constant; + +/** + * 商城相关常量 + * + * @author zjf + * @date 2025-01-27 + */ +public class MallConstants { + + /** + * 角色编码常量 + */ + public static class RoleCode { + /** 商户用户角色 */ + public static final String MERCHANT_USER = "MALL_MCH"; + + /** 商城用户角色 */ +// public static final String MALL_USER = "Mall_USER"; + /** 商城管理员角色 */ +// public static final String MALL_ADMIN = "Mall_ADMIN"; + } + + /** + * 商户用户角色类型 + */ + public static class MerchantUserRoleType { + /** 管理员 */ + public static final Integer ADMIN = 1; + /** 普通用户 */ + public static final Integer USER = 2; + } + + /** + * 状态常量 + */ + public static class Status { + /** 启用 */ + public static final Integer ENABLED = 1; + /** 禁用 */ + public static final Integer DISABLED = 0; + } + + /** + * 商品相关常量 + */ + public static class Product { + /** 商品状态 - 上架 */ + public static final Integer STATUS_ON_SALE = 1; + /** 商品状态 - 下架 */ + public static final Integer STATUS_OFF_SALE = 0; + /** 商品状态 - 草稿 */ + public static final Integer STATUS_DRAFT = 2; + + /** 价格类型 - 统一价格 */ + public static final Integer PRICE_TYPE_UNIFIED = 1; + /** 价格类型 - SKU价格 */ + public static final Integer PRICE_TYPE_SKU = 2; + } + + /** + * 分类相关常量 + */ + public static class Category { + /** 分类状态 - 启用 */ + public static final Integer STATUS_ENABLED = 0; + /** 分类状态 - 禁用 */ + public static final Integer STATUS_DISABLED = 1; + } + + /** + * 商户相关常量 + */ + public static class Merchant { + /** 商户状态 - 正常 */ + public static final Integer STATUS_NORMAL = 1; + /** 商户状态 - 停用 */ + public static final Integer STATUS_DISABLED = 0; + /** 商户状态 - 审核中 */ + public static final Integer STATUS_PENDING = 2; + } + + /** + * 库存相关常量 + */ + public static class Stock { + /** 库存操作 - 增加 */ + public static final String OPERATION_ADD = "ADD"; + /** 库存操作 - 减少 */ + public static final String OPERATION_REDUCE = "REDUCE"; + /** 库存操作 - 设置 */ + public static final String OPERATION_SET = "SET"; + } + + /** + * 订单相关常量 + */ + public static class Order { + /** 订单状态 - 待支付 */ + public static final Integer STATUS_PENDING_PAYMENT = 1; + /** 订单状态 - 已支付 */ + public static final Integer STATUS_PAID = 2; + /** 订单状态 - 已发货 */ + public static final Integer STATUS_SHIPPED = 3; + /** 订单状态 - 已完成 */ + public static final Integer STATUS_COMPLETED = 4; + /** 订单状态 - 已取消 */ + public static final Integer STATUS_CANCELLED = 5; + /** 订单状态 - 已退款 */ + public static final Integer STATUS_REFUNDED = 6; + } + + /** + * 权限相关常量 + */ + public static class Permission { + /** 商品管理权限前缀 */ + public static final String PRODUCT_PREFIX = "supermarket_mallProduct_"; + /** 商户管理权限前缀 */ + public static final String MERCHANT_PREFIX = "supermarket_mallMerchant_"; + /** 分类管理权限前缀 */ + public static final String CATEGORY_PREFIX = "supermarket_mallCategory_"; + /** 商户用户绑定权限前缀 */ + public static final String MERCHANT_USER_PREFIX = "supermarket_mallMerchantUser_"; + + /** 查看权限 */ + public static final String VIEW = "view"; + /** 新增权限 */ + public static final String ADD = "add"; + /** 编辑权限 */ + public static final String EDIT = "edit"; + /** 删除权限 */ + public static final String DEL = "del"; + /** 导出权限 */ + public static final String EXPORT = "export"; + } + + /** + * 缓存相关常量 + */ + public static class Cache { + /** 商品缓存前缀 */ + public static final String PRODUCT_PREFIX = "mall:product:"; + /** 商户缓存前缀 */ + public static final String MERCHANT_PREFIX = "mall:merchant:"; + /** 分类缓存前缀 */ + public static final String CATEGORY_PREFIX = "mall:category:"; + /** 用户商户绑定缓存前缀 */ + public static final String MERCHANT_USER_PREFIX = "mall:merchant_user:"; + + /** 缓存过期时间 - 1小时 */ + public static final long EXPIRE_1_HOUR = 3600; + /** 缓存过期时间 - 1天 */ + public static final long EXPIRE_1_DAY = 86400; + /** 缓存过期时间 - 1周 */ + public static final long EXPIRE_1_WEEK = 604800; + } + + /** + * 分页相关常量 + */ + public static class Page { + /** 默认页码 */ + public static final Integer DEFAULT_PAGE_NUM = 1; + /** 默认页大小 */ + public static final Integer DEFAULT_PAGE_SIZE = 10; + /** 最大页大小 */ + public static final Integer MAX_PAGE_SIZE = 100; + } + + /** + * 文件上传相关常量 + */ + public static class File { + /** 图片文件类型 */ + public static final String[] IMAGE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "webp"}; + /** 最大文件大小 - 5MB */ + public static final long MAX_FILE_SIZE = 5 * 1024 * 1024; + /** 商品图片路径 */ + public static final String PRODUCT_IMAGE_PATH = "/mall/product/"; + /** 商户图片路径 */ + public static final String MERCHANT_IMAGE_PATH = "/mall/merchant/"; + } + + /** + * 业务异常相关常量 + */ + public static class ErrorCode { + /** 用户未绑定商户 */ + public static final String USER_NOT_BOUND_MERCHANT = "USER_NOT_BOUND_MERCHANT"; + /** 商户不存在 */ + public static final String MERCHANT_NOT_EXISTS = "MERCHANT_NOT_EXISTS"; + /** 商品不存在 */ + public static final String PRODUCT_NOT_EXISTS = "PRODUCT_NOT_EXISTS"; + /** 库存不足 */ + public static final String INSUFFICIENT_STOCK = "INSUFFICIENT_STOCK"; + /** 权限不足 */ + public static final String INSUFFICIENT_PERMISSION = "INSUFFICIENT_PERMISSION"; + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java new file mode 100644 index 000000000..1c8f577f3 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java @@ -0,0 +1,534 @@ +package com.bsd.pjcloud.supermarket.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.admin.api.entity.SysRole; +import com.bsd.pjcloud.admin.api.feign.RemoteRoleService; +import com.bsd.pjcloud.common.core.util.BeanUtils; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.security.util.SecurityUtils; +import com.bsd.pjcloud.supermarket.dto.MallProductCreateDTO; +import com.bsd.pjcloud.supermarket.entity.MallCategoryEntity; +import com.bsd.pjcloud.supermarket.entity.MallMerchantEntity; +import com.bsd.pjcloud.supermarket.entity.MallProductEntity; +import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; +import com.bsd.pjcloud.supermarket.exception.BusinessException; +import com.bsd.pjcloud.supermarket.service.MallCategoryService; +import com.bsd.pjcloud.supermarket.service.MallMerchantService; +import com.bsd.pjcloud.supermarket.service.MallMerchantUserService; +import com.bsd.pjcloud.supermarket.service.MallProductService; +import com.bsd.pjcloud.supermarket.service.MallProductSkuService; +import com.bsd.pjcloud.supermarket.vo.mall.MallCategoryTreeVo; +import com.bsd.pjcloud.supermarket.vo.app.MallProductListAppVo; +import com.bsd.pjcloud.supermarket.vo.app.MallProductDetailAppVo; +import com.bsd.pjcloud.supermarket.vo.app.MallProductSkuAppVo; +import com.bsd.pjcloud.supermarket.dto.SkuStockChangeDTO; +import com.bsd.pjcloud.supermarket.constant.MallConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 移动端商城接口 + * + * @author zjf + * @date 2025-01-27 + */ +@RestController +@RequestMapping("/app/mall") +@Tag(name = "移动端商城", description = "移动端商城接口") +@RequiredArgsConstructor +public class MallAppController { + + private final MallCategoryService mallCategoryService; + private final MallMerchantService mallMerchantService; + private final MallMerchantUserService mallMerchantUserService; + private final MallProductService mallProductService; + private final MallProductSkuService mallProductSkuService; + private final RemoteRoleService remoteRoleService; + + // ==================== 分类相关接口 ==================== + + /** + * 获取分类树结构 + */ + @GetMapping("/category/tree") + @Operation(summary = "获取分类树", description = "获取所有分类的树结构") + public R> getCategoryTree() { + List all = mallCategoryService.list(Wrappers.lambdaQuery() + .eq(MallCategoryEntity::getStatus, 1)); // 只查启用状态 + List tree = buildCategoryTree(all, 0L); + return R.ok(tree); + } + + /** + * 根据父级ID获取子分类 + */ + @GetMapping("/category/listByParent/{parentId}") + @Operation(summary = "根据父级ID获取子分类", description = "根据父级ID获取子分类列表") + public R> getCategoryListByParent(@PathVariable("parentId") Long parentId) { + List list = mallCategoryService.list( + Wrappers.lambdaQuery() + .eq(MallCategoryEntity::getParentId, parentId) + .eq(MallCategoryEntity::getStatus, 1) + ); + return R.ok(list); + } + + // ==================== 商户相关接口 ==================== + + /** + * 获取商户列表 + */ + @GetMapping("/merchant/list") + @Operation(summary = "获取商户列表", description = "获取所有启用的商户列表") + public R> getMerchantList() { + List list = mallMerchantService.list( + Wrappers.lambdaQuery() + .eq(MallMerchantEntity::getStatus, 1) + .orderByAsc(MallMerchantEntity::getName) + ); + return R.ok(list); + } + + /** + * 获取商户详情 + */ + @GetMapping("/merchant/{id}") + @Operation(summary = "获取商户详情", description = "根据ID获取商户详情") + public R getMerchantDetail(@PathVariable("id") Long id) { + MallMerchantEntity merchant = mallMerchantService.getById(id); + if (merchant == null) { + return R.failed("商户不存在"); + } + return R.ok(merchant); + } + + // ==================== 商品相关接口 ==================== + + /** + * 商品分页查询 + */ + @GetMapping("/product/page") + @Operation(summary = "商品分页查询", description = "移动端商品分页查询") + public R> getProductPage( + @ParameterObject Page page, + @RequestParam(required = false) Long categoryId, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Long merchantId + ) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(categoryId != null, MallProductEntity::getCategoryId, categoryId); + wrapper.like(StrUtil.isNotBlank(keyword), MallProductEntity::getTitle, keyword); + wrapper.eq(merchantId != null, MallProductEntity::getMerchantId, merchantId); + wrapper.eq(MallProductEntity::getStatus, 1); // 只查上架商品 + + IPage productPage = mallProductService.page(page, wrapper); + + // 获取商品ID列表 + List productIds = productPage.getRecords().stream() + .map(MallProductEntity::getId) + .collect(Collectors.toList()); + + // 查询SKU信息 + List skuList = productIds.isEmpty() ? List.of() : + mallProductSkuService.list( + Wrappers.lambdaQuery() + .in(MallProductSkuEntity::getProductId, productIds) + ); + + // 按商品ID分组SKU + Map> skuMap = skuList.stream() + .collect(Collectors.groupingBy(MallProductSkuEntity::getProductId)); + + // 转换为VO + IPage voPage = productPage.convert(product -> { + MallProductListAppVo vo = BeanUtils.toBean(product, MallProductListAppVo.class); + List skus = skuMap.getOrDefault(product.getId(), List.of()); + + // 计算最低价格 + vo.setMinPrice(skus.stream() + .map(MallProductSkuEntity::getPrice) + .filter(Objects::nonNull) + .min(java.math.BigDecimal::compareTo) + .orElse(java.math.BigDecimal.ZERO)); + + // 计算总库存 + vo.setTotalStock(skus.stream() + .map(MallProductSkuEntity::getStock) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum()); + + return vo; + }); + + return R.ok(voPage); + } + + /** + * 商品详情 + */ + @GetMapping("/product/{id}") + @Operation(summary = "商品详情", description = "根据ID获取商品详情") + public R getProductDetail(@PathVariable("id") Long id) { + MallProductEntity product = mallProductService.getById(id); + if (product == null) { + return R.failed("商品不存在"); + } + + MallProductDetailAppVo vo = BeanUtils.toBean(product, MallProductDetailAppVo.class); + + // 查询SKU列表 + List skuList = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, id) + ); + + vo.setSkuList(skuList.stream() + .map(sku -> BeanUtils.toBean(sku, MallProductSkuAppVo.class)) + .collect(Collectors.toList())); + + return R.ok(vo); + } + + /** + * 批量获取商品详情 + */ + @GetMapping("/product/batch") + @Operation(summary = "批量获取商品详情", description = "根据商品ID列表批量获取商品详情") + public R> getProductBatch(@RequestParam List ids) { + List products = mallProductService.listByIds(ids); + List result = new java.util.ArrayList<>(); + + for (MallProductEntity product : products) { + MallProductDetailAppVo vo = BeanUtils.toBean(product, MallProductDetailAppVo.class); + + // 查询SKU列表 + List skuList = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, product.getId()) + ); + + vo.setSkuList(skuList.stream() + .map(sku -> BeanUtils.toBean(sku, MallProductSkuAppVo.class)) + .collect(Collectors.toList())); + + result.add(vo); + } + + return R.ok(result); + } + + // ==================== 商户商品管理接口 ==================== + + /** + * 商户创建商品 + */ + @PostMapping("/merchant/product/create") + @Operation(summary = "商户创建商品", description = "移动端商户创建商品,支持包含SKU") + public R createProduct(@RequestBody MallProductCreateDTO productCreateDTO) { + try { + // 获取当前用户绑定的商户ID + Long currentUserId = SecurityUtils.getUser().getId(); + Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); + + if (userMerchantId == null) { + return R.failed("用户未绑定商户"); + } + + // 自动设置商户ID + productCreateDTO.setMerchantId(userMerchantId); + + // 验证商户权限和状态 + validateMerchant(userMerchantId); + + // 创建商品 + boolean success = mallProductService.createProductWithSkus(productCreateDTO); + if (success) { + return R.ok("商品创建成功"); + } else { + return R.failed("商品创建失败"); + } + } catch (Exception e) { + return R.failed("商品创建失败:" + e.getMessage()); + } + } + + /** + * 商户更新商品 + */ + @PutMapping("/merchant/product/update") + @Operation(summary = "商户更新商品", description = "移动端商户更新商品,支持包含SKU") + public R updateProduct(@RequestBody MallProductCreateDTO productCreateDTO) { + try { + // 获取当前用户绑定的商户ID + Long currentUserId = SecurityUtils.getUser().getId(); + + Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); + + if (userMerchantId == null) { + return R.failed("用户未绑定商户"); + } + + // 验证商品ID + if (productCreateDTO.getId() == null) { + return R.failed("商品ID不能为空"); + } + + // 验证商品是否存在 + MallProductEntity existingProduct = mallProductService.getById(productCreateDTO.getId()); + if (existingProduct == null) { + return R.failed("商品不存在"); + } + + // 验证商户权限(只能更新自己的商品) + if (!userMerchantId.equals(existingProduct.getMerchantId())) { + return R.failed("无权限更新此商品"); + } + + // 自动设置商户ID + productCreateDTO.setMerchantId(userMerchantId); + + // 验证商户权限和状态 + validateMerchant(userMerchantId); + + // 更新商品 + boolean success = mallProductService.updateProductWithSkus(productCreateDTO); + if (success) { + return R.ok("商品更新成功"); + } else { + return R.failed("商品更新失败"); + } + } catch (Exception e) { + return R.failed("商品更新失败:" + e.getMessage()); + } + } + + private void validateMerchant(Long merchantId) { + // 获取当前用户ID + Long currentUserId = SecurityUtils.getUser().getId(); + if (currentUserId == null) { + throw new BusinessException("用户未登录"); + } + + // 验证用户是否有商户角色 + List roleIds = SecurityUtils.getRoleIds(); + R> mallMchUserLists = remoteRoleService.findRolesByCodes(Arrays.asList(MallConstants.RoleCode.MERCHANT_USER)); + if (mallMchUserLists.isOk()) { + List list = mallMchUserLists.getData(); + if (CollectionUtils.isEmpty(list) || CollUtil.isEmpty(roleIds) || !roleIds.contains(list.get(0).getRoleId())) { + throw new BusinessException("请分配商户角色"); + } + } else { + throw new BusinessException("基础用户服务异常"); + } + + // 验证用户是否绑定到指定商户 + if (merchantId != null) { + boolean hasPermission = mallMerchantUserService.hasPermission(currentUserId, merchantId); + if (!hasPermission) { + throw new BusinessException("无权限操作此商户的商品"); + } + } else { + // 如果没有指定商户ID,获取用户绑定的商户ID + Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); + if (userMerchantId == null) { + throw new BusinessException("用户未绑定商户"); + } + } + + // 验证商户是否存在和状态 + if (merchantId != null) { + MallMerchantEntity merchant = mallMerchantService.getById(merchantId); + if (merchant == null) { + throw new BusinessException("商户不存在"); + } + if (!MallConstants.Status.ENABLED.equals(merchant.getStatus())) { + throw new BusinessException("商户状态异常,无法操作商品"); + } + } + } + /** + * 商户获取自己的商品列表 + */ + @GetMapping("/merchant/product/list") + @Operation(summary = "商户商品列表", description = "移动端商户获取自己的商品列表") + public R> getMerchantProductList( + @ParameterObject Page page, + @RequestParam(required = false) Long merchantId, + @RequestParam(required = false) Long categoryId, + @RequestParam(required = false) String keyword, + @RequestParam(required = false) Integer status + ) { + try { + // 获取当前用户绑定的商户ID + Long currentUserId = SecurityUtils.getUser().getId(); + Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); + + if (userMerchantId == null) { + return R.failed("用户未绑定商户"); + } + + // 如果指定了商户ID,验证权限 + if (merchantId != null && !merchantId.equals(userMerchantId)) { + return R.failed("无权限查看其他商户的商品"); + } + + // 使用用户绑定的商户ID + Long targetMerchantId = merchantId != null ? merchantId : userMerchantId; + + // 验证商户是否存在 + MallMerchantEntity merchant = mallMerchantService.getById(targetMerchantId); + if (merchant == null) { + return R.failed("商户不存在"); + } + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallProductEntity::getMerchantId, targetMerchantId); + wrapper.eq(categoryId != null, MallProductEntity::getCategoryId, categoryId); + wrapper.like(StrUtil.isNotBlank(keyword), MallProductEntity::getTitle, keyword); + wrapper.eq(status != null, MallProductEntity::getStatus, status); + + IPage productPage = mallProductService.page(page, wrapper); + + // 获取商品ID列表 + List productIds = productPage.getRecords().stream() + .map(MallProductEntity::getId) + .collect(Collectors.toList()); + + // 查询SKU信息 + List skuList = productIds.isEmpty() ? List.of() : + mallProductSkuService.list( + Wrappers.lambdaQuery() + .in(MallProductSkuEntity::getProductId, productIds) + ); + + // 按商品ID分组SKU + Map> skuMap = skuList.stream() + .collect(Collectors.groupingBy(MallProductSkuEntity::getProductId)); + + // 转换为VO + IPage voPage = productPage.convert(product -> { + MallProductListAppVo vo = BeanUtils.toBean(product, MallProductListAppVo.class); + List skus = skuMap.getOrDefault(product.getId(), List.of()); + + // 计算最低价格 + vo.setMinPrice(skus.stream() + .map(MallProductSkuEntity::getPrice) + .filter(Objects::nonNull) + .min(java.math.BigDecimal::compareTo) + .orElse(java.math.BigDecimal.ZERO)); + + // 计算总库存 + vo.setTotalStock(skus.stream() + .map(MallProductSkuEntity::getStock) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum()); + + return vo; + }); + + return R.ok(voPage); + } catch (Exception e) { + return R.failed("获取商品列表失败:" + e.getMessage()); + } + } + + /** + * 商户删除商品 + */ + @DeleteMapping("/merchant/product/{id}") + @Operation(summary = "商户删除商品", description = "移动端商户删除自己的商品") + public R deleteProduct(@PathVariable("id") Long id) { + try { + // 获取当前用户绑定的商户ID + Long currentUserId = SecurityUtils.getUser().getId(); + Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); + + if (userMerchantId == null) { + return R.failed("用户未绑定商户"); + } + + // 验证商品是否存在 + MallProductEntity product = mallProductService.getById(id); + if (product == null) { + return R.failed("商品不存在"); + } + + // 验证商户权限 + if (!userMerchantId.equals(product.getMerchantId())) { + return R.failed("无权限删除此商品"); + } + + // 删除商品(逻辑删除) + boolean success = mallProductService.removeById(id); + if (success) { + return R.ok("商品删除成功"); + } else { + return R.failed("商品删除失败"); + } + } catch (Exception e) { + return R.failed("商品删除失败:" + e.getMessage()); + } + } + + // ==================== 库存相关接口 ==================== + + /** + * 批量增减库存 + */ + @PostMapping("/stock/deduct") + @Operation(summary = "批量增减库存", description = "正加负减,任一失败全失败") + public R deductStock(@RequestBody SkuStockChangeDTO changes) { + boolean result = mallProductSkuService.deductStockBatch(changes); + return result ? R.ok(true) : R.failed("库存不足或扣减失败"); + } + + /** + * 检查商品库存 + */ + @GetMapping("/stock/check") + @Operation(summary = "检查商品库存", description = "检查指定SKU的库存是否充足") + public R checkStock(@RequestParam("skuId") Long skuId, @RequestParam("incrCount") Integer incrCount) { + MallProductSkuEntity sku = mallProductSkuService.getById(skuId); + if (sku == null) { + return R.failed("商品SKU不存在"); + } + if (sku.getStock() < Math.abs(incrCount)) { + return R.failed("库存不足"); + } + return R.ok(true); + } + + // ==================== 私有方法 ==================== + + /** + * 构建分类树 + */ + private List buildCategoryTree(List all, Long parentId) { + List result = new java.util.ArrayList<>(); + for (MallCategoryEntity cat : all) { + if (Objects.equals(cat.getParentId(), parentId)) { + MallCategoryTreeVo vo = BeanUtils.toBean(cat, MallCategoryTreeVo.class); + vo.setChildren(buildCategoryTree(all, cat.getId())); + result.add(vo); + } + } + return result; + } +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java new file mode 100644 index 000000000..cab064c28 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java @@ -0,0 +1,261 @@ +package com.bsd.pjcloud.supermarket.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.admin.api.feign.RemoteUserService; +import com.bsd.pjcloud.admin.api.vo.UserSimpleVo; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.log.annotation.SysLog; +import com.bsd.pjcloud.supermarket.dto.MerchantUserBindDTO; +import com.bsd.pjcloud.supermarket.dto.MerchantUserSingleBindDTO; +import com.bsd.pjcloud.supermarket.entity.MallMerchantUserEntity; +import com.bsd.pjcloud.supermarket.service.MallMerchantUserService; +import com.bsd.pjcloud.supermarket.constant.MallConstants; +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.springdoc.core.annotations.ParameterObject; +import org.springframework.http.HttpHeaders; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; +import java.util.List; + +/** + * 商户用户绑定管理 + * + * @author zjf + * @date 2025-01-27 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/mallMerchantUser") +@Tag(description = "mallMerchantUser", name = "商户用户绑定管理") +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class MallMerchantUserController { + + private final MallMerchantUserService mallMerchantUserService; + + private final RemoteUserService remoteUserService; + + /** + * 分页查询商户用户绑定关系 + */ + @Operation(summary = "分页查询", description = "分页查询商户用户绑定关系") + @GetMapping("/page") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')" ) + public R> getPage( + @ParameterObject Page page, + @RequestParam(required = false) Long merchantId, + @RequestParam(required = false) Long userId, + @RequestParam(required = false) Integer roleType, + @RequestParam(required = false) Integer status + ) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(merchantId != null, MallMerchantUserEntity::getMerchantId, merchantId) + .eq(userId != null, MallMerchantUserEntity::getUserId, userId) + .eq(roleType != null, MallMerchantUserEntity::getRoleType, roleType) + .eq(status != null, MallMerchantUserEntity::getStatus, status) + .orderByDesc(MallMerchantUserEntity::getCreateTime); + + Page result = mallMerchantUserService.page(page, wrapper); + return R.ok(result); + } + + /** + * 根据商户ID获取绑定的用户列表 + */ + @Operation(summary = "获取商户用户列表", description = "根据商户ID获取绑定的用户列表") + @GetMapping("/merchant/{merchantId}/users") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')" ) + public R> getMerchantUsers(@PathVariable("merchantId") Long merchantId) { + List users = mallMerchantUserService.getMerchantUsers(merchantId); + return R.ok(users); + } + + /** + * 根据用户ID获取绑定的商户信息 + */ + @Operation(summary = "获取用户商户信息", description = "根据用户ID获取绑定的商户信息") + @GetMapping("/user/{userId}/merchant") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')" ) + public R getUserMerchant(@PathVariable("userId") Long userId) { + MallMerchantUserEntity merchantUser = mallMerchantUserService.getByUserId(userId); + return R.ok(merchantUser); + } + + /** + * 单个用户绑定到商户 + */ + @Operation(summary = "单个用户绑定", description = "单个用户绑定到商户") + @PostMapping("/bind") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + @SysLog("商户用户绑定") + public R bindUser(@Valid @RequestBody MerchantUserSingleBindDTO bindDTO) { + try { + boolean success = mallMerchantUserService.bindUserToMerchant( + bindDTO.getMerchantId(), + bindDTO.getUserId(), + bindDTO.getUserName(), + bindDTO.getUserPhone(), + bindDTO.getRoleType() + ); + + if (success) { + return R.ok("用户绑定成功"); + } else { + return R.failed("用户绑定失败"); + } + } catch (Exception e) { + return R.failed("用户绑定失败:" + e.getMessage()); + } + } + + /** + * 批量绑定用户到商户 + */ + @Operation(summary = "批量绑定用户", description = "批量绑定用户到商户") + @PostMapping("/batchBind") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + @SysLog("批量商户用户绑定") + public R batchBindUsers(@Valid @RequestBody MerchantUserBindDTO bindDTO) { + try { + int successCount = mallMerchantUserService.batchBindUsersToMerchant( + bindDTO.getMerchantId(), + bindDTO.getUserIds(), + bindDTO.getRoleType() + ); + + int totalCount = bindDTO.getUserIds().size(); + if (successCount == totalCount) { + return R.ok(String.format("成功绑定%d个用户", successCount)); + } else if (successCount > 0) { + return R.ok(String.format("部分绑定成功,成功%d个,失败%d个", successCount, totalCount - successCount)); + } else { + return R.failed("批量绑定失败"); + } + } catch (Exception e) { + return R.failed("批量绑定失败:" + e.getMessage()); + } + } + + /** + * 解绑单个用户 + */ + @Operation(summary = "解绑用户", description = "解绑单个用户") + @DeleteMapping("/unbind/{userId}") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + @SysLog("商户用户解绑") + public R unbindUser(@PathVariable("userId") Long userId) { + try { + boolean success = mallMerchantUserService.unbindUserFromMerchant(userId); + if (success) { + return R.ok("用户解绑成功"); + } else { + return R.failed("用户解绑失败"); + } + } catch (Exception e) { + return R.failed("用户解绑失败:" + e.getMessage()); + } + } + + /** + * 批量解绑用户 + */ + @Operation(summary = "批量解绑用户", description = "批量解绑用户") + @DeleteMapping("/batchUnbind") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + @SysLog("批量商户用户解绑") + public R batchUnbindUsers(@RequestBody List userIds) { + try { + int successCount = mallMerchantUserService.batchUnbindUsersFromMerchant(userIds); + if (successCount > 0) { + return R.ok(String.format("成功解绑%d个用户", successCount)); + } else { + return R.failed("批量解绑失败"); + } + } catch (Exception e) { + return R.failed("批量解绑失败:" + e.getMessage()); + } + } + + /** + * 更新用户角色类型 + */ + @Operation(summary = "更新用户角色", description = "更新用户在商户中的角色类型") + @PutMapping("/updateRole") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + @SysLog("更新商户用户角色") + public R updateUserRole( + @RequestParam Long userId, + @RequestParam Integer roleType + ) { + try { + MallMerchantUserEntity merchantUser = mallMerchantUserService.getByUserId(userId); + if (merchantUser == null) { + return R.failed("用户绑定关系不存在"); + } + + merchantUser.setRoleType(roleType); + boolean success = mallMerchantUserService.updateById(merchantUser); + if (success) { + return R.ok("角色更新成功"); + } else { + return R.failed("角色更新失败"); + } + } catch (Exception e) { + return R.failed("角色更新失败:" + e.getMessage()); + } + } + + /** + * 启用/禁用用户绑定 + */ + @Operation(summary = "启用/禁用绑定", description = "启用或禁用用户绑定关系") + @PutMapping("/updateStatus") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + @SysLog("更新商户用户状态") + public R updateUserStatus( + @RequestParam Long userId, + @RequestParam Integer status + ) { + try { + MallMerchantUserEntity merchantUser = mallMerchantUserService.getByUserId(userId); + if (merchantUser == null) { + return R.failed("用户绑定关系不存在"); + } + + merchantUser.setStatus(status); + boolean success = mallMerchantUserService.updateById(merchantUser); + if (success) { + return R.ok("状态更新成功"); + } else { + return R.failed("状态更新失败"); + } + } catch (Exception e) { + return R.failed("状态更新失败:" + e.getMessage()); + } + } + + + /** + * 获取商户可绑定用户列表 + */ + @Operation(summary = "获取可绑定用户列表", description = "获取商户可绑定用户列表") + @GetMapping("/merchant/users") + @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) + public R> getMerchantBindableUsers(){ + + R> userListByRoleCode = remoteUserService.getUserListByRoleCode(MallConstants.RoleCode.MERCHANT_USER); + if (!userListByRoleCode.isOk()){ + return R.failed(userListByRoleCode.getMsg()); + } + List data = userListByRoleCode.getData(); + + return R.ok(data); + } + +} 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 4c27cd664..88e2a5c41 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 @@ -38,7 +38,7 @@ import java.util.Objects; @SecurityRequirement(name = HttpHeaders.AUTHORIZATION) public class MallProductController { - private final MallProductService mallProductService; + private final MallProductService mallProductService; /** * 分页查询 @@ -63,10 +63,10 @@ public class MallProductController { * @param withSkus 是否包含SKU信息 * @return R */ - @Operation(summary = "通过id查询" , description = "通过id查询" ) + @Operation(summary = "通过id查询" , description = "通过id查询商品信息,支持包含SKU" ) @GetMapping("/{id}" ) @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_view')" ) - public R getById(@PathVariable("id" ) Long id, @RequestParam(value = "withSkus", defaultValue = "false") Boolean withSkus) { + public R getById(@PathVariable("id" ) Long id, @RequestParam(value = "withSkus", defaultValue = "true") Boolean withSkus) { try { if (withSkus) { MallProductCreateDTO productDTO = mallProductService.getProductWithSkus(id); @@ -80,30 +80,21 @@ public class MallProductController { } /** - * 新增共富商城商品信息 - * @param requestBody 请求体(可以是MallProductEntity或MallProductCreateDTO) + * 新增商品 + * @param productCreateDTO 商品创建DTO * @return R */ - @Operation(summary = "新增共富商城商品信息" , description = "新增共富商城商品信息" ) - @SysLog("新增共富商城商品信息" ) + @Operation(summary = "新增商品" , description = "新增商品信息,支持包含SKU" ) + @SysLog("新增商品" ) @PostMapping @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_add')" ) - public R save(@Valid @RequestBody Object requestBody) { + public R save(@Valid @RequestBody MallProductCreateDTO productCreateDTO) { try { - // 判断请求体类型 - if (requestBody instanceof MallProductCreateDTO) { - // 包含SKU的创建 - MallProductCreateDTO productCreateDTO = (MallProductCreateDTO) requestBody; - boolean success = mallProductService.createProductWithSkus(productCreateDTO); - if (success) { - return R.ok("商品创建成功"); - } else { - return R.failed("商品创建失败"); - } + boolean success = mallProductService.createProductWithSkus(productCreateDTO); + if (success) { + return R.ok("商品创建成功"); } else { - // 普通SPU创建 - MallProductEntity mallProduct = (MallProductEntity) requestBody; - return R.ok(mallProductService.save(mallProduct)); + return R.failed("商品创建失败"); } } catch (Exception e) { return R.failed("商品创建失败:" + e.getMessage()); @@ -111,30 +102,21 @@ public class MallProductController { } /** - * 修改共富商城商品信息 - * @param requestBody 请求体(可以是MallProductEntity或MallProductCreateDTO) + * 修改商品 + * @param productCreateDTO 商品创建DTO * @return R */ - @Operation(summary = "修改共富商城商品信息" , description = "修改共富商城商品信息" ) - @SysLog("修改共富商城商品信息" ) + @Operation(summary = "修改商品" , description = "修改商品信息,支持包含SKU" ) + @SysLog("修改商品" ) @PutMapping @PreAuthorize("@pms.hasPermission('supermarket_mallProduct_edit')" ) - public R updateById(@RequestBody Object requestBody) { + public R updateById(@RequestBody MallProductCreateDTO productCreateDTO) { try { - // 判断请求体类型 - if (requestBody instanceof MallProductCreateDTO) { - // 包含SKU的更新 - MallProductCreateDTO productCreateDTO = (MallProductCreateDTO) requestBody; - boolean success = mallProductService.updateProductWithSkus(productCreateDTO); - if (success) { - return R.ok("商品更新成功"); - } else { - return R.failed("商品更新失败"); - } + boolean success = mallProductService.updateProductWithSkus(productCreateDTO); + if (success) { + return R.ok("商品更新成功"); } else { - // 普通SPU更新 - MallProductEntity mallProduct = (MallProductEntity) requestBody; - return R.ok(mallProductService.updateById(mallProduct)); + return R.failed("商品更新失败"); } } 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 index b5f0e5f05..5fb5f8f5a 100644 --- 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 @@ -67,3 +67,4 @@ public class SelectOptionController { 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 index 83c213d56..3452392eb 100644 --- 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 @@ -1,5 +1,6 @@ package com.bsd.pjcloud.supermarket.dto; +import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; @@ -106,6 +107,7 @@ public class MallProductCreateDTO { * 开售时间 */ @Schema(description = "开售时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime saleStartTime; /** 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 index 8081f8c17..41fcfe397 100644 --- 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 @@ -76,3 +76,4 @@ public class MallProductSkuCreateDTO { private Integer status; } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java new file mode 100644 index 000000000..70c30fdab --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java @@ -0,0 +1,29 @@ +package com.bsd.pjcloud.supermarket.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +/** + * 商户用户绑定DTO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "商户用户绑定DTO") +public class MerchantUserBindDTO { + + @NotNull(message = "商户ID不能为空") + @Schema(description = "商户ID") + private Long merchantId; + + @NotNull(message = "用户ID列表不能为空") + @Schema(description = "用户ID列表") + private List userIds; + + @Schema(description = "角色类型(1:管理员 2:普通用户)", example = "1") + private Integer roleType = 1; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java new file mode 100644 index 000000000..5baea32ca --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java @@ -0,0 +1,34 @@ +package com.bsd.pjcloud.supermarket.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import jakarta.validation.constraints.NotNull; + +/** + * 单个商户用户绑定DTO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "单个商户用户绑定DTO") +public class MerchantUserSingleBindDTO { + + @NotNull(message = "商户ID不能为空") + @Schema(description = "商户ID") + private Long merchantId; + + @NotNull(message = "用户ID不能为空") + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "用户名称") + private String userName; + + @Schema(description = "用户手机号") + private String userPhone; + + @Schema(description = "角色类型(1:管理员 2:普通用户)", example = "1") + private Integer roleType = 1; +} 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 index 056e8aad1..35ca50ceb 100644 --- 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 @@ -64,3 +64,4 @@ public class SelectOptionDTO { this.extra = extra; } } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java new file mode 100644 index 000000000..c4a7db4c4 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java @@ -0,0 +1,108 @@ +package com.bsd.pjcloud.supermarket.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import com.bsd.pjcloud.common.core.util.TenantTable; +import java.time.LocalDateTime; + +/** + * 商户用户绑定表 + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@TenantTable +@TableName("mall_merchant_user") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "商户用户绑定表") +public class MallMerchantUserEntity extends Model { + + /** + * 主键ID + */ + @TableId(type = IdType.ASSIGN_ID) + @Schema(description = "主键ID") + private Long id; + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 用户ID + */ + @Schema(description = "用户ID") + private Long userId; + + /** + * 用户名称 + */ + @Schema(description = "用户名称") + private String userName; + + /** + * 用户手机号 + */ + @Schema(description = "用户手机号") + private String userPhone; + + /** + * 角色类型(1:管理员 2:普通用户) + */ + @Schema(description = "角色类型(1:管理员 2:普通用户)") + private Integer roleType; + + /** + * 状态(1:启用 0:禁用) + */ + @Schema(description = "状态(1:启用 0:禁用)") + private Integer status; + + /** + * 创建者 + */ + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建者") + private String createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** + * 更新者 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "更新者") + private String updateBy; + + /** + * 更新时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + /** + * 是否删除(0=正常 1=删除) + */ + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description = "是否删除(0=正常 1=删除)") + private String delFlag; + + /** + * 租户编号 + */ + @Schema(description = "租户编号") + private Long tenantId; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/exception/BusinessException.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/exception/BusinessException.java new file mode 100644 index 000000000..b2ad5661a --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/exception/BusinessException.java @@ -0,0 +1,11 @@ +package com.bsd.pjcloud.supermarket.exception; + +public class BusinessException extends RuntimeException{ + public BusinessException(String message) { + super(message); + } + + public BusinessException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java new file mode 100644 index 000000000..7ec015c88 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java @@ -0,0 +1,10 @@ +package com.bsd.pjcloud.supermarket.mapper; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.supermarket.entity.MallMerchantUserEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MallMerchantUserMapper extends PjcloudBaseMapper { + +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallProductSkuMapper.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallProductSkuMapper.java index 67573c189..fe0aca3a1 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallProductSkuMapper.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallProductSkuMapper.java @@ -3,9 +3,18 @@ package com.bsd.pjcloud.supermarket.mapper; import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Update; @Mapper public interface MallProductSkuMapper extends PjcloudBaseMapper { - + /** + * 更新库存 + * @param skuId SKU ID + * @param stockChange 库存变化量(正数增加,负数减少) + * @return 影响行数 + */ + @Update("UPDATE mall_product_sku SET stock = stock + #{stockChange} WHERE id = #{skuId} AND stock >= #{stockChange}") + int updateStock(@Param("skuId") Long skuId, @Param("stockChange") Integer stockChange); } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java new file mode 100644 index 000000000..159b71622 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java @@ -0,0 +1,79 @@ +package com.bsd.pjcloud.supermarket.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.supermarket.entity.MallMerchantUserEntity; + +import java.util.List; + +public interface MallMerchantUserService extends IService { + + /** + * 根据用户ID获取商户信息 + * @param userId 用户ID + * @return 商户用户绑定信息 + */ + MallMerchantUserEntity getByUserId(Long userId); + + /** + * 根据用户ID获取商户ID + * @param userId 用户ID + * @return 商户ID + */ + Long getMerchantIdByUserId(Long userId); + + /** + * 根据商户ID获取用户列表 + * @param merchantId 商户ID + * @return 用户列表 + */ + List getUsersByMerchantId(Long merchantId); + + /** + * 绑定用户到商户 + * @param merchantId 商户ID + * @param userId 用户ID + * @param userName 用户名称 + * @param userPhone 用户手机号 + * @param roleType 角色类型 + * @return 是否成功 + */ + boolean bindUserToMerchant(Long merchantId, Long userId, String userName, String userPhone, Integer roleType); + + /** + * 解绑用户和商户 + * @param userId 用户ID + * @return 是否成功 + */ + boolean unbindUserFromMerchant(Long userId); + + /** + * 验证用户是否有权限操作指定商户 + * @param userId 用户ID + * @param merchantId 商户ID + * @return 是否有权限 + */ + boolean hasPermission(Long userId, Long merchantId); + + /** + * 批量绑定用户到商户 + * @param merchantId 商户ID + * @param userIds 用户ID列表 + * @param roleType 角色类型 + * @return 绑定成功的数量 + */ + int batchBindUsersToMerchant(Long merchantId, List userIds, Integer roleType); + + /** + * 批量解绑用户和商户 + * @param userIds 用户ID列表 + * @return 解绑成功的数量 + */ + int batchUnbindUsersFromMerchant(List userIds); + + /** + * 根据商户ID获取绑定的用户列表(包含用户信息) + * @param merchantId 商户ID + * @return 用户绑定信息列表 + */ + List getMerchantUsers(Long merchantId); +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductSkuService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductSkuService.java index 79927fb50..395056401 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductSkuService.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallProductSkuService.java @@ -2,7 +2,21 @@ package com.bsd.pjcloud.supermarket.service; import com.baomidou.mybatisplus.extension.service.IService; import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; +import com.bsd.pjcloud.supermarket.dto.SkuStockChangeDTO; public interface MallProductSkuService extends IService { + /** + * 修改库存(乐观锁) + * 正数:增加库存,负数:扣减库存 + * @param skuId sku主键 + * @param num 增减数量(正数加,负数减) + * @return 是否成功 + */ + boolean deductStock(Long skuId, Double num); + + /** + * 批量增减库存(正数增加,负数扣减),任一失败则返回false + */ + boolean deductStockBatch(SkuStockChangeDTO changes); } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java new file mode 100644 index 000000000..021d9c451 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java @@ -0,0 +1,147 @@ +package com.bsd.pjcloud.supermarket.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.supermarket.entity.MallMerchantUserEntity; +import com.bsd.pjcloud.supermarket.mapper.MallMerchantUserMapper; +import com.bsd.pjcloud.supermarket.service.MallMerchantUserService; +import com.bsd.pjcloud.supermarket.constant.MallConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 商户用户绑定服务实现 + * + * @author zjf + * @date 2025-01-27 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class MallMerchantUserServiceImpl extends ServiceImpl implements MallMerchantUserService { + + @Override + public MallMerchantUserEntity getByUserId(Long userId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallMerchantUserEntity::getUserId, userId) + .eq(MallMerchantUserEntity::getStatus, MallConstants.Status.ENABLED); + return this.getOne(wrapper); + } + + @Override + public Long getMerchantIdByUserId(Long userId) { + MallMerchantUserEntity merchantUser = getByUserId(userId); + return merchantUser != null ? merchantUser.getMerchantId() : null; + } + + @Override + public List getUsersByMerchantId(Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallMerchantUserEntity::getMerchantId, merchantId) + .eq(MallMerchantUserEntity::getStatus, MallConstants.Status.ENABLED); + return this.list(wrapper); + } + + @Override + public boolean bindUserToMerchant(Long merchantId, Long userId, String userName, String userPhone, Integer roleType) { + // 先检查是否已经绑定 + MallMerchantUserEntity existing = getByUserId(userId); + if (existing != null) { + // 如果已经绑定,更新信息 + existing.setMerchantId(merchantId); + existing.setUserName(userName); + existing.setUserPhone(userPhone); + existing.setRoleType(roleType); + existing.setStatus(MallConstants.Status.ENABLED); + return this.updateById(existing); + } else { + // 创建新的绑定关系 + MallMerchantUserEntity merchantUser = new MallMerchantUserEntity(); + merchantUser.setMerchantId(merchantId); + merchantUser.setUserId(userId); + merchantUser.setUserName(userName); + merchantUser.setUserPhone(userPhone); + merchantUser.setRoleType(roleType); + merchantUser.setStatus(MallConstants.Status.ENABLED); + return this.save(merchantUser); + } + } + + @Override + public boolean unbindUserFromMerchant(Long userId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallMerchantUserEntity::getUserId, userId); + return this.remove(wrapper); + } + + @Override + public boolean hasPermission(Long userId, Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallMerchantUserEntity::getUserId, userId) + .eq(MallMerchantUserEntity::getMerchantId, merchantId) + .eq(MallMerchantUserEntity::getStatus, MallConstants.Status.ENABLED); + return this.count(wrapper) > 0; + } + + @Override + public int batchBindUsersToMerchant(Long merchantId, List userIds, Integer roleType) { + if (merchantId == null || userIds == null || userIds.isEmpty()) { + return 0; + } + + int successCount = 0; + for (Long userId : userIds) { + try { + // 检查是否已经绑定 + MallMerchantUserEntity existing = getByUserId(userId); + if (existing != null) { + // 如果已经绑定,更新商户和角色 + existing.setMerchantId(merchantId); + existing.setRoleType(roleType); + existing.setStatus(MallConstants.Status.ENABLED); + if (this.updateById(existing)) { + successCount++; + } + } else { + // 创建新的绑定关系 + MallMerchantUserEntity merchantUser = new MallMerchantUserEntity(); + merchantUser.setMerchantId(merchantId); + merchantUser.setUserId(userId); + merchantUser.setRoleType(roleType); + merchantUser.setStatus(MallConstants.Status.ENABLED); + if (this.save(merchantUser)) { + successCount++; + } + } + } catch (Exception e) { + // 记录日志但继续处理其他用户 + log.error("绑定用户{}到商户{}失败: {}", userId, merchantId, e.getMessage()); + } + } + return successCount; + } + + @Override + public int batchUnbindUsersFromMerchant(List userIds) { + if (userIds == null || userIds.isEmpty()) { + return 0; + } + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.in(MallMerchantUserEntity::getUserId, userIds); + return this.remove(wrapper) ? userIds.size() : 0; + } + + @Override + public List getMerchantUsers(Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallMerchantUserEntity::getMerchantId, merchantId) + .eq(MallMerchantUserEntity::getStatus, MallConstants.Status.ENABLED) + .orderByDesc(MallMerchantUserEntity::getCreateTime); + return this.list(wrapper); + } +} 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 78cf5c2a1..31e53649c 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 @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; /** @@ -96,6 +97,10 @@ public class MallProductServiceImpl extends ServiceImpl deleteWrapper = Wrappers.lambdaQuery(); - deleteWrapper.eq(MallProductSkuEntity::getProductId, product.getId()); - mallProductSkuService.remove(deleteWrapper); + // 多SKU模式,精确处理SKU的增删改 + updateSkusForProduct(product.getId(), productCreateDTO.getSkuList()); - 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() + List currentSkus = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, product.getId()) + ); + int totalStock = currentSkus.stream() .mapToInt(sku -> sku.getStock() != null ? sku.getStock() : 0) .sum(); product.setTotalStock(totalStock); this.updateById(product); - } else if (productCreateDTO.getPriceType() == 2) { - // 单价SPU模式,删除所有SKU + } else { + // 单价SPU模式或没有SKU信息,删除所有SKU LambdaQueryWrapper deleteWrapper = Wrappers.lambdaQuery(); deleteWrapper.eq(MallProductSkuEntity::getProductId, product.getId()); mallProductSkuService.remove(deleteWrapper); @@ -184,6 +169,99 @@ public class MallProductServiceImpl extends ServiceImpl newSkuList) { + // 1. 获取数据库中现有的SKU列表 + List existingSkus = mallProductSkuService.list( + Wrappers.lambdaQuery() + .eq(MallProductSkuEntity::getProductId, productId) + ); + + // 2. 按SKU编码分组现有SKU + Map existingSkuMap = existingSkus.stream() + .collect(Collectors.toMap( + MallProductSkuEntity::getSkuCode, + sku -> sku, + (existing, replacement) -> existing // 如果有重复,保留第一个 + )); + + // 3. 按SKU编码分组新SKU + Map newSkuMap = newSkuList.stream() + .collect(Collectors.toMap( + MallProductSkuCreateDTO::getSkuCode, + sku -> sku, + (existing, replacement) -> replacement // 如果有重复,保留最后一个 + )); + + // 4. 找出需要删除的SKU(在数据库中存在,但在新列表中不存在) + List skuIdsToDelete = existingSkus.stream() + .filter(sku -> !newSkuMap.containsKey(sku.getSkuCode())) + .map(MallProductSkuEntity::getId) + .collect(Collectors.toList()); + + // 5. 找出需要新增的SKU(在新列表中存在,但在数据库中不存在) + List skusToAdd = newSkuList.stream() + .filter(skuDTO -> !existingSkuMap.containsKey(skuDTO.getSkuCode())) + .map(skuDTO -> { + MallProductSkuEntity sku = new MallProductSkuEntity(); + sku.setProductId(productId); + 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()); + + // 6. 找出需要更新的SKU(在数据库和新列表中都存在,但内容不同) + List skusToUpdate = newSkuList.stream() + .filter(skuDTO -> existingSkuMap.containsKey(skuDTO.getSkuCode())) + .map(skuDTO -> { + MallProductSkuEntity existingSku = existingSkuMap.get(skuDTO.getSkuCode()); + // 更新字段 + existingSku.setSkuName(skuDTO.getSkuName()); + existingSku.setPrice(skuDTO.getPrice()); + existingSku.setCostPrice(skuDTO.getCostPrice()); + existingSku.setStock(skuDTO.getStock()); + existingSku.setBarcode(skuDTO.getBarcode()); + existingSku.setWeightG(skuDTO.getWeightG()); + existingSku.setVolumeCm3(skuDTO.getVolumeCm3()); + existingSku.setStatus(skuDTO.getStatus()); + return existingSku; + }) + .collect(Collectors.toList()); + + // 7. 执行删除操作 + if (!skuIdsToDelete.isEmpty()) { + mallProductSkuService.removeByIds(skuIdsToDelete); + } + + // 8. 执行新增操作 + if (!skusToAdd.isEmpty()) { + boolean addResult = mallProductSkuService.saveBatch(skusToAdd); + if (!addResult) { + throw new RuntimeException("新增SKU失败"); + } + } + + // 9. 执行更新操作 + if (!skusToUpdate.isEmpty()) { + boolean updateResult = mallProductSkuService.updateBatchById(skusToUpdate); + if (!updateResult) { + throw new RuntimeException("更新SKU失败"); + } + } + } + @Override public MallProductCreateDTO getProductWithSkus(Long id) { // 1. 获取SPU信息 @@ -249,7 +327,8 @@ public class MallProductServiceImpl extends ServiceImpl skuCodes = productCreateDTO.getSkuList().stream() + .map(MallProductSkuCreateDTO::getSkuCode) + .collect(Collectors.toList()); + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.in(MallProductSkuEntity::getSkuCode, skuCodes); + if (productCreateDTO.getId() != null) { + // 编辑时排除当前商品的SKU + wrapper.ne(MallProductSkuEntity::getProductId, productCreateDTO.getId()); + } + + long existingCount = mallProductSkuService.count(wrapper); + if (existingCount > 0) { + throw new RuntimeException("SKU编码已存在,请使用不同的编码"); + } } // 单价SPU模式验证 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductSkuServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductSkuServiceImpl.java index 4f45ac027..a4ddd8c0e 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductSkuServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallProductSkuServiceImpl.java @@ -1,10 +1,14 @@ package com.bsd.pjcloud.supermarket.service.impl; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.bsd.pjcloud.supermarket.entity.MallProductSkuEntity; import com.bsd.pjcloud.supermarket.mapper.MallProductSkuMapper; import com.bsd.pjcloud.supermarket.service.MallProductSkuService; +import com.bsd.pjcloud.supermarket.dto.SkuStockChangeDTO; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + /** * 共富商城商品SKU * @@ -13,4 +17,46 @@ import org.springframework.stereotype.Service; */ @Service public class MallProductSkuServiceImpl extends ServiceImpl implements MallProductSkuService { + + /** + * 修改库存(乐观锁) + * 正数:增加库存,负数:扣减库存 + */ + @Override + public boolean deductStock(Long skuId, Double num) { + for (int i = 0; i < 3; i++) { // 最多重试3次 + MallProductSkuEntity sku = this.getById(skuId); + if (sku == null) return false; + // 扣减时判断库存是否足够 + if (num < 0 && sku.getStock() < Math.abs(num)) return false; + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(MallProductSkuEntity::getId, skuId); + MallProductSkuEntity currentSku = baseMapper.selectOne(wrapper); + if (currentSku == null) { + return false; + } + + // 直接更新库存 + int updated = baseMapper.updateStock(skuId, num.intValue()); + if (updated > 0) return true; + } + return false; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean deductStockBatch(SkuStockChangeDTO changes) { + if (changes == null || changes.getItems() == null || changes.getItems().isEmpty()) { + return true; + } + for (SkuStockChangeDTO.Item item : changes.getItems()) { + if (item == null || item.getId() == null || item.getIncrCount() == null) return false; + boolean ok = deductStock(item.getId(), item.getIncrCount()); + if (!ok) { + throw new RuntimeException("扣减库存失败,skuId=" + item.getId()); + } + } + return true; + } } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java new file mode 100644 index 000000000..5fd0590ba --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java @@ -0,0 +1,127 @@ +package com.bsd.pjcloud.supermarket.vo.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 移动端商品详情VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "移动端商品详情VO") +public class MallProductDetailAppVo { + + /** + * 商品ID + */ + @Schema(description = "商品ID") + private Long id; + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 类目ID + */ + @Schema(description = "类目ID") + private Long categoryId; + + /** + * 商品标题 + */ + @Schema(description = "商品标题") + private String title; + + /** + * 副标题 + */ + @Schema(description = "副标题") + private String subTitle; + + /** + * 品牌 + */ + @Schema(description = "品牌") + private String brand; + + /** + * 主图 + */ + @Schema(description = "主图") + private String coverImage; + + /** + * 轮播图 + */ + @Schema(description = "轮播图") + private String carouselImages; + + /** + * 定价方式(1多SKU 2单价SPU) + */ + @Schema(description = "定价方式") + private Integer priceType; + + /** + * SPU售价 + */ + @Schema(description = "SPU售价") + private BigDecimal spuPrice; + + /** + * 划线价 + */ + @Schema(description = "划线价") + private BigDecimal originalPrice; + + /** + * 总库存 + */ + @Schema(description = "总库存") + private Integer totalStock; + + /** + * 计量单位 + */ + @Schema(description = "计量单位") + private String unit; + + /** + * 状态 + */ + @Schema(description = "状态") + private Integer status; + + /** + * 开售时间 + */ + @Schema(description = "开售时间") + private LocalDateTime saleStartTime; + + /** + * 停售时间 + */ + @Schema(description = "停售时间") + private LocalDateTime saleEndTime; + + /** + * 详情描述 + */ + @Schema(description = "详情描述") + private String description; + + /** + * SKU列表 + */ + @Schema(description = "SKU列表") + private List skuList; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java new file mode 100644 index 000000000..2b95cbfb9 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java @@ -0,0 +1,107 @@ +package com.bsd.pjcloud.supermarket.vo.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 移动端商品列表VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "移动端商品列表VO") +public class MallProductListAppVo { + + /** + * 商品ID + */ + @Schema(description = "商品ID") + private Long id; + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 类目ID + */ + @Schema(description = "类目ID") + private Long categoryId; + + /** + * 商品标题 + */ + @Schema(description = "商品标题") + private String title; + + /** + * 副标题 + */ + @Schema(description = "副标题") + private String subTitle; + + /** + * 品牌 + */ + @Schema(description = "品牌") + private String brand; + + /** + * 主图 + */ + @Schema(description = "主图") + private String coverImage; + + /** + * 轮播图 + */ + @Schema(description = "轮播图") + private String carouselImages; + + /** + * 定价方式(1多SKU 2单价SPU) + */ + @Schema(description = "定价方式") + private Integer priceType; + + /** + * SPU售价 + */ + @Schema(description = "SPU售价") + private BigDecimal spuPrice; + + /** + * 划线价 + */ + @Schema(description = "划线价") + private BigDecimal originalPrice; + + /** + * 总库存 + */ + @Schema(description = "总库存") + private Integer totalStock; + + /** + * 计量单位 + */ + @Schema(description = "计量单位") + private String unit; + + /** + * 状态 + */ + @Schema(description = "状态") + private Integer status; + + /** + * 最低价格(从SKU中计算) + */ + @Schema(description = "最低价格") + private BigDecimal minPrice; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java new file mode 100644 index 000000000..26d6adafa --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java @@ -0,0 +1,83 @@ +package com.bsd.pjcloud.supermarket.vo.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 移动端商品SKU VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "移动端商品SKU VO") +public class MallProductSkuAppVo { + + /** + * SKU ID + */ + @Schema(description = "SKU ID") + private Long id; + + /** + * 商品ID + */ + @Schema(description = "商品ID") + private Long productId; + + /** + * SKU编码 + */ + @Schema(description = "SKU编码") + private String skuCode; + + /** + * SKU名称 + */ + @Schema(description = "SKU名称") + private String skuName; + + /** + * 售价 + */ + @Schema(description = "售价") + private BigDecimal price; + + /** + * 成本价 + */ + @Schema(description = "成本价") + private BigDecimal costPrice; + + /** + * 库存 + */ + @Schema(description = "库存") + private Integer stock; + + /** + * 条码 + */ + @Schema(description = "条码") + private String barcode; + + /** + * 重量(克) + */ + @Schema(description = "重量(克)") + private Integer weightG; + + /** + * 体积(立方厘米) + */ + @Schema(description = "体积(立方厘米)") + private Integer volumeCm3; + + /** + * 状态 + */ + @Schema(description = "状态") + private Integer status; +} 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 index 5a954e1cf..89fa2b0df 100644 --- 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 @@ -102,3 +102,4 @@ public class MallProductServiceTest { assert result : "商品创建应该成功"; } } + diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteRoleService.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteRoleService.java index 34de9de4e..e6fe4e9c1 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteRoleService.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteRoleService.java @@ -19,16 +19,21 @@ package com.bsd.pjcloud.admin.api.feign; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.bsd.pjcloud.admin.api.entity.SysRole; +import com.bsd.pjcloud.admin.api.entity.SysUserRole; import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; import com.bsd.pjcloud.common.core.util.R; import com.bsd.pjcloud.common.feign.annotation.NoToken; import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import java.util.List; +import java.util.stream.Collectors; /** * @author pjcloud @@ -52,4 +57,12 @@ public interface RemoteRoleService { @PostMapping("/role/by-codes") R> findRolesByCodes(@RequestBody List codes); + + /** + * 是否是共富商城商户角色 + * @return + */ + @GetMapping("/role/isMallMchUser") + R isMallMchUser(); + } diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java index ad02ffbeb..e3069f1b6 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/feign/RemoteUserService.java @@ -23,6 +23,7 @@ import com.bsd.pjcloud.admin.api.dto.UserDTO; import com.bsd.pjcloud.admin.api.dto.UserDeptSimpleInfo; import com.bsd.pjcloud.admin.api.dto.UserInfo; import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.vo.UserSimpleVo; import com.bsd.pjcloud.admin.api.vo.UserVO; import com.bsd.pjcloud.common.core.constant.ServiceNameConstants; import com.bsd.pjcloud.common.core.util.R; @@ -193,4 +194,13 @@ public interface RemoteUserService { @GetMapping("/user/get-name-or-mobile") R> getUserByNameOrMobile(@RequestParam("keyword") String keyword); + + /** + * 根据角色编码查询用户列表 + * @param code + * @return + */ + @GetMapping("/user/getUserListByRoleCode") + R> getUserListByRoleCode(@RequestParam("code") String code); + } diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/UserSimpleVo.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/UserSimpleVo.java new file mode 100644 index 000000000..84d8cd7f5 --- /dev/null +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/UserSimpleVo.java @@ -0,0 +1,12 @@ +package com.bsd.pjcloud.admin.api.vo; + +import lombok.Data; + +@Data +public class UserSimpleVo { + + private Long userId; +// private String username; + private String name; + private String phone; +} diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysRoleController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysRoleController.java index afe32836e..4e50e6c30 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysRoleController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysRoleController.java @@ -189,6 +189,22 @@ public class SysRoleController { return R.ok(true); } + /** + * 是否是共富商户角色 + * @return + */ + @GetMapping("/isMallMchUser") + public R isMallMchUser() { + Long id = SecurityUtils.getUser().getId(); + List sysUserRoles = sysUserRoleService.list(Wrappers.lambdaQuery().eq(SysUserRole::getUserId, id)); + List roleIds = sysUserRoles.stream().map(sysUserRole -> sysUserRole.getRoleId()).collect(Collectors.toList()); + List list = sysRoleService.list(Wrappers.lambdaQuery().eq(SysRole::getRoleCode, "Mall_MCH_USER")); + if (CollectionUtils.isEmpty(list) || CollUtil.isEmpty(roleIds) || !roleIds.contains(list.get(0).getRoleId())) { + return R.ok(false); + } + return R.ok(true); + } + /** * 分页查询角色信息 diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java index d71d39ff6..8c37f4894 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserController.java @@ -29,9 +29,13 @@ import com.bsd.pjcloud.admin.api.dto.UserDTO; import com.bsd.pjcloud.admin.api.dto.UserDeptSimpleInfo; import com.bsd.pjcloud.admin.api.dto.UserInfo; import com.bsd.pjcloud.admin.api.entity.SysDept; +import com.bsd.pjcloud.admin.api.entity.SysRole; import com.bsd.pjcloud.admin.api.entity.SysUser; +import com.bsd.pjcloud.admin.api.entity.SysUserRole; import com.bsd.pjcloud.admin.api.vo.*; import com.bsd.pjcloud.admin.service.SysDeptService; +import com.bsd.pjcloud.admin.service.SysRoleService; +import com.bsd.pjcloud.admin.service.SysUserRoleService; import com.bsd.pjcloud.admin.service.SysUserService; import com.bsd.pjcloud.common.core.constant.CommonConstants; import com.bsd.pjcloud.common.core.exception.ErrorCodes; @@ -87,6 +91,10 @@ public class SysUserController { private final RemoterVisitorService remoterVisitorService; private final SysDeptService sysDeptService; + + private final SysRoleService sysRoleService; + + private final SysUserRoleService sysUserRoleService; /* ************************************* start 回调 start ************************************* */ @Inner(value = false) @@ -524,4 +532,28 @@ public class SysUserController { } return R.ok(true); } + + /** + * 根据角色编码获取用户列表 + */ + @GetMapping("/getUserListByRoleCode") + public R> getUserListByRoleCode(@RequestParam("code") String code) { + SysRole sysRole = sysRoleService.getOne(Wrappers.lambdaQuery().eq(SysRole::getRoleCode, code)); + if (sysRole == null) { + return R.failed("不存在共富商城商户角色的用户!"); + } + List list = sysUserRoleService.list(Wrappers.lambdaQuery().eq(SysUserRole::getRoleId, sysRole.getRoleId())); + List userIds = list.stream().map(sysUserRole -> sysUserRole.getUserId()).collect(Collectors.toList()); + List listData = userService.list(Wrappers.lambdaQuery().in(SysUser::getUserId, userIds)); + //转换成vo,只返回id+手机号+姓名,安全考虑 + List userVOList = listData.stream().map(sysUser -> { + UserSimpleVo userVO = new UserSimpleVo(); + userVO.setUserId(sysUser.getUserId()); + userVO.setPhone(sysUser.getPhone()); + userVO.setName(sysUser.getName()); +// userVO.setUsername(sysUser.getUsername()); + return userVO; + }).collect(Collectors.toList()); + return R.ok(userVOList); + } } From b5c78ec3d4df6573a3e6e58b68d78db9619dc425 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 5 Sep 2025 19:30:05 +0800 Subject: [PATCH 04/27] =?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 --- .../entity/MallCategoryEntity.java | 2 +- .../supermarket/constant/MallConstants.java | 231 +++++------------- .../controller/MallAppController.java | 142 ++++------- .../MallMerchantUserController.java | 66 ++++- .../supermarket/dto/MallProductCreateDTO.java | 8 +- .../dto/MallProductSkuCreateDTO.java | 2 +- .../supermarket/dto/MerchantUserBindDTO.java | 3 +- .../dto/MerchantUserSingleBindDTO.java | 1 + .../entity/MallMerchantEntity.java | 2 +- .../entity/MallMerchantUserEntity.java | 3 +- .../supermarket/entity/MallProductEntity.java | 2 +- .../entity/MallProductSkuEntity.java | 2 +- .../mapper/MallMerchantUserMapper.java | 1 + .../service/MallMerchantUserService.java | 6 +- .../service/SelectOptionService.java | 5 +- .../impl/MallMerchantUserServiceImpl.java | 12 +- .../supermarket/vo/MallMerchantUserVo.java | 85 +++++++ .../vo/app/MallProductDetailAppVo.java | 1 + .../vo/app/MallProductListAppVo.java | 1 + .../vo/app/MallProductSkuAppVo.java | 1 + 20 files changed, 278 insertions(+), 298 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java index e1dc650a6..07bd86854 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java @@ -58,7 +58,7 @@ public class MallCategoryEntity extends Model { * 状态(0正常,1下架) */ @Schema(description="状态(0正常,1下架)") - private Integer status; + private String status; /** * 分类图片 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java index 9dfda84fd..46d0ddb22 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java @@ -3,198 +3,95 @@ package com.bsd.pjcloud.supermarket.constant; /** * 商城相关常量 * - * @author zjf + * @author pjcloud * @date 2025-01-27 */ -public class MallConstants { +public interface MallConstants { /** - * 角色编码常量 + * 状态相关常量 */ - public static class RoleCode { - /** 商户用户角色 */ - public static final String MERCHANT_USER = "MALL_MCH"; - - /** 商城用户角色 */ -// public static final String MALL_USER = "Mall_USER"; - /** 商城管理员角色 */ -// public static final String MALL_ADMIN = "Mall_ADMIN"; + interface Status { + /** + * 启用状态 + */ + String ENABLED = "0"; + + /** + * 禁用状态 + */ + String DISABLED = "1"; + + /** + * 上架状态 + */ + String ON_SALE = "1"; + + /** + * 下架状态 + */ + String OFF_SALE = "0"; } /** - * 商户用户角色类型 + * 角色相关常量 */ - public static class MerchantUserRoleType { - /** 管理员 */ - public static final Integer ADMIN = 1; - /** 普通用户 */ - public static final Integer USER = 2; - } - - /** - * 状态常量 - */ - public static class Status { - /** 启用 */ - public static final Integer ENABLED = 1; - /** 禁用 */ - public static final Integer DISABLED = 0; + interface RoleCode { + /** + * 商户用户角色 + */ + String MERCHANT_USER = "MALL_MCH"; + + /** + * 超市管理员角色 + */ + String SUPERMARKET_ADMIN = "SUPERMARKET_ADMIN"; } /** * 商品相关常量 */ - public static class Product { - /** 商品状态 - 上架 */ - public static final Integer STATUS_ON_SALE = 1; - /** 商品状态 - 下架 */ - public static final Integer STATUS_OFF_SALE = 0; - /** 商品状态 - 草稿 */ - public static final Integer STATUS_DRAFT = 2; + interface Product { + /** + * 定价方式 - 多SKU + */ + Integer PRICE_TYPE_MULTI_SKU = 1; - /** 价格类型 - 统一价格 */ - public static final Integer PRICE_TYPE_UNIFIED = 1; - /** 价格类型 - SKU价格 */ - public static final Integer PRICE_TYPE_SKU = 2; - } - - /** - * 分类相关常量 - */ - public static class Category { - /** 分类状态 - 启用 */ - public static final Integer STATUS_ENABLED = 0; - /** 分类状态 - 禁用 */ - public static final Integer STATUS_DISABLED = 1; + /** + * 定价方式 - 单价SPU + */ + Integer PRICE_TYPE_SINGLE_SPU = 2; } /** * 商户相关常量 */ - public static class Merchant { - /** 商户状态 - 正常 */ - public static final Integer STATUS_NORMAL = 1; - /** 商户状态 - 停用 */ - public static final Integer STATUS_DISABLED = 0; - /** 商户状态 - 审核中 */ - public static final Integer STATUS_PENDING = 2; - } - - /** - * 库存相关常量 - */ - public static class Stock { - /** 库存操作 - 增加 */ - public static final String OPERATION_ADD = "ADD"; - /** 库存操作 - 减少 */ - public static final String OPERATION_REDUCE = "REDUCE"; - /** 库存操作 - 设置 */ - public static final String OPERATION_SET = "SET"; - } - - /** - * 订单相关常量 - */ - public static class Order { - /** 订单状态 - 待支付 */ - public static final Integer STATUS_PENDING_PAYMENT = 1; - /** 订单状态 - 已支付 */ - public static final Integer STATUS_PAID = 2; - /** 订单状态 - 已发货 */ - public static final Integer STATUS_SHIPPED = 3; - /** 订单状态 - 已完成 */ - public static final Integer STATUS_COMPLETED = 4; - /** 订单状态 - 已取消 */ - public static final Integer STATUS_CANCELLED = 5; - /** 订单状态 - 已退款 */ - public static final Integer STATUS_REFUNDED = 6; - } - - /** - * 权限相关常量 - */ - public static class Permission { - /** 商品管理权限前缀 */ - public static final String PRODUCT_PREFIX = "supermarket_mallProduct_"; - /** 商户管理权限前缀 */ - public static final String MERCHANT_PREFIX = "supermarket_mallMerchant_"; - /** 分类管理权限前缀 */ - public static final String CATEGORY_PREFIX = "supermarket_mallCategory_"; - /** 商户用户绑定权限前缀 */ - public static final String MERCHANT_USER_PREFIX = "supermarket_mallMerchantUser_"; + interface Merchant { + /** + * 商户状态 - 正常 + */ + String STATUS_NORMAL = "1"; - /** 查看权限 */ - public static final String VIEW = "view"; - /** 新增权限 */ - public static final String ADD = "add"; - /** 编辑权限 */ - public static final String EDIT = "edit"; - /** 删除权限 */ - public static final String DEL = "del"; - /** 导出权限 */ - public static final String EXPORT = "export"; + /** + * 商户状态 - 停用 + */ + String STATUS_DISABLED = "0"; } /** - * 缓存相关常量 + * 删除标记 */ - public static class Cache { - /** 商品缓存前缀 */ - public static final String PRODUCT_PREFIX = "mall:product:"; - /** 商户缓存前缀 */ - public static final String MERCHANT_PREFIX = "mall:merchant:"; - /** 分类缓存前缀 */ - public static final String CATEGORY_PREFIX = "mall:category:"; - /** 用户商户绑定缓存前缀 */ - public static final String MERCHANT_USER_PREFIX = "mall:merchant_user:"; + interface DelFlag { + /** + * 正常 + */ + String NORMAL = "0"; - /** 缓存过期时间 - 1小时 */ - public static final long EXPIRE_1_HOUR = 3600; - /** 缓存过期时间 - 1天 */ - public static final long EXPIRE_1_DAY = 86400; - /** 缓存过期时间 - 1周 */ - public static final long EXPIRE_1_WEEK = 604800; - } - - /** - * 分页相关常量 - */ - public static class Page { - /** 默认页码 */ - public static final Integer DEFAULT_PAGE_NUM = 1; - /** 默认页大小 */ - public static final Integer DEFAULT_PAGE_SIZE = 10; - /** 最大页大小 */ - public static final Integer MAX_PAGE_SIZE = 100; - } - - /** - * 文件上传相关常量 - */ - public static class File { - /** 图片文件类型 */ - public static final String[] IMAGE_TYPES = {"jpg", "jpeg", "png", "gif", "bmp", "webp"}; - /** 最大文件大小 - 5MB */ - public static final long MAX_FILE_SIZE = 5 * 1024 * 1024; - /** 商品图片路径 */ - public static final String PRODUCT_IMAGE_PATH = "/mall/product/"; - /** 商户图片路径 */ - public static final String MERCHANT_IMAGE_PATH = "/mall/merchant/"; - } - - /** - * 业务异常相关常量 - */ - public static class ErrorCode { - /** 用户未绑定商户 */ - public static final String USER_NOT_BOUND_MERCHANT = "USER_NOT_BOUND_MERCHANT"; - /** 商户不存在 */ - public static final String MERCHANT_NOT_EXISTS = "MERCHANT_NOT_EXISTS"; - /** 商品不存在 */ - public static final String PRODUCT_NOT_EXISTS = "PRODUCT_NOT_EXISTS"; - /** 库存不足 */ - public static final String INSUFFICIENT_STOCK = "INSUFFICIENT_STOCK"; - /** 权限不足 */ - public static final String INSUFFICIENT_PERMISSION = "INSUFFICIENT_PERMISSION"; + /** + * 删除 + */ + String DELETED = "1"; } } + + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java index 1c8f577f3..c336912e4 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java @@ -242,20 +242,11 @@ public class MallAppController { @Operation(summary = "商户创建商品", description = "移动端商户创建商品,支持包含SKU") public R createProduct(@RequestBody MallProductCreateDTO productCreateDTO) { try { - // 获取当前用户绑定的商户ID - Long currentUserId = SecurityUtils.getUser().getId(); - Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); - - if (userMerchantId == null) { - return R.failed("用户未绑定商户"); - } - + Long userMerchantId = validateMerchant(); + // 自动设置商户ID productCreateDTO.setMerchantId(userMerchantId); - // 验证商户权限和状态 - validateMerchant(userMerchantId); - // 创建商品 boolean success = mallProductService.createProductWithSkus(productCreateDTO); if (success) { @@ -275,37 +266,17 @@ public class MallAppController { @Operation(summary = "商户更新商品", description = "移动端商户更新商品,支持包含SKU") public R updateProduct(@RequestBody MallProductCreateDTO productCreateDTO) { try { - // 获取当前用户绑定的商户ID - Long currentUserId = SecurityUtils.getUser().getId(); - - Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); - - if (userMerchantId == null) { - return R.failed("用户未绑定商户"); - } + + //验证商户 + Long merchantId = validateMerchant(); + //验证商品数据 + validateProductPermission(productCreateDTO.getId(),merchantId); + // 验证商品ID if (productCreateDTO.getId() == null) { return R.failed("商品ID不能为空"); } - - // 验证商品是否存在 - MallProductEntity existingProduct = mallProductService.getById(productCreateDTO.getId()); - if (existingProduct == null) { - return R.failed("商品不存在"); - } - - // 验证商户权限(只能更新自己的商品) - if (!userMerchantId.equals(existingProduct.getMerchantId())) { - return R.failed("无权限更新此商品"); - } - - // 自动设置商户ID - productCreateDTO.setMerchantId(userMerchantId); - - // 验证商户权限和状态 - validateMerchant(userMerchantId); - // 更新商品 boolean success = mallProductService.updateProductWithSkus(productCreateDTO); if (success) { @@ -318,13 +289,18 @@ public class MallAppController { } } - private void validateMerchant(Long merchantId) { + /** + * 验证商户权限和状态 + * @return 商户id + */ + private Long validateMerchant() { // 获取当前用户ID Long currentUserId = SecurityUtils.getUser().getId(); if (currentUserId == null) { throw new BusinessException("用户未登录"); } + Long merchantIdByUserId=null; // 验证用户是否有商户角色 List roleIds = SecurityUtils.getRoleIds(); R> mallMchUserLists = remoteRoleService.findRolesByCodes(Arrays.asList(MallConstants.RoleCode.MERCHANT_USER)); @@ -333,35 +309,34 @@ public class MallAppController { if (CollectionUtils.isEmpty(list) || CollUtil.isEmpty(roleIds) || !roleIds.contains(list.get(0).getRoleId())) { throw new BusinessException("请分配商户角色"); } + //有商户角色,然后查询自己绑定的商户信息 + merchantIdByUserId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); + if (merchantIdByUserId == null){ + throw new BusinessException("当前用户未绑定商户"); + } + } else { throw new BusinessException("基础用户服务异常"); } - // 验证用户是否绑定到指定商户 - if (merchantId != null) { - boolean hasPermission = mallMerchantUserService.hasPermission(currentUserId, merchantId); - if (!hasPermission) { - throw new BusinessException("无权限操作此商户的商品"); - } - } else { - // 如果没有指定商户ID,获取用户绑定的商户ID - Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); - if (userMerchantId == null) { - throw new BusinessException("用户未绑定商户"); - } - } + return merchantIdByUserId; + } - // 验证商户是否存在和状态 - if (merchantId != null) { - MallMerchantEntity merchant = mallMerchantService.getById(merchantId); - if (merchant == null) { - throw new BusinessException("商户不存在"); - } - if (!MallConstants.Status.ENABLED.equals(merchant.getStatus())) { - throw new BusinessException("商户状态异常,无法操作商品"); - } + + /** + * 验证是否有处理该条商品数据的权限 + */ + private void validateProductPermission(Long productId, Long merchantId) { + + MallProductEntity mallProduct = mallProductService.getById(productId); + if (mallProduct == null) { + throw new BusinessException("商品不存在"); + } + if (!merchantId.equals(mallProduct.getMerchantId())) { + throw new BusinessException("无权限处理此商品"); } } + /** * 商户获取自己的商品列表 */ @@ -375,28 +350,9 @@ public class MallAppController { @RequestParam(required = false) Integer status ) { try { - // 获取当前用户绑定的商户ID - Long currentUserId = SecurityUtils.getUser().getId(); - Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); - - if (userMerchantId == null) { - return R.failed("用户未绑定商户"); - } - - // 如果指定了商户ID,验证权限 - if (merchantId != null && !merchantId.equals(userMerchantId)) { - return R.failed("无权限查看其他商户的商品"); - } - - // 使用用户绑定的商户ID - Long targetMerchantId = merchantId != null ? merchantId : userMerchantId; - - // 验证商户是否存在 - MallMerchantEntity merchant = mallMerchantService.getById(targetMerchantId); - if (merchant == null) { - return R.failed("商户不存在"); - } - + + Long targetMerchantId = validateMerchant(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(MallProductEntity::getMerchantId, targetMerchantId); wrapper.eq(categoryId != null, MallProductEntity::getCategoryId, categoryId); @@ -456,25 +412,9 @@ public class MallAppController { @Operation(summary = "商户删除商品", description = "移动端商户删除自己的商品") public R deleteProduct(@PathVariable("id") Long id) { try { - // 获取当前用户绑定的商户ID - Long currentUserId = SecurityUtils.getUser().getId(); - Long userMerchantId = mallMerchantUserService.getMerchantIdByUserId(currentUserId); - - if (userMerchantId == null) { - return R.failed("用户未绑定商户"); - } - - // 验证商品是否存在 - MallProductEntity product = mallProductService.getById(id); - if (product == null) { - return R.failed("商品不存在"); - } - - // 验证商户权限 - if (!userMerchantId.equals(product.getMerchantId())) { - return R.failed("无权限删除此商品"); - } - + + Long l = validateMerchant(); + validateProductPermission(id, l); // 删除商品(逻辑删除) boolean success = mallProductService.removeById(id); if (success) { diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java index cab064c28..297e78766 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallMerchantUserController.java @@ -12,6 +12,7 @@ import com.bsd.pjcloud.supermarket.dto.MerchantUserSingleBindDTO; import com.bsd.pjcloud.supermarket.entity.MallMerchantUserEntity; import com.bsd.pjcloud.supermarket.service.MallMerchantUserService; import com.bsd.pjcloud.supermarket.constant.MallConstants; +import com.bsd.pjcloud.supermarket.vo.MallMerchantUserVo; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; @@ -71,9 +72,24 @@ public class MallMerchantUserController { @Operation(summary = "获取商户用户列表", description = "根据商户ID获取绑定的用户列表") @GetMapping("/merchant/{merchantId}/users") @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_view')" ) - public R> getMerchantUsers(@PathVariable("merchantId") Long merchantId) { + public R> getMerchantUsers(@PathVariable("merchantId") Long merchantId) { List users = mallMerchantUserService.getMerchantUsers(merchantId); - return R.ok(users); + + // 转换为VO对象 + List userVos = users.stream().map(entity -> { + MallMerchantUserVo vo = new MallMerchantUserVo(); + vo.setId(entity.getId()); + vo.setMerchantId(entity.getMerchantId()); + vo.setUserId(entity.getUserId()); + vo.setName(entity.getUserName()); + vo.setPhone(entity.getUserPhone()); + vo.setStatus(entity.getStatus()); + vo.setStatusDesc("1".equals(entity.getStatus()) ? "启用" : "0".equals(entity.getStatus()) ? "禁用" : "未知"); + + return vo; + }).toList(); + + return R.ok(userVos); } /** @@ -122,10 +138,20 @@ public class MallMerchantUserController { @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) @SysLog("批量商户用户绑定") public R batchBindUsers(@Valid @RequestBody MerchantUserBindDTO bindDTO) { + // 添加调试日志 + System.out.println("接收到的数据: " + bindDTO); + System.out.println("merchantId: " + bindDTO.getMerchantId()); + System.out.println("userIds: " + bindDTO.getUserIds()); + System.out.println("roleType: " + bindDTO.getRoleType()); try { + // 将String类型的userIds转换为Long类型 + List userIds = bindDTO.getUserIds().stream() + .map(Long::valueOf) + .collect(java.util.stream.Collectors.toList()); + int successCount = mallMerchantUserService.batchBindUsersToMerchant( bindDTO.getMerchantId(), - bindDTO.getUserIds(), + userIds, bindDTO.getRoleType() ); @@ -146,12 +172,18 @@ public class MallMerchantUserController { * 解绑单个用户 */ @Operation(summary = "解绑用户", description = "解绑单个用户") - @DeleteMapping("/unbind/{userId}") + @DeleteMapping("/unbind") @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) @SysLog("商户用户解绑") - public R unbindUser(@PathVariable("userId") Long userId) { + public R unbindUser(@RequestBody MerchantUserBindDTO bindDTO) { try { - boolean success = mallMerchantUserService.unbindUserFromMerchant(userId); + // 从userIds中取第一个用户ID + if (bindDTO.getUserIds() == null || bindDTO.getUserIds().isEmpty()) { + return R.failed("用户ID不能为空"); + } + + Long userId = Long.valueOf(bindDTO.getUserIds().get(0)); + boolean success = mallMerchantUserService.unbindUserFromMerchant(bindDTO.getMerchantId(), userId); if (success) { return R.ok("用户解绑成功"); } else { @@ -169,9 +201,14 @@ public class MallMerchantUserController { @DeleteMapping("/batchUnbind") @PreAuthorize("@pms.hasPermission('supermarket_mallMerchant_add')" ) @SysLog("批量商户用户解绑") - public R batchUnbindUsers(@RequestBody List userIds) { + public R batchUnbindUsers(@RequestBody MerchantUserBindDTO bindDTO) { try { - int successCount = mallMerchantUserService.batchUnbindUsersFromMerchant(userIds); + // 将String类型的userIds转换为Long类型 + List userIds = bindDTO.getUserIds().stream() + .map(Long::valueOf) + .collect(java.util.stream.Collectors.toList()); + + int successCount = mallMerchantUserService.batchUnbindUsersFromMerchant(bindDTO.getMerchantId(), userIds); if (successCount > 0) { return R.ok(String.format("成功解绑%d个用户", successCount)); } else { @@ -220,14 +257,14 @@ public class MallMerchantUserController { @SysLog("更新商户用户状态") public R updateUserStatus( @RequestParam Long userId, - @RequestParam Integer status + @RequestParam String status ) { try { MallMerchantUserEntity merchantUser = mallMerchantUserService.getByUserId(userId); if (merchantUser == null) { return R.failed("用户绑定关系不存在"); } - + merchantUser.setStatus(status); boolean success = mallMerchantUserService.updateById(merchantUser); if (success) { @@ -258,4 +295,13 @@ public class MallMerchantUserController { return R.ok(data); } + /** + * 测试JSON反序列化 + */ + @Operation(summary = "测试JSON反序列化", description = "测试JSON反序列化") + @PostMapping("/testJson") + public R testJson(@RequestBody MerchantUserBindDTO bindDTO) { + return R.ok("JSON反序列化成功: " + bindDTO); + } + } 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 index 3452392eb..f7e2be73f 100644 --- 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 @@ -30,9 +30,9 @@ public class MallProductCreateDTO { /** * 商户ID */ - @NotNull(message = "商户ID不能为空") - @Schema(description = "商户ID") - private Long merchantId; +// @NotNull(message = "商户ID不能为空") +// @Schema(description = "商户ID") +// private Long merchantId; /** * 类目ID @@ -101,7 +101,7 @@ public class MallProductCreateDTO { * 状态(1上架 0下架) */ @Schema(description = "状态(1上架 0下架)") - private Integer status; + private String status; /** * 开售时间 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 index 41fcfe397..45a10ffde 100644 --- 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 @@ -73,7 +73,7 @@ public class MallProductSkuCreateDTO { * 状态(1启用 0停用) */ @Schema(description = "状态(1启用 0停用)") - private Integer status; + private String status; } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java index 70c30fdab..24ff2ab78 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserBindDTO.java @@ -22,8 +22,9 @@ public class MerchantUserBindDTO { @NotNull(message = "用户ID列表不能为空") @Schema(description = "用户ID列表") - private List userIds; + private List userIds; @Schema(description = "角色类型(1:管理员 2:普通用户)", example = "1") private Integer roleType = 1; } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java index 5baea32ca..aef6b1b2a 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/dto/MerchantUserSingleBindDTO.java @@ -32,3 +32,4 @@ public class MerchantUserSingleBindDTO { @Schema(description = "角色类型(1:管理员 2:普通用户)", example = "1") private Integer roleType = 1; } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java index f0a301b98..d4267aafd 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java @@ -63,7 +63,7 @@ public class MallMerchantEntity extends Model { * 状态(1启用 0停用) */ @Schema(description="状态(1启用 0停用)") - private Integer status; + private String status; /** * 营业执照号 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java index c4a7db4c4..aee72b7c8 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantUserEntity.java @@ -62,7 +62,7 @@ public class MallMerchantUserEntity extends Model { * 状态(1:启用 0:禁用) */ @Schema(description = "状态(1:启用 0:禁用)") - private Integer status; + private String status; /** * 创建者 @@ -106,3 +106,4 @@ public class MallMerchantUserEntity extends Model { @Schema(description = "租户编号") private Long tenantId; } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java index 4e29f7c68..7ff265f53 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java @@ -106,7 +106,7 @@ public class MallProductEntity extends Model { * 状态(1上架 0下架) */ @Schema(description="状态(1上架 0下架)") - private Integer status; + private String status; /** * 开售时间 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java index 616fa5fd9..48243a29f 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java @@ -88,7 +88,7 @@ public class MallProductSkuEntity extends Model { * 状态(1启用 0停用) */ @Schema(description="状态(1启用 0停用)") - private Integer status; + private String status; /** * 部门ID diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java index 7ec015c88..b5dcbfcb4 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/mapper/MallMerchantUserMapper.java @@ -8,3 +8,4 @@ import org.apache.ibatis.annotations.Mapper; public interface MallMerchantUserMapper extends PjcloudBaseMapper { } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java index 159b71622..273223a33 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/MallMerchantUserService.java @@ -41,10 +41,11 @@ public interface MallMerchantUserService extends IService userIds); + int batchUnbindUsersFromMerchant(Long merchantId, List userIds); /** * 根据商户ID获取绑定的用户列表(包含用户信息) 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 index 949cf8a65..10f5cbf98 100644 --- 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 @@ -2,6 +2,7 @@ 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.constant.MallConstants; import com.bsd.pjcloud.supermarket.dto.SelectOptionDTO; import com.bsd.pjcloud.supermarket.entity.MallCategoryEntity; import com.bsd.pjcloud.supermarket.entity.MallMerchantEntity; @@ -61,7 +62,7 @@ public class SelectOptionService { public List getCategorySelectList() { return mallCategoryService.listScope() .stream() - .filter(category -> category.getStatus() == 1) // 只返回启用状态的分类 + .filter(category -> category.getStatus() == MallConstants.Status.ENABLED) // 只返回启用状态的分类 .map(category -> new SelectOptionDTO( category.getId(), category.getName(), @@ -89,7 +90,7 @@ public class SelectOptionService { */ private List buildCategorySelectTree(List allCategories, Long parentId, String prefix) { return allCategories.stream() - .filter(category -> category.getStatus() == 1 && + .filter(category -> category.getStatus() == MallConstants.Status.ENABLED && (parentId == null ? category.getParentId() == null : parentId.equals(category.getParentId()))) .map(category -> { String label = prefix + category.getName(); diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java index 021d9c451..b930544c0 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/service/impl/MallMerchantUserServiceImpl.java @@ -72,9 +72,10 @@ public class MallMerchantUserServiceImpl extends ServiceImpl wrapper = Wrappers.lambdaQuery(); - wrapper.eq(MallMerchantUserEntity::getUserId, userId); + wrapper.eq(MallMerchantUserEntity::getMerchantId, merchantId) + .eq(MallMerchantUserEntity::getUserId, userId); return this.remove(wrapper); } @@ -126,13 +127,14 @@ public class MallMerchantUserServiceImpl extends ServiceImpl userIds) { - if (userIds == null || userIds.isEmpty()) { + public int batchUnbindUsersFromMerchant(Long merchantId, List userIds) { + if (merchantId == null || userIds == null || userIds.isEmpty()) { return 0; } LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.in(MallMerchantUserEntity::getUserId, userIds); + wrapper.eq(MallMerchantUserEntity::getMerchantId, merchantId) + .in(MallMerchantUserEntity::getUserId, userIds); return this.remove(wrapper) ? userIds.size() : 0; } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java new file mode 100644 index 000000000..c95fe7513 --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java @@ -0,0 +1,85 @@ +package com.bsd.pjcloud.supermarket.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 商户用户绑定信息VO + * + * @author pjcloud + * @date 2025-01-27 + */ +@Data +@Schema(description = "商户用户绑定信息") +public class MallMerchantUserVo { + + /** + * 主键ID + */ + @Schema(description = "主键ID") + private Long id; + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long merchantId; + + /** + * 用户ID + */ + @Schema(description = "用户ID") + private Long userId; + + /** + * 用户名称 + */ + @Schema(description = "用户名称") + private String name; + + /** + * 用户手机号 + */ + @Schema(description = "用户手机号") + private String phone; + + /** + * 角色类型(1:管理员 2:普通用户) + */ + @Schema(description = "角色类型(1:管理员 2:普通用户)") + private Integer roleType; + + /** + * 角色类型描述 + */ + @Schema(description = "角色类型描述") + private String roleTypeDesc; + + /** + * 状态(1:启用 0:禁用) + */ + @Schema(description = "状态(1:启用 0:禁用)") + private String status; + + /** + * 状态描述 + */ + @Schema(description = "状态描述") + private String statusDesc; + + /** + * 创建时间 + */ + @Schema(description = "创建时间") + private LocalDateTime createTime; + + /** + * 更新时间 + */ + @Schema(description = "更新时间") + private LocalDateTime updateTime; +} + + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java index 5fd0590ba..6b8a45fde 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java @@ -125,3 +125,4 @@ public class MallProductDetailAppVo { @Schema(description = "SKU列表") private List skuList; } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java index 2b95cbfb9..44b489230 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java @@ -105,3 +105,4 @@ public class MallProductListAppVo { @Schema(description = "最低价格") private BigDecimal minPrice; } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java index 26d6adafa..16b68fff9 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java @@ -81,3 +81,4 @@ public class MallProductSkuAppVo { @Schema(description = "状态") private Integer status; } + From 49a5a7161d962de69519aa019ec6a5a539be5be5 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Fri, 5 Sep 2025 19:30:57 +0800 Subject: [PATCH 05/27] =?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 --- .../bsd/pjcloud/supermarket/dto/MallProductCreateDTO.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index f7e2be73f..99a17445e 100644 --- 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 @@ -30,9 +30,9 @@ public class MallProductCreateDTO { /** * 商户ID */ -// @NotNull(message = "商户ID不能为空") -// @Schema(description = "商户ID") -// private Long merchantId; + @NotNull(message = "商户ID不能为空") + @Schema(description = "商户ID") + private Long merchantId; /** * 类目ID From 056bb95229cf116ff7c20bbcac25e14688baf61a Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 8 Sep 2025 15:35:52 +0800 Subject: [PATCH 06/27] =?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 --- .../entity/MallCategoryEntity.java | 6 + .../supermarket/constant/MallConstants.java | 1 + .../controller/MallAppController.java | 242 +++++++++++++++++- .../controller/MallCategoryController.java | 30 ++- .../entity/MallMerchantEntity.java | 6 + .../supermarket/vo/MallMerchantUserVo.java | 1 + .../vo/app/MallMerchantListAppVo.java | 101 ++++++++ .../vo/app/MallProductListAppVo.java | 1 + .../resources/mapper/MallCategoryMapper.xml | 1 + 9 files changed, 370 insertions(+), 19 deletions(-) create mode 100644 pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallMerchantListAppVo.java diff --git a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java index 07bd86854..47fc6e962 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-api/src/main/java/com/bsd/pjcloud/supermarket/entity/MallCategoryEntity.java @@ -113,4 +113,10 @@ public class MallCategoryEntity extends Model { */ @Schema(description="租户编号") private Long tenantId; + + /** + * 商户ID + */ + @Schema(description="商户ID") + private Long merchantId; } \ No newline at end of file diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java index 46d0ddb22..8220d8f7e 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/constant/MallConstants.java @@ -95,3 +95,4 @@ public interface MallConstants { } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java index c336912e4..556b28d0f 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java @@ -27,6 +27,7 @@ import com.bsd.pjcloud.supermarket.vo.mall.MallCategoryTreeVo; import com.bsd.pjcloud.supermarket.vo.app.MallProductListAppVo; import com.bsd.pjcloud.supermarket.vo.app.MallProductDetailAppVo; import com.bsd.pjcloud.supermarket.vo.app.MallProductSkuAppVo; +import com.bsd.pjcloud.supermarket.vo.app.MallMerchantListAppVo; import com.bsd.pjcloud.supermarket.dto.SkuStockChangeDTO; import com.bsd.pjcloud.supermarket.constant.MallConstants; import io.swagger.v3.oas.annotations.Operation; @@ -35,6 +36,7 @@ import lombok.RequiredArgsConstructor; import org.springdoc.core.annotations.ParameterObject; import org.springframework.web.bind.annotation.*; +import java.time.LocalDateTime; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -67,9 +69,12 @@ public class MallAppController { */ @GetMapping("/category/tree") @Operation(summary = "获取分类树", description = "获取所有分类的树结构") - public R> getCategoryTree() { - List all = mallCategoryService.list(Wrappers.lambdaQuery() - .eq(MallCategoryEntity::getStatus, 1)); // 只查启用状态 + public R> getCategoryTree(@RequestParam(required = false) Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallCategoryEntity::getStatus, 1); // 只查启用状态 + // 添加商户ID过滤条件 + wrapper.eq(merchantId != null, MallCategoryEntity::getMerchantId, merchantId); + List all = mallCategoryService.list(wrapper); List tree = buildCategoryTree(all, 0L); return R.ok(tree); } @@ -79,12 +84,13 @@ public class MallAppController { */ @GetMapping("/category/listByParent/{parentId}") @Operation(summary = "根据父级ID获取子分类", description = "根据父级ID获取子分类列表") - public R> getCategoryListByParent(@PathVariable("parentId") Long parentId) { - List list = mallCategoryService.list( - Wrappers.lambdaQuery() - .eq(MallCategoryEntity::getParentId, parentId) - .eq(MallCategoryEntity::getStatus, 1) - ); + public R> getCategoryListByParent(@PathVariable("parentId") Long parentId, @RequestParam(required = false) Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallCategoryEntity::getParentId, parentId); + wrapper.eq(MallCategoryEntity::getStatus, 1); + // 添加商户ID过滤条件 + wrapper.eq(merchantId != null, MallCategoryEntity::getMerchantId, merchantId); + List list = mallCategoryService.list(wrapper); return R.ok(list); } @@ -94,14 +100,112 @@ public class MallAppController { * 获取商户列表 */ @GetMapping("/merchant/list") - @Operation(summary = "获取商户列表", description = "获取所有启用的商户列表") - public R> getMerchantList() { - List list = mallMerchantService.list( + @Operation(summary = "获取商户列表", description = "获取所有启用的商户列表,包含特色商品") + public R> getMerchantList(@RequestParam(defaultValue = "3") Integer productLimit) { + // 查询所有启用的商户 + List merchants = mallMerchantService.list( Wrappers.lambdaQuery() - .eq(MallMerchantEntity::getStatus, 1) + .eq(MallMerchantEntity::getStatus, MallConstants.Status.ENABLED) .orderByAsc(MallMerchantEntity::getName) ); - return R.ok(list); + + if (merchants.isEmpty()) { + return R.ok(List.of()); + } + + // 获取商户ID列表 + List merchantIds = merchants.stream() + .map(MallMerchantEntity::getId) + .collect(Collectors.toList()); + + // 查询每个商户的特色商品(按创建时间倒序,取前N个) + LocalDateTime now = LocalDateTime.now(); + List products = mallProductService.list( + Wrappers.lambdaQuery() + .in(MallProductEntity::getMerchantId, merchantIds) + .eq(MallProductEntity::getStatus, MallConstants.Status.ENABLED) // 只查上架商品 + .and(wrapper -> wrapper + .isNull(MallProductEntity::getSaleStartTime) + .or() + .le(MallProductEntity::getSaleStartTime, now) + ) + .and(wrapper -> wrapper + .isNull(MallProductEntity::getSaleEndTime) + .or() + .ge(MallProductEntity::getSaleEndTime, now) + ) + .orderByDesc(MallProductEntity::getCreateTime) + ); + + // 按商户ID分组商品 + Map> productMap = products.stream() + .collect(Collectors.groupingBy(MallProductEntity::getMerchantId)); + + // 获取商品ID列表,查询SKU信息 + List productIds = products.stream() + .map(MallProductEntity::getId) + .collect(Collectors.toList()); + + List skuList = productIds.isEmpty() ? List.of() : + mallProductSkuService.list( + Wrappers.lambdaQuery() + .in(MallProductSkuEntity::getProductId, productIds) + ); + + // 按商品ID分组SKU + Map> skuMap = skuList.stream() + .collect(Collectors.groupingBy(MallProductSkuEntity::getProductId)); + + // 转换为VO + List result = merchants.stream().map(merchant -> { + MallMerchantListAppVo vo = BeanUtils.toBean(merchant, MallMerchantListAppVo.class); + + // 获取该商户的商品列表 + List merchantProducts = productMap.getOrDefault(merchant.getId(), List.of()); + + // 设置商品总数 + vo.setProductCount(merchantProducts.size()); + + // 获取特色商品(限制数量) + List featuredProducts = merchantProducts.stream() + .limit(productLimit) + .map(product -> { + MallProductListAppVo productVo = BeanUtils.toBean(product, MallProductListAppVo.class); + List skus = skuMap.getOrDefault(product.getId(), List.of()); + + // 计算最低价格 + productVo.setMinPrice(skus.stream() + .map(MallProductSkuEntity::getPrice) + .filter(Objects::nonNull) + .min(java.math.BigDecimal::compareTo) + .orElse(java.math.BigDecimal.ZERO)); + + // 计算总库存 + productVo.setTotalStock(skus.stream() + .map(MallProductSkuEntity::getStock) + .filter(Objects::nonNull) + .mapToInt(Integer::intValue) + .sum()); + + + return productVo; + }) + .collect(Collectors.toList()); + + vo.setFeaturedProducts(featuredProducts); + + // 设置商户营业时间 + if (merchant.getBusinessHours() != null && !merchant.getBusinessHours().trim().isEmpty()) { + vo.setBusinessHours(merchant.getBusinessHours()); + } else { + // 默认商户营业时间 + vo.setBusinessHours("10:00-22:00"); + } + + return vo; + }).collect(Collectors.toList()); + + return R.ok(result); } /** @@ -427,6 +531,116 @@ public class MallAppController { } } + // ==================== 商户分类管理接口 ==================== + + /** + * 商户创建分类 + */ + @PostMapping("/merchant/category/create") + @Operation(summary = "商户创建分类", description = "移动端商户创建分类") + public R createCategory(@RequestBody MallCategoryEntity category) { + try { + Long merchantId = validateMerchant(); + // 自动设置商户ID + category.setMerchantId(merchantId); + category.setDeptId(SecurityUtils.getUser().getDeptId()); + + // 创建分类 + boolean success = mallCategoryService.save(category); + if (success) { + return R.ok("分类创建成功"); + } else { + return R.failed("分类创建失败"); + } + } catch (Exception e) { + return R.failed("分类创建失败:" + e.getMessage()); + } + } + + /** + * 商户更新分类 + */ + @PutMapping("/merchant/category/update") + @Operation(summary = "商户更新分类", description = "移动端商户更新分类") + public R updateCategory(@RequestBody MallCategoryEntity category) { + try { + Long merchantId = validateMerchant(); + // 验证分类权限 + validateCategoryPermission(category.getId(), merchantId); + + // 验证分类ID + if (category.getId() == null) { + return R.failed("分类ID不能为空"); + } + + // 更新分类 + boolean success = mallCategoryService.updateById(category); + if (success) { + return R.ok("分类更新成功"); + } else { + return R.failed("分类更新失败"); + } + } catch (Exception e) { + return R.failed("分类更新失败:" + e.getMessage()); + } + } + + /** + * 商户删除分类 + */ + @DeleteMapping("/merchant/category/{id}") + @Operation(summary = "商户删除分类", description = "移动端商户删除自己的分类") + public R deleteCategory(@PathVariable("id") Long id) { + try { + Long merchantId = validateMerchant(); + validateCategoryPermission(id, merchantId); + + // 删除分类(逻辑删除) + boolean success = mallCategoryService.removeById(id); + if (success) { + return R.ok("分类删除成功"); + } else { + return R.failed("分类删除失败"); + } + } catch (Exception e) { + return R.failed("分类删除失败:" + e.getMessage()); + } + } + + /** + * 商户获取自己的分类列表 + */ + @GetMapping("/merchant/category/list") + @Operation(summary = "商户分类列表", description = "移动端商户获取自己的分类列表") + public R> getMerchantCategoryList(@RequestParam(required = false) Long parentId) { + try { + Long merchantId = validateMerchant(); + + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallCategoryEntity::getMerchantId, merchantId); + wrapper.eq(parentId != null, MallCategoryEntity::getParentId, parentId); + wrapper.orderByAsc(MallCategoryEntity::getSort); + + List list = mallCategoryService.list(wrapper); + return R.ok(list); + } catch (Exception e) { + return R.failed("获取分类列表失败:" + e.getMessage()); + } + } + + /** + * 验证是否有处理该条分类数据的权限 + */ + private void validateCategoryPermission(Long categoryId, Long merchantId) { + MallCategoryEntity category = mallCategoryService.getById(categoryId); + if (category == null) { + throw new BusinessException("分类不存在"); + } + if (!merchantId.equals(category.getMerchantId())) { + throw new BusinessException("无权限处理此分类"); + } + } + // ==================== 库存相关接口 ==================== /** 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 d161fd429..d3d1c6833 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 @@ -52,6 +52,8 @@ public class MallCategoryController { public R getMallCategoryPage(@ParameterObject Page page, @ParameterObject MallCategoryEntity MallCategory) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(StrUtil.isNotBlank(MallCategory.getName()),MallCategoryEntity::getName,MallCategory.getName()); + // 添加商户ID过滤条件 + wrapper.eq(MallCategory.getMerchantId() != null, MallCategoryEntity::getMerchantId, MallCategory.getMerchantId()); return R.ok(MallCategoryService.pageScope(page, wrapper)); } @@ -79,6 +81,13 @@ public class MallCategoryController { @PreAuthorize("@pms.hasPermission('supermarket_mallCategory_add')" ) public R save(@RequestBody MallCategoryEntity MallCategory) { MallCategory.setDeptId(SecurityUtils.getUser().getDeptId()); + // 设置商户ID + if (MallCategory.getMerchantId() == null) { + return R.failed("商户ID不能为空"); + } + // 如果前端没有传商户ID,可以从当前用户信息中获取 + // 这里需要根据实际业务逻辑来设置,可能需要从用户关联的商户信息中获取 + // MallCategory.setMerchantId(getCurrentUserMerchantId()); return R.ok(MallCategoryService.save(MallCategory)); } @@ -119,7 +128,11 @@ public class MallCategoryController { @GetMapping("/export") @PreAuthorize("@pms.hasPermission('supermarket_mallCategory_export')" ) public List export(MallCategoryEntity MallCategory,Long[] ids) { - return MallCategoryService.list(Wrappers.lambdaQuery(MallCategory).in(ArrayUtil.isNotEmpty(ids), MallCategoryEntity::getId, ids)); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(MallCategory); + wrapper.in(ArrayUtil.isNotEmpty(ids), MallCategoryEntity::getId, ids); + // 添加商户ID过滤条件 + wrapper.eq(MallCategory.getMerchantId() != null, MallCategoryEntity::getMerchantId, MallCategory.getMerchantId()); + return MallCategoryService.list(wrapper); } /** @@ -127,8 +140,11 @@ public class MallCategoryController { */ @GetMapping("/tree") @Operation(summary = "分类树", description = "获取所有分类的树结构") - public R> tree() { - List all = MallCategoryService.listScope(); + public R> tree(@RequestParam(required = false) Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + // 添加商户ID过滤条件 + wrapper.eq(merchantId != null, MallCategoryEntity::getMerchantId, merchantId); + List all = MallCategoryService.list(wrapper); List tree = buildCategoryTree(all, 0L); return R.ok(tree); } @@ -150,8 +166,12 @@ public class MallCategoryController { */ @GetMapping("/listByParent/{parentId}") @Operation(summary = "根据父级ID查子分类", description = "根据父级ID查子分类") - public R> listByParent(@PathVariable("parentId") Long parentId) { - List list = MallCategoryService.list(Wrappers.lambdaQuery().eq(MallCategoryEntity::getParentId, parentId)); + public R> listByParent(@PathVariable("parentId") Long parentId, @RequestParam(required = false) Long merchantId) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(MallCategoryEntity::getParentId, parentId); + // 添加商户ID过滤条件 + wrapper.eq(merchantId != null, MallCategoryEntity::getMerchantId, merchantId); + List list = MallCategoryService.list(wrapper); return R.ok(list); } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java index d4267aafd..5f129cc65 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallMerchantEntity.java @@ -89,6 +89,12 @@ public class MallMerchantEntity extends Model { @Schema(description="备注") private String remark; + /** + * 营业时间(格式:HH:mm-HH:mm,如:10:30-22:00) + */ + @Schema(description="营业时间(格式:HH:mm-HH:mm)") + private String businessHours; + /** * 部门ID */ diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java index c95fe7513..f90cc862e 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/MallMerchantUserVo.java @@ -83,3 +83,4 @@ public class MallMerchantUserVo { } + diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallMerchantListAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallMerchantListAppVo.java new file mode 100644 index 000000000..29b63942e --- /dev/null +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallMerchantListAppVo.java @@ -0,0 +1,101 @@ +package com.bsd.pjcloud.supermarket.vo.app; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.util.List; + +/** + * 移动端商户列表VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "移动端商户列表VO") +public class MallMerchantListAppVo { + + /** + * 商户ID + */ + @Schema(description = "商户ID") + private Long id; + + /** + * 商户名称 + */ + @Schema(description = "商户名称") + private String name; + + /** + * 商户简称 + */ + @Schema(description = "商户简称") + private String shortName; + + /** + * 联系人 + */ + @Schema(description = "联系人") + private String contactName; + + /** + * 联系电话 + */ + @Schema(description = "联系电话") + private String contactPhone; + + /** + * 邮箱 + */ + @Schema(description = "邮箱") + private String email; + + /** + * 状态(1启用 0停用) + */ + @Schema(description = "状态") + private String status; + + /** + * 营业执照号 + */ + @Schema(description = "营业执照号") + private String licenseNo; + + /** + * 详细地址 + */ + @Schema(description = "详细地址") + private String address; + + /** + * 商户图片URL(逗号分隔) + */ + @Schema(description = "商户图片URL") + private String images; + + /** + * 备注 + */ + @Schema(description = "备注") + private String remark; + + /** + * 特色商品列表(最多显示3个) + */ + @Schema(description = "特色商品列表") + private List featuredProducts; + + /** + * 商品总数 + */ + @Schema(description = "商品总数") + private Integer productCount; + + /** + * 商户营业时间(格式:HH:mm-HH:mm,如:10:30-22:00) + */ + @Schema(description = "商户营业时间") + private String businessHours; +} diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java index 44b489230..86e0bea61 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java @@ -104,5 +104,6 @@ public class MallProductListAppVo { */ @Schema(description = "最低价格") private BigDecimal minPrice; + } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/MallCategoryMapper.xml b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/MallCategoryMapper.xml index e89e9fa33..bd8c249b9 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/MallCategoryMapper.xml +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/resources/mapper/MallCategoryMapper.xml @@ -18,5 +18,6 @@ + \ No newline at end of file From 5968204663ce1675b256820f6aa1ca8e5ef84230 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 8 Sep 2025 15:43:09 +0800 Subject: [PATCH 07/27] =?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 --- .../service/MallProductServiceTest.java | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) 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 index 89fa2b0df..59ee4c6cb 100644 --- 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 @@ -3,10 +3,10 @@ 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.beans.factory.annotation.Autowired; 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; @@ -21,7 +21,7 @@ import java.util.Arrays; @ActiveProfiles("test") public class MallProductServiceTest { - @Resource + @Autowired private MallProductService mallProductService; @Test @@ -38,11 +38,10 @@ public class MallProductServiceTest { productCreateDTO.setPriceType(1); // 多SKU模式 productCreateDTO.setOriginalPrice(new BigDecimal("999.00")); productCreateDTO.setUnit("台"); - productCreateDTO.setStatus(1); + productCreateDTO.setStatus("0"); productCreateDTO.setSaleStartTime(LocalDateTime.now()); productCreateDTO.setSaleEndTime(LocalDateTime.now().plusYears(1)); productCreateDTO.setDescription("测试商品描述"); - productCreateDTO.setDeptId(1L); // 创建SKU列表 MallProductSkuCreateDTO sku1 = new MallProductSkuCreateDTO(); @@ -54,8 +53,7 @@ public class MallProductServiceTest { sku1.setBarcode("1234567890123"); sku1.setWeightG(200); sku1.setVolumeCm3(300); - sku1.setStatus(1); - sku1.setDeptId(1L); + sku1.setStatus("1"); MallProductSkuCreateDTO sku2 = new MallProductSkuCreateDTO(); sku2.setSkuCode("TEST-SKU-002"); @@ -66,8 +64,7 @@ public class MallProductServiceTest { sku2.setBarcode("1234567890124"); sku2.setWeightG(250); sku2.setVolumeCm3(350); - sku2.setStatus(1); - sku2.setDeptId(1L); + sku2.setStatus("1"); productCreateDTO.setSkuList(Arrays.asList(sku1, sku2)); @@ -91,12 +88,10 @@ public class MallProductServiceTest { productCreateDTO.setSpuPrice(new BigDecimal("599.00")); productCreateDTO.setOriginalPrice(new BigDecimal("999.00")); productCreateDTO.setUnit("台"); - productCreateDTO.setStatus(1); + 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 : "商品创建应该成功"; From 1699a33b1e8d8ef6bddf080cb2a2d734b6099e48 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Mon, 8 Sep 2025 18:37:54 +0800 Subject: [PATCH 08/27] =?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 --- .../controller/MallAppController.java | 24 +++++++++---------- .../supermarket/entity/MallProductEntity.java | 14 +++++------ .../entity/MallProductSkuEntity.java | 14 +++++------ .../vo/app/MallProductDetailAppVo.java | 14 +++++------ .../vo/app/MallProductListAppVo.java | 20 ++++++++-------- .../vo/app/MallProductSkuAppVo.java | 14 +++++------ 6 files changed, 50 insertions(+), 50 deletions(-) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java index 556b28d0f..381463fcf 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java @@ -153,7 +153,7 @@ public class MallAppController { ); // 按商品ID分组SKU - Map> skuMap = skuList.stream() + Map> skuMap = skuList.stream() .collect(Collectors.groupingBy(MallProductSkuEntity::getProductId)); // 转换为VO @@ -177,14 +177,14 @@ public class MallAppController { productVo.setMinPrice(skus.stream() .map(MallProductSkuEntity::getPrice) .filter(Objects::nonNull) - .min(java.math.BigDecimal::compareTo) - .orElse(java.math.BigDecimal.ZERO)); + .min(Integer::compareTo) + .orElse(0)); // 计算总库存 productVo.setTotalStock(skus.stream() .map(MallProductSkuEntity::getStock) .filter(Objects::nonNull) - .mapToInt(Integer::intValue) + .mapToDouble(Double::doubleValue) .sum()); @@ -232,13 +232,13 @@ public class MallAppController { @ParameterObject Page page, @RequestParam(required = false) Long categoryId, @RequestParam(required = false) String keyword, - @RequestParam(required = false) Long merchantId + Long merchantId ) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(categoryId != null, MallProductEntity::getCategoryId, categoryId); wrapper.like(StrUtil.isNotBlank(keyword), MallProductEntity::getTitle, keyword); wrapper.eq(merchantId != null, MallProductEntity::getMerchantId, merchantId); - wrapper.eq(MallProductEntity::getStatus, 1); // 只查上架商品 + wrapper.eq(MallProductEntity::getStatus, MallConstants.Status.ENABLED); // 只查上架商品 IPage productPage = mallProductService.page(page, wrapper); @@ -267,14 +267,14 @@ public class MallAppController { vo.setMinPrice(skus.stream() .map(MallProductSkuEntity::getPrice) .filter(Objects::nonNull) - .min(java.math.BigDecimal::compareTo) - .orElse(java.math.BigDecimal.ZERO)); + .min(Integer::compareTo) + .orElse(0)); // 计算总库存 vo.setTotalStock(skus.stream() .map(MallProductSkuEntity::getStock) .filter(Objects::nonNull) - .mapToInt(Integer::intValue) + .mapToDouble(Double::doubleValue) .sum()); return vo; @@ -490,14 +490,14 @@ public class MallAppController { vo.setMinPrice(skus.stream() .map(MallProductSkuEntity::getPrice) .filter(Objects::nonNull) - .min(java.math.BigDecimal::compareTo) - .orElse(java.math.BigDecimal.ZERO)); + .min(Integer::compareTo) + .orElse(0)); // 计算总库存 vo.setTotalStock(skus.stream() .map(MallProductSkuEntity::getStock) .filter(Objects::nonNull) - .mapToInt(Integer::intValue) + .mapToDouble(Double::doubleValue) .sum()); return vo; diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java index 7ff265f53..51cdbfff5 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductEntity.java @@ -79,22 +79,22 @@ public class MallProductEntity extends Model { private Integer priceType; /** - * SPU售价(当price_type=2有效) + * SPU售价(当price_type=2有效,单位:分) */ - @Schema(description="SPU售价(当price_type=2有效)") - private BigDecimal spuPrice; + @Schema(description="SPU售价(当price_type=2有效,单位:分)") + private Integer spuPrice; /** - * 划线价 + * 划线价(单位:分) */ - @Schema(description="划线价") - private BigDecimal originalPrice; + @Schema(description="划线价(单位:分)") + private Integer originalPrice; /** * 总库存(冗余) */ @Schema(description="总库存(冗余)") - private Integer totalStock; + private Double totalStock; /** * 计量单位 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java index 48243a29f..f2ef60795 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/entity/MallProductSkuEntity.java @@ -49,22 +49,22 @@ public class MallProductSkuEntity extends Model { private String skuName; /** - * 售价 + * 售价(单位:分) */ - @Schema(description="售价") - private BigDecimal price; + @Schema(description="售价(单位:分)") + private Integer price; /** - * 成本价 + * 成本价(单位:分) */ - @Schema(description="成本价") - private BigDecimal costPrice; + @Schema(description="成本价(单位:分)") + private Integer costPrice; /** * 库存 */ @Schema(description="库存") - private Integer stock; + private Double stock; /** * 条码 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java index 6b8a45fde..4a84b61d9 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductDetailAppVo.java @@ -72,22 +72,22 @@ public class MallProductDetailAppVo { private Integer priceType; /** - * SPU售价 + * SPU售价(单位:分) */ - @Schema(description = "SPU售价") - private BigDecimal spuPrice; + @Schema(description = "SPU售价(单位:分)") + private Integer spuPrice; /** - * 划线价 + * 划线价(单位:分) */ - @Schema(description = "划线价") - private BigDecimal originalPrice; + @Schema(description = "划线价(单位:分)") + private Integer originalPrice; /** * 总库存 */ @Schema(description = "总库存") - private Integer totalStock; + private Double totalStock; /** * 计量单位 diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java index 86e0bea61..5d83cc422 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductListAppVo.java @@ -70,22 +70,22 @@ public class MallProductListAppVo { private Integer priceType; /** - * SPU售价 + * SPU售价(单位:分) */ - @Schema(description = "SPU售价") - private BigDecimal spuPrice; + @Schema(description = "SPU售价(单位:分)") + private Integer spuPrice; /** - * 划线价 + * 划线价(单位:分) */ - @Schema(description = "划线价") - private BigDecimal originalPrice; + @Schema(description = "划线价(单位:分)") + private Integer originalPrice; /** * 总库存 */ @Schema(description = "总库存") - private Integer totalStock; + private Double totalStock; /** * 计量单位 @@ -100,10 +100,10 @@ public class MallProductListAppVo { private Integer status; /** - * 最低价格(从SKU中计算) + * 最低价格(从SKU中计算,单位:分) */ - @Schema(description = "最低价格") - private BigDecimal minPrice; + @Schema(description = "最低价格(单位:分)") + private Integer minPrice; } diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java index 16b68fff9..fce283f81 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/vo/app/MallProductSkuAppVo.java @@ -40,22 +40,22 @@ public class MallProductSkuAppVo { private String skuName; /** - * 售价 + * 售价(单位:分) */ - @Schema(description = "售价") - private BigDecimal price; + @Schema(description = "售价(单位:分)") + private Integer price; /** - * 成本价 + * 成本价(单位:分) */ - @Schema(description = "成本价") - private BigDecimal costPrice; + @Schema(description = "成本价(单位:分)") + private Integer costPrice; /** * 库存 */ @Schema(description = "库存") - private Integer stock; + private Double stock; /** * 条码 From fa8f937b145598db4daaea96b723280626999709 Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Tue, 9 Sep 2025 11:29:15 +0800 Subject: [PATCH 09/27] =?UTF-8?q?=E4=BB=B7=E6=A0=BC=E4=BF=AE=E6=94=B9int?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../supermarket/dto/MallProductCreateDTO.java | 4 ++-- .../supermarket/dto/MallProductSkuCreateDTO.java | 6 +++--- .../service/impl/MallProductServiceImpl.java | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) 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 index 99a17445e..6908008e1 100644 --- 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 @@ -83,13 +83,13 @@ public class MallProductCreateDTO { * SPU售价(当price_type=2有效) */ @Schema(description = "SPU售价(当price_type=2有效)") - private BigDecimal spuPrice; + private Integer spuPrice; /** * 划线价 */ @Schema(description = "划线价") - private BigDecimal originalPrice; + private Integer originalPrice; /** * 计量单位 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 index 45a10ffde..23632de36 100644 --- 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 @@ -36,20 +36,20 @@ public class MallProductSkuCreateDTO { */ @NotNull(message = "售价不能为空") @Schema(description = "售价") - private BigDecimal price; + private Integer price; /** * 成本价 */ @Schema(description = "成本价") - private BigDecimal costPrice; + private Integer costPrice; /** * 库存 */ @NotNull(message = "库存不能为空") @Schema(description = "库存") - private Integer stock; + private Double stock; /** * 条码 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 31e53649c..10dbb1724 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 @@ -88,18 +88,18 @@ public class MallProductServiceImpl extends ServiceImpl sku.getStock() != null ? sku.getStock() : 0) + Double totalStock = skuList.stream() + .mapToDouble(sku -> sku.getStock() != null ? sku.getStock() : 0D) .sum(); product.setTotalStock(totalStock); this.updateById(product); } else if (productCreateDTO.getPriceType() == 2) { // 单价SPU模式,设置总库存为0(或根据业务需求设置) - product.setTotalStock(0); + product.setTotalStock(0D); this.updateById(product); } else { // 没有SKU信息,设置总库存为0 - product.setTotalStock(0); + product.setTotalStock(0D); this.updateById(product); } @@ -151,8 +151,8 @@ public class MallProductServiceImpl extends ServiceImpllambdaQuery() .eq(MallProductSkuEntity::getProductId, product.getId()) ); - int totalStock = currentSkus.stream() - .mapToInt(sku -> sku.getStock() != null ? sku.getStock() : 0) + Double totalStock = currentSkus.stream() + .mapToDouble(sku -> sku.getStock() != null ? sku.getStock() : 0D) .sum(); product.setTotalStock(totalStock); this.updateById(product); @@ -162,7 +162,7 @@ public class MallProductServiceImpl extends ServiceImpl Date: Tue, 9 Sep 2025 15:44:45 +0800 Subject: [PATCH 10/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bsd/pjcloud/supermarket/controller/MallAppController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java index 381463fcf..e199ea3b3 100644 --- a/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java +++ b/pjcloud-supermarket/pjcloud-supermarket-biz/src/main/java/com/bsd/pjcloud/supermarket/controller/MallAppController.java @@ -71,7 +71,7 @@ public class MallAppController { @Operation(summary = "获取分类树", description = "获取所有分类的树结构") public R> getCategoryTree(@RequestParam(required = false) Long merchantId) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(MallCategoryEntity::getStatus, 1); // 只查启用状态 + wrapper.eq(MallCategoryEntity::getStatus, MallConstants.Status.ENABLED); // 只查启用状态 // 添加商户ID过滤条件 wrapper.eq(merchantId != null, MallCategoryEntity::getMerchantId, merchantId); List all = mallCategoryService.list(wrapper); @@ -87,7 +87,7 @@ public class MallAppController { public R> getCategoryListByParent(@PathVariable("parentId") Long parentId, @RequestParam(required = false) Long merchantId) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(MallCategoryEntity::getParentId, parentId); - wrapper.eq(MallCategoryEntity::getStatus, 1); + wrapper.eq(MallCategoryEntity::getStatus, MallConstants.Status.ENABLED); // 添加商户ID过滤条件 wrapper.eq(merchantId != null, MallCategoryEntity::getMerchantId, merchantId); List list = mallCategoryService.list(wrapper); From 7bfd882c836b60f1fd68cff63d5df1e685f3884e Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Tue, 9 Sep 2025 15:50:47 +0800 Subject: [PATCH 11/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=88=86=E7=B1=BB?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/MallProductServiceTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 index 59ee4c6cb..8f9ade4b5 100644 --- 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 @@ -36,7 +36,7 @@ public class MallProductServiceTest { 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.setOriginalPrice(1); productCreateDTO.setUnit("台"); productCreateDTO.setStatus("0"); productCreateDTO.setSaleStartTime(LocalDateTime.now()); @@ -47,9 +47,9 @@ public class MallProductServiceTest { 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.setPrice(1); + sku1.setCostPrice(1); + sku1.setStock(1D); sku1.setBarcode("1234567890123"); sku1.setWeightG(200); sku1.setVolumeCm3(300); @@ -58,9 +58,9 @@ public class MallProductServiceTest { 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.setPrice(1); + sku2.setCostPrice(1); + sku2.setStock(50D); sku2.setBarcode("1234567890124"); sku2.setWeightG(250); sku2.setVolumeCm3(350); @@ -85,8 +85,8 @@ public class MallProductServiceTest { 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.setSpuPrice(1); + productCreateDTO.setOriginalPrice(1); productCreateDTO.setUnit("台"); productCreateDTO.setStatus("1"); productCreateDTO.setSaleStartTime(LocalDateTime.now()); From bb313806713327f5905ba84ffdac2c15a8ced33d Mon Sep 17 00:00:00 2001 From: zjf <1799650030@qq.com> Date: Wed, 10 Sep 2025 17:00:55 +0800 Subject: [PATCH 12/27] =?UTF-8?q?=E4=BC=9A=E8=AE=AE=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=AE=8C=E5=96=84=EF=BC=8C=E4=BF=AE=E5=A4=8D=E5=85=B1=E5=AF=8C?= =?UTF-8?q?=E5=95=86=E6=88=B7=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/meeting/MeetingBookingSaveDto.java | 6 + .../vo/clean/CleanUserDropdownVo.java | 29 +++ .../vo/meeting/MeetingBookingPageVo.java | 6 + .../MEETING_BOOKING_FLOW_EXAMPLE.md | 228 ++++++++++++++++++ .../MEETING_DESK_LAYOUT_API.md | 135 +++++++++++ .../constant/IntegratedConstants.java | 75 ++++++ .../controller/CleanUserController.java | 30 ++- .../controller/MeetingBookingController.java | 7 +- .../MeetingDeskLayoutController.java | 143 +++++++++++ .../controller/MeetingRoomController.java | 3 +- .../entity/MeetingBookingEntity.java | 12 + .../entity/MeetingDeskLayoutEntity.java | 133 ++++++++++ .../entity/MeetingRoomEquipmentEntity.java | 2 +- .../mapper/MeetingDeskLayoutMapper.java | 11 + .../service/MeetingDeskLayoutService.java | 8 + .../impl/MeetingDeskLayoutServiceImpl.java | 16 ++ .../integrated/vo/MeetingDeskLayoutAppVO.java | 56 +++++ .../resources/mapper/MeetingBookingMapper.xml | 2 + .../mapper/MeetingDeskLayoutMapper.xml | 25 ++ .../controller/MallAppController.java | 4 +- 20 files changed, 920 insertions(+), 11 deletions(-) create mode 100644 pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/clean/CleanUserDropdownVo.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/MEETING_BOOKING_FLOW_EXAMPLE.md create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/MEETING_DESK_LAYOUT_API.md create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/constant/IntegratedConstants.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingDeskLayoutController.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingDeskLayoutEntity.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/mapper/MeetingDeskLayoutMapper.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/MeetingDeskLayoutService.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/impl/MeetingDeskLayoutServiceImpl.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/vo/MeetingDeskLayoutAppVO.java create mode 100644 pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/mapper/MeetingDeskLayoutMapper.xml diff --git a/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/dto/meeting/MeetingBookingSaveDto.java b/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/dto/meeting/MeetingBookingSaveDto.java index 0f4313050..4a29d1231 100644 --- a/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/dto/meeting/MeetingBookingSaveDto.java +++ b/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/dto/meeting/MeetingBookingSaveDto.java @@ -22,4 +22,10 @@ public class MeetingBookingSaveDto { @Schema(description = "结束时间,格式HH:mm") private String endTime; + + @Schema(description = "是否需要会务服务(0=不需要 1=需要)") + private String needService; + + @Schema(description = "桌子摆放形态") + private String deskLayout; } \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/clean/CleanUserDropdownVo.java b/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/clean/CleanUserDropdownVo.java new file mode 100644 index 000000000..3391008b4 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/clean/CleanUserDropdownVo.java @@ -0,0 +1,29 @@ +package com.bsd.pjcloud.integrated.vo.clean; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 保洁用户下拉列表VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "保洁用户下拉列表") +public class CleanUserDropdownVo { + + @Schema(description = "id") + private Long id; + + @Schema(description = "姓名") + private String name; + + @Schema(description = "手机号") + private String phone; + + + @Schema(description = "1保洁阿姨,2会务人员") + private String type; +} + diff --git a/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/meeting/MeetingBookingPageVo.java b/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/meeting/MeetingBookingPageVo.java index c089a6306..f429deb25 100644 --- a/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/meeting/MeetingBookingPageVo.java +++ b/pjcloud-integrated/pjcloud-integrated-api/src/main/java/com/bsd/pjcloud/integrated/vo/meeting/MeetingBookingPageVo.java @@ -49,4 +49,10 @@ public class MeetingBookingPageVo { @Schema(description = "审核原因") private String checkReason; + + @Schema(description = "是否需要会务服务(0=不需要 1=需要)") + private String needService; + + @Schema(description = "桌子摆放形态") + private String deskLayout; } \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/MEETING_BOOKING_FLOW_EXAMPLE.md b/pjcloud-integrated/pjcloud-integrated-biz/MEETING_BOOKING_FLOW_EXAMPLE.md new file mode 100644 index 000000000..22bf7d6ed --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/MEETING_BOOKING_FLOW_EXAMPLE.md @@ -0,0 +1,228 @@ +# 会议室预约流程动态审批人实现示例 + +## 📋 概述 + +本文档展示如何在会议室预约的save方法中实现根据会议室管理员字段动态确定审批人的功能。 + +## 🔧 核心修改 + +### 1. 添加必要的依赖注入 + +```java +@RestController +@RequiredArgsConstructor +public class MeetingBookingController { + + private final MeetingBookingService meetingBookingService; + private final MeetingRoomService meetingRoomService; + private final RemoteFlowApiFlowService remoteFlowApiFlowService; + private final RemoteUserService remoteUserService; // 新增:用于获取用户信息 +} +``` + +### 2. 修改save方法中的流程启动部分 + +```java +@PostMapping +@Transactional +public R save(@RequestBody MeetingBookingSaveDto dto) { + // ... 前面的验证逻辑保持不变 ... + + // 7. 发起流程 + var processByBusinessKey = remoteFlowApiFlowService.getProcessByBusinessKey(IntegratedFlowConstant.INTEGRATED_MEETING_BOOKING); + if (!processByBusinessKey.isOk() || processByBusinessKey.getData() == null) { + throw new ServiceException("流程配置获取失败: " + processByBusinessKey.getMsg()); + } + Process process = processByBusinessKey.getData(); + + // 构建流程参数 + MeetingFlowDto meetingFlowDto = new MeetingFlowDto(); + meetingFlowDto.setRoomName(room.getRoomName()); + meetingFlowDto.setApplicantName(SecurityUtils.getUser().getUsername()); + meetingFlowDto.setMeetingTime(bookingDate + " " + startTime + "-" + endTime); + Map stringObjectMap = BeanUtil.beanToMap(meetingFlowDto); + + // 🔥 关键:动态设置审批人 - 根据会议室管理员 + List approvers = determineApproversByRoomAdmin(room); + if (!approvers.isEmpty()) { + // 注意:这里的 "approval_node_1" 需要根据实际流程设计中的节点ID调整 + stringObjectMap.put("approval_node_1_assignee_select", approvers); + } + + ProcessInstanceParamDto processInstanceParamDto = new ProcessInstanceParamDto(); + processInstanceParamDto.setFlowId(process.getFlowId()); + processInstanceParamDto.setParamMap(stringObjectMap); + var stringR = remoteFlowApiFlowService.startProcessInstance(processInstanceParamDto); + + // ... 后续逻辑保持不变 ... +} +``` + +### 3. 实现动态审批人确定方法 + +```java +/** + * 根据会议室管理员确定审批人 + * @param room 会议室信息 + * @return 审批人列表 + */ +private List determineApproversByRoomAdmin(MeetingRoomEntity room) { + List approvers = new ArrayList<>(); + + // 1. 优先使用会议室指定的管理员 + if (room.getAdminId() != null) { + try { + // 获取管理员用户信息 + R> userResult = remoteUserService.getUserById(Arrays.asList(room.getAdminId())); + if (userResult.isOk() && !userResult.getData().isEmpty()) { + SysUser adminUser = userResult.getData().get(0); + NodeUser approver = NodeUser.builder() + .id(adminUser.getUserId()) + .name(adminUser.getName()) + .type("user") + .selected(true) + .avatar(adminUser.getAvatar()) + .build(); + approvers.add(approver); + } + } catch (Exception e) { + log.error("获取会议室管理员信息失败: {}", e.getMessage()); + } + } + + // 2. 如果会议室没有管理员,使用部门经理 + if (approvers.isEmpty() && room.getDeptId() != null) { + approvers.addAll(getDeptManagerAsApprovers(room.getDeptId())); + } + + // 3. 如果仍然没有审批人,使用默认审批人 + if (approvers.isEmpty()) { + approvers.addAll(getDefaultApprovers()); + } + + return approvers; +} + +/** + * 获取部门经理作为审批人 + */ +private List getDeptManagerAsApprovers(Long deptId) { + List approvers = new ArrayList<>(); + try { + // 这里需要根据实际的部门服务接口获取部门经理 + // 示例:假设有部门服务可以获取部门经理 + /* + R deptResult = remoteDeptService.getDeptById(deptId); + if (deptResult.isOk() && deptResult.getData() != null) { + SysDept dept = deptResult.getData(); + Long managerId = dept.getManagerId(); + if (managerId != null) { + R> userResult = remoteUserService.getUserById(Arrays.asList(managerId)); + if (userResult.isOk() && !userResult.getData().isEmpty()) { + SysUser manager = userResult.getData().get(0); + NodeUser approver = NodeUser.builder() + .id(manager.getUserId()) + .name(manager.getName()) + .type("user") + .selected(true) + .avatar(manager.getAvatar()) + .build(); + approvers.add(approver); + } + } + } + */ + } catch (Exception e) { + log.error("获取部门经理信息失败: {}", e.getMessage()); + } + return approvers; +} + +/** + * 获取默认审批人 + */ +private List getDefaultApprovers() { + List approvers = new ArrayList<>(); + try { + // 设置系统管理员作为默认审批人 + Long systemAdminId = 1L; // 需要替换为实际的系统管理员ID + NodeUser defaultApprover = NodeUser.builder() + .id(systemAdminId) + .name("系统管理员") + .type("user") + .selected(true) + .build(); + approvers.add(defaultApprover); + } catch (Exception e) { + log.error("获取默认审批人失败: {}", e.getMessage()); + } + return approvers; +} +``` + +## 🎯 关键要点 + +### 1. 流程节点ID配置 +```java +// 这个key的格式是:{节点ID}_assignee_select +stringObjectMap.put("approval_node_1_assignee_select", approvers); +``` +- `approval_node_1` 需要替换为实际流程设计中的审批节点ID +- 节点ID可以在流程设计器中查看 + +### 2. NodeUser对象构建 +```java +NodeUser approver = NodeUser.builder() + .id(adminUser.getUserId()) // 用户ID + .name(adminUser.getName()) // 用户姓名 + .type("user") // 用户类型,固定为"user" + .selected(true) // 是否选中 + .avatar(adminUser.getAvatar()) // 用户头像(可选) + .build(); +``` + +### 3. 审批人优先级 +1. **会议室管理员** - 最高优先级 +2. **部门经理** - 次优先级 +3. **默认审批人** - 兜底方案 + +### 4. 错误处理 +- 所有获取用户信息的操作都有异常处理 +- 确保即使获取审批人失败,流程也能正常启动 +- 使用日志记录错误信息,便于排查问题 + +## 🔄 流程设计配置 + +在流程设计器中,需要将审批节点配置为: +- **分配类型**: `SELF_SELECT` (发起人自选) +- **节点ID**: 与代码中的key保持一致 +- **多实例模式**: 根据需要选择会签/或签/顺签 + +## 📝 使用示例 + +```java +// 会议室预约时,系统会自动: +// 1. 检查会议室是否有管理员 +// 2. 如果有管理员,使用管理员作为审批人 +// 3. 如果没有管理员,使用部门经理 +// 4. 如果都没有,使用系统管理员 + +// 流程启动后,审批人会收到待办任务 +// 审批人可以在流程中看到会议预约的详细信息 +``` + +## ⚠️ 注意事项 + +1. **节点ID一致性**: 确保代码中的节点ID与流程设计中的节点ID一致 +2. **用户服务可用性**: 确保用户服务接口可用,避免获取用户信息失败 +3. **权限控制**: 确保选择的审批人有相应的审批权限 +4. **测试验证**: 建议在测试环境先验证流程是否正常工作 + +## 🚀 扩展功能 + +可以根据业务需求进一步扩展: + +1. **多级审批**: 根据会议室等级设置多级审批 +2. **时间限制**: 根据预约时间长度确定审批人 +3. **部门规则**: 根据部门特殊规则确定审批人 +4. **用户角色**: 根据用户角色确定审批人 diff --git a/pjcloud-integrated/pjcloud-integrated-biz/MEETING_DESK_LAYOUT_API.md b/pjcloud-integrated/pjcloud-integrated-biz/MEETING_DESK_LAYOUT_API.md new file mode 100644 index 000000000..004e315cb --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/MEETING_DESK_LAYOUT_API.md @@ -0,0 +1,135 @@ +# 移动端摆放形式选择接口使用说明 + +## 📋 概述 + +本文档介绍移动端预约课程时选择摆放形式的接口使用方法。 + +## 🌐 接口信息 + +### 获取摆放形式列表接口 + +- **接口地址**: `GET /meetingDeskLayout/app/list` +- **请求方式**: GET +- **认证方式**: 无需认证(移动端公开接口) +- **数据格式**: JSON + +## 📡 响应格式 + +### 成功响应 +```json +{ + "code": 0, + "msg": "操作成功", + "data": [ + { + "id": 1, + "layoutName": "U型摆放", + "layoutImage": "https://example.com/images/u-layout.jpg", + "capacityMin": 8, + "capacityMax": 12, + "description": "适合小组讨论的U型摆放方式" + }, + { + "id": 2, + "layoutName": "课桌式摆放", + "layoutImage": "https://example.com/images/classroom-layout.jpg", + "capacityMin": 20, + "capacityMax": 50, + "description": "传统课桌式摆放,适合培训课程" + }, + { + "id": 3, + "layoutName": "圆桌式摆放", + "layoutImage": "https://example.com/images/round-table-layout.jpg", + "capacityMin": 6, + "capacityMax": 10, + "description": "圆桌式摆放,促进互动交流" + } + ] +} +``` + +### 字段说明 + +| 字段名 | 类型 | 说明 | +|--------|------|------| +| id | Long | 摆放形式ID | +| layoutName | String | 摆放形式名称 | +| layoutImage | String | 摆放形式示意图URL | +| capacityMin | Integer | 最小容纳人数 | +| capacityMax | Integer | 最大容纳人数 | +| description | String | 摆放形式描述 | + +## 🔍 业务逻辑说明 + +1. **数据过滤**: 只返回状态为"可用"(isAvailable = "0")的摆放形式 +2. **排序规则**: 按照sortOrder字段升序排列 +3. **数据精简**: 只返回移动端需要的核心字段,减少数据传输量 + +## 📱 使用示例 + +### JavaScript 调用示例 +```javascript +fetch('/meetingDeskLayout/app/list', { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } +}) +.then(response => response.json()) +.then(data => { + if (data.code === 0) { + // 处理摆放形式数据 + const layouts = data.data; + layouts.forEach(layout => { + console.log(`摆放形式: ${layout.layoutName}, 容纳人数: ${layout.capacityMin}-${layout.capacityMax}人`); + }); + } +}) +.catch(error => { + console.error('获取摆放形式失败:', error); +}); +``` + +### React Native 调用示例 +```javascript +const fetchDeskLayouts = async () => { + try { + const response = await fetch('/meetingDeskLayout/app/list'); + const result = await response.json(); + + if (result.code === 0) { + return result.data; + } else { + throw new Error(result.msg); + } + } catch (error) { + console.error('获取摆放形式失败:', error); + return []; + } +}; +``` + +### cURL 调用示例 +```bash +curl -X GET "http://your-domain/meetingDeskLayout/app/list" \ + -H "Content-Type: application/json" +``` + +## 🎯 应用场景 + +1. **课程预约**: 用户在预约课程时选择适合的摆放形式 +2. **会议室配置**: 根据参会人数推荐合适的摆放形式 +3. **活动策划**: 根据活动类型选择合适的桌椅摆放方式 + +## ⚠️ 注意事项 + +1. **数据一致性**: 接口返回的数据与后台管理系统的摆放形式数据保持一致 +2. **图片加载**: layoutImage字段可能为空,前端需要处理图片加载失败的情况 +3. **容量限制**: 建议根据实际参会人数选择合适的摆放形式 +4. **缓存策略**: 建议前端对摆放形式数据进行适当缓存,减少重复请求 + +## 🔄 版本更新 + +- **v1.0.0** (2025-01-27): 初始版本,提供基本的摆放形式选择功能 + diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/constant/IntegratedConstants.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/constant/IntegratedConstants.java new file mode 100644 index 000000000..f6616592d --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/constant/IntegratedConstants.java @@ -0,0 +1,75 @@ +package com.bsd.pjcloud.integrated.constant; + +/** + * 商城相关常量 + * + * @author pjcloud + * @date 2025-01-27 + */ +public interface IntegratedConstants { + + /** + * 会议保洁/会务人员用户类型 + */ + interface MeetingUserType { + /** + * 启用状态 + */ + String BJ_USER = "1"; + + /** + * 禁用状态 + */ + String HW_USER = "2"; + + } + + /** + * 角色相关常量 + */ + interface RoleCode { + /** + * 商户用户角色 + */ + String MERCHANT_USER = "MALL_MCH"; + + /** + * 超市管理员角色 + */ + String SUPERMARKET_ADMIN = "SUPERMARKET_ADMIN"; + } + + + /** + * 商户相关常量 + */ + interface Merchant { + /** + * 商户状态 - 正常 + */ + String STATUS_NORMAL = "1"; + + /** + * 商户状态 - 停用 + */ + String STATUS_DISABLED = "0"; + } + + /** + * 删除标记 + */ + interface DelFlag { + /** + * 正常 + */ + String NORMAL = "0"; + + /** + * 删除 + */ + String DELETED = "1"; + } +} + + + diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/CleanUserController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/CleanUserController.java index 14747104b..31ea01dea 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/CleanUserController.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/CleanUserController.java @@ -7,10 +7,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.core.util.BeanUtils; import com.bsd.pjcloud.common.log.annotation.SysLog; import com.bsd.pjcloud.common.security.util.SecurityUtils; import com.bsd.pjcloud.integrated.entity.CleanUserEntity; import com.bsd.pjcloud.integrated.service.CleanUserService; +import com.bsd.pjcloud.integrated.vo.clean.CleanUserDropdownVo; import org.springframework.security.access.prepost.PreAuthorize; import com.bsd.pjcloud.common.excel.annotation.ResponseExcel; import io.swagger.v3.oas.annotations.security.SecurityRequirement; @@ -52,6 +54,7 @@ public class CleanUserController { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(StrUtil.isNotBlank(cleanUser.getName()),CleanUserEntity::getName,cleanUser.getName()); wrapper.eq(StrUtil.isNotBlank(cleanUser.getPhone()),CleanUserEntity::getPhone,cleanUser.getPhone()); + wrapper.eq(CleanUserEntity::getType, cleanUser.getType()); return R.ok(cleanUserService.page(page, wrapper)); } @@ -105,6 +108,15 @@ public class CleanUserController { @DeleteMapping @PreAuthorize("@pms.hasPermission('integrated_cleanUser_del')" ) public R removeById(@RequestBody Long[] ids) { + // 验证要删除的记录是否属于指定的type + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.in(CleanUserEntity::getId, CollUtil.toList(ids)); + List validRecords = cleanUserService.list(wrapper); + + if (validRecords.size() != ids.length) { + return R.failed("部分记录不属于指定类型,无法删除"); + } + return R.ok(cleanUserService.removeBatchByIds(CollUtil.toList(ids))); } @@ -113,13 +125,18 @@ public class CleanUserController { * 导出excel 表格 * @param cleanUser 查询条件 * @param ids 导出指定ID + * @param type 类型:1保洁阿姨,2会务人员 * @return excel 文件流 */ @ResponseExcel @GetMapping("/export") @PreAuthorize("@pms.hasPermission('integrated_cleanUser_export')" ) - public List export(CleanUserEntity cleanUser,Long[] ids) { - return cleanUserService.list(Wrappers.lambdaQuery(cleanUser).in(ArrayUtil.isNotEmpty(ids), CleanUserEntity::getId, ids)); + public List export(CleanUserEntity cleanUser, Long[] ids, + @RequestParam(value = "type", required = false) String type) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(cleanUser); + wrapper.in(ArrayUtil.isNotEmpty(ids), CleanUserEntity::getId, ids); + wrapper.eq(StrUtil.isNotBlank(type), CleanUserEntity::getType, type); + return cleanUserService.list(wrapper); } @@ -130,12 +147,13 @@ public class CleanUserController { @Operation(summary = "获取保洁/会务人员用户下拉列表", description = "获取保洁/会务人员用户下拉列表") @GetMapping("/dropdown") // @PreAuthorize("@pms.hasPermission('integrated_cleanUser_view')") - public R getCleanUserDropdown(@RequestParam("type")String type) { + public R> getCleanUserDropdown(@RequestParam(value = "type",required = false)String type) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(CleanUserEntity::getType, type); - wrapper.select(CleanUserEntity::getId, CleanUserEntity::getName, CleanUserEntity::getPhone); + wrapper.eq(type != null,CleanUserEntity::getType, type); +// wrapper.select(CleanUserEntity::getId, CleanUserEntity::getName, CleanUserEntity::getPhone); wrapper.orderByAsc(CleanUserEntity::getName); List list = cleanUserService.list(wrapper); - return R.ok(list); + List voList = BeanUtils.toBean(list, CleanUserDropdownVo.class); + return R.ok(voList); } } \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingBookingController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingBookingController.java index b7bc3adb7..0742db7e5 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingBookingController.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingBookingController.java @@ -100,6 +100,8 @@ public class MeetingBookingController { vo.setCreateTime(e.getCreateTime()); vo.setCreateBy(e.getCreateBy()); vo.setDescription(e.getDescription()); + vo.setNeedService(e.getNeedService()); + vo.setDeskLayout(e.getDeskLayout()); return vo; }).toList(); @@ -140,6 +142,8 @@ public class MeetingBookingController { meetingBooking.setBookingDate(LocalDate.parse(dto.getBookingDate())); meetingBooking.setStartTime(LocalTime.parse(dto.getStartTime())); meetingBooking.setEndTime(LocalTime.parse(dto.getEndTime())); + meetingBooking.setNeedService(dto.getNeedService()); + meetingBooking.setDeskLayout(dto.getDeskLayout()); // 2. 验证会议室是否存在 MeetingRoomEntity room = meetingRoomService.getById(meetingBooking.getRoomId()); if (room == null) { @@ -184,7 +188,8 @@ public class MeetingBookingController { // 7. 发起流程 var processByBusinessKey = remoteFlowApiFlowService.getProcessByBusinessKey(IntegratedFlowConstant.INTEGRATED_MEETING_BOOKING); if (!processByBusinessKey.isOk() || processByBusinessKey.getData() == null) { - throw new ServiceException("流程配置获取失败: " + processByBusinessKey.getMsg()); +// throw new ServiceException("流程配置获取失败: " + processByBusinessKey.getMsg()); + return R.failed("流程配置获取失败"); } Process process = processByBusinessKey.getData(); MeetingFlowDto meetingFlowDto = new MeetingFlowDto(); diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingDeskLayoutController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingDeskLayoutController.java new file mode 100644 index 000000000..8a5de4465 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingDeskLayoutController.java @@ -0,0 +1,143 @@ +package com.bsd.pjcloud.integrated.controller; + +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.collection.CollUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.bsd.pjcloud.common.core.util.R; +import com.bsd.pjcloud.common.log.annotation.SysLog; +import com.bsd.pjcloud.integrated.entity.MeetingDeskLayoutEntity; +import com.bsd.pjcloud.integrated.service.MeetingDeskLayoutService; +import com.bsd.pjcloud.integrated.vo.MeetingDeskLayoutAppVO; +import com.bsd.pjcloud.common.core.util.BeanUtils; +import org.springframework.security.access.prepost.PreAuthorize; +import com.bsd.pjcloud.common.excel.annotation.ResponseExcel; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import org.springdoc.core.annotations.ParameterObject; +import org.springframework.http.HttpHeaders; +import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Objects; + +/** + * 摆放形式 + * + * @author zjf + * @date 2025-09-10 12:56:45 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping("/meetingDeskLayout" ) +@Tag(description = "meetingDeskLayout" , name = "摆放形式管理" ) +@SecurityRequirement(name = HttpHeaders.AUTHORIZATION) +public class MeetingDeskLayoutController { + + private final MeetingDeskLayoutService meetingDeskLayoutService; + + /** + * 分页查询 + * @param page 分页对象 + * @param meetingDeskLayout 摆放形式 + * @return + */ + @Operation(summary = "分页查询" , description = "分页查询" ) + @GetMapping("/page" ) + @PreAuthorize("@pms.hasPermission('integrated_meetingDeskLayout_view')" ) + public R getMeetingDeskLayoutPage(@ParameterObject Page page, @ParameterObject MeetingDeskLayoutEntity meetingDeskLayout) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + return R.ok(meetingDeskLayoutService.page(page, wrapper)); + } + + + /** + * 通过id查询摆放形式 + * @param id id + * @return R + */ + @Operation(summary = "通过id查询" , description = "通过id查询" ) + @GetMapping("/{id}" ) + @PreAuthorize("@pms.hasPermission('integrated_meetingDeskLayout_view')" ) + public R getById(@PathVariable("id" ) Long id) { + return R.ok(meetingDeskLayoutService.getById(id)); + } + + /** + * 新增摆放形式 + * @param meetingDeskLayout 摆放形式 + * @return R + */ + @Operation(summary = "新增摆放形式" , description = "新增摆放形式" ) + @SysLog("新增摆放形式" ) + @PostMapping + @PreAuthorize("@pms.hasPermission('integrated_meetingDeskLayout_add')" ) + public R save(@RequestBody MeetingDeskLayoutEntity meetingDeskLayout) { + return R.ok(meetingDeskLayoutService.save(meetingDeskLayout)); + } + + /** + * 修改摆放形式 + * @param meetingDeskLayout 摆放形式 + * @return R + */ + @Operation(summary = "修改摆放形式" , description = "修改摆放形式" ) + @SysLog("修改摆放形式" ) + @PutMapping + @PreAuthorize("@pms.hasPermission('integrated_meetingDeskLayout_edit')" ) + public R updateById(@RequestBody MeetingDeskLayoutEntity meetingDeskLayout) { + return R.ok(meetingDeskLayoutService.updateById(meetingDeskLayout)); + } + + /** + * 通过id删除摆放形式 + * @param ids id列表 + * @return R + */ + @Operation(summary = "通过id删除摆放形式" , description = "通过id删除摆放形式" ) + @SysLog("通过id删除摆放形式" ) + @DeleteMapping + @PreAuthorize("@pms.hasPermission('integrated_meetingDeskLayout_del')" ) + public R removeById(@RequestBody Long[] ids) { + return R.ok(meetingDeskLayoutService.removeBatchByIds(CollUtil.toList(ids))); + } + + + /** + * 导出excel 表格 + * @param meetingDeskLayout 查询条件 + * @param ids 导出指定ID + * @return excel 文件流 + */ + @ResponseExcel + @GetMapping("/export") + @PreAuthorize("@pms.hasPermission('integrated_meetingDeskLayout_export')" ) + public List export(MeetingDeskLayoutEntity meetingDeskLayout,Long[] ids) { + return meetingDeskLayoutService.list(Wrappers.lambdaQuery(meetingDeskLayout).in(ArrayUtil.isNotEmpty(ids), MeetingDeskLayoutEntity::getId, ids)); + } + + + + //----------------------------------APP---------------------------------*/ + /** + * 移动端获取可用的摆放形式列表 + * @return R + */ + @Operation(summary = "移动端获取摆放形式列表", description = "移动端预约课程时选择摆放形式") + @GetMapping("/app/list") + public R> getAppLayoutList() { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + // 只查询可用的摆放形式 + wrapper.eq(MeetingDeskLayoutEntity::getIsAvailable, "0"); + // 按排序字段排序 + wrapper.orderByAsc(MeetingDeskLayoutEntity::getSortOrder); + + List entityList = meetingDeskLayoutService.list(wrapper); + List voList = BeanUtils.toBean(entityList, MeetingDeskLayoutAppVO.class); + + return R.ok(voList); + } +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingRoomController.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingRoomController.java index c2ec1551c..f4917a742 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingRoomController.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/controller/MeetingRoomController.java @@ -320,7 +320,8 @@ public class MeetingRoomController { //发起流程 R processByBusinessKey = remoteFlowApiFlowService.getProcessByBusinessKey(IntegratedFlowConstant.INTEGRATED_MEETING_BOOKING); if (!processByBusinessKey.isOk() || processByBusinessKey.getData() == null) { - throw new ServiceException("流程配置获取失败: " + processByBusinessKey.getMsg()); +// throw new ServiceException("流程配置获取失败: " + processByBusinessKey.getMsg()); + return R.failed("流程配置获取失败"); } Process process = processByBusinessKey.getData(); diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingBookingEntity.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingBookingEntity.java index 2f54aec98..b87c3bbb5 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingBookingEntity.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingBookingEntity.java @@ -143,4 +143,16 @@ public class MeetingBookingEntity extends Model { @Schema(description = "审批原因") private String checkReason; + /** + * 是否需要会务服务(0=不需要 1=需要) + */ + @Schema(description = "是否需要会务服务(0=不需要 1=需要)") + private String needService; + + /** + * 桌子摆放形态 + */ + @Schema(description = "桌子摆放形态") + private String deskLayout; + } \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingDeskLayoutEntity.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingDeskLayoutEntity.java new file mode 100644 index 000000000..61533a2bf --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingDeskLayoutEntity.java @@ -0,0 +1,133 @@ +package com.bsd.pjcloud.integrated.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import com.bsd.pjcloud.common.core.util.TenantTable; +import java.time.LocalDateTime; + +/** + * 摆放形式 + * + * @author zjf + * @date 2025-09-10 12:56:45 + */ +@Data +@TenantTable +@TableName("meeting_desk_layout") +@EqualsAndHashCode(callSuper = true) +@Schema(description = "摆放形式") +public class MeetingDeskLayoutEntity extends Model { + + + /** + * 编号 + */ + @TableId(type = IdType.ASSIGN_ID) + @Schema(description="编号") + private Long id; + + /** + * 摆放形式名称 + */ + @Schema(description="摆放形式名称") + private String layoutName; + + /** + * 摆放形式编码 + */ + @Schema(description="摆放形式编码") + private String layoutCode; + + /** + * 摆放形式描述 + */ + @Schema(description="摆放形式描述") + private String description; + + /** + * 最小容纳人数 + */ + @Schema(description="最小容纳人数") + private Integer capacityMin; + + /** + * 最大容纳人数 + */ + @Schema(description="最大容纳人数") + private Integer capacityMax; + + /** + * 摆放形式示意图 + */ + @Schema(description="摆放形式示意图") + private String layoutImage; + + /** + * 布置所需时间(分钟) + */ + @Schema(description="布置所需时间(分钟)") + private Integer setupTime; + + /** + * 是否可用(0=可用 1=不可用) + */ + @Schema(description="是否可用(0=可用 1=不可用)") + private String isAvailable; + + /** + * 排序 + */ + @Schema(description="排序") + private Integer sortOrder; + + /** + * 备注 + */ + @Schema(description="备注") + private String remark; + + /** + * 创建人 + */ + @TableField(fill = FieldFill.INSERT) + @Schema(description="创建人") + private String createBy; + + /** + * 创建时间 + */ + @TableField(fill = FieldFill.INSERT) + @Schema(description="创建时间") + private LocalDateTime createTime; + + /** + * 修改人 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description="修改人") + private String updateBy; + + /** + * 修改时间 + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + @Schema(description="修改时间") + private LocalDateTime updateTime; + + /** + * 删除标记 + */ + @TableLogic + @TableField(fill = FieldFill.INSERT) + @Schema(description="删除标记") + private String delFlag; + + /** + * 租户ID + */ + @Schema(description="租户ID") + private Long tenantId; +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingRoomEquipmentEntity.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingRoomEquipmentEntity.java index 3ef964314..6444b66ee 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingRoomEquipmentEntity.java +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/entity/MeetingRoomEquipmentEntity.java @@ -16,7 +16,7 @@ import java.time.LocalDateTime; */ @Data @TenantTable -@TableName("meeting__room_equipment") +@TableName("meeting_room_equipment") @EqualsAndHashCode(callSuper = true) @Schema(description = "设备会议室关联表") public class MeetingRoomEquipmentEntity extends Model { diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/mapper/MeetingDeskLayoutMapper.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/mapper/MeetingDeskLayoutMapper.java new file mode 100644 index 000000000..6796656c3 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/mapper/MeetingDeskLayoutMapper.java @@ -0,0 +1,11 @@ +package com.bsd.pjcloud.integrated.mapper; + +import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper; +import com.bsd.pjcloud.integrated.entity.MeetingDeskLayoutEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface MeetingDeskLayoutMapper extends PjcloudBaseMapper { + + +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/MeetingDeskLayoutService.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/MeetingDeskLayoutService.java new file mode 100644 index 000000000..6aa2dca25 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/MeetingDeskLayoutService.java @@ -0,0 +1,8 @@ +package com.bsd.pjcloud.integrated.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.bsd.pjcloud.integrated.entity.MeetingDeskLayoutEntity; + +public interface MeetingDeskLayoutService extends IService { + +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/impl/MeetingDeskLayoutServiceImpl.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/impl/MeetingDeskLayoutServiceImpl.java new file mode 100644 index 000000000..bac2495ea --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/service/impl/MeetingDeskLayoutServiceImpl.java @@ -0,0 +1,16 @@ +package com.bsd.pjcloud.integrated.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.bsd.pjcloud.integrated.entity.MeetingDeskLayoutEntity; +import com.bsd.pjcloud.integrated.mapper.MeetingDeskLayoutMapper; +import com.bsd.pjcloud.integrated.service.MeetingDeskLayoutService; +import org.springframework.stereotype.Service; +/** + * 摆放形式 + * + * @author zjf + * @date 2025-09-10 12:56:45 + */ +@Service +public class MeetingDeskLayoutServiceImpl extends ServiceImpl implements MeetingDeskLayoutService { +} \ No newline at end of file diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/vo/MeetingDeskLayoutAppVO.java b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/vo/MeetingDeskLayoutAppVO.java new file mode 100644 index 000000000..6bae17658 --- /dev/null +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/java/com/bsd/pjcloud/integrated/vo/MeetingDeskLayoutAppVO.java @@ -0,0 +1,56 @@ +package com.bsd.pjcloud.integrated.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.io.Serializable; + +/** + * 移动端摆放形式选择VO + * + * @author zjf + * @date 2025-01-27 + */ +@Data +@Schema(description = "移动端摆放形式选择VO") +public class MeetingDeskLayoutAppVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 编号 + */ + @Schema(description = "编号") + private Long id; + + /** + * 摆放形式名称 + */ + @Schema(description = "摆放形式名称") + private String layoutName; + + /** + * 摆放形式示意图 + */ + @Schema(description = "摆放形式示意图") + private String layoutImage; + + /** + * 最小容纳人数 + */ + @Schema(description = "最小容纳人数") + private Integer capacityMin; + + /** + * 最大容纳人数 + */ + @Schema(description = "最大容纳人数") + private Integer capacityMax; + + /** + * 摆放形式描述 + */ + @Schema(description = "摆放形式描述") + private String description; +} + diff --git a/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/mapper/MeetingBookingMapper.xml b/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/mapper/MeetingBookingMapper.xml index b82fabfc3..755d43be2 100644 --- a/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/mapper/MeetingBookingMapper.xml +++ b/pjcloud-integrated/pjcloud-integrated-biz/src/main/resources/mapper/MeetingBookingMapper.xml @@ -24,6 +24,8 @@ + +