You've already forked FrameTour-BE
feat(pricing): 添加场景优惠券功能
- 创建场景优惠券领取控制器,提供前端优惠券领取接口 - 创建场景优惠券配置管理控制器,提供后台管理端配置接口 - 定义场景优惠券领取和配置相关的请求响应DTO - 创建场景优惠券配置实体和数据库表结构 - 实现场景优惠券配置的数据访问和业务逻辑处理 - 实现场景优惠券领取功能,支持景区隔离和默认配置回退 - 添加优惠券领取状态检查和用户限制验证逻辑 - 实现分页查询和配置管理功能
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
package com.ycwl.basic.pricing.controller;
|
||||||
|
|
||||||
|
import com.ycwl.basic.constant.BaseContextHandler;
|
||||||
|
import com.ycwl.basic.pricing.dto.CouponClaimResult;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponClaimReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponAvailableResp;
|
||||||
|
import com.ycwl.basic.pricing.service.ISceneCouponService;
|
||||||
|
import com.ycwl.basic.utils.ApiResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券领取控制器(前端/移动端)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/pricing/scene-coupon")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SceneCouponClaimController {
|
||||||
|
|
||||||
|
private final ISceneCouponService sceneCouponService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询场景下可领取的优惠券列表
|
||||||
|
*
|
||||||
|
* @param sceneKey 场景标识
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 可领取优惠券列表(包含用户领取状态)
|
||||||
|
*/
|
||||||
|
@GetMapping("/available")
|
||||||
|
public ApiResponse<List<SceneCouponAvailableResp>> getAvailableCoupons(
|
||||||
|
@RequestParam String sceneKey,
|
||||||
|
@RequestParam Long scenicId) {
|
||||||
|
try {
|
||||||
|
Long userId = getUserId();
|
||||||
|
List<SceneCouponAvailableResp> list = sceneCouponService.getAvailableCoupons(sceneKey, scenicId, userId);
|
||||||
|
return ApiResponse.success(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|可领取列表查询失败 sceneKey={}, scenicId={}", sceneKey, scenicId, e);
|
||||||
|
return ApiResponse.fail("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领取场景优惠券
|
||||||
|
*
|
||||||
|
* @param req 领取请求
|
||||||
|
* @return 领取结果列表
|
||||||
|
*/
|
||||||
|
@PostMapping("/claim")
|
||||||
|
public ApiResponse<List<CouponClaimResult>> claimCoupons(@RequestBody SceneCouponClaimReq req) {
|
||||||
|
try {
|
||||||
|
Long userId = getUserId();
|
||||||
|
if (userId == null) {
|
||||||
|
return ApiResponse.fail("用户未登录");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<CouponClaimResult> results = sceneCouponService.claimCoupons(req, userId);
|
||||||
|
|
||||||
|
// 判断整体结果
|
||||||
|
boolean hasSuccess = results.stream().anyMatch(CouponClaimResult::isSuccess);
|
||||||
|
if (!hasSuccess && !results.isEmpty()) {
|
||||||
|
// 全部失败,返回第一个错误信息
|
||||||
|
return ApiResponse.fail(results.get(0).getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.success(results);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|领取失败 req={}", req, e);
|
||||||
|
return ApiResponse.fail("领取失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前登录用户ID
|
||||||
|
*/
|
||||||
|
private Long getUserId() {
|
||||||
|
try {
|
||||||
|
String userIdStr = BaseContextHandler.getUserId();
|
||||||
|
if (userIdStr == null || userIdStr.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Long.valueOf(userIdStr);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
log.warn("无法解析用户ID: {}", BaseContextHandler.getUserId());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package com.ycwl.basic.pricing.controller;
|
||||||
|
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponConfigPageReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponConfigSaveReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponConfigResp;
|
||||||
|
import com.ycwl.basic.pricing.service.ISceneCouponService;
|
||||||
|
import com.ycwl.basic.utils.ApiResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券配置管理控制器(后台管理端)
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/pricing/admin/scene-coupon")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SceneCouponConfigController {
|
||||||
|
|
||||||
|
private final ISceneCouponService sceneCouponService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询场景优惠券配置
|
||||||
|
*/
|
||||||
|
@PostMapping("/page")
|
||||||
|
public ApiResponse<PageInfo<SceneCouponConfigResp>> page(@RequestBody SceneCouponConfigPageReq req) {
|
||||||
|
try {
|
||||||
|
PageInfo<SceneCouponConfigResp> pageInfo = sceneCouponService.pageConfig(req);
|
||||||
|
return ApiResponse.success(pageInfo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|分页查询失败", e);
|
||||||
|
return ApiResponse.fail("分页查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/detail/{id}")
|
||||||
|
public ApiResponse<SceneCouponConfigResp> getDetail(@PathVariable("id") Long id) {
|
||||||
|
try {
|
||||||
|
SceneCouponConfigResp detail = sceneCouponService.getConfigDetail(id);
|
||||||
|
if (detail == null) {
|
||||||
|
return ApiResponse.fail("记录不存在");
|
||||||
|
}
|
||||||
|
return ApiResponse.success(detail);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|详情查询失败 id={}", id, e);
|
||||||
|
return ApiResponse.fail("详情查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存配置(新增/更新)
|
||||||
|
*/
|
||||||
|
@PostMapping("/save")
|
||||||
|
public ApiResponse<Boolean> save(@RequestBody SceneCouponConfigSaveReq req) {
|
||||||
|
try {
|
||||||
|
boolean success = sceneCouponService.saveConfig(req);
|
||||||
|
return ApiResponse.success(success);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return ApiResponse.fail(e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|保存失败", e);
|
||||||
|
return ApiResponse.fail("保存失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除配置
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/delete/{id}")
|
||||||
|
public ApiResponse<Boolean> delete(@PathVariable("id") Long id) {
|
||||||
|
try {
|
||||||
|
boolean success = sceneCouponService.deleteConfig(id);
|
||||||
|
return ApiResponse.success(success);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|删除失败 id={}", id, e);
|
||||||
|
return ApiResponse.fail("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已配置的场景列表(用于前端下拉选择)
|
||||||
|
*/
|
||||||
|
@GetMapping("/scenes")
|
||||||
|
public ApiResponse<List<String>> listSceneKeys() {
|
||||||
|
try {
|
||||||
|
List<String> sceneKeys = sceneCouponService.listSceneKeys();
|
||||||
|
return ApiResponse.success(sceneKeys);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|场景列表查询失败", e);
|
||||||
|
return ApiResponse.fail("场景列表查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据场景和景区查询配置列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/by-scene")
|
||||||
|
public ApiResponse<List<SceneCouponConfigResp>> listByScene(
|
||||||
|
@RequestParam String sceneKey,
|
||||||
|
@RequestParam Long scenicId) {
|
||||||
|
try {
|
||||||
|
List<SceneCouponConfigResp> list = sceneCouponService.listBySceneKeyAndScenicId(sceneKey, scenicId);
|
||||||
|
return ApiResponse.success(list);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("场景优惠券|按场景查询失败 sceneKey={}, scenicId={}", sceneKey, scenicId, e);
|
||||||
|
return ApiResponse.fail("按场景查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto.req;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券领取请求
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SceneCouponClaimReq {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景标识符(必填)
|
||||||
|
*/
|
||||||
|
private String sceneKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID(必填)
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定领取的优惠券ID(可选,不传则领取场景下所有可领取的优惠券)
|
||||||
|
*/
|
||||||
|
private Long couponId;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto.req;
|
||||||
|
|
||||||
|
import com.ycwl.basic.model.common.BaseQueryParameterReq;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券配置分页查询请求
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class SceneCouponConfigPageReq extends BaseQueryParameterReq {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景标识(模糊匹配)
|
||||||
|
*/
|
||||||
|
private String sceneKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID;为空表示不筛选
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券ID
|
||||||
|
*/
|
||||||
|
private Long couponId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用:1启用 0禁用
|
||||||
|
*/
|
||||||
|
private Integer enabled;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto.req;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券配置保存请求(新增/修改)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SceneCouponConfigSaveReq {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID(为空表示新增;不为空表示更新)
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景标识符
|
||||||
|
*/
|
||||||
|
private String sceneKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的优惠券ID
|
||||||
|
*/
|
||||||
|
private Long couponId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID;0=默认配置
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用:1启用 0禁用
|
||||||
|
*/
|
||||||
|
private Integer enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序顺序(越小越靠前)
|
||||||
|
*/
|
||||||
|
private Integer sortOrder;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景下可领取优惠券响应
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SceneCouponAvailableResp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券ID
|
||||||
|
*/
|
||||||
|
private Long couponId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券名称
|
||||||
|
*/
|
||||||
|
private String couponName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券类型 (PERCENTAGE/FIXED_AMOUNT)
|
||||||
|
*/
|
||||||
|
private String couponType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠值
|
||||||
|
*/
|
||||||
|
private BigDecimal discountValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最小使用金额
|
||||||
|
*/
|
||||||
|
private BigDecimal minAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大优惠金额
|
||||||
|
*/
|
||||||
|
private BigDecimal maxDiscount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期起始
|
||||||
|
*/
|
||||||
|
private LocalDateTime validFrom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 有效期结束
|
||||||
|
*/
|
||||||
|
private LocalDateTime validUntil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户可领取数量限制(每人最多可领,null或0表示无限制)
|
||||||
|
*/
|
||||||
|
private Integer userClaimLimit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户已领取数量
|
||||||
|
*/
|
||||||
|
private Integer userClaimedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户剩余可领取数量(-1表示无限制,0表示已达上限)
|
||||||
|
*/
|
||||||
|
private Integer userRemaining;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可领取(综合判断:库存、用户限制、有效期等)
|
||||||
|
*/
|
||||||
|
private Boolean canClaim;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不可领取原因(当 canClaim=false 时)
|
||||||
|
*/
|
||||||
|
private String cannotClaimReason;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto.resp;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券配置响应(包含优惠券详情)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SceneCouponConfigResp {
|
||||||
|
|
||||||
|
// ========== 配置信息 ==========
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
private String sceneKey;
|
||||||
|
|
||||||
|
private Long couponId;
|
||||||
|
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
private Integer enabled;
|
||||||
|
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
// ========== 关联的优惠券信息 ==========
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券名称
|
||||||
|
*/
|
||||||
|
private String couponName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券类型 (PERCENTAGE/FIXED_AMOUNT)
|
||||||
|
*/
|
||||||
|
private String couponType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠值
|
||||||
|
*/
|
||||||
|
private BigDecimal discountValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券是否启用
|
||||||
|
*/
|
||||||
|
private Boolean couponActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券有效期起始
|
||||||
|
*/
|
||||||
|
private LocalDateTime couponValidFrom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优惠券有效期结束
|
||||||
|
*/
|
||||||
|
private LocalDateTime couponValidUntil;
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package com.ycwl.basic.pricing.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景-优惠券关联配置实体
|
||||||
|
* <p>
|
||||||
|
* 用于配置不同场景下可领取的优惠券,支持分景区配置。
|
||||||
|
* scenicId=0 表示默认配置(所有景区通用),具体景区配置优先级高于默认配置。
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("price_scene_coupon_config")
|
||||||
|
public class PriceSceneCouponConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键ID
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景标识符(如 home_banner, checkout_page, new_user_popup)
|
||||||
|
*/
|
||||||
|
private String sceneKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联的优惠券ID (price_coupon_config.id)
|
||||||
|
*/
|
||||||
|
private Long couponId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID;0=默认配置(所有景区通用)
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用:1启用 0禁用
|
||||||
|
*/
|
||||||
|
private Integer enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序顺序(越小越靠前)
|
||||||
|
*/
|
||||||
|
private Integer sortOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private Date updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package com.ycwl.basic.pricing.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponConfigResp;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceSceneCouponConfig;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券配置Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface PriceSceneCouponConfigMapper extends BaseMapper<PriceSceneCouponConfig> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询场景下已启用的优惠券配置(带优惠券详情)
|
||||||
|
* 用于前端领取接口
|
||||||
|
*
|
||||||
|
* @param sceneKey 场景标识
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 配置列表(带优惠券信息)
|
||||||
|
*/
|
||||||
|
@Select("SELECT scc.id, scc.scene_key, scc.coupon_id, scc.scenic_id, scc.enabled, " +
|
||||||
|
"scc.sort_order, scc.create_time, scc.update_time, " +
|
||||||
|
"c.coupon_name, c.coupon_type, c.discount_value, " +
|
||||||
|
"c.is_active AS coupon_active, c.valid_from AS coupon_valid_from, c.valid_until AS coupon_valid_until " +
|
||||||
|
"FROM price_scene_coupon_config scc " +
|
||||||
|
"JOIN price_coupon_config c ON scc.coupon_id = c.id AND c.deleted = 0 " +
|
||||||
|
"WHERE scc.scene_key = #{sceneKey} AND scc.scenic_id = #{scenicId} " +
|
||||||
|
"AND scc.enabled = 1 AND c.is_active = 1 " +
|
||||||
|
"AND (c.valid_from IS NULL OR c.valid_from <= NOW()) " +
|
||||||
|
"AND (c.valid_until IS NULL OR c.valid_until > NOW()) " +
|
||||||
|
"ORDER BY scc.sort_order ASC, scc.id ASC")
|
||||||
|
List<SceneCouponConfigResp> selectEnabledBySceneKeyAndScenicId(
|
||||||
|
@Param("sceneKey") String sceneKey,
|
||||||
|
@Param("scenicId") Long scenicId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查场景下是否存在已启用的配置(用于判断是否需要回退到默认)
|
||||||
|
*
|
||||||
|
* @param sceneKey 场景标识
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 配置数量
|
||||||
|
*/
|
||||||
|
@Select("SELECT COUNT(*) FROM price_scene_coupon_config " +
|
||||||
|
"WHERE scene_key = #{sceneKey} AND scenic_id = #{scenicId} AND enabled = 1")
|
||||||
|
int countEnabledBySceneKeyAndScenicId(
|
||||||
|
@Param("sceneKey") String sceneKey,
|
||||||
|
@Param("scenicId") Long scenicId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有已配置的场景列表(去重)
|
||||||
|
*
|
||||||
|
* @return 场景标识列表
|
||||||
|
*/
|
||||||
|
@Select("SELECT DISTINCT scene_key FROM price_scene_coupon_config ORDER BY scene_key")
|
||||||
|
List<String> selectDistinctSceneKeys();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理端:带优惠券信息的分页查询
|
||||||
|
*
|
||||||
|
* @param sceneKey 场景标识(模糊匹配,可为null)
|
||||||
|
* @param scenicId 景区ID(精确匹配,可为null)
|
||||||
|
* @param couponId 优惠券ID(精确匹配,可为null)
|
||||||
|
* @param enabled 启用状态(精确匹配,可为null)
|
||||||
|
* @return 配置列表(带优惠券信息)
|
||||||
|
*/
|
||||||
|
@Select("<script>" +
|
||||||
|
"SELECT scc.id, scc.scene_key, scc.coupon_id, scc.scenic_id, scc.enabled, " +
|
||||||
|
"scc.sort_order, scc.create_time, scc.update_time, " +
|
||||||
|
"c.coupon_name, c.coupon_type, c.discount_value, " +
|
||||||
|
"c.is_active AS coupon_active, c.valid_from AS coupon_valid_from, c.valid_until AS coupon_valid_until " +
|
||||||
|
"FROM price_scene_coupon_config scc " +
|
||||||
|
"LEFT JOIN price_coupon_config c ON scc.coupon_id = c.id AND c.deleted = 0 " +
|
||||||
|
"<where>" +
|
||||||
|
"<if test='sceneKey != null and sceneKey != \"\"'>" +
|
||||||
|
"AND scc.scene_key LIKE CONCAT('%', #{sceneKey}, '%') " +
|
||||||
|
"</if>" +
|
||||||
|
"<if test='scenicId != null'>" +
|
||||||
|
"AND scc.scenic_id = #{scenicId} " +
|
||||||
|
"</if>" +
|
||||||
|
"<if test='couponId != null'>" +
|
||||||
|
"AND scc.coupon_id = #{couponId} " +
|
||||||
|
"</if>" +
|
||||||
|
"<if test='enabled != null'>" +
|
||||||
|
"AND scc.enabled = #{enabled} " +
|
||||||
|
"</if>" +
|
||||||
|
"</where>" +
|
||||||
|
"ORDER BY scc.scenic_id DESC, scc.scene_key ASC, scc.sort_order ASC, scc.id DESC" +
|
||||||
|
"</script>")
|
||||||
|
List<SceneCouponConfigResp> selectPageWithCouponInfo(
|
||||||
|
@Param("sceneKey") String sceneKey,
|
||||||
|
@Param("scenicId") Long scenicId,
|
||||||
|
@Param("couponId") Long couponId,
|
||||||
|
@Param("enabled") Integer enabled);
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
package com.ycwl.basic.pricing.service;
|
||||||
|
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.CouponClaimResult;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponClaimReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponConfigPageReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponConfigSaveReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponAvailableResp;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponConfigResp;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券服务接口
|
||||||
|
*/
|
||||||
|
public interface ISceneCouponService {
|
||||||
|
|
||||||
|
// ==================== 后台管理接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询场景优惠券配置
|
||||||
|
*
|
||||||
|
* @param req 查询请求
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
PageInfo<SceneCouponConfigResp> pageConfig(SceneCouponConfigPageReq req);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取配置详情
|
||||||
|
*
|
||||||
|
* @param id 配置ID
|
||||||
|
* @return 配置详情
|
||||||
|
*/
|
||||||
|
SceneCouponConfigResp getConfigDetail(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存配置(新增/更新)
|
||||||
|
*
|
||||||
|
* @param req 保存请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
boolean saveConfig(SceneCouponConfigSaveReq req);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除配置
|
||||||
|
*
|
||||||
|
* @param id 配置ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
boolean deleteConfig(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有已配置的场景列表
|
||||||
|
*
|
||||||
|
* @return 场景标识列表
|
||||||
|
*/
|
||||||
|
List<String> listSceneKeys();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据场景和景区查询配置列表
|
||||||
|
*
|
||||||
|
* @param sceneKey 场景标识
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 配置列表
|
||||||
|
*/
|
||||||
|
List<SceneCouponConfigResp> listBySceneKeyAndScenicId(String sceneKey, Long scenicId);
|
||||||
|
|
||||||
|
// ==================== 前端领取接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询场景下可领取的优惠券列表
|
||||||
|
*
|
||||||
|
* @param sceneKey 场景标识
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @param userId 用户ID(用于判断用户已领取数量)
|
||||||
|
* @return 可领取优惠券列表
|
||||||
|
*/
|
||||||
|
List<SceneCouponAvailableResp> getAvailableCoupons(String sceneKey, Long scenicId, Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 领取场景优惠券
|
||||||
|
*
|
||||||
|
* @param req 领取请求
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 领取结果列表
|
||||||
|
*/
|
||||||
|
List<CouponClaimResult> claimCoupons(SceneCouponClaimReq req, Long userId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,360 @@
|
|||||||
|
package com.ycwl.basic.pricing.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.github.pagehelper.PageHelper;
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.ycwl.basic.model.common.BaseQueryParameterReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.CouponClaimRequest;
|
||||||
|
import com.ycwl.basic.pricing.dto.CouponClaimResult;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponClaimReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponConfigPageReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.req.SceneCouponConfigSaveReq;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponAvailableResp;
|
||||||
|
import com.ycwl.basic.pricing.dto.resp.SceneCouponConfigResp;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceCouponConfig;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceSceneCouponConfig;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceCouponClaimRecordMapper;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceCouponConfigMapper;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceSceneCouponConfigMapper;
|
||||||
|
import com.ycwl.basic.pricing.service.ICouponService;
|
||||||
|
import com.ycwl.basic.pricing.service.ISceneCouponService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 场景优惠券服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SceneCouponServiceImpl implements ISceneCouponService {
|
||||||
|
|
||||||
|
private static final int MAX_PAGE_SIZE = 200;
|
||||||
|
private static final Long DEFAULT_SCENIC_ID = 0L;
|
||||||
|
|
||||||
|
private final PriceSceneCouponConfigMapper sceneCouponConfigMapper;
|
||||||
|
private final PriceCouponConfigMapper couponConfigMapper;
|
||||||
|
private final PriceCouponClaimRecordMapper claimRecordMapper;
|
||||||
|
private final ICouponService couponService;
|
||||||
|
|
||||||
|
// ==================== 后台管理接口实现 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageInfo<SceneCouponConfigResp> pageConfig(SceneCouponConfigPageReq req) {
|
||||||
|
if (req == null) {
|
||||||
|
req = new SceneCouponConfigPageReq();
|
||||||
|
}
|
||||||
|
sanitizePage(req);
|
||||||
|
|
||||||
|
PageHelper.startPage(req.getPageNum(), req.getPageSize());
|
||||||
|
List<SceneCouponConfigResp> list = sceneCouponConfigMapper.selectPageWithCouponInfo(
|
||||||
|
req.getSceneKey(), req.getScenicId(), req.getCouponId(), req.getEnabled());
|
||||||
|
return new PageInfo<>(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SceneCouponConfigResp getConfigDetail(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PriceSceneCouponConfig config = sceneCouponConfigMapper.selectById(id);
|
||||||
|
if (config == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneCouponConfigResp resp = new SceneCouponConfigResp();
|
||||||
|
resp.setId(config.getId());
|
||||||
|
resp.setSceneKey(config.getSceneKey());
|
||||||
|
resp.setCouponId(config.getCouponId());
|
||||||
|
resp.setScenicId(config.getScenicId());
|
||||||
|
resp.setEnabled(config.getEnabled());
|
||||||
|
resp.setSortOrder(config.getSortOrder());
|
||||||
|
resp.setCreateTime(config.getCreateTime());
|
||||||
|
resp.setUpdateTime(config.getUpdateTime());
|
||||||
|
|
||||||
|
// 填充优惠券信息
|
||||||
|
PriceCouponConfig coupon = couponConfigMapper.selectById(config.getCouponId());
|
||||||
|
if (coupon != null) {
|
||||||
|
resp.setCouponName(coupon.getCouponName());
|
||||||
|
resp.setCouponType(coupon.getCouponType() != null ? coupon.getCouponType().name() : null);
|
||||||
|
resp.setDiscountValue(coupon.getDiscountValue());
|
||||||
|
resp.setCouponActive(coupon.getIsActive());
|
||||||
|
resp.setCouponValidFrom(coupon.getValidFrom());
|
||||||
|
resp.setCouponValidUntil(coupon.getValidUntil());
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean saveConfig(SceneCouponConfigSaveReq req) {
|
||||||
|
String err = validateSaveReq(req);
|
||||||
|
if (err != null) {
|
||||||
|
throw new IllegalArgumentException(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
PriceSceneCouponConfig entity = new PriceSceneCouponConfig();
|
||||||
|
entity.setSceneKey(req.getSceneKey().trim());
|
||||||
|
entity.setCouponId(req.getCouponId());
|
||||||
|
entity.setScenicId(req.getScenicId());
|
||||||
|
entity.setEnabled(req.getEnabled());
|
||||||
|
entity.setSortOrder(Objects.requireNonNullElse(req.getSortOrder(), 0));
|
||||||
|
entity.setUpdateTime(new Date());
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (req.getId() != null) {
|
||||||
|
// 更新
|
||||||
|
PriceSceneCouponConfig existing = sceneCouponConfigMapper.selectById(req.getId());
|
||||||
|
if (existing == null) {
|
||||||
|
throw new IllegalArgumentException("记录不存在");
|
||||||
|
}
|
||||||
|
entity.setId(req.getId());
|
||||||
|
return sceneCouponConfigMapper.updateById(entity) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增前检查唯一性
|
||||||
|
PriceSceneCouponConfig existing = sceneCouponConfigMapper.selectOne(
|
||||||
|
new QueryWrapper<PriceSceneCouponConfig>()
|
||||||
|
.eq("scene_key", entity.getSceneKey())
|
||||||
|
.eq("coupon_id", entity.getCouponId())
|
||||||
|
.eq("scenic_id", entity.getScenicId()));
|
||||||
|
if (existing != null) {
|
||||||
|
// 已存在则更新
|
||||||
|
entity.setId(existing.getId());
|
||||||
|
return sceneCouponConfigMapper.updateById(entity) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.setCreateTime(new Date());
|
||||||
|
return sceneCouponConfigMapper.insert(entity) > 0;
|
||||||
|
|
||||||
|
} catch (DuplicateKeyException e) {
|
||||||
|
throw new IllegalArgumentException("保存失败:配置已存在(sceneKey+couponId+scenicId重复)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteConfig(Long id) {
|
||||||
|
if (id == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return sceneCouponConfigMapper.deleteById(id) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> listSceneKeys() {
|
||||||
|
return sceneCouponConfigMapper.selectDistinctSceneKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SceneCouponConfigResp> listBySceneKeyAndScenicId(String sceneKey, Long scenicId) {
|
||||||
|
return sceneCouponConfigMapper.selectPageWithCouponInfo(sceneKey, scenicId, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 前端领取接口实现 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SceneCouponAvailableResp> getAvailableCoupons(String sceneKey, Long scenicId, Long userId) {
|
||||||
|
if (sceneKey == null || sceneKey.isBlank() || scenicId == null) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 景区隔离查询:先查具体景区,为空则回退到默认配置
|
||||||
|
List<SceneCouponConfigResp> configs = getConfigsWithFallback(sceneKey, scenicId);
|
||||||
|
if (configs.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SceneCouponAvailableResp> result = new ArrayList<>();
|
||||||
|
for (SceneCouponConfigResp config : configs) {
|
||||||
|
SceneCouponAvailableResp resp = buildAvailableResp(config, userId);
|
||||||
|
result.add(resp);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public List<CouponClaimResult> claimCoupons(SceneCouponClaimReq req, Long userId) {
|
||||||
|
if (req.getSceneKey() == null || req.getSceneKey().isBlank() || req.getScenicId() == null) {
|
||||||
|
return List.of(CouponClaimResult.failure(CouponClaimResult.ERROR_INVALID_PARAMS, "sceneKey和scenicId不能为空"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取场景下的优惠券配置
|
||||||
|
List<SceneCouponConfigResp> configs = getConfigsWithFallback(req.getSceneKey(), req.getScenicId());
|
||||||
|
if (configs.isEmpty()) {
|
||||||
|
return List.of(CouponClaimResult.failure("SCENE_NOT_FOUND", "该场景暂无可领取的优惠券"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果指定了couponId,只领取指定的
|
||||||
|
if (req.getCouponId() != null) {
|
||||||
|
configs = configs.stream()
|
||||||
|
.filter(c -> req.getCouponId().equals(c.getCouponId()))
|
||||||
|
.toList();
|
||||||
|
if (configs.isEmpty()) {
|
||||||
|
return List.of(CouponClaimResult.failure("COUPON_NOT_IN_SCENE", "指定的优惠券不在该场景中"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用现有的领取服务
|
||||||
|
List<CouponClaimResult> results = new ArrayList<>();
|
||||||
|
for (SceneCouponConfigResp config : configs) {
|
||||||
|
CouponClaimRequest claimReq = new CouponClaimRequest();
|
||||||
|
claimReq.setUserId(userId);
|
||||||
|
claimReq.setCouponId(config.getCouponId());
|
||||||
|
claimReq.setScenicId(String.valueOf(req.getScenicId()));
|
||||||
|
claimReq.setClaimSource("scene:" + req.getSceneKey());
|
||||||
|
|
||||||
|
CouponClaimResult claimResult = couponService.claimCoupon(claimReq);
|
||||||
|
results.add(claimResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 私有方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 带景区回退的配置查询
|
||||||
|
* 如果景区有配置(哪怕只有一条),则使用景区的配置;
|
||||||
|
* 如果景区没有配置,则使用默认景区(scenicId=0)的配置。
|
||||||
|
*/
|
||||||
|
private List<SceneCouponConfigResp> getConfigsWithFallback(String sceneKey, Long scenicId) {
|
||||||
|
// 1. 先查具体景区是否有配置
|
||||||
|
int count = sceneCouponConfigMapper.countEnabledBySceneKeyAndScenicId(sceneKey, scenicId);
|
||||||
|
if (count > 0) {
|
||||||
|
// 景区有配置,使用景区的配置
|
||||||
|
return sceneCouponConfigMapper.selectEnabledBySceneKeyAndScenicId(sceneKey, scenicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 景区没有配置,回退到默认配置 (scenicId=0)
|
||||||
|
return sceneCouponConfigMapper.selectEnabledBySceneKeyAndScenicId(sceneKey, DEFAULT_SCENIC_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建可领取优惠券响应
|
||||||
|
*/
|
||||||
|
private SceneCouponAvailableResp buildAvailableResp(SceneCouponConfigResp config, Long userId) {
|
||||||
|
SceneCouponAvailableResp resp = new SceneCouponAvailableResp();
|
||||||
|
|
||||||
|
// 查询优惠券详情
|
||||||
|
PriceCouponConfig coupon = couponConfigMapper.selectById(config.getCouponId());
|
||||||
|
if (coupon == null) {
|
||||||
|
resp.setCouponId(config.getCouponId());
|
||||||
|
resp.setCanClaim(false);
|
||||||
|
resp.setCannotClaimReason("优惠券不存在");
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.setCouponId(coupon.getId());
|
||||||
|
resp.setCouponName(coupon.getCouponName());
|
||||||
|
resp.setCouponType(coupon.getCouponType() != null ? coupon.getCouponType().name() : null);
|
||||||
|
resp.setDiscountValue(coupon.getDiscountValue());
|
||||||
|
resp.setMinAmount(coupon.getMinAmount());
|
||||||
|
resp.setMaxDiscount(coupon.getMaxDiscount());
|
||||||
|
resp.setValidFrom(coupon.getValidFrom());
|
||||||
|
resp.setValidUntil(coupon.getValidUntil());
|
||||||
|
resp.setUserClaimLimit(coupon.getUserClaimLimit());
|
||||||
|
|
||||||
|
// 查询用户已领取数量
|
||||||
|
int userClaimedCount = 0;
|
||||||
|
if (userId != null) {
|
||||||
|
userClaimedCount = claimRecordMapper.countUserCouponClaims(userId, coupon.getId());
|
||||||
|
}
|
||||||
|
resp.setUserClaimedCount(userClaimedCount);
|
||||||
|
|
||||||
|
// 计算剩余可领取数量
|
||||||
|
int userRemaining;
|
||||||
|
if (coupon.getUserClaimLimit() == null || coupon.getUserClaimLimit() <= 0) {
|
||||||
|
userRemaining = -1; // -1表示无限制
|
||||||
|
} else {
|
||||||
|
userRemaining = Math.max(0, coupon.getUserClaimLimit() - userClaimedCount);
|
||||||
|
}
|
||||||
|
resp.setUserRemaining(userRemaining);
|
||||||
|
|
||||||
|
// 综合判断是否可领取
|
||||||
|
String cannotClaimReason = checkCanClaim(coupon, userClaimedCount);
|
||||||
|
resp.setCanClaim(cannotClaimReason == null);
|
||||||
|
resp.setCannotClaimReason(cannotClaimReason);
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否可领取
|
||||||
|
*
|
||||||
|
* @return null表示可领取,否则返回不可领取原因
|
||||||
|
*/
|
||||||
|
private String checkCanClaim(PriceCouponConfig coupon, int userClaimedCount) {
|
||||||
|
// 1. 检查启用状态
|
||||||
|
if (!Boolean.TRUE.equals(coupon.getIsActive())) {
|
||||||
|
return "优惠券已停用";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 检查有效期
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
if (coupon.getValidFrom() != null && now.isBefore(coupon.getValidFrom())) {
|
||||||
|
return "优惠券尚未生效";
|
||||||
|
}
|
||||||
|
if (coupon.getValidUntil() != null && now.isAfter(coupon.getValidUntil())) {
|
||||||
|
return "优惠券已过期";
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 检查库存
|
||||||
|
if (coupon.getTotalQuantity() != null && coupon.getTotalQuantity() > 0) {
|
||||||
|
int claimed = coupon.getClaimedQuantity() == null ? 0 : coupon.getClaimedQuantity();
|
||||||
|
if (claimed >= coupon.getTotalQuantity()) {
|
||||||
|
return "优惠券已领完";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 检查用户领取限制
|
||||||
|
if (coupon.getUserClaimLimit() != null && coupon.getUserClaimLimit() > 0) {
|
||||||
|
if (userClaimedCount >= coupon.getUserClaimLimit()) {
|
||||||
|
return "您已达到领取上限";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // 可领取
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void sanitizePage(BaseQueryParameterReq req) {
|
||||||
|
if (req.getPageNum() == null || req.getPageNum() < 1) {
|
||||||
|
req.setPageNum(1);
|
||||||
|
}
|
||||||
|
if (req.getPageSize() == null || req.getPageSize() < 1) {
|
||||||
|
req.setPageSize(10);
|
||||||
|
}
|
||||||
|
if (req.getPageSize() > MAX_PAGE_SIZE) {
|
||||||
|
req.setPageSize(MAX_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String validateSaveReq(SceneCouponConfigSaveReq req) {
|
||||||
|
if (req == null) {
|
||||||
|
return "请求体不能为空";
|
||||||
|
}
|
||||||
|
if (req.getSceneKey() == null || req.getSceneKey().isBlank()) {
|
||||||
|
return "sceneKey不能为空";
|
||||||
|
}
|
||||||
|
if (req.getCouponId() == null) {
|
||||||
|
return "couponId不能为空";
|
||||||
|
}
|
||||||
|
if (req.getScenicId() == null) {
|
||||||
|
return "scenicId不能为空";
|
||||||
|
}
|
||||||
|
if (req.getEnabled() == null || (req.getEnabled() != 0 && req.getEnabled() != 1)) {
|
||||||
|
return "enabled必须为0或1";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user