You've already forked FrameTour-BE
Compare commits
7 Commits
0a13bd8b12
...
6039f337cb
| Author | SHA1 | Date | |
|---|---|---|---|
| 6039f337cb | |||
| 13bd60f24b | |||
| 1b1e5f1690 | |||
| 50c84ac1c9 | |||
| 5210b50adb | |||
| bd077b9252 | |||
| 4427c7fde1 |
11
pom.xml
11
pom.xml
@@ -325,17 +325,6 @@
|
|||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</snapshots>
|
</snapshots>
|
||||||
</repository>
|
</repository>
|
||||||
<repository>
|
|
||||||
<id>sonatype-nexus-staging</id>
|
|
||||||
<name>Sonatype Nexus Staging</name>
|
|
||||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
|
|
||||||
<releases>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
</releases>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>true</enabled>
|
|
||||||
</snapshots>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
</repositories>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import com.ycwl.basic.model.pc.price.entity.PriceConfigEntity;
|
|||||||
import com.ycwl.basic.model.pc.price.resp.GoodsListRespVO;
|
import com.ycwl.basic.model.pc.price.resp.GoodsListRespVO;
|
||||||
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
|
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
|
||||||
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
|
import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceOnePriceConfig;
|
||||||
|
import com.ycwl.basic.pricing.service.IOnePricePurchaseService;
|
||||||
import com.ycwl.basic.repository.FaceRepository;
|
import com.ycwl.basic.repository.FaceRepository;
|
||||||
import com.ycwl.basic.repository.PriceRepository;
|
import com.ycwl.basic.repository.PriceRepository;
|
||||||
import com.ycwl.basic.repository.ScenicRepository;
|
import com.ycwl.basic.repository.ScenicRepository;
|
||||||
@@ -37,6 +39,8 @@ public class PriceBiz {
|
|||||||
private FaceRepository faceRepository;
|
private FaceRepository faceRepository;
|
||||||
@Autowired
|
@Autowired
|
||||||
private CouponBiz couponBiz;
|
private CouponBiz couponBiz;
|
||||||
|
@Autowired
|
||||||
|
private IOnePricePurchaseService onePricePurchaseService;
|
||||||
|
|
||||||
public List<GoodsListRespVO> listGoodsByScenic(Long scenicId) {
|
public List<GoodsListRespVO> listGoodsByScenic(Long scenicId) {
|
||||||
List<GoodsListRespVO> goodsList = new ArrayList<>();
|
List<GoodsListRespVO> goodsList = new ArrayList<>();
|
||||||
@@ -139,7 +143,7 @@ public class PriceBiz {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
respVO.setShare(false);
|
respVO.setShare(false);
|
||||||
if (face != null && face.getMemberId().equals(userId)) {
|
if (face == null || !face.getMemberId().equals(userId)) {
|
||||||
respVO.setShare(true);
|
respVO.setShare(true);
|
||||||
}
|
}
|
||||||
return respVO;
|
return respVO;
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ public class PriceCalculationException extends RuntimeException {
|
|||||||
// 在PricingExceptionHandler中统一处理
|
// 在PricingExceptionHandler中统一处理
|
||||||
@ExceptionHandler(PriceCalculationException.class)
|
@ExceptionHandler(PriceCalculationException.class)
|
||||||
public ApiResponse<String> handlePriceCalculationException(PriceCalculationException e) {
|
public ApiResponse<String> handlePriceCalculationException(PriceCalculationException e) {
|
||||||
return ApiResponse.error(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
|
return ApiResponse.fail(ErrorCode.PRICE_CALCULATION_ERROR, e.getMessage());
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,192 @@
|
|||||||
|
package com.ycwl.basic.pricing.controller;
|
||||||
|
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.OnePriceConfigFilterRequest;
|
||||||
|
import com.ycwl.basic.pricing.dto.OnePriceConfigRequest;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceOnePriceConfig;
|
||||||
|
import com.ycwl.basic.pricing.service.IOnePricePurchaseService;
|
||||||
|
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/one-price")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OnePricePurchaseController {
|
||||||
|
|
||||||
|
private final IOnePricePurchaseService onePricePurchaseService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询一口价配置
|
||||||
|
*/
|
||||||
|
@GetMapping("/")
|
||||||
|
public ApiResponse<PageInfo<PriceOnePriceConfig>> pageConfigs(OnePriceConfigFilterRequest request) {
|
||||||
|
log.info("分页查询一口价配置: {}", request);
|
||||||
|
|
||||||
|
PageInfo<PriceOnePriceConfig> pageInfo = onePricePurchaseService.pageConfigs(request);
|
||||||
|
|
||||||
|
log.info("查询到一口价配置数量: {}", pageInfo.getList().size());
|
||||||
|
|
||||||
|
return ApiResponse.success(pageInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有一口价配置
|
||||||
|
*/
|
||||||
|
@GetMapping("/all")
|
||||||
|
public ApiResponse<List<PriceOnePriceConfig>> getAllConfigs() {
|
||||||
|
log.info("查询所有一口价配置");
|
||||||
|
|
||||||
|
List<PriceOnePriceConfig> configs = onePricePurchaseService.getAllConfigsForAdmin();
|
||||||
|
|
||||||
|
log.info("查询到一口价配置数量: {}", configs.size());
|
||||||
|
|
||||||
|
return ApiResponse.success(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询一口价配置
|
||||||
|
*/
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public ApiResponse<PriceOnePriceConfig> getConfigById(@PathVariable Long id) {
|
||||||
|
log.info("根据ID查询一口价配置: id={}", id);
|
||||||
|
|
||||||
|
PriceOnePriceConfig config = onePricePurchaseService.getConfigById(id);
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
return ApiResponse.fail("配置不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApiResponse.success(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一口价配置
|
||||||
|
*/
|
||||||
|
@PostMapping("/")
|
||||||
|
public ApiResponse<Long> createConfig(@RequestBody OnePriceConfigRequest request) {
|
||||||
|
log.info("创建一口价配置: {}", request);
|
||||||
|
|
||||||
|
// 转换为实体对象
|
||||||
|
PriceOnePriceConfig config = new PriceOnePriceConfig();
|
||||||
|
config.setConfigName(request.getConfigName());
|
||||||
|
config.setScenicId(request.getScenicId());
|
||||||
|
config.setOnePrice(request.getOnePrice());
|
||||||
|
config.setOriginalPrice(request.getOriginalPrice());
|
||||||
|
config.setDescription(request.getDescription());
|
||||||
|
config.setIsActive(request.getIsActive());
|
||||||
|
config.setStartTime(request.getStartTime());
|
||||||
|
config.setEndTime(request.getEndTime());
|
||||||
|
config.setCanUseCoupon(request.getCanUseCoupon());
|
||||||
|
config.setCanUseVoucher(request.getCanUseVoucher());
|
||||||
|
|
||||||
|
Long configId = onePricePurchaseService.createConfig(config);
|
||||||
|
|
||||||
|
if (configId != null) {
|
||||||
|
log.info("一口价配置创建成功: configId={}", configId);
|
||||||
|
return ApiResponse.success(configId);
|
||||||
|
} else {
|
||||||
|
return ApiResponse.fail("创建一口价配置失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新一口价配置
|
||||||
|
*/
|
||||||
|
@PutMapping("/{id}")
|
||||||
|
public ApiResponse<Boolean> updateConfig(@PathVariable Long id, @RequestBody OnePriceConfigRequest request) {
|
||||||
|
log.info("更新一口价配置: id={}, request={}", id, request);
|
||||||
|
|
||||||
|
// 转换为实体对象
|
||||||
|
PriceOnePriceConfig config = new PriceOnePriceConfig();
|
||||||
|
config.setId(id);
|
||||||
|
config.setConfigName(request.getConfigName());
|
||||||
|
config.setScenicId(request.getScenicId());
|
||||||
|
config.setOnePrice(request.getOnePrice());
|
||||||
|
config.setOriginalPrice(request.getOriginalPrice());
|
||||||
|
config.setDescription(request.getDescription());
|
||||||
|
config.setIsActive(request.getIsActive());
|
||||||
|
config.setStartTime(request.getStartTime());
|
||||||
|
config.setEndTime(request.getEndTime());
|
||||||
|
config.setCanUseCoupon(request.getCanUseCoupon());
|
||||||
|
config.setCanUseVoucher(request.getCanUseVoucher());
|
||||||
|
|
||||||
|
boolean success = onePricePurchaseService.updateConfig(config);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
log.info("一口价配置更新成功: id={}", id);
|
||||||
|
return ApiResponse.success(true);
|
||||||
|
} else {
|
||||||
|
return ApiResponse.fail("更新一口价配置失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一口价配置
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{id}")
|
||||||
|
public ApiResponse<Boolean> deleteConfig(@PathVariable Long id) {
|
||||||
|
log.info("删除一口价配置: id={}", id);
|
||||||
|
|
||||||
|
boolean success = onePricePurchaseService.deleteConfig(id);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
log.info("一口价配置删除成功: id={}", id);
|
||||||
|
return ApiResponse.success(true);
|
||||||
|
} else {
|
||||||
|
return ApiResponse.fail("删除一口价配置失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用/禁用一口价配置
|
||||||
|
*/
|
||||||
|
@PutMapping("/{id}/status")
|
||||||
|
public ApiResponse<Boolean> updateConfigStatus(@PathVariable Long id, @RequestParam Boolean isActive) {
|
||||||
|
log.info("更新一口价配置状态: id={}, isActive={}", id, isActive);
|
||||||
|
|
||||||
|
boolean success = onePricePurchaseService.updateConfigStatus(id, isActive);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
log.info("一口价配置状态更新成功: id={}, isActive={}", id, isActive);
|
||||||
|
return ApiResponse.success(true);
|
||||||
|
} else {
|
||||||
|
return ApiResponse.fail("更新一口价配置状态失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据景区查询启用的一口价配置
|
||||||
|
*/
|
||||||
|
@GetMapping("/scenic/{scenicId}")
|
||||||
|
public ApiResponse<List<PriceOnePriceConfig>> getConfigsByScenic(@PathVariable Long scenicId) {
|
||||||
|
log.info("根据景区查询启用的一口价配置: scenicId={}", scenicId);
|
||||||
|
|
||||||
|
List<PriceOnePriceConfig> configs = onePricePurchaseService.getActiveConfigsByScenic(scenicId);
|
||||||
|
|
||||||
|
log.info("景区 {} 查询到一口价配置数量: {}", scenicId, configs.size());
|
||||||
|
|
||||||
|
return ApiResponse.success(configs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查景区是否适用一口价
|
||||||
|
*/
|
||||||
|
@GetMapping("/check/{scenicId}")
|
||||||
|
public ApiResponse<Boolean> checkOnePriceApplicable(@PathVariable Long scenicId) {
|
||||||
|
log.info("检查景区是否适用一口价: scenicId={}", scenicId);
|
||||||
|
|
||||||
|
boolean applicable = onePricePurchaseService.isOnePriceApplicable(scenicId);
|
||||||
|
|
||||||
|
log.info("景区 {} 一口价适用性: {}", scenicId, applicable);
|
||||||
|
|
||||||
|
return ApiResponse.success(applicable);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,15 +75,28 @@ public class DiscountDetail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一口价折扣明细
|
* 创建打包购买折扣明细
|
||||||
*/
|
*/
|
||||||
public static DiscountDetail createBundleDiscount(BigDecimal discountAmount) {
|
public static DiscountDetail createBundleDiscount(BigDecimal discountAmount) {
|
||||||
DiscountDetail detail = new DiscountDetail();
|
DiscountDetail detail = new DiscountDetail();
|
||||||
detail.setDiscountType("BUNDLE");
|
detail.setDiscountType("BUNDLE");
|
||||||
detail.setDiscountName("一口价优惠");
|
detail.setDiscountName("打包购买优惠");
|
||||||
detail.setDiscountAmount(discountAmount);
|
detail.setDiscountAmount(discountAmount);
|
||||||
detail.setDescription("一口价购买更优惠");
|
detail.setDescription("多商品打包购买更优惠");
|
||||||
detail.setSortOrder(4); // 一口价排在最后
|
detail.setSortOrder(5); // 打包购买排在后面
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一口价购买折扣明细
|
||||||
|
*/
|
||||||
|
public static DiscountDetail createOnePriceDiscount(BigDecimal discountAmount) {
|
||||||
|
DiscountDetail detail = new DiscountDetail();
|
||||||
|
detail.setDiscountType("ONE_PRICE_PURCHASE");
|
||||||
|
detail.setDiscountName("一口价购买优惠");
|
||||||
|
detail.setDiscountAmount(discountAmount);
|
||||||
|
detail.setDescription("一口价购买,价格更优惠");
|
||||||
|
detail.setSortOrder(4); // 一口价排在打包购买之前
|
||||||
return detail;
|
return detail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,4 +79,9 @@ public class DiscountInfo {
|
|||||||
* 优惠券ID(如果是coupon类型)
|
* 优惠券ID(如果是coupon类型)
|
||||||
*/
|
*/
|
||||||
private Long couponId;
|
private Long couponId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价信息(如果是一口价优惠)
|
||||||
|
*/
|
||||||
|
private OnePriceInfo onePriceInfo;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价配置筛选请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OnePriceConfigFilterRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页码
|
||||||
|
*/
|
||||||
|
private Integer pageNum = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页大小
|
||||||
|
*/
|
||||||
|
private Integer pageSize = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID(筛选条件)
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置名称(模糊查询)
|
||||||
|
*/
|
||||||
|
private String configName;
|
||||||
|
|
||||||
|
// 移除faceId字段,一口价是景区级别的统一价格
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用状态
|
||||||
|
*/
|
||||||
|
private Boolean isActive;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价配置请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OnePriceConfigRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置名称
|
||||||
|
*/
|
||||||
|
private String configName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
// 移除faceId字段,一口价是景区级别的统一价格
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价格
|
||||||
|
*/
|
||||||
|
private BigDecimal onePrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原价(用于计算和展示优惠金额)
|
||||||
|
*/
|
||||||
|
private BigDecimal originalPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可与优惠券叠加使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseCoupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可与券码叠加使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseVoucher;
|
||||||
|
}
|
||||||
59
src/main/java/com/ycwl/basic/pricing/dto/OnePriceInfo.java
Normal file
59
src/main/java/com/ycwl/basic/pricing/dto/OnePriceInfo.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package com.ycwl.basic.pricing.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价信息DTO(用于优惠检测)
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class OnePriceInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置ID
|
||||||
|
*/
|
||||||
|
private Long configId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置名称
|
||||||
|
*/
|
||||||
|
private String configName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
// 移除faceId字段,一口价是景区级别的统一价格
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价格
|
||||||
|
*/
|
||||||
|
private BigDecimal onePrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原价(用于计算和展示优惠金额)
|
||||||
|
*/
|
||||||
|
private BigDecimal originalPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际优惠金额(当前总价 - 一口价)
|
||||||
|
*/
|
||||||
|
private BigDecimal actualDiscountAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可与优惠券叠加使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseCoupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可与券码叠加使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseVoucher;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
}
|
||||||
@@ -60,6 +60,16 @@ public class PriceBundleConfig {
|
|||||||
*/
|
*/
|
||||||
private Boolean isActive;
|
private Boolean isActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可使用优惠券
|
||||||
|
*/
|
||||||
|
private Boolean canUseCoupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可使用券码
|
||||||
|
*/
|
||||||
|
private Boolean canUseVoucher;
|
||||||
|
|
||||||
@TableField("create_time")
|
@TableField("create_time")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.ycwl.basic.pricing.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价购买配置实体
|
||||||
|
* 一口价:用户(faceId)在特定景区内所有商品的一次性购买优惠价格
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("price_one_price_config")
|
||||||
|
public class PriceOnePriceConfig {
|
||||||
|
|
||||||
|
@TableId(value = "id", type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置名称
|
||||||
|
*/
|
||||||
|
private String configName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 景区ID
|
||||||
|
*/
|
||||||
|
private Long scenicId;
|
||||||
|
|
||||||
|
// 移除faceId字段,一口价是景区级别的统一价格
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价格(该景区内所有商品的统一价格)
|
||||||
|
*/
|
||||||
|
private BigDecimal onePrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 原价(用于计算和展示优惠金额)
|
||||||
|
*/
|
||||||
|
private BigDecimal originalPrice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用
|
||||||
|
*/
|
||||||
|
private Boolean isActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始时间(可选,用于限时一口价)
|
||||||
|
*/
|
||||||
|
private LocalDateTime startTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束时间(可选,用于限时一口价)
|
||||||
|
*/
|
||||||
|
private LocalDateTime endTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可与优惠券叠加使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseCoupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可与券码叠加使用
|
||||||
|
*/
|
||||||
|
private Boolean canUseVoucher;
|
||||||
|
|
||||||
|
@TableField("create_time")
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@TableField("update_time")
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
private Long createBy;
|
||||||
|
|
||||||
|
private Long updateBy;
|
||||||
|
|
||||||
|
private Integer deleted;
|
||||||
|
|
||||||
|
@TableField("deleted_at")
|
||||||
|
private Date deletedAt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否在有效时间范围内
|
||||||
|
*/
|
||||||
|
public boolean isTimeValid() {
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
|
||||||
|
// 如果没有设置时间范围,则始终有效
|
||||||
|
if (startTime == null && endTime == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查开始时间
|
||||||
|
if (startTime != null && now.isBefore(startTime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查结束时间
|
||||||
|
if (endTime != null && now.isAfter(endTime)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除isUserMatch方法,一口价适用于所有用户
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查景区是否匹配
|
||||||
|
*/
|
||||||
|
public boolean isScenicMatch(Long userScenicId) {
|
||||||
|
return scenicId != null && scenicId.equals(userScenicId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,6 +59,16 @@ public class PriceProductConfig {
|
|||||||
*/
|
*/
|
||||||
private Boolean isActive;
|
private Boolean isActive;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可使用优惠券
|
||||||
|
*/
|
||||||
|
private Boolean canUseCoupon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否可使用券码
|
||||||
|
*/
|
||||||
|
private Boolean canUseVoucher;
|
||||||
|
|
||||||
@TableField("create_time")
|
@TableField("create_time")
|
||||||
private Date createTime;
|
private Date createTime;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
|||||||
*/
|
*/
|
||||||
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
||||||
"included_products, excluded_products, " +
|
"included_products, excluded_products, " +
|
||||||
"description, is_active, create_time, update_time " +
|
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time " +
|
||||||
"FROM price_bundle_config WHERE is_active = 1")
|
"FROM price_bundle_config WHERE is_active = 1")
|
||||||
@Results({
|
@Results({
|
||||||
@Result(column = "included_products", property = "includedProducts",
|
@Result(column = "included_products", property = "includedProducts",
|
||||||
@@ -32,7 +32,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
|||||||
*/
|
*/
|
||||||
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
||||||
"included_products, excluded_products, " +
|
"included_products, excluded_products, " +
|
||||||
"description, is_active, create_time, update_time " +
|
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time " +
|
||||||
"FROM price_bundle_config WHERE id = #{id} AND is_active = 1")
|
"FROM price_bundle_config WHERE id = #{id} AND is_active = 1")
|
||||||
@Results({
|
@Results({
|
||||||
@Result(column = "included_products", property = "includedProducts",
|
@Result(column = "included_products", property = "includedProducts",
|
||||||
@@ -49,7 +49,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
|||||||
*/
|
*/
|
||||||
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
@Select("SELECT id, bundle_name, scenic_id, bundle_price, " +
|
||||||
"included_products, excluded_products, " +
|
"included_products, excluded_products, " +
|
||||||
"description, is_active, create_time, update_time " +
|
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time " +
|
||||||
"FROM price_bundle_config ORDER BY is_active DESC, bundle_name ASC")
|
"FROM price_bundle_config ORDER BY is_active DESC, bundle_name ASC")
|
||||||
@Results({
|
@Results({
|
||||||
@Result(column = "included_products", property = "includedProducts",
|
@Result(column = "included_products", property = "includedProducts",
|
||||||
@@ -63,9 +63,9 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
|||||||
* 插入一口价配置
|
* 插入一口价配置
|
||||||
*/
|
*/
|
||||||
@Insert("INSERT INTO price_bundle_config (bundle_name, scenic_id, bundle_price, included_products, excluded_products, " +
|
@Insert("INSERT INTO price_bundle_config (bundle_name, scenic_id, bundle_price, included_products, excluded_products, " +
|
||||||
"description, is_active, create_time, update_time) VALUES " +
|
"description, is_active, can_use_coupon, can_use_voucher, create_time, update_time) VALUES " +
|
||||||
"(#{bundleName}, #{scenicId}, #{bundlePrice}, #{includedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, #{excludedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, " +
|
"(#{bundleName}, #{scenicId}, #{bundlePrice}, #{includedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, #{excludedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, " +
|
||||||
"#{description}, #{isActive}, NOW(), NOW())")
|
"#{description}, #{isActive}, #{canUseCoupon}, #{canUseVoucher}, NOW(), NOW())")
|
||||||
int insertBundleConfig(PriceBundleConfig config);
|
int insertBundleConfig(PriceBundleConfig config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,7 +73,7 @@ public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
|||||||
*/
|
*/
|
||||||
@Update("UPDATE price_bundle_config SET bundle_name = #{bundleName}, scenic_id = #{scenicId}, bundle_price = #{bundlePrice}, " +
|
@Update("UPDATE price_bundle_config SET bundle_name = #{bundleName}, scenic_id = #{scenicId}, bundle_price = #{bundlePrice}, " +
|
||||||
"included_products = #{includedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, excluded_products = #{excludedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, " +
|
"included_products = #{includedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, excluded_products = #{excludedProducts,typeHandler=com.ycwl.basic.pricing.handler.BundleProductListTypeHandler}, " +
|
||||||
"description = #{description}, is_active = #{isActive}, update_time = NOW() WHERE id = #{id}")
|
"description = #{description}, is_active = #{isActive}, can_use_coupon = #{canUseCoupon}, can_use_voucher = #{canUseVoucher}, update_time = NOW() WHERE id = #{id}")
|
||||||
int updateBundleConfig(PriceBundleConfig config);
|
int updateBundleConfig(PriceBundleConfig config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.ycwl.basic.pricing.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceOnePriceConfig;
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价配置Mapper
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface PriceOnePriceConfigMapper extends BaseMapper<PriceOnePriceConfig> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询启用的一口价配置
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM price_one_price_config WHERE is_active = 1 AND deleted = 0 " +
|
||||||
|
"ORDER BY scenic_id")
|
||||||
|
List<PriceOnePriceConfig> selectActiveConfigs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据景区ID查询启用的配置
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM price_one_price_config " +
|
||||||
|
"WHERE is_active = 1 AND deleted = 0 " +
|
||||||
|
"AND scenic_id = #{scenicId} " +
|
||||||
|
"LIMIT 1")
|
||||||
|
PriceOnePriceConfig selectConfigByScenic(@Param("scenicId") Long scenicId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询特定景区的所有配置
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM price_one_price_config " +
|
||||||
|
"WHERE is_active = 1 AND deleted = 0 " +
|
||||||
|
"AND scenic_id = #{scenicId}")
|
||||||
|
List<PriceOnePriceConfig> selectConfigsByScenic(@Param("scenicId") Long scenicId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询启用的配置
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM price_one_price_config " +
|
||||||
|
"WHERE id = #{id} AND is_active = 1 AND deleted = 0")
|
||||||
|
PriceOnePriceConfig selectActiveConfigById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有配置(包含禁用的)- 管理端使用
|
||||||
|
*/
|
||||||
|
@Select("SELECT * FROM price_one_price_config WHERE deleted = 0 " +
|
||||||
|
"ORDER BY is_active DESC, create_time DESC")
|
||||||
|
List<PriceOnePriceConfig> selectAllConfigsForAdmin();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插入配置
|
||||||
|
*/
|
||||||
|
@Insert("INSERT INTO price_one_price_config " +
|
||||||
|
"(config_name, scenic_id, one_price, original_price, description, is_active, " +
|
||||||
|
"start_time, end_time, can_use_coupon, can_use_voucher, " +
|
||||||
|
"create_time, update_time, create_by, deleted) " +
|
||||||
|
"VALUES " +
|
||||||
|
"(#{configName}, #{scenicId}, #{onePrice}, #{originalPrice}, #{description}, #{isActive}, " +
|
||||||
|
"#{startTime}, #{endTime}, #{canUseCoupon}, #{canUseVoucher}, " +
|
||||||
|
"NOW(), NOW(), #{createBy}, 0)")
|
||||||
|
@Options(useGeneratedKeys = true, keyProperty = "id")
|
||||||
|
int insertConfig(PriceOnePriceConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置
|
||||||
|
*/
|
||||||
|
@Update("UPDATE price_one_price_config SET " +
|
||||||
|
"config_name = #{configName}, scenic_id = #{scenicId}, " +
|
||||||
|
"one_price = #{onePrice}, original_price = #{originalPrice}, " +
|
||||||
|
"description = #{description}, is_active = #{isActive}, " +
|
||||||
|
"start_time = #{startTime}, end_time = #{endTime}, " +
|
||||||
|
"can_use_coupon = #{canUseCoupon}, can_use_voucher = #{canUseVoucher}, " +
|
||||||
|
"update_time = NOW(), update_by = #{updateBy} " +
|
||||||
|
"WHERE id = #{id} AND deleted = 0")
|
||||||
|
int updateConfig(PriceOnePriceConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新配置状态
|
||||||
|
*/
|
||||||
|
@Update("UPDATE price_one_price_config SET is_active = #{isActive}, update_time = NOW() " +
|
||||||
|
"WHERE id = #{id} AND deleted = 0")
|
||||||
|
int updateConfigStatus(@Param("id") Long id, @Param("isActive") Boolean isActive);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 逻辑删除配置
|
||||||
|
*/
|
||||||
|
@Update("UPDATE price_one_price_config SET deleted = 1, deleted_at = NOW() " +
|
||||||
|
"WHERE id = #{id}")
|
||||||
|
int deleteConfig(Long id);
|
||||||
|
}
|
||||||
@@ -57,15 +57,15 @@ public interface PriceProductConfigMapper extends BaseMapper<PriceProductConfig>
|
|||||||
/**
|
/**
|
||||||
* 插入商品价格配置
|
* 插入商品价格配置
|
||||||
*/
|
*/
|
||||||
@Insert("INSERT INTO price_product_config (product_type, product_id, scenic_id, product_name, base_price, original_price, unit, is_active, create_time, update_time) " +
|
@Insert("INSERT INTO price_product_config (product_type, product_id, scenic_id, product_name, base_price, original_price, unit, is_active, can_use_coupon, can_use_voucher, create_time, update_time) " +
|
||||||
"VALUES (#{productType}, #{productId}, #{scenicId}, #{productName}, #{basePrice}, #{originalPrice}, #{unit}, #{isActive}, NOW(), NOW())")
|
"VALUES (#{productType}, #{productId}, #{scenicId}, #{productName}, #{basePrice}, #{originalPrice}, #{unit}, #{isActive}, #{canUseCoupon}, #{canUseVoucher}, NOW(), NOW())")
|
||||||
int insertProductConfig(PriceProductConfig config);
|
int insertProductConfig(PriceProductConfig config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新商品价格配置
|
* 更新商品价格配置
|
||||||
*/
|
*/
|
||||||
@Update("UPDATE price_product_config SET product_id = #{productId}, scenic_id = #{scenicId}, product_name = #{productName}, base_price = #{basePrice}, " +
|
@Update("UPDATE price_product_config SET product_id = #{productId}, scenic_id = #{scenicId}, product_name = #{productName}, base_price = #{basePrice}, " +
|
||||||
"original_price = #{originalPrice}, unit = #{unit}, is_active = #{isActive}, update_time = NOW() WHERE id = #{id}")
|
"original_price = #{originalPrice}, unit = #{unit}, is_active = #{isActive}, can_use_coupon = #{canUseCoupon}, can_use_voucher = #{canUseVoucher}, update_time = NOW() WHERE id = #{id}")
|
||||||
int updateProductConfig(PriceProductConfig config);
|
int updateProductConfig(PriceProductConfig config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
package com.ycwl.basic.pricing.service;
|
||||||
|
|
||||||
|
import com.github.pagehelper.PageInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.OnePriceConfigFilterRequest;
|
||||||
|
import com.ycwl.basic.pricing.dto.OnePriceInfo;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceOnePriceConfig;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价购买服务接口
|
||||||
|
* 一口价:特定景区内所有商品的统一优惠价格
|
||||||
|
*/
|
||||||
|
public interface IOnePricePurchaseService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查指定景区是否适用一口价
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 是否适用
|
||||||
|
*/
|
||||||
|
boolean isOnePriceApplicable(Long scenicId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定景区的一口价信息
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @param currentAmount 当前总金额(用于计算优惠)
|
||||||
|
* @return 一口价信息,如果不适用则返回null
|
||||||
|
*/
|
||||||
|
OnePriceInfo getOnePriceInfo(Long scenicId, BigDecimal currentAmount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有启用的一口价配置
|
||||||
|
*
|
||||||
|
* @return 一口价配置列表
|
||||||
|
*/
|
||||||
|
List<PriceOnePriceConfig> getActiveConfigs();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据景区查询启用的一口价配置
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 一口价配置列表
|
||||||
|
*/
|
||||||
|
List<PriceOnePriceConfig> getActiveConfigsByScenic(Long scenicId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据景区查询启用的一口价配置(单个)
|
||||||
|
* 通常每个景区只有一个生效的一口价配置
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 一口价配置,如果不存在则返回null
|
||||||
|
*/
|
||||||
|
PriceOnePriceConfig getActiveConfigByScenic(Long scenicId);
|
||||||
|
|
||||||
|
// ==================== 管理端接口 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一口价配置
|
||||||
|
*
|
||||||
|
* @param config 配置信息
|
||||||
|
* @return 配置ID
|
||||||
|
*/
|
||||||
|
Long createConfig(PriceOnePriceConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新一口价配置
|
||||||
|
*
|
||||||
|
* @param config 配置信息
|
||||||
|
* @return 是否更新成功
|
||||||
|
*/
|
||||||
|
boolean updateConfig(PriceOnePriceConfig config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除一口价配置
|
||||||
|
*
|
||||||
|
* @param id 配置ID
|
||||||
|
* @return 是否删除成功
|
||||||
|
*/
|
||||||
|
boolean deleteConfig(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启用/禁用一口价配置
|
||||||
|
*
|
||||||
|
* @param id 配置ID
|
||||||
|
* @param isActive 是否启用
|
||||||
|
* @return 是否操作成功
|
||||||
|
*/
|
||||||
|
boolean updateConfigStatus(Long id, Boolean isActive);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据ID查询配置
|
||||||
|
*
|
||||||
|
* @param id 配置ID
|
||||||
|
* @return 配置信息
|
||||||
|
*/
|
||||||
|
PriceOnePriceConfig getConfigById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询一口价配置
|
||||||
|
*
|
||||||
|
* @param request 筛选请求参数
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
PageInfo<PriceOnePriceConfig> pageConfigs(OnePriceConfigFilterRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有配置(包含禁用的)- 管理端使用
|
||||||
|
*
|
||||||
|
* @return 配置列表
|
||||||
|
*/
|
||||||
|
List<PriceOnePriceConfig> getAllConfigsForAdmin();
|
||||||
|
}
|
||||||
@@ -29,6 +29,14 @@ public interface IPriceBundleService {
|
|||||||
*/
|
*/
|
||||||
BigDecimal getBundlePrice(List<ProductItem> products);
|
BigDecimal getBundlePrice(List<ProductItem> products);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取适用的一口价配置
|
||||||
|
*
|
||||||
|
* @param products 商品列表
|
||||||
|
* @return 适用的一口价配置,如果不适用则返回null
|
||||||
|
*/
|
||||||
|
PriceBundleConfig getBundleConfig(List<ProductItem> products);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有启用的一口价配置
|
* 获取所有启用的一口价配置
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package com.ycwl.basic.pricing.service.impl;
|
package com.ycwl.basic.pricing.service.impl;
|
||||||
|
|
||||||
import com.ycwl.basic.pricing.dto.*;
|
import com.ycwl.basic.pricing.dto.*;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||||
import com.ycwl.basic.pricing.service.ICouponService;
|
import com.ycwl.basic.pricing.service.ICouponService;
|
||||||
import com.ycwl.basic.pricing.service.IDiscountProvider;
|
import com.ycwl.basic.pricing.service.IDiscountProvider;
|
||||||
|
import com.ycwl.basic.pricing.service.IPriceBundleService;
|
||||||
|
import com.ycwl.basic.pricing.service.IProductConfigService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
@@ -20,6 +24,8 @@ import java.util.List;
|
|||||||
public class CouponDiscountProvider implements IDiscountProvider {
|
public class CouponDiscountProvider implements IDiscountProvider {
|
||||||
|
|
||||||
private final ICouponService couponService;
|
private final ICouponService couponService;
|
||||||
|
private final IProductConfigService productConfigService;
|
||||||
|
private final IPriceBundleService bundleService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProviderType() {
|
public String getProviderType() {
|
||||||
@@ -39,6 +45,12 @@ public class CouponDiscountProvider implements IDiscountProvider {
|
|||||||
return discounts;
|
return discounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查商品配置和打包配置是否允许使用优惠券
|
||||||
|
if (!checkCanUseCoupon(context)) {
|
||||||
|
log.debug("商品配置不允许使用优惠券,跳过优惠券检测");
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CouponInfo bestCoupon = couponService.selectBestCoupon(
|
CouponInfo bestCoupon = couponService.selectBestCoupon(
|
||||||
context.getUserId(),
|
context.getUserId(),
|
||||||
@@ -101,6 +113,70 @@ public class CouponDiscountProvider implements IDiscountProvider {
|
|||||||
public boolean canApply(DiscountInfo discountInfo, DiscountDetectionContext context) {
|
public boolean canApply(DiscountInfo discountInfo, DiscountDetectionContext context) {
|
||||||
return "COUPON".equals(discountInfo.getDiscountType()) &&
|
return "COUPON".equals(discountInfo.getDiscountType()) &&
|
||||||
Boolean.TRUE.equals(context.getAutoUseCoupon()) &&
|
Boolean.TRUE.equals(context.getAutoUseCoupon()) &&
|
||||||
context.getUserId() != null;
|
context.getUserId() != null &&
|
||||||
|
checkCanUseCoupon(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查商品配置和打包配置是否允许使用优惠券
|
||||||
|
*/
|
||||||
|
private boolean checkCanUseCoupon(DiscountDetectionContext context) {
|
||||||
|
if (context.getProducts() == null || context.getProducts().isEmpty()) {
|
||||||
|
return true; // 如果没有商品信息,默认允许
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查是否使用了打包购买
|
||||||
|
BigDecimal bundlePrice = bundleService.getBundlePrice(context.getProducts());
|
||||||
|
if (bundlePrice != null) {
|
||||||
|
// 如果使用了打包购买,检查打包配置的优惠券使用开关
|
||||||
|
PriceBundleConfig bundleConfig = bundleService.getBundleConfig(context.getProducts());
|
||||||
|
if (bundleConfig != null) {
|
||||||
|
boolean canUseCoupon = Boolean.TRUE.equals(bundleConfig.getCanUseCoupon());
|
||||||
|
log.debug("打包配置优惠券开关检查: bundleId={}, canUseCoupon={}", bundleConfig.getId(), canUseCoupon);
|
||||||
|
return canUseCoupon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查单个商品的优惠券使用开关
|
||||||
|
for (ProductItem product : context.getProducts()) {
|
||||||
|
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||||
|
|
||||||
|
try {
|
||||||
|
PriceProductConfig productConfig = productConfigService.getProductConfig(
|
||||||
|
product.getProductType().getCode(), productId);
|
||||||
|
|
||||||
|
if (productConfig != null) {
|
||||||
|
if (!Boolean.TRUE.equals(productConfig.getCanUseCoupon())) {
|
||||||
|
log.debug("商品配置不允许使用优惠券: productType={}, productId={}",
|
||||||
|
product.getProductType().getCode(), productId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取具体商品配置失败,尝试获取default配置
|
||||||
|
try {
|
||||||
|
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
|
||||||
|
product.getProductType().getCode(), "default");
|
||||||
|
|
||||||
|
if (defaultConfig != null) {
|
||||||
|
if (!Boolean.TRUE.equals(defaultConfig.getCanUseCoupon())) {
|
||||||
|
log.debug("商品默认配置不允许使用优惠券: productType={}",
|
||||||
|
product.getProductType().getCode());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("获取商品配置失败,默认允许使用优惠券: productType={}, productId={}",
|
||||||
|
product.getProductType().getCode(), productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查优惠券使用开关时发生异常,默认允许使用", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 默认允许使用优惠券
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
package com.ycwl.basic.pricing.service.impl;
|
||||||
|
|
||||||
|
import com.ycwl.basic.pricing.dto.DiscountDetectionContext;
|
||||||
|
import com.ycwl.basic.pricing.dto.DiscountInfo;
|
||||||
|
import com.ycwl.basic.pricing.dto.DiscountResult;
|
||||||
|
import com.ycwl.basic.pricing.dto.OnePriceInfo;
|
||||||
|
import com.ycwl.basic.pricing.service.IDiscountProvider;
|
||||||
|
import com.ycwl.basic.pricing.service.IOnePricePurchaseService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价购买优惠提供者
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OnePricePurchaseDiscountProvider implements IDiscountProvider {
|
||||||
|
|
||||||
|
private final IOnePricePurchaseService onePricePurchaseService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderType() {
|
||||||
|
return "ONE_PRICE_PURCHASE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPriority() {
|
||||||
|
return 60; // 中等优先级,在券码和优惠券之间
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DiscountInfo> detectAvailableDiscounts(DiscountDetectionContext context) {
|
||||||
|
List<DiscountInfo> discounts = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (context.getScenicId() == null || context.getCurrentAmount() == null) {
|
||||||
|
log.debug("一口价检测失败: 景区ID或当前金额为空");
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否适用一口价
|
||||||
|
if (!onePricePurchaseService.isOnePriceApplicable(context.getScenicId())) {
|
||||||
|
log.debug("景区 {} 不适用一口价", context.getScenicId());
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取一口价信息
|
||||||
|
OnePriceInfo onePriceInfo = onePricePurchaseService.getOnePriceInfo(
|
||||||
|
context.getScenicId(), context.getCurrentAmount());
|
||||||
|
|
||||||
|
if (onePriceInfo == null) {
|
||||||
|
log.debug("景区 {} 未找到一口价配置", context.getScenicId());
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有当一口价小于当前金额时才算优惠
|
||||||
|
if (onePriceInfo.getOnePrice().compareTo(context.getCurrentAmount()) >= 0) {
|
||||||
|
log.debug("景区 {} 一口价 {} 不小于当前金额 {}",
|
||||||
|
context.getScenicId(), onePriceInfo.getOnePrice(), context.getCurrentAmount());
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建优惠信息
|
||||||
|
DiscountInfo discountInfo = new DiscountInfo();
|
||||||
|
discountInfo.setProviderType(getProviderType());
|
||||||
|
discountInfo.setDiscountName("一口价购买优惠");
|
||||||
|
discountInfo.setDiscountAmount(onePriceInfo.getActualDiscountAmount());
|
||||||
|
discountInfo.setDiscountDescription("景区一口价购买,价格更优惠");
|
||||||
|
discountInfo.setOnePriceInfo(onePriceInfo);
|
||||||
|
|
||||||
|
discounts.add(discountInfo);
|
||||||
|
|
||||||
|
log.info("检测到一口价优惠: 景区={}, 一口价={}, 优惠金额={}",
|
||||||
|
context.getScenicId(), onePriceInfo.getOnePrice(),
|
||||||
|
onePriceInfo.getActualDiscountAmount());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("一口价优惠检测失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiscountResult applyDiscount(DiscountInfo discountInfo, DiscountDetectionContext context) {
|
||||||
|
DiscountResult result = new DiscountResult();
|
||||||
|
result.setDiscountInfo(discountInfo);
|
||||||
|
result.setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!getProviderType().equals(discountInfo.getProviderType())) {
|
||||||
|
result.setFailureReason("优惠类型不匹配");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnePriceInfo onePriceInfo = discountInfo.getOnePriceInfo();
|
||||||
|
if (onePriceInfo == null) {
|
||||||
|
result.setFailureReason("一口价信息为空");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查优惠的叠加限制
|
||||||
|
boolean canUseWithOtherDiscounts = checkDiscountCombinationRules(onePriceInfo, context);
|
||||||
|
if (!canUseWithOtherDiscounts) {
|
||||||
|
result.setFailureReason("一口价不可与其他优惠叠加使用");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用一口价优惠
|
||||||
|
BigDecimal discountAmount = onePriceInfo.getActualDiscountAmount();
|
||||||
|
BigDecimal finalAmount = context.getCurrentAmount().subtract(discountAmount);
|
||||||
|
|
||||||
|
// 如果使用一口价,则最终金额就是一口价金额
|
||||||
|
finalAmount = onePriceInfo.getOnePrice();
|
||||||
|
discountAmount = context.getCurrentAmount().subtract(finalAmount);
|
||||||
|
|
||||||
|
result.setSuccess(true);
|
||||||
|
result.setActualDiscountAmount(discountAmount);
|
||||||
|
result.setFinalAmount(finalAmount);
|
||||||
|
result.setFailureReason("一口价购买优惠已应用");
|
||||||
|
|
||||||
|
log.info("一口价优惠应用成功: 优惠金额={}, 最终金额={}", discountAmount, finalAmount);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("一口价优惠应用失败", e);
|
||||||
|
result.setFailureReason("一口价优惠应用失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canApply(DiscountInfo discountInfo, DiscountDetectionContext context) {
|
||||||
|
try {
|
||||||
|
if (!getProviderType().equals(discountInfo.getProviderType())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnePriceInfo onePriceInfo = discountInfo.getOnePriceInfo();
|
||||||
|
if (onePriceInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查一口价是否仍然有效(时间范围等)
|
||||||
|
return onePricePurchaseService.isOnePriceApplicable(context.getScenicId());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查一口价可用性失败", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BigDecimal getMaxPossibleDiscount(DiscountInfo discountInfo, DiscountDetectionContext context) {
|
||||||
|
try {
|
||||||
|
OnePriceInfo onePriceInfo = discountInfo.getOnePriceInfo();
|
||||||
|
if (onePriceInfo != null && onePriceInfo.getActualDiscountAmount() != null) {
|
||||||
|
return onePriceInfo.getActualDiscountAmount();
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取一口价最大优惠金额失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查优惠叠加规则
|
||||||
|
*/
|
||||||
|
private boolean checkDiscountCombinationRules(OnePriceInfo onePriceInfo, DiscountDetectionContext context) {
|
||||||
|
// 根据配置检查是否可以与优惠券、券码叠加使用
|
||||||
|
// 这里可以根据业务需求实现具体的叠加规则
|
||||||
|
|
||||||
|
// 如果配置了不可与优惠券叠加,且请求中要使用优惠券,则返回false
|
||||||
|
if (Boolean.FALSE.equals(onePriceInfo.getCanUseCoupon()) &&
|
||||||
|
Boolean.TRUE.equals(context.getAutoUseCoupon())) {
|
||||||
|
log.debug("一口价配置不允许与优惠券叠加使用");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果配置了不可与券码叠加,且请求中要使用券码,则返回false
|
||||||
|
if (Boolean.FALSE.equals(onePriceInfo.getCanUseVoucher()) &&
|
||||||
|
(Boolean.TRUE.equals(context.getAutoUseVoucher()) ||
|
||||||
|
context.getVoucherCode() != null)) {
|
||||||
|
log.debug("一口价配置不允许与券码叠加使用");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
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.pricing.dto.OnePriceConfigFilterRequest;
|
||||||
|
import com.ycwl.basic.pricing.dto.OnePriceInfo;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceOnePriceConfig;
|
||||||
|
import com.ycwl.basic.pricing.mapper.PriceOnePriceConfigMapper;
|
||||||
|
import com.ycwl.basic.pricing.service.IOnePricePurchaseService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一口价购买服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class OnePricePurchaseServiceImpl implements IOnePricePurchaseService {
|
||||||
|
|
||||||
|
private final PriceOnePriceConfigMapper onePriceConfigMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOnePriceApplicable(Long scenicId) {
|
||||||
|
PriceOnePriceConfig config = findConfigByScenic(scenicId);
|
||||||
|
return config != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OnePriceInfo getOnePriceInfo(Long scenicId, BigDecimal currentAmount) {
|
||||||
|
PriceOnePriceConfig config = findConfigByScenic(scenicId);
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnePriceInfo info = new OnePriceInfo();
|
||||||
|
info.setConfigId(config.getId());
|
||||||
|
info.setConfigName(config.getConfigName());
|
||||||
|
info.setScenicId(scenicId);
|
||||||
|
info.setOnePrice(config.getOnePrice());
|
||||||
|
info.setOriginalPrice(config.getOriginalPrice());
|
||||||
|
info.setDescription(config.getDescription());
|
||||||
|
info.setCanUseCoupon(config.getCanUseCoupon());
|
||||||
|
info.setCanUseVoucher(config.getCanUseVoucher());
|
||||||
|
|
||||||
|
// 计算实际优惠金额
|
||||||
|
if (currentAmount != null && config.getOnePrice().compareTo(currentAmount) < 0) {
|
||||||
|
BigDecimal discountAmount = currentAmount.subtract(config.getOnePrice());
|
||||||
|
info.setActualDiscountAmount(discountAmount);
|
||||||
|
} else {
|
||||||
|
info.setActualDiscountAmount(BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PriceOnePriceConfig> getActiveConfigs() {
|
||||||
|
return onePriceConfigMapper.selectActiveConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PriceOnePriceConfig> getActiveConfigsByScenic(Long scenicId) {
|
||||||
|
return onePriceConfigMapper.selectConfigsByScenic(scenicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PriceOnePriceConfig getActiveConfigByScenic(Long scenicId) {
|
||||||
|
return onePriceConfigMapper.selectConfigByScenic(scenicId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 管理端接口实现 ====================
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long createConfig(PriceOnePriceConfig config) {
|
||||||
|
try {
|
||||||
|
// 设置默认值
|
||||||
|
if (config.getIsActive() == null) {
|
||||||
|
config.setIsActive(true);
|
||||||
|
}
|
||||||
|
if (config.getCanUseCoupon() == null) {
|
||||||
|
config.setCanUseCoupon(true);
|
||||||
|
}
|
||||||
|
if (config.getCanUseVoucher() == null) {
|
||||||
|
config.setCanUseVoucher(true);
|
||||||
|
}
|
||||||
|
if (config.getDeleted() == null) {
|
||||||
|
config.setDeleted(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = onePriceConfigMapper.insertConfig(config);
|
||||||
|
if (result > 0) {
|
||||||
|
log.info("创建一口价配置成功: id={}, name={}", config.getId(), config.getConfigName());
|
||||||
|
return config.getId();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建一口价配置失败", e);
|
||||||
|
throw new RuntimeException("创建一口价配置失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateConfig(PriceOnePriceConfig config) {
|
||||||
|
try {
|
||||||
|
int result = onePriceConfigMapper.updateConfig(config);
|
||||||
|
if (result > 0) {
|
||||||
|
log.info("更新一口价配置成功: id={}", config.getId());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新一口价配置失败: id={}", config.getId(), e);
|
||||||
|
throw new RuntimeException("更新一口价配置失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteConfig(Long id) {
|
||||||
|
try {
|
||||||
|
int result = onePriceConfigMapper.deleteConfig(id);
|
||||||
|
if (result > 0) {
|
||||||
|
log.info("删除一口价配置成功: id={}", id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除一口价配置失败: id={}", id, e);
|
||||||
|
throw new RuntimeException("删除一口价配置失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateConfigStatus(Long id, Boolean isActive) {
|
||||||
|
try {
|
||||||
|
int result = onePriceConfigMapper.updateConfigStatus(id, isActive);
|
||||||
|
if (result > 0) {
|
||||||
|
log.info("更新一口价配置状态成功: id={}, isActive={}", id, isActive);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新一口价配置状态失败: id={}", id, e);
|
||||||
|
throw new RuntimeException("更新一口价配置状态失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PriceOnePriceConfig getConfigById(Long id) {
|
||||||
|
return onePriceConfigMapper.selectById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageInfo<PriceOnePriceConfig> pageConfigs(OnePriceConfigFilterRequest request) {
|
||||||
|
// 开启分页
|
||||||
|
PageHelper.startPage(request.getPageNum(), request.getPageSize());
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
QueryWrapper<PriceOnePriceConfig> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("deleted", 0); // 未删除的记录
|
||||||
|
|
||||||
|
// 添加筛选条件
|
||||||
|
if (request.getScenicId() != null) {
|
||||||
|
queryWrapper.eq("scenic_id", request.getScenicId());
|
||||||
|
}
|
||||||
|
if (StringUtils.hasText(request.getConfigName())) {
|
||||||
|
queryWrapper.like("config_name", request.getConfigName()); // 模糊查询
|
||||||
|
}
|
||||||
|
if (request.getIsActive() != null) {
|
||||||
|
queryWrapper.eq("is_active", request.getIsActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间降序排列
|
||||||
|
queryWrapper.orderByDesc("create_time");
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
List<PriceOnePriceConfig> list = onePriceConfigMapper.selectList(queryWrapper);
|
||||||
|
|
||||||
|
// 返回分页结果
|
||||||
|
return new PageInfo<>(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PriceOnePriceConfig> getAllConfigsForAdmin() {
|
||||||
|
return onePriceConfigMapper.selectAllConfigsForAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 私有方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据景区查找配置
|
||||||
|
*/
|
||||||
|
private PriceOnePriceConfig findConfigByScenic(Long scenicId) {
|
||||||
|
if (scenicId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询匹配的配置
|
||||||
|
PriceOnePriceConfig config = onePriceConfigMapper.selectConfigByScenic(scenicId);
|
||||||
|
|
||||||
|
if (config == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查时间有效性
|
||||||
|
if (!config.isTimeValid()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查景区匹配性
|
||||||
|
if (!config.isScenicMatch(scenicId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,6 +75,27 @@ public class PriceBundleServiceImpl implements IPriceBundleService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PriceBundleConfig getBundleConfig(List<ProductItem> products) {
|
||||||
|
if (!isBundleApplicable(products)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<PriceBundleConfig> bundles = getActiveBundles();
|
||||||
|
Set<String> productTypes = new HashSet<>();
|
||||||
|
for (ProductItem product : products) {
|
||||||
|
productTypes.add(product.getProductType().getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PriceBundleConfig bundle : bundles) {
|
||||||
|
if (isProductsMatchBundle(productTypes, bundle)) {
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
// @Cacheable(value = "active-bundles")
|
// @Cacheable(value = "active-bundles")
|
||||||
public List<PriceBundleConfig> getActiveBundles() {
|
public List<PriceBundleConfig> getActiveBundles() {
|
||||||
|
|||||||
@@ -49,13 +49,13 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
|||||||
discountDetails.add(DiscountDetail.createLimitedTimeDiscount(limitedTimeDiscount));
|
discountDetails.add(DiscountDetail.createLimitedTimeDiscount(limitedTimeDiscount));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查一口价优惠
|
// 检查打包购买优惠
|
||||||
BigDecimal bundlePrice = bundleService.getBundlePrice(request.getProducts());
|
BigDecimal packagePrice = bundleService.getBundlePrice(request.getProducts());
|
||||||
if (bundlePrice != null && bundlePrice.compareTo(totalAmount) < 0) {
|
if (packagePrice != null && packagePrice.compareTo(totalAmount) < 0) {
|
||||||
BigDecimal bundleDiscount = totalAmount.subtract(bundlePrice);
|
BigDecimal packageDiscount = totalAmount.subtract(packagePrice);
|
||||||
discountDetails.add(DiscountDetail.createBundleDiscount(bundleDiscount));
|
discountDetails.add(DiscountDetail.createBundleDiscount(packageDiscount));
|
||||||
totalAmount = bundlePrice;
|
totalAmount = packagePrice;
|
||||||
log.info("使用一口价: {}, 优惠: {}", bundlePrice, bundleDiscount);
|
log.info("使用打包购买: {}, 优惠: {}", packagePrice, packageDiscount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建价格计算结果
|
// 构建价格计算结果
|
||||||
@@ -77,7 +77,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
|||||||
// 重新排序
|
// 重新排序
|
||||||
allDiscountDetails.sort(Comparator.comparing(DiscountDetail::getSortOrder));
|
allDiscountDetails.sort(Comparator.comparing(DiscountDetail::getSortOrder));
|
||||||
|
|
||||||
// 计算总优惠金额(包括限时立减、一口价和其他优惠)
|
// 计算总优惠金额(包括限时立减、打包购买和其他优惠)
|
||||||
BigDecimal totalDiscountAmount = allDiscountDetails.stream()
|
BigDecimal totalDiscountAmount = allDiscountDetails.stream()
|
||||||
.map(DiscountDetail::getDiscountAmount)
|
.map(DiscountDetail::getDiscountAmount)
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
@@ -98,7 +98,7 @@ public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
|||||||
} else {
|
} else {
|
||||||
log.warn("优惠计算失败: {}", discountResult.getErrorMessage());
|
log.warn("优惠计算失败: {}", discountResult.getErrorMessage());
|
||||||
|
|
||||||
// 降级处理:仅使用基础优惠(限时立减、一口价)
|
// 降级处理:仅使用基础优惠(限时立减、打包购买)
|
||||||
BigDecimal totalDiscountAmount = discountDetails.stream()
|
BigDecimal totalDiscountAmount = discountDetails.stream()
|
||||||
.map(DiscountDetail::getDiscountAmount)
|
.map(DiscountDetail::getDiscountAmount)
|
||||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
package com.ycwl.basic.pricing.service.impl;
|
package com.ycwl.basic.pricing.service.impl;
|
||||||
|
|
||||||
import com.ycwl.basic.pricing.dto.*;
|
import com.ycwl.basic.pricing.dto.*;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||||
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
|
import com.ycwl.basic.pricing.enums.VoucherDiscountType;
|
||||||
import com.ycwl.basic.pricing.service.IDiscountProvider;
|
import com.ycwl.basic.pricing.service.IDiscountProvider;
|
||||||
|
import com.ycwl.basic.pricing.service.IPriceBundleService;
|
||||||
|
import com.ycwl.basic.pricing.service.IProductConfigService;
|
||||||
import com.ycwl.basic.pricing.service.IVoucherService;
|
import com.ycwl.basic.pricing.service.IVoucherService;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -22,6 +26,8 @@ import java.util.List;
|
|||||||
public class VoucherDiscountProvider implements IDiscountProvider {
|
public class VoucherDiscountProvider implements IDiscountProvider {
|
||||||
|
|
||||||
private final IVoucherService voucherService;
|
private final IVoucherService voucherService;
|
||||||
|
private final IProductConfigService productConfigService;
|
||||||
|
private final IPriceBundleService bundleService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getProviderType() {
|
public String getProviderType() {
|
||||||
@@ -41,6 +47,12 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
|||||||
return discounts;
|
return discounts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查商品配置和打包配置是否允许使用券码
|
||||||
|
if (!checkCanUseVoucher(context)) {
|
||||||
|
log.debug("商品配置不允许使用券码,跳过券码检测");
|
||||||
|
return discounts;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
VoucherInfo voucherInfo = null;
|
VoucherInfo voucherInfo = null;
|
||||||
|
|
||||||
@@ -149,7 +161,8 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
|||||||
return "VOUCHER".equals(discountInfo.getDiscountType()) &&
|
return "VOUCHER".equals(discountInfo.getDiscountType()) &&
|
||||||
context.getFaceId() != null &&
|
context.getFaceId() != null &&
|
||||||
context.getScenicId() != null &&
|
context.getScenicId() != null &&
|
||||||
StringUtils.hasText(discountInfo.getVoucherCode());
|
StringUtils.hasText(discountInfo.getVoucherCode()) &&
|
||||||
|
checkCanUseVoucher(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -172,4 +185,67 @@ public class VoucherDiscountProvider implements IDiscountProvider {
|
|||||||
// 全场免费券码不可与其他优惠叠加
|
// 全场免费券码不可与其他优惠叠加
|
||||||
return voucherInfo.getDiscountType() != VoucherDiscountType.FREE_ALL;
|
return voucherInfo.getDiscountType() != VoucherDiscountType.FREE_ALL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查商品配置和打包配置是否允许使用券码
|
||||||
|
*/
|
||||||
|
private boolean checkCanUseVoucher(DiscountDetectionContext context) {
|
||||||
|
if (context.getProducts() == null || context.getProducts().isEmpty()) {
|
||||||
|
return true; // 如果没有商品信息,默认允许
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查是否使用了打包购买
|
||||||
|
BigDecimal bundlePrice = bundleService.getBundlePrice(context.getProducts());
|
||||||
|
if (bundlePrice != null) {
|
||||||
|
// 如果使用了打包购买,检查打包配置的券码使用开关
|
||||||
|
PriceBundleConfig bundleConfig = bundleService.getBundleConfig(context.getProducts());
|
||||||
|
if (bundleConfig != null) {
|
||||||
|
boolean canUseVoucher = Boolean.TRUE.equals(bundleConfig.getCanUseVoucher());
|
||||||
|
log.debug("打包配置券码开关检查: bundleId={}, canUseVoucher={}", bundleConfig.getId(), canUseVoucher);
|
||||||
|
return canUseVoucher;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查单个商品的券码使用开关
|
||||||
|
for (ProductItem product : context.getProducts()) {
|
||||||
|
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||||
|
|
||||||
|
try {
|
||||||
|
PriceProductConfig productConfig = productConfigService.getProductConfig(
|
||||||
|
product.getProductType().getCode(), productId);
|
||||||
|
|
||||||
|
if (productConfig != null) {
|
||||||
|
if (!Boolean.TRUE.equals(productConfig.getCanUseVoucher())) {
|
||||||
|
log.debug("商品配置不允许使用券码: productType={}, productId={}",
|
||||||
|
product.getProductType().getCode(), productId);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果获取具体商品配置失败,尝试获取default配置
|
||||||
|
try {
|
||||||
|
PriceProductConfig defaultConfig = productConfigService.getProductConfig(
|
||||||
|
product.getProductType().getCode(), "default");
|
||||||
|
|
||||||
|
if (defaultConfig != null) {
|
||||||
|
if (!Boolean.TRUE.equals(defaultConfig.getCanUseVoucher())) {
|
||||||
|
log.debug("商品默认配置不允许使用券码: productType={}",
|
||||||
|
product.getProductType().getCode());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.warn("获取商品配置失败,默认允许使用券码: productType={}, productId={}",
|
||||||
|
product.getProductType().getCode(), productId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("检查券码使用开关时发生异常,默认允许使用", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 默认允许使用券码
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.ycwl.basic.repository;
|
package com.ycwl.basic.repository;
|
||||||
|
|
||||||
|
import com.ycwl.basic.pricing.entity.PriceOnePriceConfig;
|
||||||
|
import com.ycwl.basic.pricing.service.IOnePricePurchaseService;
|
||||||
import com.ycwl.basic.utils.JacksonUtil;
|
import com.ycwl.basic.utils.JacksonUtil;
|
||||||
import com.ycwl.basic.mapper.PriceConfigMapper;
|
import com.ycwl.basic.mapper.PriceConfigMapper;
|
||||||
import com.ycwl.basic.model.pc.price.entity.PriceConfigEntity;
|
import com.ycwl.basic.model.pc.price.entity.PriceConfigEntity;
|
||||||
@@ -7,7 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@@ -19,8 +21,24 @@ public class PriceRepository {
|
|||||||
private RedisTemplate<String, String> redisTemplate;
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
public static final String PRICE_SCENIC_TYPE_GOODS_CACHE = "price:s%s:t%s:g%s";
|
public static final String PRICE_SCENIC_TYPE_GOODS_CACHE = "price:s%s:t%s:g%s";
|
||||||
public static final String PRICE_ID_CACHE = "price:%s";
|
public static final String PRICE_ID_CACHE = "price:%s";
|
||||||
|
@Autowired
|
||||||
|
private IOnePricePurchaseService onePricePurchaseService;
|
||||||
|
|
||||||
public PriceConfigEntity getPriceConfigByScenicTypeGoods(Long scenicId, Integer type, String goodsId) {
|
public PriceConfigEntity getPriceConfigByScenicTypeGoods(Long scenicId, Integer type, String goodsId) {
|
||||||
|
if (type == -1) {
|
||||||
|
PriceOnePriceConfig config = onePricePurchaseService.getActiveConfigByScenic(scenicId);
|
||||||
|
if (config == null || !config.isTimeValid() || !config.isScenicMatch(scenicId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
PriceConfigEntity entity = new PriceConfigEntity();
|
||||||
|
entity.setId(config.getId().intValue());
|
||||||
|
entity.setScenicId(config.getScenicId());
|
||||||
|
entity.setType(type);
|
||||||
|
entity.setSlashPrice(config.getOriginalPrice());
|
||||||
|
entity.setPrice(config.getOnePrice());
|
||||||
|
entity.setGoodsIds(goodsId);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
String cacheKey = String.format(PRICE_SCENIC_TYPE_GOODS_CACHE, scenicId, type, goodsId);
|
String cacheKey = String.format(PRICE_SCENIC_TYPE_GOODS_CACHE, scenicId, type, goodsId);
|
||||||
PriceConfigEntity priceConfigEntity = null;
|
PriceConfigEntity priceConfigEntity = null;
|
||||||
if (redisTemplate.hasKey(cacheKey)) {
|
if (redisTemplate.hasKey(cacheKey)) {
|
||||||
@@ -37,6 +55,17 @@ public class PriceRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public PriceConfigEntity getPriceConfig(Integer id) {
|
public PriceConfigEntity getPriceConfig(Integer id) {
|
||||||
|
PriceOnePriceConfig config = onePricePurchaseService.getConfigById(Long.valueOf(id));
|
||||||
|
if (config != null && config.isTimeValid() && config.getIsActive()) {
|
||||||
|
PriceConfigEntity priceConfig = new PriceConfigEntity();
|
||||||
|
priceConfig.setId(config.getId().intValue());
|
||||||
|
priceConfig.setScenicId(config.getScenicId());
|
||||||
|
priceConfig.setType(-1);
|
||||||
|
priceConfig.setGoodsIds("");
|
||||||
|
priceConfig.setSlashPrice(config.getOriginalPrice());
|
||||||
|
priceConfig.setPrice(config.getOnePrice());
|
||||||
|
return priceConfig;
|
||||||
|
}
|
||||||
String cacheKey = String.format(PRICE_ID_CACHE, id);
|
String cacheKey = String.format(PRICE_ID_CACHE, id);
|
||||||
PriceConfigEntity priceConfigEntity = null;
|
PriceConfigEntity priceConfigEntity = null;
|
||||||
if (redisTemplate.hasKey(cacheKey)) {
|
if (redisTemplate.hasKey(cacheKey)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user