From 4eed1940b9e0ec8189b0c6608e263c5f83b3d770 Mon Sep 17 00:00:00 2001
From: sonjinyon <2476687577@qq.com>
Date: Tue, 8 Oct 2024 20:36:57 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=A7=AF=E5=88=86=E6=A8=A1?=
=?UTF-8?q?=E5=9D=97?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../point/activity/PointActivityForm.vue | 227 +++++++++++++
.../mall/promotion/point/activity/index.vue | 219 +++++++++++++
.../point/activity/pointActivity.data.ts | 55 ++++
.../point/components/PointShowcase.vue | 154 +++++++++
.../point/components/PointTableSelect.vue | 300 +++++++++++++++++
.../util/collection/CollectionUtils.java | 8 +
.../promotion/api/point/PointActivityApi.java | 42 +++
.../point/dto/PointValidateJoinRespDTO.java | 24 ++
.../promotion/enums/ErrorCodeConstants.java | 12 +
.../api/point/PointActivityApiImpl.java | 37 +++
.../admin/diy/DiyPageController.java | 8 +
.../admin/point/PointActivityController.java | 141 ++++++++
.../vo/activity/PointActivityPageReqVO.java | 36 ++
.../vo/activity/PointActivityRespVO.java | 72 ++++
.../vo/activity/PointActivitySaveReqVO.java | 31 ++
.../point/vo/product/PointProductRespVO.java | 39 +++
.../vo/product/PointProductSaveReqVO.java | 47 +++
.../app/diy/AppDiyPageController.java | 5 -
.../app/point/AppPointActivityController.java | 121 +++++++
.../vo/AppPointActivityDetailRespVO.java | 65 ++++
.../point/vo/AppPointActivityPageReqVO.java | 15 +
.../app/point/vo/AppPointActivityRespVO.java | 67 ++++
.../dal/dataobject/point/PointActivityDO.java | 57 ++++
.../dal/dataobject/point/PointProductDO.java | 67 ++++
.../dal/mysql/point/PointActivityMapper.java | 59 ++++
.../dal/mysql/point/PointProductMapper.java | 66 ++++
.../service/point/PointActivityService.java | 112 +++++++
.../point/PointActivityServiceImpl.java | 309 ++++++++++++++++++
28 files changed, 2390 insertions(+), 5 deletions(-)
create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue
create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue
create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts
create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue
create mode 100644 yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue
create mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java
create mode 100644 yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java
create mode 100644 yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java
diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue b/yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue
new file mode 100644
index 0000000..a09565c
--- /dev/null
+++ b/yudao-admin-vue3/src/views/mall/promotion/point/activity/PointActivityForm.vue
@@ -0,0 +1,227 @@
+
+
+
+
+
diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue b/yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue
new file mode 100644
index 0000000..ceceb7b
--- /dev/null
+++ b/yudao-admin-vue3/src/views/mall/promotion/point/activity/index.vue
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+ 重置
+
+
+
+ 新增
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ getRedeemedQuantity(row) }}
+
+
+
+
+
+
+ 编辑
+
+
+ 关闭
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts b/yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts
new file mode 100644
index 0000000..a3334ea
--- /dev/null
+++ b/yudao-admin-vue3/src/views/mall/promotion/point/activity/pointActivity.data.ts
@@ -0,0 +1,55 @@
+import type { CrudSchema } from '@/hooks/web/useCrudSchemas'
+
+// 表单校验
+export const rules = reactive({
+ spuId: [required],
+ sort: [required]
+})
+
+// CrudSchema https://doc.iocoder.cn/vue3/crud-schema/
+const crudSchemas = reactive([
+ {
+ label: '排序',
+ field: 'sort',
+ form: {
+ component: 'InputNumber',
+ value: 0
+ },
+ table: {
+ width: 80
+ }
+ },
+ {
+ label: '积分商城活动商品',
+ field: 'spuId',
+ isTable: true,
+ isSearch: false,
+ form: {
+ colProps: {
+ span: 24
+ }
+ },
+ table: {
+ width: 300
+ }
+ },
+ {
+ label: '备注',
+ field: 'remark',
+ isSearch: false,
+ form: {
+ component: 'Input',
+ componentProps: {
+ type: 'textarea',
+ rows: 4
+ },
+ colProps: {
+ span: 24
+ }
+ },
+ table: {
+ width: 300
+ }
+ }
+])
+export const { allSchemas } = useCrudSchemas(crudSchemas)
diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue
new file mode 100644
index 0000000..82e490c
--- /dev/null
+++ b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointShowcase.vue
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
diff --git a/yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue
new file mode 100644
index 0000000..d68b5f1
--- /dev/null
+++ b/yudao-admin-vue3/src/views/mall/promotion/point/components/PointTableSelect.vue
@@ -0,0 +1,300 @@
+
+
+
+
+
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
index 91f5347..2a581d1 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -290,6 +290,14 @@ public class CollectionUtils {
return valueFunc.apply(t);
}
+ public static > T getMinObject(List from, Function valueFunc) {
+ if (CollUtil.isEmpty(from)) {
+ return null;
+ }
+ assert from.size() > 0; // 断言,避免告警
+ return from.stream().min(Comparator.comparing(valueFunc)).get();
+ }
+
public static > V getSumValue(List from, Function valueFunc,
BinaryOperator accumulator) {
return getSumValue(from, valueFunc, accumulator, null);
diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java
new file mode 100644
index 0000000..0b612cf
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApi.java
@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.promotion.api.point;
+
+import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO;
+
+/**
+ * 积分商城活动 API 接口
+ *
+ * @author HUIHUI
+ */
+public interface PointActivityApi {
+
+ /**
+ * 【下单前】校验是否参与积分商城活动
+ *
+ * 如果校验失败,则抛出业务异常
+ *
+ * @param activityId 活动编号
+ * @param skuId SKU 编号
+ * @param count 数量
+ * @return 积分商城商品信息
+ */
+ PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count);
+
+ /**
+ * 更新积分商城商品库存(减少)
+ *
+ * @param id 活动编号
+ * @param skuId sku 编号
+ * @param count 数量(正数)
+ */
+ void updatePointStockDecr(Long id, Long skuId, Integer count);
+
+ /**
+ * 更新积分商城商品库存(增加)
+ *
+ * @param id 活动编号
+ * @param skuId sku 编号
+ * @param count 数量(正数)
+ */
+ void updatePointStockIncr(Long id, Long skuId, Integer count);
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java
new file mode 100644
index 0000000..e3b9934
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/api/point/dto/PointValidateJoinRespDTO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.promotion.api.point.dto;
+
+import lombok.Data;
+
+/**
+ * 校验参与积分商城 Response DTO
+ */
+@Data
+public class PointValidateJoinRespDTO {
+
+ /**
+ * 可兑换次数
+ */
+ private Integer count;
+ /**
+ * 所需兑换积分
+ */
+ private Integer point;
+ /**
+ * 所需兑换金额,单位:分
+ */
+ private Integer price;
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
index 9f3ba83..1038bad 100644
--- a/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
+++ b/yudao-module-mall/yudao-module-promotion-api/src/main/java/cn/iocoder/yudao/module/promotion/enums/ErrorCodeConstants.java
@@ -137,4 +137,16 @@ public interface ErrorCodeConstants {
ErrorCode POINT_MALL_NOT_EXISTS = new ErrorCode(1_013_023_000, "积分商城不存在");
+
+ // ========== 积分商城活动 1-013-007-000 ==========
+ ErrorCode POINT_ACTIVITY_NOT_EXISTS = new ErrorCode(1_013_007_000, "积分商城活动不存在");
+ ErrorCode POINT_ACTIVITY_SPU_CONFLICTS = new ErrorCode(1_013_007_001, "存在商品参加了其它积分商城活动");
+ ErrorCode POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_002, "积分商城活动已关闭,不能修改");
+ ErrorCode POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END = new ErrorCode(1_013_007_003, "积分商城活动未关闭或未结束,不能删除");
+ ErrorCode POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED = new ErrorCode(1_013_007_004, "积分商城活动已关闭,不能重复关闭");
+ ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_STATUS_CLOSED = new ErrorCode(1_013_007_005, "积分商品兑换失败,原因:积分商城活动已关闭");
+ ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED = new ErrorCode(1_013_007_006, "积分商品兑换失败,原因:单次限购超出");
+ ErrorCode POINT_ACTIVITY_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS = new ErrorCode(1_013_007_007, "积分商品兑换失败,原因:商品不存在");
+ ErrorCode POINT_ACTIVITY_UPDATE_STOCK_FAIL = new ErrorCode(1_013_007_008, "积分商品兑换失败,原因:积分商品库存不足");
+
}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java
new file mode 100644
index 0000000..8b0ef1e
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/api/point/PointActivityApiImpl.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.promotion.api.point;
+
+import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO;
+import cn.iocoder.yudao.module.promotion.service.point.PointActivityService;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+/**
+ * 积分商城活动 Api 接口实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class PointActivityApiImpl implements PointActivityApi {
+
+ @Resource
+ private PointActivityService pointActivityService;
+
+ @Override
+ public PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count) {
+ return pointActivityService.validateJoinPointActivity(activityId, skuId, count);
+ }
+
+ @Override
+ public void updatePointStockDecr(Long id, Long skuId, Integer count) {
+ pointActivityService.updatePointStockDecr(id, skuId, count);
+ }
+
+ @Override
+ public void updatePointStockIncr(Long id, Long skuId, Integer count) {
+ pointActivityService.updatePointStockIncr(id, skuId, count);
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java
index 4da7064..f819630 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/diy/DiyPageController.java
@@ -2,7 +2,9 @@ package cn.iocoder.yudao.module.promotion.controller.admin.diy;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.promotion.controller.admin.diy.vo.page.*;
+import cn.iocoder.yudao.module.promotion.controller.app.diy.vo.AppDiyPagePropertyRespVO;
import cn.iocoder.yudao.module.promotion.convert.diy.DiyPageConvert;
import cn.iocoder.yudao.module.promotion.dal.dataobject.diy.DiyPageDO;
import cn.iocoder.yudao.module.promotion.service.diy.DiyPageService;
@@ -96,4 +98,10 @@ public class DiyPageController {
return success(true);
}
+ @GetMapping("/getDiyPage")
+ public CommonResult> getPromotion() {
+ List diyPage = diyPageService.getDiyPage();
+ return success(BeanUtils.toBean(diyPage, AppDiyPagePropertyRespVO.class));
+ }
+
}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
new file mode 100644
index 0000000..1a16698
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/PointActivityController.java
@@ -0,0 +1,141 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.point;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityRespVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductRespVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
+import cn.iocoder.yudao.module.promotion.service.point.PointActivityService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+
+@Tag(name = "管理后台 - 积分商城活动")
+@RestController
+@RequestMapping("/promotion/point-activity")
+@Validated
+public class PointActivityController {
+
+ @Resource
+ private PointActivityService pointActivityService;
+ @Resource
+ private ProductSpuApi productSpuApi;
+
+ @PostMapping("/create")
+ @Operation(summary = "创建积分商城活动")
+ @PreAuthorize("@ss.hasPermission('promotion:point-activity:create')")
+ public CommonResult createPointActivity(@Valid @RequestBody PointActivitySaveReqVO createReqVO) {
+ return success(pointActivityService.createPointActivity(createReqVO));
+ }
+
+ @PutMapping("/update")
+ @Operation(summary = "更新积分商城活动")
+ @PreAuthorize("@ss.hasPermission('promotion:point-activity:update')")
+ public CommonResult updatePointActivity(@Valid @RequestBody PointActivitySaveReqVO updateReqVO) {
+ pointActivityService.updatePointActivity(updateReqVO);
+ return success(true);
+ }
+
+ @PutMapping("/close")
+ @Operation(summary = "关闭积分商城活动")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('promotion:point-activity:close')")
+ public CommonResult closeSeckillActivity(@RequestParam("id") Long id) {
+ pointActivityService.closePointActivity(id);
+ return success(true);
+ }
+
+ @DeleteMapping("/delete")
+ @Operation(summary = "删除积分商城活动")
+ @Parameter(name = "id", description = "编号", required = true)
+ @PreAuthorize("@ss.hasPermission('promotion:point-activity:delete')")
+ public CommonResult deletePointActivity(@RequestParam("id") Long id) {
+ pointActivityService.deletePointActivity(id);
+ return success(true);
+ }
+
+ @GetMapping("/get")
+ @Operation(summary = "获得积分商城活动")
+ @Parameter(name = "id", description = "编号", required = true, example = "1024")
+ @PreAuthorize("@ss.hasPermission('promotion:point-activity:query')")
+ public CommonResult getPointActivity(@RequestParam("id") Long id) {
+ PointActivityDO pointActivity = pointActivityService.getPointActivity(id);
+ if (pointActivity == null) {
+ return success(null);
+ }
+
+ List products = pointActivityService.getPointProductListByActivityIds(Collections.singletonList(id));
+ PointActivityRespVO respVO = BeanUtils.toBean(pointActivity, PointActivityRespVO.class);
+ respVO.setProducts(BeanUtils.toBean(products, PointProductRespVO.class));
+ return success(respVO);
+ }
+
+ @GetMapping("/page")
+ @Operation(summary = "获得积分商城活动分页")
+ @PreAuthorize("@ss.hasPermission('promotion:point-activity:query')")
+ public CommonResult> getPointActivityPage(@Valid PointActivityPageReqVO pageReqVO) {
+ PageResult pageResult = pointActivityService.getPointActivityPage(pageReqVO);
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return success(PageResult.empty(pageResult.getTotal()));
+ }
+
+ // 拼接数据
+ List resultList = buildPointActivityRespVOList(pageResult.getList());
+ return success(new PageResult<>(resultList, pageResult.getTotal()));
+ }
+
+ @GetMapping("/list-by-ids")
+ @Operation(summary = "获得积分商城活动列表,基于活动编号数组")
+ @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
+ public CommonResult> getPointActivityListByIds(@RequestParam("ids") List ids) {
+ // 1. 获得开启的活动列表
+ List activityList = pointActivityService.getPointActivityListByIds(ids);
+ activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
+ if (CollUtil.isEmpty(activityList)) {
+ return success(Collections.emptyList());
+ }
+ // 2. 拼接返回
+ List result = buildPointActivityRespVOList(activityList);
+ return success(result);
+ }
+
+ private List buildPointActivityRespVOList(List activityList) {
+ List products = pointActivityService.getPointProductListByActivityIds(
+ convertSet(activityList, PointActivityDO::getId));
+ Map> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
+ Map spuMap = productSpuApi.getSpusMap(
+ convertSet(activityList, PointActivityDO::getSpuId));
+ List result = BeanUtils.toBean(activityList, PointActivityRespVO.class);
+ result.forEach(activity -> {
+ // 设置 product 信息
+ PointProductDO minProduct = getMinObject(productsMap.get(activity.getId()), PointProductDO::getPoint);
+ assert minProduct != null;
+ activity.setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice());
+ findAndThen(spuMap, activity.getSpuId(),
+ spu -> activity.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
+ });
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java
new file mode 100644
index 0000000..89786c2
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityPageReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - 积分商城活动分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PointActivityPageReqVO extends PageParam {
+
+ @Schema(description = "积分商城活动商品", example = "19509")
+ private Long spuId;
+
+ @Schema(description = "活动状态", example = "2")
+ private Integer status;
+
+ @Schema(description = "备注", example = "你说的对")
+ private String remark;
+
+ @Schema(description = "排序")
+ private Integer sort;
+
+ @Schema(description = "创建时间")
+ @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+ private LocalDateTime[] createTime;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java
new file mode 100644
index 0000000..d81b3d6
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivityRespVO.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity;
+
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductRespVO;
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - 积分商城活动 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class PointActivityRespVO {
+
+ @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373")
+ @ExcelProperty("积分商城活动编号")
+ private Long id;
+
+ @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509")
+ @ExcelProperty("积分商城活动商品")
+ private Long spuId;
+
+ @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("活动状态")
+ private Integer status;
+
+ @Schema(description = "积分商城活动库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("积分商城活动库存")
+ private Integer stock; // 剩余库存积分兑换时扣减
+
+ @Schema(description = "积分商城活动总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("积分商城活动总库存")
+ private Integer totalStock;
+
+ @Schema(description = "备注", example = "你说的对")
+ @ExcelProperty("备注")
+ private String remark;
+
+ @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("排序")
+ private Integer sort;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+ @Schema(description = "积分商城商品", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List products;
+
+ // ========== 商品字段 ==========
+
+ @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取
+ example = "618大促")
+ private String spuName;
+ @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
+ example = "https://www.iocoder.cn/xx.png")
+ private String picUrl;
+ @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取
+ example = "50")
+ private Integer marketPrice;
+
+ //======================= 显示所需兑换积分最少的 sku 信息 =======================
+
+ @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer point;
+
+ @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860")
+ private Integer price;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java
new file mode 100644
index 0000000..c81db10
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/activity/PointActivitySaveReqVO.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity;
+
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductSaveReqVO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Schema(description = "管理后台 - 积分商城活动新增/修改 Request VO")
+@Data
+public class PointActivitySaveReqVO {
+
+ @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373")
+ private Long id;
+
+ @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509")
+ @NotNull(message = "积分商城活动商品不能为空")
+ private Long spuId;
+
+ @Schema(description = "备注", example = "你说的对")
+ private String remark;
+
+ @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "排序不能为空")
+ private Integer sort;
+
+ @Schema(description = "积分商城商品", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List products;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java
new file mode 100644
index 0000000..8e8250b
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductRespVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 积分商城商品 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class PointProductRespVO {
+
+ @Schema(description = "积分商城商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31718")
+ private Long id;
+
+ @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "29388")
+ private Long activityId;
+
+ @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8112")
+ private Long spuId;
+
+ @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2736")
+ private Long skuId;
+
+ @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926")
+ private Integer count;
+
+ @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer point;
+
+ @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860")
+ private Integer price;
+
+ @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer stock;
+
+ @Schema(description = "积分商城商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer activityStatus;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java
new file mode 100644
index 0000000..9863e73
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/point/vo/product/PointProductSaveReqVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotNull;
+
+@Schema(description = "管理后台 - 积分商城商品新增/修改 Request VO")
+@Data
+public class PointProductSaveReqVO {
+
+ @Schema(description = "积分商城商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31718")
+ private Long id;
+
+ @Schema(description = "积分商城活动 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "29388")
+ @NotNull(message = "积分商城活动 id不能为空")
+ private Long activityId;
+
+ @Schema(description = "商品 SPU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "8112")
+ @NotNull(message = "商品 SPU 编号不能为空")
+ private Long spuId;
+
+ @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2736")
+ @NotNull(message = "商品 SKU 编号不能为空")
+ private Long skuId;
+
+ @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926")
+ @NotNull(message = "可兑换数量不能为空")
+ private Integer count;
+
+ @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
+ @NotNull(message = "兑换积分不能为空")
+ private Integer point;
+
+ @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860")
+ @NotNull(message = "兑换金额,单位:分不能为空")
+ private Integer price;
+
+ @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ @NotNull(message = "积分商城商品不能为空")
+ private Integer stock;
+
+ @Schema(description = "积分商城商品状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @NotNull(message = "积分商城商品状态不能为空")
+ private Integer activityStatus;
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java
index c9a1f1c..c683ef3 100644
--- a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/diy/AppDiyPageController.java
@@ -38,11 +38,6 @@ public class AppDiyPageController {
}
- @GetMapping("/getDiyPage")
- public CommonResult> getPromotion() {
- List diyPage = diyPageService.getDiyPage();
- return success(BeanUtils.toBean(diyPage, AppDiyPagePropertyRespVO.class));
- }
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
new file mode 100644
index 0000000..8c2f3e3
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/AppPointActivityController.java
@@ -0,0 +1,121 @@
+package cn.iocoder.yudao.module.promotion.controller.app.point;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.point.vo.AppPointActivityDetailRespVO;
+import cn.iocoder.yudao.module.promotion.controller.app.point.vo.AppPointActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.app.point.vo.AppPointActivityRespVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
+import cn.iocoder.yudao.module.promotion.service.point.PointActivityService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+
+@Tag(name = "用户 App - 积分商城活动")
+@RestController
+@RequestMapping("/promotion/point-activity")
+@Validated
+public class AppPointActivityController {
+
+ @Resource
+ private PointActivityService pointActivityService;
+
+ @Resource
+ private ProductSpuApi productSpuApi;
+
+ @GetMapping("/page")
+ @Operation(summary = "获得积分商城活动分页")
+ @PermitAll
+ public CommonResult> getPointActivityPage(AppPointActivityPageReqVO pageReqVO) {
+ // 1. 查询满足当前阶段的活动
+ PageResult pageResult = pointActivityService.getPointActivityPage(
+ BeanUtils.toBean(pageReqVO, PointActivityPageReqVO.class));
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return success(PageResult.empty(pageResult.getTotal()));
+ }
+
+ // 2. 拼接数据
+ List resultList = buildAppPointActivityRespVOList(pageResult.getList());
+ return success(new PageResult<>(resultList, pageResult.getTotal()));
+ }
+
+ @GetMapping("/get-detail")
+ @Operation(summary = "获得积分商城活动明细")
+ @Parameter(name = "id", description = "活动编号", required = true, example = "1024")
+ @PermitAll
+ public CommonResult getPointActivity(@RequestParam("id") Long id) {
+ // 1. 获取活动
+ PointActivityDO activity = pointActivityService.getPointActivity(id);
+ if (activity == null
+ || ObjUtil.equal(activity.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {
+ return success(null);
+ }
+
+ // 2. 拼接数据
+ List products = pointActivityService.getPointProductListByActivityIds(Collections.singletonList(id));
+ PointProductDO minProduct = getMinObject(products, PointProductDO::getPoint);
+ assert minProduct != null;
+ AppPointActivityDetailRespVO respVO = BeanUtils.toBean(activity, AppPointActivityDetailRespVO.class)
+ .setProducts(BeanUtils.toBean(products, AppPointActivityDetailRespVO.Product.class))
+ .setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice());
+ return success(respVO);
+ }
+
+ @GetMapping("/list-by-ids")
+ @Operation(summary = "获得积分商城活动列表,基于活动编号数组")
+ @Parameter(name = "ids", description = "活动编号数组", required = true, example = "[1024, 1025]")
+ @PermitAll
+ public CommonResult> getCombinationActivityListByIds(@RequestParam("ids") List ids) {
+ // 1. 获得开启的活动列表
+ List activityList = pointActivityService.getPointActivityListByIds(ids);
+ activityList.removeIf(activity -> CommonStatusEnum.isDisable(activity.getStatus()));
+ if (CollUtil.isEmpty(activityList)) {
+ return success(Collections.emptyList());
+ }
+ // 2. 拼接返回
+ List result = buildAppPointActivityRespVOList(activityList);
+ return success(result);
+ }
+
+ private List buildAppPointActivityRespVOList(List activityList) {
+ List products = pointActivityService.getPointProductListByActivityIds(
+ convertSet(activityList, PointActivityDO::getId));
+ Map> productsMap = convertMultiMap(products, PointProductDO::getActivityId);
+ Map spuMap = productSpuApi.getSpusMap(
+ convertSet(activityList, PointActivityDO::getSpuId));
+ List result = BeanUtils.toBean(activityList, AppPointActivityRespVO.class);
+ result.forEach(activity -> {
+ // 设置 product 信息
+ PointProductDO minProduct = getMinObject(productsMap.get(activity.getId()), PointProductDO::getPoint);
+ assert minProduct != null;
+ activity.setPoint(minProduct.getPoint()).setPrice(minProduct.getPrice());
+ findAndThen(spuMap, activity.getSpuId(),
+ spu -> activity.setSpuName(spu.getName()).setPicUrl(spu.getPicUrl()).setMarketPrice(spu.getMarketPrice()));
+ });
+ return result;
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java
new file mode 100644
index 0000000..2e02f10
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityDetailRespVO.java
@@ -0,0 +1,65 @@
+package cn.iocoder.yudao.module.promotion.controller.app.point.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.util.List;
+
+@Schema(description = "用户 App - 积分商城活动的详细 Response VO")
+@Data
+public class AppPointActivityDetailRespVO {
+
+ @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373")
+ private Long id;
+
+ @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509")
+ private Long spuId;
+
+ @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer status;
+
+ @Schema(description = "积分商城活动库存(剩余库存积分兑换时扣减)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer stock;
+
+ @Schema(description = "积分商城活动总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ private Integer totalStock;
+
+ @Schema(description = "备注", example = "你说的对")
+ private String remark;
+
+ @Schema(description = "商品信息数组", requiredMode = Schema.RequiredMode.REQUIRED)
+ private List products;
+
+ //======================= 显示所需兑换积分最少的 SKU 信息 =======================
+
+ @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer point;
+
+ @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860")
+ private Integer price;
+
+ @Schema(description = "商品信息")
+ @Data
+ public static class Product {
+
+ @Schema(description = "积分商城商品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "31718")
+ private Long id;
+
+ @Schema(description = "商品 SKU 编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2736")
+ private Long skuId;
+
+ @Schema(description = "可兑换数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3926")
+ private Integer count;
+
+ @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer point;
+
+ @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860")
+ private Integer price;
+
+ @Schema(description = "积分商城商品库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+ private Integer stock;
+
+ }
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java
new file mode 100644
index 0000000..6a41195
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityPageReqVO.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.promotion.controller.app.point.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+@Schema(description = "用户 App - 积分商城活动分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class AppPointActivityPageReqVO extends PageParam {
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java
new file mode 100644
index 0000000..29f4f97
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/app/point/vo/AppPointActivityRespVO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.promotion.controller.app.point.vo;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "用户 App - 积分商城活动 Response VO")
+@Data
+public class AppPointActivityRespVO {
+
+ @Schema(description = "积分商城活动编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11373")
+ @ExcelProperty("积分商城活动编号")
+ private Long id;
+
+ @Schema(description = "积分商城活动商品", requiredMode = Schema.RequiredMode.REQUIRED, example = "19509")
+ @ExcelProperty("积分商城活动商品")
+ private Long spuId;
+
+ @Schema(description = "活动状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("活动状态")
+ private Integer status;
+
+ @Schema(description = "积分商城活动库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("积分商城活动库存")
+ private Integer stock; // 剩余库存积分兑换时扣减
+
+ @Schema(description = "积分商城活动总库存", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
+ @ExcelProperty("积分商城活动总库存")
+ private Integer totalStock;
+
+ // TODO @puhui999:只返回必要的字段,例如说 remark、sort、createTime 应该是不需要的呢。也可以看看别的也不需要哈。
+
+ @Schema(description = "备注", example = "你说的对")
+ @ExcelProperty("备注")
+ private String remark;
+
+ @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("排序")
+ private Integer sort;
+
+ @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
+ @ExcelProperty("创建时间")
+ private LocalDateTime createTime;
+
+ // ========== 商品字段 ==========
+
+ @Schema(description = "商品名称", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 name 读取
+ example = "618大促")
+ private String spuName;
+ @Schema(description = "商品主图", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 picUrl 读取
+ example = "https://www.iocoder.cn/xx.png")
+ private String picUrl;
+ @Schema(description = "商品市场价,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, // 从 SPU 的 marketPrice 读取
+ example = "50")
+ private Integer marketPrice;
+
+ //======================= 显示所需兑换积分最少的 sku 信息 =======================
+
+ @Schema(description = "兑换积分", requiredMode = Schema.RequiredMode.REQUIRED)
+ private Integer point;
+
+ @Schema(description = "兑换金额,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "15860")
+ private Integer price;
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java
new file mode 100644
index 0000000..c3345be
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointActivityDO.java
@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.module.promotion.dal.dataobject.point;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+/**
+ * 积分商城活动 DO
+ *
+ * @author HUIHUI
+ */
+@TableName(value = "promotion_point_activity", autoResultMap = true)
+@KeySequence("promotion_point_activity_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class PointActivityDO extends BaseDO {
+
+ /**
+ * 积分商城活动编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 积分商城活动商品
+ */
+ private Long spuId;
+ /**
+ * 活动状态
+ *
+ * 枚举 {@link CommonStatusEnum 对应的类}
+ */
+ private Integer status;
+ /**
+ * 备注
+ */
+ private String remark;
+ /**
+ * 排序
+ */
+ private Integer sort;
+
+ /**
+ * 积分商城活动库存(剩余库存积分兑换时扣减)
+ */
+ private Integer stock;
+ /**
+ * 积分商城活动总库存
+ */
+ private Integer totalStock;
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java
new file mode 100644
index 0000000..041ac5a
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/dataobject/point/PointProductDO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.promotion.dal.dataobject.point;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * 积分商城商品 DO
+ *
+ * @author HUIHUI
+ */
+@TableName("promotion_point_product")
+@KeySequence("promotion_point_product_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class PointProductDO extends BaseDO {
+
+ /**
+ * 积分商城商品编号
+ */
+ @TableId
+ private Long id;
+ /**
+ * 积分商城活动 id
+ *
+ * 关联 {@link PointActivityDO#getId()}
+ */
+ private Long activityId;
+ /**
+ * 商品 SPU 编号
+ */
+ private Long spuId;
+ /**
+ * 商品 SKU 编号
+ */
+ private Long skuId;
+ /**
+ * 可兑换次数
+ */
+ private Integer count;
+ /**
+ * 所需兑换积分
+ */
+ private Integer point;
+ /**
+ * 所需兑换金额,单位:分
+ */
+ private Integer price;
+ /**
+ * 积分商城商品库存
+ */
+ private Integer stock;
+ /**
+ * 积分商城商品状态
+ *
+ * 枚举 {@link CommonStatusEnum 对应的类}
+ */
+ private Integer activityStatus;
+
+}
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java
new file mode 100644
index 0000000..d724c32
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointActivityMapper.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.point;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 积分商城活动 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface PointActivityMapper extends BaseMapperX {
+
+ default PageResult selectPage(PointActivityPageReqVO reqVO) {
+ return selectPage(reqVO, new LambdaQueryWrapperX()
+ .eqIfPresent(PointActivityDO::getSpuId, reqVO.getSpuId())
+ .eqIfPresent(PointActivityDO::getStatus, reqVO.getStatus())
+ .eqIfPresent(PointActivityDO::getRemark, reqVO.getRemark())
+ .eqIfPresent(PointActivityDO::getSort, reqVO.getSort())
+ .betweenIfPresent(PointActivityDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(PointActivityDO::getId));
+ }
+
+ /**
+ * 更新活动库存(减少)
+ *
+ * @param id 活动编号
+ * @param count 扣减的库存数量(正数)
+ * @return 影响的行数
+ */
+ default int updateStockDecr(Long id, int count) {
+ Assert.isTrue(count > 0);
+ return update(null, new LambdaUpdateWrapper()
+ .eq(PointActivityDO::getId, id)
+ .ge(PointActivityDO::getStock, count)
+ .setSql("stock = stock - " + count));
+ }
+
+ /**
+ * 更新活动库存(增加)
+ *
+ * @param id 活动编号
+ * @param count 增加的库存数量(正数)
+ * @return 影响的行数
+ */
+ default int updateStockIncr(Long id, int count) {
+ Assert.isTrue(count > 0);
+ return update(null, new LambdaUpdateWrapper()
+ .eq(PointActivityDO::getId, id)
+ .setSql("stock = stock + " + count));
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java
new file mode 100644
index 0000000..3c6d469
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/dal/mysql/point/PointProductMapper.java
@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.module.promotion.dal.mysql.point;
+
+import cn.hutool.core.lang.Assert;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 积分商城商品 Mapper
+ *
+ * @author HUIHUI
+ */
+@Mapper
+public interface PointProductMapper extends BaseMapperX {
+
+ default List selectListByActivityId(Collection activityIds) {
+ return selectList(PointProductDO::getActivityId, activityIds);
+ }
+
+ default List selectListByActivityId(Long activityId) {
+ return selectList(PointProductDO::getActivityId, activityId);
+ }
+
+ default void updateByActivityId(PointProductDO pointProductDO) {
+ update(pointProductDO, new LambdaUpdateWrapper()
+ .eq(PointProductDO::getActivityId, pointProductDO.getActivityId()));
+ }
+
+ default PointProductDO selectListByActivityIdAndSkuId(Long activityId, Long skuId) {
+ return selectOne(PointProductDO::getActivityId, activityId,
+ PointProductDO::getSkuId, skuId);
+ }
+
+ /**
+ * 更新活动库存(减少)
+ *
+ * @param id 活动编号
+ * @param count 扣减的库存数量(减少库存)
+ * @return 影响的行数
+ */
+ default int updateStockDecr(Long id, int count) {
+ Assert.isTrue(count > 0);
+ return update(null, new LambdaUpdateWrapper()
+ .eq(PointProductDO::getId, id)
+ .ge(PointProductDO::getStock, count)
+ .setSql("stock = stock - " + count));
+ }
+
+ /**
+ * 更新活动库存(增加)
+ *
+ * @param id 活动编号
+ * @param count 需要增加的库存(增加库存)
+ * @return 影响的行数
+ */
+ default int updateStockIncr(Long id, int count) {
+ Assert.isTrue(count > 0);
+ return update(null, new LambdaUpdateWrapper()
+ .eq(PointProductDO::getId, id)
+ .setSql("stock = stock + " + count));
+ }
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java
new file mode 100644
index 0000000..e27b227
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityService.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.promotion.service.point;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
+
+import javax.validation.Valid;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 积分商城活动 Service 接口
+ *
+ * @author HUIHUI
+ */
+public interface PointActivityService {
+
+ /**
+ * 创建积分商城活动
+ *
+ * @param createReqVO 创建信息
+ * @return 编号
+ */
+ Long createPointActivity(@Valid PointActivitySaveReqVO createReqVO);
+
+ /**
+ * 更新积分商城活动
+ *
+ * @param updateReqVO 更新信息
+ */
+ void updatePointActivity(@Valid PointActivitySaveReqVO updateReqVO);
+
+ /**
+ * 更新积分商城商品库存(减少)
+ *
+ * @param id 活动编号
+ * @param skuId sku 编号
+ * @param count 数量(正数)
+ */
+ void updatePointStockDecr(Long id, Long skuId, Integer count);
+
+ /**
+ * 更新积分商城商品库存(增加)
+ *
+ * @param id 活动编号
+ * @param skuId sku 编号
+ * @param count 数量(正数)
+ */
+ void updatePointStockIncr(Long id, Long skuId, Integer count);
+
+ /**
+ * 关闭积分商城活动
+ *
+ * @param id 编号
+ */
+ void closePointActivity(Long id);
+
+ /**
+ * 删除积分商城活动
+ *
+ * @param id 编号
+ */
+ void deletePointActivity(Long id);
+
+ /**
+ * 获得积分商城活动
+ *
+ * @param id 编号
+ * @return 积分商城活动
+ */
+ PointActivityDO getPointActivity(Long id);
+
+ /**
+ * 获得积分商城活动分页
+ *
+ * @param pageReqVO 分页查询
+ * @return 积分商城活动分页
+ */
+ PageResult getPointActivityPage(PointActivityPageReqVO pageReqVO);
+
+ /**
+ * 获得积分商城活动列表
+ *
+ * @param ids 活动编号
+ * @return 积分商城活动列表
+ */
+ List getPointActivityListByIds(Collection ids);
+
+ /**
+ * 获得活动商品
+ *
+ * @param activityIds 活动编号
+ * @return 获得活动商品
+ */
+ List getPointProductListByActivityIds(Collection activityIds);
+
+ /**
+ * 【下单前】校验是否参与积分商城活动
+ *
+ * 如果校验失败,则抛出业务异常
+ *
+ * @param activityId 活动编号
+ * @param skuId SKU 编号
+ * @param count 数量
+ * @return 积分商城商品信息
+ */
+ PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count);
+
+}
\ No newline at end of file
diff --git a/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java
new file mode 100644
index 0000000..697279c
--- /dev/null
+++ b/yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/service/point/PointActivityServiceImpl.java
@@ -0,0 +1,309 @@
+package cn.iocoder.yudao.module.promotion.service.point;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.product.api.sku.ProductSkuApi;
+import cn.iocoder.yudao.module.product.api.sku.dto.ProductSkuRespDTO;
+import cn.iocoder.yudao.module.product.api.spu.ProductSpuApi;
+import cn.iocoder.yudao.module.product.api.spu.dto.ProductSpuRespDTO;
+import cn.iocoder.yudao.module.promotion.api.point.dto.PointValidateJoinRespDTO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivityPageReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.activity.PointActivitySaveReqVO;
+import cn.iocoder.yudao.module.promotion.controller.admin.point.vo.product.PointProductSaveReqVO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointActivityDO;
+import cn.iocoder.yudao.module.promotion.dal.dataobject.point.PointProductDO;
+import cn.iocoder.yudao.module.promotion.dal.mysql.point.PointActivityMapper;
+import cn.iocoder.yudao.module.promotion.dal.mysql.point.PointProductMapper;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.hutool.core.collection.CollUtil.intersectionDistinct;
+import static cn.hutool.core.collection.CollUtil.isNotEmpty;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SKU_NOT_EXISTS;
+import static cn.iocoder.yudao.module.product.enums.ErrorCodeConstants.SPU_NOT_EXISTS;
+import static cn.iocoder.yudao.module.promotion.enums.ErrorCodeConstants.*;
+import static java.util.Collections.singletonList;
+
+/**
+ * 积分商城活动 Service 实现类
+ *
+ * @author HUIHUI
+ */
+@Service
+@Validated
+public class PointActivityServiceImpl implements PointActivityService {
+
+ @Resource
+ private PointActivityMapper pointActivityMapper;
+ @Resource
+ private PointProductMapper pointProductMapper;
+
+ @Resource
+ private ProductSpuApi productSpuApi;
+ @Resource
+ private ProductSkuApi productSkuApi;
+
+ private static List buildPointProductDO(PointActivityDO pointActivity, List products) {
+ return BeanUtils.toBean(products, PointProductDO.class, product ->
+ product.setSpuId(pointActivity.getSpuId()).setActivityId(pointActivity.getId())
+ .setActivityStatus(pointActivity.getStatus()));
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public Long createPointActivity(PointActivitySaveReqVO createReqVO) {
+ // 1.1 校验商品是否存在
+ validateProductExists(createReqVO.getSpuId(), createReqVO.getProducts());
+ // 1.2 校验商品是否已经参加别的活动
+ validatePointActivityProductConflicts(null, createReqVO.getProducts());
+
+ // 2.1 插入积分商城活动
+ PointActivityDO pointActivity = BeanUtils.toBean(createReqVO, PointActivityDO.class)
+ .setStatus(CommonStatusEnum.ENABLE.getStatus())
+ .setStock(getSumValue(createReqVO.getProducts(), PointProductSaveReqVO::getStock, Integer::sum));
+ pointActivity.setTotalStock(pointActivity.getStock());
+ pointActivityMapper.insert(pointActivity);
+ // 2.2 插入积分商城活动商品
+ pointProductMapper.insertBatch(buildPointProductDO(pointActivity, createReqVO.getProducts()));
+ return pointActivity.getId();
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updatePointActivity(PointActivitySaveReqVO updateReqVO) {
+ // 1.1 校验存在
+ PointActivityDO activity = validatePointActivityExists(updateReqVO.getId());
+ if (CommonStatusEnum.DISABLE.getStatus().equals(activity.getStatus())) {
+ throw exception(POINT_ACTIVITY_UPDATE_FAIL_STATUS_CLOSED);
+ }
+ // 1.2 校验商品是否存在
+ validateProductExists(updateReqVO.getSpuId(), updateReqVO.getProducts());
+ // 1.3 校验商品是否已经参加别的活动
+ validatePointActivityProductConflicts(updateReqVO.getId(), updateReqVO.getProducts());
+
+ // 2.1 更新积分商城活动
+ PointActivityDO updateObj = BeanUtils.toBean(updateReqVO, PointActivityDO.class)
+ .setStock(getSumValue(updateReqVO.getProducts(), PointProductSaveReqVO::getStock, Integer::sum));
+ if (updateObj.getStock() > activity.getTotalStock()) { // 如果更新的库存大于原来的库存,则更新总库存
+ updateObj.setTotalStock(updateObj.getStock());
+ }
+ pointActivityMapper.updateById(updateObj);
+ // 2.2 更新商品
+ updateSeckillProduct(updateObj, updateReqVO.getProducts());
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updatePointStockDecr(Long id, Long skuId, Integer count) {
+ // 1.1 校验活动库存是否充足
+ PointActivityDO activity = validatePointActivityExists(id);
+ if (count > activity.getStock()) {
+ throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL);
+ }
+ // 1.2 校验商品库存是否充足
+ PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(id, skuId);
+ if (product == null || count > product.getStock()) {
+ throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL);
+ }
+
+ // 2.1 更新活动商品库存
+ int updateCount = pointProductMapper.updateStockDecr(product.getId(), count);
+ if (updateCount == 0) {
+ throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL);
+ }
+
+ // 2.2 更新活动库存
+ updateCount = pointActivityMapper.updateStockDecr(id, count);
+ if (updateCount == 0) {
+ throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL);
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void updatePointStockIncr(Long id, Long skuId, Integer count) {
+ PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(id, skuId);
+ // 更新活动商品库存
+ pointProductMapper.updateStockIncr(product.getId(), count);
+ // 更新活动库存
+ pointActivityMapper.updateStockIncr(id, count);
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void closePointActivity(Long id) {
+ // 校验存在
+ PointActivityDO pointActivity = validatePointActivityExists(id);
+ if (CommonStatusEnum.DISABLE.getStatus().equals(pointActivity.getStatus())) {
+ throw exception(POINT_ACTIVITY_CLOSE_FAIL_STATUS_CLOSED);
+ }
+
+ // 更新
+ pointActivityMapper.updateById(new PointActivityDO().setId(id).setStatus(CommonStatusEnum.DISABLE.getStatus()));
+ // 更新活动商品状态
+ pointProductMapper.updateByActivityId(new PointProductDO().setActivityId(id).setActivityStatus(
+ CommonStatusEnum.DISABLE.getStatus()));
+ }
+
+ /**
+ * 更新秒杀商品
+ *
+ * @param activity 秒杀活动
+ * @param products 该活动的最新商品配置
+ */
+ private void updateSeckillProduct(PointActivityDO activity, List products) {
+ // 第一步,对比新老数据,获得添加、修改、删除的列表
+ List newList = buildPointProductDO(activity, products);
+ List oldList = pointProductMapper.selectListByActivityId(activity.getId());
+ List> diffList = diffList(oldList, newList, (oldVal, newVal) -> {
+ boolean same = ObjectUtil.equal(oldVal.getSkuId(), newVal.getSkuId());
+ if (same) {
+ newVal.setId(oldVal.getId());
+ }
+ return same;
+ });
+
+ // 第二步,批量添加、修改、删除
+ if (isNotEmpty(diffList.get(0))) {
+ pointProductMapper.insertBatch(diffList.get(0));
+ }
+ if (isNotEmpty(diffList.get(1))) {
+ pointProductMapper.updateBatch(diffList.get(1));
+ }
+ if (isNotEmpty(diffList.get(2))) {
+ pointProductMapper.deleteByIds(convertList(diffList.get(2), PointProductDO::getId));
+ }
+ }
+
+ @Override
+ @Transactional(rollbackFor = Exception.class)
+ public void deletePointActivity(Long id) {
+ // 校验存在
+ PointActivityDO pointActivity = validatePointActivityExists(id);
+ if (CommonStatusEnum.ENABLE.getStatus().equals(pointActivity.getStatus())) {
+ throw exception(POINT_ACTIVITY_DELETE_FAIL_STATUS_NOT_CLOSED_OR_END);
+ }
+
+ // 删除商城活动
+ pointActivityMapper.deleteById(id);
+ // 删除活动商品
+ List products = pointProductMapper.selectListByActivityId(id);
+ pointProductMapper.deleteByIds(convertSet(products, PointProductDO::getId));
+ }
+
+ private PointActivityDO validatePointActivityExists(Long id) {
+ PointActivityDO pointActivityDO = pointActivityMapper.selectById(id);
+ if (pointActivityDO == null) {
+ throw exception(POINT_ACTIVITY_NOT_EXISTS);
+ }
+ return pointActivityDO;
+ }
+
+ /**
+ * 校验秒杀商品是否都存在
+ *
+ * @param spuId 商品 SPU 编号
+ * @param products 秒杀商品
+ */
+ private void validateProductExists(Long spuId, List products) {
+ // 1. 校验商品 spu 是否存在
+ ProductSpuRespDTO spu = productSpuApi.getSpu(spuId);
+ if (spu == null) {
+ throw exception(SPU_NOT_EXISTS);
+ }
+
+ // 2. 校验商品 sku 都存在
+ List skus = productSkuApi.getSkuListBySpuId(singletonList(spuId));
+ Map skuMap = convertMap(skus, ProductSkuRespDTO::getId);
+ products.forEach(product -> {
+ if (!skuMap.containsKey(product.getSkuId())) {
+ throw exception(SKU_NOT_EXISTS);
+ }
+ });
+ }
+
+ /**
+ * 校验商品是否冲突
+ *
+ * @param id 编号
+ * @param products 商品列表
+ */
+ private void validatePointActivityProductConflicts(Long id, List products) {
+ // 1.1 查询所有开启的积分商城活动
+ List activityList = pointActivityMapper.selectList(PointActivityDO::getStatus,
+ CommonStatusEnum.ENABLE.getStatus());
+ if (id != null) { // 更新时排除自己
+ activityList.removeIf(item -> ObjectUtil.equal(item.getId(), id));
+ }
+ // 1.2 查询活动下的所有商品
+ List productList = pointProductMapper.selectListByActivityId(
+ convertList(activityList, PointActivityDO::getId));
+ Map> productListMap = convertMultiMap(productList, PointProductDO::getActivityId);
+
+ // 2. 校验商品是否冲突
+ activityList.forEach(item -> {
+ findAndThen(productListMap, item.getId(), discountProducts -> {
+ if (!intersectionDistinct(convertList(discountProducts, PointProductDO::getSpuId),
+ convertList(products, PointProductSaveReqVO::getSpuId)).isEmpty()) {
+ throw exception(POINT_ACTIVITY_SPU_CONFLICTS);
+ }
+ });
+ });
+ }
+
+ @Override
+ public PointActivityDO getPointActivity(Long id) {
+ return pointActivityMapper.selectById(id);
+ }
+
+ @Override
+ public PageResult getPointActivityPage(PointActivityPageReqVO pageReqVO) {
+ return pointActivityMapper.selectPage(pageReqVO);
+ }
+
+ @Override
+ public List getPointActivityListByIds(Collection ids) {
+ return pointActivityMapper.selectList(PointActivityDO::getId, ids);
+ }
+
+ @Override
+ public List getPointProductListByActivityIds(Collection activityIds) {
+ return pointProductMapper.selectListByActivityId(activityIds);
+ }
+
+ @Override
+ public PointValidateJoinRespDTO validateJoinPointActivity(Long activityId, Long skuId, Integer count) {
+ // 1. 校验积分商城活动是否存在
+ PointActivityDO activity = validatePointActivityExists(activityId);
+ if (CommonStatusEnum.isDisable(activity.getStatus())) {
+ throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_STATUS_CLOSED);
+ }
+
+ // 2.1 校验积分商城商品是否存在
+ PointProductDO product = pointProductMapper.selectListByActivityIdAndSkuId(activityId, skuId);
+ if (product == null) {
+ throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_PRODUCT_NOT_EXISTS);
+ }
+ // 2.2 超过单次购买限制
+ if (count > product.getCount()) {
+ throw exception(POINT_ACTIVITY_JOIN_ACTIVITY_SINGLE_LIMIT_COUNT_EXCEED);
+ }
+ // 2.2 校验库存是否充足
+ if (count > product.getStock()) {
+ throw exception(POINT_ACTIVITY_UPDATE_STOCK_FAIL);
+ }
+ return BeanUtils.toBean(product, PointValidateJoinRespDTO.class);
+ }
+
+}
\ No newline at end of file