You've already forked FrameTour-BE
价格查询,待处理订单内容
This commit is contained in:
4
pom.xml
4
pom.xml
@@ -135,6 +135,10 @@
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 引入commons-lang3 工具类 -->
|
||||
<dependency>
|
||||
|
@@ -1,10 +1,16 @@
|
||||
package com.ycwl.basic.config;
|
||||
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Configuration
|
||||
public class JacksonConfiguration {
|
||||
|
||||
@@ -13,6 +19,16 @@ public class JacksonConfiguration {
|
||||
return builder -> {
|
||||
// 把 Long 类型序列化为 String
|
||||
builder.serializerByType(Long.class, ToStringSerializer.instance);
|
||||
|
||||
// 添加 JavaTimeModule 以支持 Java 8 时间类型
|
||||
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public JavaTimeModule javaTimeModule() {
|
||||
return new JavaTimeModule();
|
||||
}
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
package com.ycwl.basic.pricing.controller;
|
||||
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import com.ycwl.basic.pricing.dto.*;
|
||||
import com.ycwl.basic.pricing.service.ICouponService;
|
||||
import com.ycwl.basic.pricing.service.IPriceCalculationService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 价格计算控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/pricing")
|
||||
@RequiredArgsConstructor
|
||||
public class PriceCalculationController {
|
||||
|
||||
private final IPriceCalculationService priceCalculationService;
|
||||
private final ICouponService couponService;
|
||||
|
||||
/**
|
||||
* 计算商品价格
|
||||
*/
|
||||
@PostMapping("/calculate")
|
||||
public ApiResponse<PriceCalculationResult> calculatePrice(@RequestBody PriceCalculationRequest request) {
|
||||
log.info("价格计算请求: userId={}, products={}", request.getUserId(), request.getProducts().size());
|
||||
|
||||
PriceCalculationResult result = priceCalculationService.calculatePrice(request);
|
||||
|
||||
log.info("价格计算完成: originalAmount={}, finalAmount={}, usedCoupon={}",
|
||||
result.getOriginalAmount(), result.getFinalAmount(),
|
||||
result.getUsedCoupon() != null ? result.getUsedCoupon().getCouponId() : null);
|
||||
|
||||
return ApiResponse.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用优惠券
|
||||
* 只能通过代码处理,不能通过接口调用
|
||||
*/
|
||||
@PostMapping("/coupons/use")
|
||||
@Deprecated
|
||||
public ApiResponse<CouponUseResult> useCoupon(@RequestBody CouponUseRequest request) {
|
||||
// log.info("优惠券使用请求: userId={}, couponId={}, orderId={}",
|
||||
// request.getUserId(), request.getCouponId(), request.getOrderId());
|
||||
//
|
||||
// CouponUseResult result = couponService.useCoupon(request);
|
||||
//
|
||||
// log.info("优惠券使用成功: couponId={}, discountAmount={}",
|
||||
// result.getCouponId(), result.getDiscountAmount());
|
||||
//
|
||||
// ApiResponse<CouponUseResult> response = ApiResponse.success(result);
|
||||
// response.setMsg("优惠券使用成功");
|
||||
// return response;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询用户可用优惠券
|
||||
*/
|
||||
@GetMapping("/coupons/my-coupons")
|
||||
public ApiResponse<List<CouponInfo>> getUserCoupons(@RequestParam Long userId) {
|
||||
log.info("查询用户可用优惠券: userId={}", userId);
|
||||
|
||||
List<CouponInfo> coupons = couponService.getUserAvailableCoupons(userId);
|
||||
|
||||
log.info("用户可用优惠券数量: {}", coupons.size());
|
||||
|
||||
return ApiResponse.success(coupons);
|
||||
}
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
package com.ycwl.basic.pricing.controller;
|
||||
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||
import com.ycwl.basic.pricing.entity.PriceTierConfig;
|
||||
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
|
||||
import com.ycwl.basic.pricing.service.IProductConfigService;
|
||||
import com.ycwl.basic.pricing.service.IPriceBundleService;
|
||||
import com.ycwl.basic.pricing.service.IPricingManagementService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 价格配置管理控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/api/pricing/config")
|
||||
@RequiredArgsConstructor
|
||||
public class PricingConfigController {
|
||||
|
||||
private final IProductConfigService productConfigService;
|
||||
private final IPriceBundleService bundleService;
|
||||
private final IPricingManagementService managementService;
|
||||
|
||||
/**
|
||||
* 获取所有商品配置
|
||||
*/
|
||||
@GetMapping("/products")
|
||||
public ApiResponse<List<PriceProductConfig>> getProductConfigs() {
|
||||
List<PriceProductConfig> configs = productConfigService.getActiveProductConfigs();
|
||||
return ApiResponse.success(configs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据商品类型获取阶梯配置
|
||||
*/
|
||||
@GetMapping("/tiers/{productType}")
|
||||
public ApiResponse<List<PriceTierConfig>> getTierConfigs(@PathVariable String productType) {
|
||||
List<PriceTierConfig> configs = productConfigService.getTierConfigs(productType);
|
||||
return ApiResponse.success(configs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID获取阶梯配置
|
||||
*/
|
||||
@GetMapping("/tiers/{productType}/{productId}")
|
||||
public ApiResponse<List<PriceTierConfig>> getTierConfigs(@PathVariable String productType,
|
||||
@PathVariable String productId) {
|
||||
List<PriceTierConfig> configs = productConfigService.getTierConfigs(productType, productId);
|
||||
return ApiResponse.success(configs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID获取具体配置
|
||||
*/
|
||||
@GetMapping("/products/{productType}/{productId}")
|
||||
public ApiResponse<PriceProductConfig> getProductConfig(@PathVariable String productType,
|
||||
@PathVariable String productId) {
|
||||
PriceProductConfig config = productConfigService.getProductConfig(productType, productId);
|
||||
return ApiResponse.success(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有阶梯配置
|
||||
*/
|
||||
@GetMapping("/tiers")
|
||||
public ApiResponse<List<PriceTierConfig>> getAllTierConfigs() {
|
||||
log.info("获取所有阶梯定价配置");
|
||||
return ApiResponse.success(List.of());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有一口价配置
|
||||
*/
|
||||
@GetMapping("/bundles")
|
||||
public ApiResponse<List<PriceBundleConfig>> getBundleConfigs() {
|
||||
List<PriceBundleConfig> configs = bundleService.getActiveBundles();
|
||||
return ApiResponse.success(configs);
|
||||
}
|
||||
|
||||
// ==================== 配置管理API(手动处理时间) ====================
|
||||
|
||||
/**
|
||||
* 创建商品配置
|
||||
*/
|
||||
@PostMapping("/products")
|
||||
public ApiResponse<Long> createProductConfig(@RequestBody PriceProductConfig config) {
|
||||
log.info("创建商品配置: {}", config.getProductName());
|
||||
Long id = managementService.createProductConfig(config);
|
||||
return ApiResponse.success(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新商品配置
|
||||
*/
|
||||
@PutMapping("/products/{id}")
|
||||
public ApiResponse<Boolean> updateProductConfig(@PathVariable Long id, @RequestBody PriceProductConfig config) {
|
||||
log.info("更新商品配置: id={}, name={}", id, config.getProductName());
|
||||
config.setId(id);
|
||||
boolean success = managementService.updateProductConfig(config);
|
||||
return ApiResponse.success(success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建阶梯定价配置
|
||||
*/
|
||||
@PostMapping("/tiers")
|
||||
public ApiResponse<Long> createTierConfig(@RequestBody PriceTierConfig config) {
|
||||
log.info("创建阶梯定价配置: productType={}, price={}", config.getProductType(), config.getPrice());
|
||||
Long id = managementService.createTierConfig(config);
|
||||
return ApiResponse.success(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新阶梯定价配置
|
||||
*/
|
||||
@PutMapping("/tiers/{id}")
|
||||
public ApiResponse<Boolean> updateTierConfig(@PathVariable Long id, @RequestBody PriceTierConfig config) {
|
||||
log.info("更新阶梯定价配置: id={}", id);
|
||||
config.setId(id);
|
||||
boolean success = managementService.updateTierConfig(config);
|
||||
return ApiResponse.success(success);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一口价配置
|
||||
*/
|
||||
@PostMapping("/bundles")
|
||||
public ApiResponse<Long> createBundleConfig(@RequestBody PriceBundleConfig config) {
|
||||
log.info("创建一口价配置: {}", config.getBundleName());
|
||||
Long id = managementService.createBundleConfig(config);
|
||||
return ApiResponse.success(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新一口价配置
|
||||
*/
|
||||
@PutMapping("/bundles/{id}")
|
||||
public ApiResponse<Boolean> updateBundleConfig(@PathVariable Long id, @RequestBody PriceBundleConfig config) {
|
||||
log.info("更新一口价配置: id={}, name={}", id, config.getBundleName());
|
||||
config.setId(id);
|
||||
boolean success = managementService.updateBundleConfig(config);
|
||||
return ApiResponse.success(success);
|
||||
}
|
||||
}
|
38
src/main/java/com/ycwl/basic/pricing/dto/CouponInfo.java
Normal file
38
src/main/java/com/ycwl/basic/pricing/dto/CouponInfo.java
Normal file
@@ -0,0 +1,38 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.CouponType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 优惠券信息DTO
|
||||
*/
|
||||
@Data
|
||||
public class CouponInfo {
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
private String couponName;
|
||||
|
||||
/**
|
||||
* 优惠类型
|
||||
*/
|
||||
private CouponType discountType;
|
||||
|
||||
/**
|
||||
* 优惠值
|
||||
*/
|
||||
private BigDecimal discountValue;
|
||||
|
||||
/**
|
||||
* 实际优惠金额
|
||||
*/
|
||||
private BigDecimal actualDiscountAmount;
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 优惠券使用请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class CouponUseRequest {
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 原始金额
|
||||
*/
|
||||
private BigDecimal originalAmount;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 优惠券使用结果DTO
|
||||
*/
|
||||
@Data
|
||||
public class CouponUseResult {
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 使用时间
|
||||
*/
|
||||
private LocalDateTime useTime;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
}
|
76
src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java
Normal file
76
src/main/java/com/ycwl/basic/pricing/dto/DiscountDetail.java
Normal file
@@ -0,0 +1,76 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 折扣明细DTO
|
||||
*/
|
||||
@Data
|
||||
public class DiscountDetail {
|
||||
|
||||
/**
|
||||
* 折扣类型
|
||||
*/
|
||||
private String discountType;
|
||||
|
||||
/**
|
||||
* 折扣名称
|
||||
*/
|
||||
private String discountName;
|
||||
|
||||
/**
|
||||
* 折扣金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 折扣描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 排序(数值越小越靠前)
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 创建限时立减折扣明细
|
||||
*/
|
||||
public static DiscountDetail createLimitedTimeDiscount(BigDecimal discountAmount) {
|
||||
DiscountDetail detail = new DiscountDetail();
|
||||
detail.setDiscountType("LIMITED_TIME");
|
||||
detail.setDiscountName("限时立减");
|
||||
detail.setDiscountAmount(discountAmount);
|
||||
detail.setDescription("限时优惠,立即享受");
|
||||
detail.setSortOrder(1); // 限时立减排在最前面
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建优惠券折扣明细
|
||||
*/
|
||||
public static DiscountDetail createCouponDiscount(String couponName, BigDecimal discountAmount) {
|
||||
DiscountDetail detail = new DiscountDetail();
|
||||
detail.setDiscountType("COUPON");
|
||||
detail.setDiscountName(couponName);
|
||||
detail.setDiscountAmount(discountAmount);
|
||||
detail.setDescription("优惠券减免");
|
||||
detail.setSortOrder(2); // 优惠券排在限时立减后面
|
||||
return detail;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一口价折扣明细
|
||||
*/
|
||||
public static DiscountDetail createBundleDiscount(BigDecimal discountAmount) {
|
||||
DiscountDetail detail = new DiscountDetail();
|
||||
detail.setDiscountType("BUNDLE");
|
||||
detail.setDiscountName("一口价优惠");
|
||||
detail.setDiscountAmount(discountAmount);
|
||||
detail.setDescription("一口价购买更优惠");
|
||||
detail.setSortOrder(3); // 一口价排在最后
|
||||
return detail;
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 价格计算请求DTO
|
||||
*/
|
||||
@Data
|
||||
public class PriceCalculationRequest {
|
||||
|
||||
/**
|
||||
* 商品列表
|
||||
*/
|
||||
private List<ProductItem> products;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 是否自动使用优惠券
|
||||
*/
|
||||
private Boolean autoUseCoupon = true;
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 价格计算结果DTO
|
||||
*/
|
||||
@Data
|
||||
public class PriceCalculationResult {
|
||||
|
||||
/**
|
||||
* 原始金额(用于前端展示的总原价)
|
||||
*/
|
||||
private BigDecimal originalAmount;
|
||||
|
||||
/**
|
||||
* 商品小计金额(按实际计算价格)
|
||||
*/
|
||||
private BigDecimal subtotalAmount;
|
||||
|
||||
/**
|
||||
* 优惠金额
|
||||
*/
|
||||
private BigDecimal discountAmount;
|
||||
|
||||
/**
|
||||
* 最终金额
|
||||
*/
|
||||
private BigDecimal finalAmount;
|
||||
|
||||
/**
|
||||
* 使用的优惠券信息
|
||||
*/
|
||||
private CouponInfo usedCoupon;
|
||||
|
||||
/**
|
||||
* 折扣明细列表(包含限时立减、优惠券、一口价等)
|
||||
*/
|
||||
private List<DiscountDetail> discountDetails;
|
||||
|
||||
/**
|
||||
* 商品明细列表
|
||||
*/
|
||||
private List<ProductItem> productDetails;
|
||||
}
|
32
src/main/java/com/ycwl/basic/pricing/dto/PriceDetails.java
Normal file
32
src/main/java/com/ycwl/basic/pricing/dto/PriceDetails.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 价格详情(内部计算用)
|
||||
*/
|
||||
@Data
|
||||
public class PriceDetails {
|
||||
|
||||
/**
|
||||
* 实际计算总金额
|
||||
*/
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
/**
|
||||
* 原价总金额
|
||||
*/
|
||||
private BigDecimal originalTotalAmount;
|
||||
|
||||
public PriceDetails() {
|
||||
this.totalAmount = BigDecimal.ZERO;
|
||||
this.originalTotalAmount = BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
public PriceDetails(BigDecimal totalAmount, BigDecimal originalTotalAmount) {
|
||||
this.totalAmount = totalAmount;
|
||||
this.originalTotalAmount = originalTotalAmount;
|
||||
}
|
||||
}
|
53
src/main/java/com/ycwl/basic/pricing/dto/ProductItem.java
Normal file
53
src/main/java/com/ycwl/basic/pricing/dto/ProductItem.java
Normal file
@@ -0,0 +1,53 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 商品项DTO
|
||||
*/
|
||||
@Data
|
||||
public class ProductItem {
|
||||
|
||||
/**
|
||||
* 商品类型
|
||||
*/
|
||||
private ProductType productType;
|
||||
|
||||
/**
|
||||
* 具体商品ID:vlog视频为具体视频ID,录像集/照相集为景区ID,打印为景区ID
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 商品子类型
|
||||
*/
|
||||
private String productSubType;
|
||||
|
||||
/**
|
||||
* 数量(如原片数量、照片数量等)
|
||||
*/
|
||||
private Integer quantity;
|
||||
|
||||
/**
|
||||
* 购买数量(购买几个该商品)
|
||||
*/
|
||||
private Integer purchaseCount;
|
||||
|
||||
/**
|
||||
* 原价(用于前端展示)
|
||||
*/
|
||||
private BigDecimal originalPrice;
|
||||
|
||||
/**
|
||||
* 单价(实际计算用的价格)
|
||||
*/
|
||||
private BigDecimal unitPrice;
|
||||
|
||||
/**
|
||||
* 小计(计算后填入)
|
||||
*/
|
||||
private BigDecimal subtotal;
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
package com.ycwl.basic.pricing.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 商品价格信息(内部计算用)
|
||||
*/
|
||||
@Data
|
||||
public class ProductPriceInfo {
|
||||
|
||||
/**
|
||||
* 实际计算价格
|
||||
*/
|
||||
private BigDecimal actualPrice;
|
||||
|
||||
/**
|
||||
* 原价(用于前端展示)
|
||||
*/
|
||||
private BigDecimal originalPrice;
|
||||
|
||||
public ProductPriceInfo(BigDecimal actualPrice, BigDecimal originalPrice) {
|
||||
this.actualPrice = actualPrice;
|
||||
this.originalPrice = originalPrice != null ? originalPrice : actualPrice;
|
||||
}
|
||||
}
|
21
src/main/java/com/ycwl/basic/pricing/entity/BaseEntity.java
Normal file
21
src/main/java/com/ycwl/basic/pricing/entity/BaseEntity.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package com.ycwl.basic.pricing.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 基础实体类
|
||||
*/
|
||||
@Data
|
||||
public class BaseEntity {
|
||||
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
private LocalDateTime createdTime;
|
||||
|
||||
private LocalDateTime updatedTime;
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.ycwl.basic.pricing.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 一口价配置实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("price_bundle_config")
|
||||
public class PriceBundleConfig extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 套餐名称
|
||||
*/
|
||||
private String bundleName;
|
||||
|
||||
/**
|
||||
* 套餐价格
|
||||
*/
|
||||
private BigDecimal bundlePrice;
|
||||
|
||||
/**
|
||||
* 包含商品(JSON)
|
||||
*/
|
||||
private String includedProducts;
|
||||
|
||||
/**
|
||||
* 排除商品(JSON)
|
||||
*/
|
||||
private String excludedProducts;
|
||||
|
||||
/**
|
||||
* 套餐描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean isActive;
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
package com.ycwl.basic.pricing.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ycwl.basic.pricing.enums.CouponStatus;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 优惠券领用记录实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("price_coupon_claim_record")
|
||||
public class PriceCouponClaimRecord extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 优惠券ID
|
||||
*/
|
||||
private Long couponId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 领取时间
|
||||
*/
|
||||
private LocalDateTime claimTime;
|
||||
|
||||
/**
|
||||
* 使用时间
|
||||
*/
|
||||
private LocalDateTime useTime;
|
||||
|
||||
/**
|
||||
* 订单ID
|
||||
*/
|
||||
private String orderId;
|
||||
|
||||
/**
|
||||
* 状态
|
||||
*/
|
||||
private CouponStatus status;
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
package com.ycwl.basic.pricing.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.ycwl.basic.pricing.enums.CouponType;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 优惠券配置实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("price_coupon_config")
|
||||
public class PriceCouponConfig extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 优惠券名称
|
||||
*/
|
||||
private String couponName;
|
||||
|
||||
/**
|
||||
* 优惠券类型
|
||||
*/
|
||||
private CouponType couponType;
|
||||
|
||||
/**
|
||||
* 优惠值
|
||||
*/
|
||||
private BigDecimal discountValue;
|
||||
|
||||
/**
|
||||
* 最小使用金额
|
||||
*/
|
||||
private BigDecimal minAmount;
|
||||
|
||||
/**
|
||||
* 最大优惠金额
|
||||
*/
|
||||
private BigDecimal maxDiscount;
|
||||
|
||||
/**
|
||||
* 适用商品类型(JSON)
|
||||
*/
|
||||
private String applicableProducts;
|
||||
|
||||
/**
|
||||
* 发行总量
|
||||
*/
|
||||
private Integer totalQuantity;
|
||||
|
||||
/**
|
||||
* 已使用数量
|
||||
*/
|
||||
private Integer usedQuantity;
|
||||
|
||||
/**
|
||||
* 生效时间
|
||||
*/
|
||||
private LocalDateTime validFrom;
|
||||
|
||||
/**
|
||||
* 失效时间
|
||||
*/
|
||||
private LocalDateTime validUntil;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean isActive;
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
package com.ycwl.basic.pricing.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 商品价格配置实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("price_product_config")
|
||||
public class PriceProductConfig extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 商品类型
|
||||
*/
|
||||
private String productType;
|
||||
|
||||
/**
|
||||
* 具体商品ID:vlog视频为具体视频ID,录像集/照相集为景区ID,打印为景区ID
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 商品名称
|
||||
*/
|
||||
private String productName;
|
||||
|
||||
/**
|
||||
* 基础价格
|
||||
*/
|
||||
private BigDecimal basePrice;
|
||||
|
||||
/**
|
||||
* 商品原价:用于前端展示优惠力度,当original_price > base_price时显示限时立减
|
||||
*/
|
||||
private BigDecimal originalPrice;
|
||||
|
||||
/**
|
||||
* 价格单位
|
||||
*/
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean isActive;
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package com.ycwl.basic.pricing.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 阶梯定价配置实体
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("price_tier_config")
|
||||
public class PriceTierConfig extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 商品类型
|
||||
*/
|
||||
private String productType;
|
||||
|
||||
/**
|
||||
* 具体商品ID:与price_product_config的product_id对应
|
||||
*/
|
||||
private String productId;
|
||||
|
||||
/**
|
||||
* 商品子类型
|
||||
*/
|
||||
private String productSubType;
|
||||
|
||||
/**
|
||||
* 最小数量
|
||||
*/
|
||||
private Integer minQuantity;
|
||||
|
||||
/**
|
||||
* 最大数量
|
||||
*/
|
||||
private Integer maxQuantity;
|
||||
|
||||
/**
|
||||
* 阶梯价格
|
||||
*/
|
||||
private BigDecimal price;
|
||||
|
||||
/**
|
||||
* 阶梯原价:用于前端展示优惠力度,当original_price > price时显示限时立减
|
||||
*/
|
||||
private BigDecimal originalPrice;
|
||||
|
||||
/**
|
||||
* 计价单位
|
||||
*/
|
||||
private String unit;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sortOrder;
|
||||
|
||||
/**
|
||||
* 是否启用
|
||||
*/
|
||||
private Boolean isActive;
|
||||
}
|
31
src/main/java/com/ycwl/basic/pricing/enums/CouponStatus.java
Normal file
31
src/main/java/com/ycwl/basic/pricing/enums/CouponStatus.java
Normal file
@@ -0,0 +1,31 @@
|
||||
package com.ycwl.basic.pricing.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 优惠券状态枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CouponStatus {
|
||||
|
||||
CLAIMED("claimed", "已领取"),
|
||||
USED("used", "已使用"),
|
||||
EXPIRED("expired", "已过期");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 根据代码获取枚举
|
||||
*/
|
||||
public static CouponStatus fromCode(String code) {
|
||||
for (CouponStatus status : values()) {
|
||||
if (status.code.equals(code)) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown coupon status code: " + code);
|
||||
}
|
||||
}
|
30
src/main/java/com/ycwl/basic/pricing/enums/CouponType.java
Normal file
30
src/main/java/com/ycwl/basic/pricing/enums/CouponType.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package com.ycwl.basic.pricing.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 优惠券类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CouponType {
|
||||
|
||||
PERCENTAGE("percentage", "百分比折扣"),
|
||||
FIXED_AMOUNT("fixed_amount", "固定金额减免");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 根据代码获取枚举
|
||||
*/
|
||||
public static CouponType fromCode(String code) {
|
||||
for (CouponType type : values()) {
|
||||
if (type.code.equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown coupon type code: " + code);
|
||||
}
|
||||
}
|
33
src/main/java/com/ycwl/basic/pricing/enums/ProductType.java
Normal file
33
src/main/java/com/ycwl/basic/pricing/enums/ProductType.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package com.ycwl.basic.pricing.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 商品类型枚举
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ProductType {
|
||||
|
||||
VLOG_VIDEO("vlog_video", "Vlog视频"),
|
||||
RECORDING_SET("recording_set", "录像集"),
|
||||
PHOTO_SET("photo_set", "照相集"),
|
||||
PHOTO_PRINT("photo_print", "照片打印"),
|
||||
MACHINE_PRINT("machine_print", "一体机打印");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* 根据代码获取枚举
|
||||
*/
|
||||
public static ProductType fromCode(String code) {
|
||||
for (ProductType type : values()) {
|
||||
if (type.code.equals(code)) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown product type code: " + code);
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.ycwl.basic.pricing.exception;
|
||||
|
||||
/**
|
||||
* 优惠券无效异常
|
||||
*/
|
||||
public class CouponInvalidException extends RuntimeException {
|
||||
|
||||
public CouponInvalidException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public CouponInvalidException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.ycwl.basic.pricing.exception;
|
||||
|
||||
/**
|
||||
* 价格计算异常
|
||||
*/
|
||||
public class PriceCalculationException extends RuntimeException {
|
||||
|
||||
public PriceCalculationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PriceCalculationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
package com.ycwl.basic.pricing.exception;
|
||||
|
||||
import com.ycwl.basic.utils.ApiResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 价格查询系统全局异常处理器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice(basePackages = "com.ycwl.basic.pricing")
|
||||
public class PricingExceptionHandler {
|
||||
|
||||
/**
|
||||
* 处理价格计算异常
|
||||
*/
|
||||
@ExceptionHandler(PriceCalculationException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Void> handlePriceCalculationException(PriceCalculationException e) {
|
||||
log.error("价格计算异常", e);
|
||||
return ApiResponse.buildResponse(400, e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理优惠券无效异常
|
||||
*/
|
||||
@ExceptionHandler(CouponInvalidException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Void> handleCouponInvalidException(CouponInvalidException e) {
|
||||
log.error("优惠券无效异常", e);
|
||||
return ApiResponse.buildResponse(400, e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理商品配置未找到异常
|
||||
*/
|
||||
@ExceptionHandler(ProductConfigNotFoundException.class)
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
public ApiResponse<Void> handleProductConfigNotFoundException(ProductConfigNotFoundException e) {
|
||||
log.error("商品配置未找到异常", e);
|
||||
return ApiResponse.buildResponse(404, e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public ApiResponse<Void> handleIllegalArgumentException(IllegalArgumentException e) {
|
||||
log.error("非法参数异常", e);
|
||||
return ApiResponse.buildResponse(400, "参数错误: " + e.getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理通用异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
public ApiResponse<Void> handleException(Exception e) {
|
||||
log.error("系统异常", e);
|
||||
return ApiResponse.buildResponse(500, "系统内部错误");
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
package com.ycwl.basic.pricing.exception;
|
||||
|
||||
/**
|
||||
* 商品配置未找到异常
|
||||
*/
|
||||
public class ProductConfigNotFoundException extends RuntimeException {
|
||||
|
||||
public ProductConfigNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ProductConfigNotFoundException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
package com.ycwl.basic.pricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 一口价配置Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PriceBundleConfigMapper extends BaseMapper<PriceBundleConfig> {
|
||||
|
||||
/**
|
||||
* 查询启用的一口价配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_bundle_config WHERE is_active = 1")
|
||||
List<PriceBundleConfig> selectActiveBundles();
|
||||
|
||||
/**
|
||||
* 根据ID查询启用的配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_bundle_config WHERE id = #{id} AND is_active = 1")
|
||||
PriceBundleConfig selectActiveBundleById(Long id);
|
||||
|
||||
/**
|
||||
* 插入一口价配置
|
||||
*/
|
||||
@Insert("INSERT INTO price_bundle_config (bundle_name, bundle_price, included_products, excluded_products, " +
|
||||
"description, is_active, created_time, updated_time) VALUES " +
|
||||
"(#{bundleName}, #{bundlePrice}, #{includedProducts}, #{excludedProducts}, " +
|
||||
"#{description}, #{isActive}, NOW(), NOW())")
|
||||
int insertBundleConfig(PriceBundleConfig config);
|
||||
|
||||
/**
|
||||
* 更新一口价配置
|
||||
*/
|
||||
@Update("UPDATE price_bundle_config SET bundle_name = #{bundleName}, bundle_price = #{bundlePrice}, " +
|
||||
"included_products = #{includedProducts}, excluded_products = #{excludedProducts}, " +
|
||||
"description = #{description}, is_active = #{isActive}, updated_time = NOW() WHERE id = #{id}")
|
||||
int updateBundleConfig(PriceBundleConfig config);
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package com.ycwl.basic.pricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponClaimRecord;
|
||||
import com.ycwl.basic.pricing.enums.CouponStatus;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券领用记录Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PriceCouponClaimRecordMapper extends BaseMapper<PriceCouponClaimRecord> {
|
||||
|
||||
/**
|
||||
* 查询用户可用的优惠券记录
|
||||
*/
|
||||
@Select("SELECT r.*, c.coupon_name, c.coupon_type, c.discount_value, c.min_amount, " +
|
||||
"c.max_discount, c.applicable_products, c.valid_from, c.valid_until " +
|
||||
"FROM price_coupon_claim_record r " +
|
||||
"JOIN price_coupon_config c ON r.coupon_id = c.id " +
|
||||
"WHERE r.user_id = #{userId} AND r.status = 'CLAIMED' " +
|
||||
"AND c.is_active = 1 AND c.valid_from <= NOW() AND c.valid_until > NOW()")
|
||||
List<PriceCouponClaimRecord> selectUserAvailableCoupons(Long userId);
|
||||
|
||||
/**
|
||||
* 查询用户特定优惠券记录
|
||||
*/
|
||||
@Select("SELECT * FROM price_coupon_claim_record " +
|
||||
"WHERE user_id = #{userId} AND coupon_id = #{couponId} AND status = 'CLAIMED'")
|
||||
PriceCouponClaimRecord selectUserCouponRecord(@Param("userId") Long userId,
|
||||
@Param("couponId") Long couponId);
|
||||
|
||||
/**
|
||||
* 更新优惠券使用状态
|
||||
*/
|
||||
@Update("UPDATE price_coupon_claim_record SET status = #{status}, " +
|
||||
"use_time = #{useTime}, order_id = #{orderId}, updated_time = NOW() " +
|
||||
"WHERE id = #{id}")
|
||||
int updateCouponStatus(@Param("id") Long id,
|
||||
@Param("status") CouponStatus status,
|
||||
@Param("useTime") java.time.LocalDateTime useTime,
|
||||
@Param("orderId") String orderId);
|
||||
|
||||
/**
|
||||
* 插入优惠券领用记录
|
||||
*/
|
||||
@Insert("INSERT INTO price_coupon_claim_record (coupon_id, user_id, claim_time, status, created_time, updated_time) " +
|
||||
"VALUES (#{couponId}, #{userId}, NOW(), #{status}, NOW(), NOW())")
|
||||
int insertClaimRecord(PriceCouponClaimRecord record);
|
||||
|
||||
/**
|
||||
* 更新优惠券记录
|
||||
*/
|
||||
@Update("UPDATE price_coupon_claim_record SET status = #{status}, use_time = #{useTime}, " +
|
||||
"order_id = #{orderId}, updated_time = NOW() WHERE id = #{id}")
|
||||
int updateClaimRecord(PriceCouponClaimRecord record);
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package com.ycwl.basic.pricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponConfig;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券配置Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PriceCouponConfigMapper extends BaseMapper<PriceCouponConfig> {
|
||||
|
||||
/**
|
||||
* 查询有效的优惠券配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_coupon_config WHERE is_active = 1 " +
|
||||
"AND valid_from <= NOW() AND valid_until > NOW() " +
|
||||
"AND used_quantity < total_quantity")
|
||||
List<PriceCouponConfig> selectValidCoupons();
|
||||
|
||||
/**
|
||||
* 根据ID查询优惠券(包括使用数量检查)
|
||||
*/
|
||||
@Select("SELECT * FROM price_coupon_config WHERE id = #{couponId} " +
|
||||
"AND is_active = 1 AND valid_from <= NOW() AND valid_until > NOW() " +
|
||||
"AND used_quantity < total_quantity")
|
||||
PriceCouponConfig selectValidCouponById(Long couponId);
|
||||
|
||||
/**
|
||||
* 增加优惠券使用数量
|
||||
*/
|
||||
@Update("UPDATE price_coupon_config SET used_quantity = used_quantity + 1, " +
|
||||
"updated_time = NOW() WHERE id = #{couponId} AND used_quantity < total_quantity")
|
||||
int incrementUsedQuantity(Long couponId);
|
||||
|
||||
/**
|
||||
* 插入优惠券配置
|
||||
*/
|
||||
@Insert("INSERT INTO price_coupon_config (coupon_name, coupon_type, discount_value, min_amount, " +
|
||||
"max_discount, applicable_products, total_quantity, used_quantity, valid_from, valid_until, " +
|
||||
"is_active, created_time, updated_time) VALUES " +
|
||||
"(#{couponName}, #{couponType}, #{discountValue}, #{minAmount}, #{maxDiscount}, " +
|
||||
"#{applicableProducts}, #{totalQuantity}, #{usedQuantity}, #{validFrom}, #{validUntil}, " +
|
||||
"#{isActive}, NOW(), NOW())")
|
||||
int insertCoupon(PriceCouponConfig coupon);
|
||||
|
||||
/**
|
||||
* 更新优惠券配置
|
||||
*/
|
||||
@Update("UPDATE price_coupon_config SET coupon_name = #{couponName}, coupon_type = #{couponType}, " +
|
||||
"discount_value = #{discountValue}, min_amount = #{minAmount}, max_discount = #{maxDiscount}, " +
|
||||
"applicable_products = #{applicableProducts}, total_quantity = #{totalQuantity}, " +
|
||||
"valid_from = #{validFrom}, valid_until = #{validUntil}, is_active = #{isActive}, " +
|
||||
"updated_time = NOW() WHERE id = #{id}")
|
||||
int updateCoupon(PriceCouponConfig coupon);
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
package com.ycwl.basic.pricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品价格配置Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PriceProductConfigMapper extends BaseMapper<PriceProductConfig> {
|
||||
|
||||
/**
|
||||
* 查询启用的商品配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_product_config WHERE is_active = 1")
|
||||
List<PriceProductConfig> selectActiveConfigs();
|
||||
|
||||
/**
|
||||
* 根据商品类型查询配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND is_active = 1")
|
||||
List<PriceProductConfig> selectByProductType(String productType);
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID查询配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_product_config WHERE product_type = #{productType} AND product_id = #{productId} AND is_active = 1")
|
||||
PriceProductConfig selectByProductTypeAndId(String productType, String productId);
|
||||
|
||||
/**
|
||||
* 插入商品价格配置
|
||||
*/
|
||||
@Insert("INSERT INTO price_product_config (product_type, product_id, product_name, base_price, original_price, unit, is_active, created_time, updated_time) " +
|
||||
"VALUES (#{productType}, #{productId}, #{productName}, #{basePrice}, #{originalPrice}, #{unit}, #{isActive}, NOW(), NOW())")
|
||||
int insertProductConfig(PriceProductConfig config);
|
||||
|
||||
/**
|
||||
* 更新商品价格配置
|
||||
*/
|
||||
@Update("UPDATE price_product_config SET product_id = #{productId}, product_name = #{productName}, base_price = #{basePrice}, " +
|
||||
"original_price = #{originalPrice}, unit = #{unit}, is_active = #{isActive}, updated_time = NOW() WHERE id = #{id}")
|
||||
int updateProductConfig(PriceProductConfig config);
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
package com.ycwl.basic.pricing.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ycwl.basic.pricing.entity.PriceTierConfig;
|
||||
import org.apache.ibatis.annotations.Insert;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 阶梯定价配置Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface PriceTierConfigMapper extends BaseMapper<PriceTierConfig> {
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID和数量查询匹配的阶梯价格
|
||||
*/
|
||||
@Select("SELECT * FROM price_tier_config WHERE product_type = #{productType} " +
|
||||
"AND product_id = #{productId} " +
|
||||
"AND (product_sub_type = #{productSubType} OR product_sub_type IS NULL) " +
|
||||
"AND #{quantity} >= min_quantity AND #{quantity} <= max_quantity " +
|
||||
"AND is_active = 1 ORDER BY sort_order ASC LIMIT 1")
|
||||
PriceTierConfig selectByProductTypeAndQuantity(@Param("productType") String productType,
|
||||
@Param("productId") String productId,
|
||||
@Param("productSubType") String productSubType,
|
||||
@Param("quantity") Integer quantity);
|
||||
|
||||
/**
|
||||
* 根据商品类型查询所有阶梯配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_tier_config WHERE product_type = #{productType} " +
|
||||
"AND is_active = 1 ORDER BY sort_order ASC")
|
||||
List<PriceTierConfig> selectByProductType(String productType);
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID查询所有阶梯配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_tier_config WHERE product_type = #{productType} " +
|
||||
"AND product_id = #{productId} AND is_active = 1 ORDER BY sort_order ASC")
|
||||
List<PriceTierConfig> selectByProductTypeAndId(@Param("productType") String productType,
|
||||
@Param("productId") String productId);
|
||||
|
||||
/**
|
||||
* 根据商品类型和子类型查询阶梯配置
|
||||
*/
|
||||
@Select("SELECT * FROM price_tier_config WHERE product_type = #{productType} " +
|
||||
"AND product_sub_type = #{productSubType} AND is_active = 1 ORDER BY sort_order ASC")
|
||||
List<PriceTierConfig> selectByProductTypeAndSubType(@Param("productType") String productType,
|
||||
@Param("productSubType") String productSubType);
|
||||
|
||||
/**
|
||||
* 插入阶梯定价配置
|
||||
*/
|
||||
@Insert("INSERT INTO price_tier_config (product_type, product_id, product_sub_type, min_quantity, max_quantity, price, " +
|
||||
"original_price, unit, sort_order, is_active, created_time, updated_time) VALUES " +
|
||||
"(#{productType}, #{productId}, #{productSubType}, #{minQuantity}, #{maxQuantity}, #{price}, " +
|
||||
"#{originalPrice}, #{unit}, #{sortOrder}, #{isActive}, NOW(), NOW())")
|
||||
int insertTierConfig(PriceTierConfig config);
|
||||
|
||||
/**
|
||||
* 更新阶梯定价配置
|
||||
*/
|
||||
@Update("UPDATE price_tier_config SET product_id = #{productId}, product_sub_type = #{productSubType}, min_quantity = #{minQuantity}, " +
|
||||
"max_quantity = #{maxQuantity}, price = #{price}, original_price = #{originalPrice}, unit = #{unit}, sort_order = #{sortOrder}, " +
|
||||
"is_active = #{isActive}, updated_time = NOW() WHERE id = #{id}")
|
||||
int updateTierConfig(PriceTierConfig config);
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
package com.ycwl.basic.pricing.service;
|
||||
|
||||
import com.ycwl.basic.pricing.dto.CouponInfo;
|
||||
import com.ycwl.basic.pricing.dto.CouponUseRequest;
|
||||
import com.ycwl.basic.pricing.dto.CouponUseResult;
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券服务接口
|
||||
*/
|
||||
public interface ICouponService {
|
||||
|
||||
/**
|
||||
* 自动选择最优优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param products 商品列表
|
||||
* @param totalAmount 总金额
|
||||
* @return 最优优惠券信息,如果没有可用优惠券则返回null
|
||||
*/
|
||||
CouponInfo selectBestCoupon(Long userId, List<ProductItem> products, BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 计算优惠券优惠金额
|
||||
*
|
||||
* @param coupon 优惠券配置
|
||||
* @param products 商品列表
|
||||
* @param totalAmount 总金额
|
||||
* @return 优惠金额
|
||||
*/
|
||||
BigDecimal calculateCouponDiscount(PriceCouponConfig coupon, List<ProductItem> products, BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 验证优惠券是否可用
|
||||
*
|
||||
* @param coupon 优惠券配置
|
||||
* @param products 商品列表
|
||||
* @param totalAmount 总金额
|
||||
* @return 是否可用
|
||||
*/
|
||||
boolean isCouponApplicable(PriceCouponConfig coupon, List<ProductItem> products, BigDecimal totalAmount);
|
||||
|
||||
/**
|
||||
* 使用优惠券
|
||||
*
|
||||
* @param request 优惠券使用请求
|
||||
* @return 使用结果
|
||||
*/
|
||||
CouponUseResult useCoupon(CouponUseRequest request);
|
||||
|
||||
/**
|
||||
* 查询用户可用优惠券
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 可用优惠券列表
|
||||
*/
|
||||
List<CouponInfo> getUserAvailableCoupons(Long userId);
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
package com.ycwl.basic.pricing.service;
|
||||
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 一口价套餐服务接口
|
||||
*/
|
||||
public interface IPriceBundleService {
|
||||
|
||||
/**
|
||||
* 检查商品是否适用一口价
|
||||
*
|
||||
* @param products 商品列表
|
||||
* @return 是否适用
|
||||
*/
|
||||
boolean isBundleApplicable(List<ProductItem> products);
|
||||
|
||||
/**
|
||||
* 获取一口价价格
|
||||
*
|
||||
* @param products 商品列表
|
||||
* @return 一口价价格,如果不适用则返回null
|
||||
*/
|
||||
BigDecimal getBundlePrice(List<ProductItem> products);
|
||||
|
||||
/**
|
||||
* 获取所有启用的一口价配置
|
||||
*
|
||||
* @return 一口价配置列表
|
||||
*/
|
||||
List<PriceBundleConfig> getActiveBundles();
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
package com.ycwl.basic.pricing.service;
|
||||
|
||||
import com.ycwl.basic.pricing.dto.PriceCalculationRequest;
|
||||
import com.ycwl.basic.pricing.dto.PriceCalculationResult;
|
||||
|
||||
/**
|
||||
* 价格计算服务接口
|
||||
*/
|
||||
public interface IPriceCalculationService {
|
||||
|
||||
/**
|
||||
* 计算商品价格(支持自动优惠券应用)
|
||||
*
|
||||
* @param request 价格计算请求
|
||||
* @return 价格计算结果
|
||||
*/
|
||||
PriceCalculationResult calculatePrice(PriceCalculationRequest request);
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
package com.ycwl.basic.pricing.service;
|
||||
|
||||
import com.ycwl.basic.pricing.entity.*;
|
||||
|
||||
/**
|
||||
* 价格管理服务接口(用于配置管理,手动处理时间字段)
|
||||
*/
|
||||
public interface IPricingManagementService {
|
||||
|
||||
/**
|
||||
* 创建商品价格配置
|
||||
*/
|
||||
Long createProductConfig(PriceProductConfig config);
|
||||
|
||||
/**
|
||||
* 更新商品价格配置
|
||||
*/
|
||||
boolean updateProductConfig(PriceProductConfig config);
|
||||
|
||||
/**
|
||||
* 创建阶梯定价配置
|
||||
*/
|
||||
Long createTierConfig(PriceTierConfig config);
|
||||
|
||||
/**
|
||||
* 更新阶梯定价配置
|
||||
*/
|
||||
boolean updateTierConfig(PriceTierConfig config);
|
||||
|
||||
/**
|
||||
* 创建优惠券配置
|
||||
*/
|
||||
Long createCouponConfig(PriceCouponConfig config);
|
||||
|
||||
/**
|
||||
* 更新优惠券配置
|
||||
*/
|
||||
boolean updateCouponConfig(PriceCouponConfig config);
|
||||
|
||||
/**
|
||||
* 创建优惠券领用记录
|
||||
*/
|
||||
Long createCouponClaimRecord(PriceCouponClaimRecord record);
|
||||
|
||||
/**
|
||||
* 更新优惠券领用记录
|
||||
*/
|
||||
boolean updateCouponClaimRecord(PriceCouponClaimRecord record);
|
||||
|
||||
/**
|
||||
* 创建一口价配置
|
||||
*/
|
||||
Long createBundleConfig(PriceBundleConfig config);
|
||||
|
||||
/**
|
||||
* 更新一口价配置
|
||||
*/
|
||||
boolean updateBundleConfig(PriceBundleConfig config);
|
||||
}
|
@@ -0,0 +1,75 @@
|
||||
package com.ycwl.basic.pricing.service;
|
||||
|
||||
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||
import com.ycwl.basic.pricing.entity.PriceTierConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品配置管理服务接口
|
||||
*/
|
||||
public interface IProductConfigService {
|
||||
|
||||
/**
|
||||
* 根据商品类型获取基础配置(兼容旧接口)
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @return 商品配置列表
|
||||
*/
|
||||
List<PriceProductConfig> getProductConfig(String productType);
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID获取精确配置
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @return 商品配置
|
||||
*/
|
||||
PriceProductConfig getProductConfig(String productType, String productId);
|
||||
|
||||
/**
|
||||
* 根据商品类型、商品ID和数量获取阶梯价格配置
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @param productSubType 商品子类型
|
||||
* @param quantity 数量
|
||||
* @return 阶梯价格配置
|
||||
*/
|
||||
PriceTierConfig getTierConfig(String productType, String productId, String productSubType, Integer quantity);
|
||||
|
||||
/**
|
||||
* 根据商品类型和数量获取阶梯价格配置(兼容旧接口)
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productSubType 商品子类型
|
||||
* @param quantity 数量
|
||||
* @return 阶梯价格配置
|
||||
*/
|
||||
@Deprecated
|
||||
PriceTierConfig getTierConfig(String productType, String productSubType, Integer quantity);
|
||||
|
||||
/**
|
||||
* 获取所有启用的商品配置
|
||||
*
|
||||
* @return 商品配置列表
|
||||
*/
|
||||
List<PriceProductConfig> getActiveProductConfigs();
|
||||
|
||||
/**
|
||||
* 根据商品类型获取所有阶梯配置
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @return 阶梯配置列表
|
||||
*/
|
||||
List<PriceTierConfig> getTierConfigs(String productType);
|
||||
|
||||
/**
|
||||
* 根据商品类型和商品ID获取所有阶梯配置
|
||||
*
|
||||
* @param productType 商品类型
|
||||
* @param productId 具体商品ID
|
||||
* @return 阶梯配置列表
|
||||
*/
|
||||
List<PriceTierConfig> getTierConfigs(String productType, String productId);
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
package com.ycwl.basic.pricing.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ycwl.basic.pricing.dto.*;
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponClaimRecord;
|
||||
import com.ycwl.basic.pricing.entity.PriceCouponConfig;
|
||||
import com.ycwl.basic.pricing.enums.CouponStatus;
|
||||
import com.ycwl.basic.pricing.enums.CouponType;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.exception.CouponInvalidException;
|
||||
import com.ycwl.basic.pricing.mapper.PriceCouponClaimRecordMapper;
|
||||
import com.ycwl.basic.pricing.mapper.PriceCouponConfigMapper;
|
||||
import com.ycwl.basic.pricing.service.ICouponService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 优惠券服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("pricingCouponServiceImpl")
|
||||
@RequiredArgsConstructor
|
||||
public class CouponServiceImpl implements ICouponService {
|
||||
|
||||
private final PriceCouponConfigMapper couponConfigMapper;
|
||||
private final PriceCouponClaimRecordMapper couponClaimRecordMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public CouponInfo selectBestCoupon(Long userId, List<ProductItem> products, BigDecimal totalAmount) {
|
||||
List<PriceCouponClaimRecord> userCoupons = couponClaimRecordMapper.selectUserAvailableCoupons(userId);
|
||||
if (userCoupons.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CouponInfo bestCoupon = null;
|
||||
BigDecimal maxDiscount = BigDecimal.ZERO;
|
||||
|
||||
for (PriceCouponClaimRecord record : userCoupons) {
|
||||
PriceCouponConfig coupon = couponConfigMapper.selectById(record.getCouponId());
|
||||
if (coupon == null || !isCouponApplicable(coupon, products, totalAmount)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal discount = calculateCouponDiscount(coupon, products, totalAmount);
|
||||
if (discount.compareTo(maxDiscount) > 0) {
|
||||
maxDiscount = discount;
|
||||
bestCoupon = buildCouponInfo(coupon, discount);
|
||||
}
|
||||
}
|
||||
|
||||
return bestCoupon;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal calculateCouponDiscount(PriceCouponConfig coupon, List<ProductItem> products, BigDecimal totalAmount) {
|
||||
if (!isCouponApplicable(coupon, products, totalAmount)) {
|
||||
return BigDecimal.ZERO;
|
||||
}
|
||||
|
||||
BigDecimal discount;
|
||||
if (coupon.getCouponType() == CouponType.PERCENTAGE) {
|
||||
discount = totalAmount.multiply(coupon.getDiscountValue().divide(BigDecimal.valueOf(100), 4, RoundingMode.HALF_UP));
|
||||
if (coupon.getMaxDiscount() != null && discount.compareTo(coupon.getMaxDiscount()) > 0) {
|
||||
discount = coupon.getMaxDiscount();
|
||||
}
|
||||
} else {
|
||||
discount = coupon.getDiscountValue();
|
||||
}
|
||||
|
||||
return discount.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCouponApplicable(PriceCouponConfig coupon, List<ProductItem> products, BigDecimal totalAmount) {
|
||||
if (totalAmount.compareTo(coupon.getMinAmount()) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (coupon.getApplicableProducts() == null || coupon.getApplicableProducts().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> applicableProductTypes = objectMapper.readValue(
|
||||
coupon.getApplicableProducts(), new TypeReference<List<String>>() {});
|
||||
|
||||
for (ProductItem product : products) {
|
||||
if (applicableProductTypes.contains(product.getProductType().getCode())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("解析适用商品类型失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public CouponUseResult useCoupon(CouponUseRequest request) {
|
||||
PriceCouponClaimRecord record = couponClaimRecordMapper.selectUserCouponRecord(
|
||||
request.getUserId(), request.getCouponId());
|
||||
|
||||
if (record == null) {
|
||||
throw new CouponInvalidException("用户未拥有该优惠券");
|
||||
}
|
||||
|
||||
if (record.getStatus() != CouponStatus.CLAIMED) {
|
||||
throw new CouponInvalidException("优惠券状态无效: " + record.getStatus());
|
||||
}
|
||||
|
||||
int updateCount = couponConfigMapper.incrementUsedQuantity(request.getCouponId());
|
||||
if (updateCount == 0) {
|
||||
throw new CouponInvalidException("优惠券使用失败,可能已达到使用上限");
|
||||
}
|
||||
|
||||
LocalDateTime useTime = LocalDateTime.now();
|
||||
|
||||
// 设置使用时间和订单信息
|
||||
record.setStatus(CouponStatus.USED);
|
||||
record.setUseTime(useTime);
|
||||
record.setOrderId(request.getOrderId());
|
||||
record.setUpdatedTime(LocalDateTime.now());
|
||||
|
||||
couponClaimRecordMapper.updateCouponStatus(
|
||||
record.getId(), CouponStatus.USED, useTime, request.getOrderId());
|
||||
|
||||
CouponUseResult result = new CouponUseResult();
|
||||
result.setCouponId(request.getCouponId());
|
||||
result.setUserId(request.getUserId());
|
||||
result.setOrderId(request.getOrderId());
|
||||
result.setUseTime(useTime);
|
||||
result.setDiscountAmount(request.getDiscountAmount());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CouponInfo> getUserAvailableCoupons(Long userId) {
|
||||
List<PriceCouponClaimRecord> records = couponClaimRecordMapper.selectUserAvailableCoupons(userId);
|
||||
List<CouponInfo> coupons = new ArrayList<>();
|
||||
|
||||
for (PriceCouponClaimRecord record : records) {
|
||||
PriceCouponConfig config = couponConfigMapper.selectById(record.getCouponId());
|
||||
if (config != null) {
|
||||
coupons.add(buildCouponInfo(config, null));
|
||||
}
|
||||
}
|
||||
|
||||
return coupons;
|
||||
}
|
||||
|
||||
private CouponInfo buildCouponInfo(PriceCouponConfig coupon, BigDecimal actualDiscountAmount) {
|
||||
CouponInfo info = new CouponInfo();
|
||||
info.setCouponId(coupon.getId());
|
||||
info.setCouponName(coupon.getCouponName());
|
||||
info.setDiscountType(coupon.getCouponType());
|
||||
info.setDiscountValue(coupon.getDiscountValue());
|
||||
info.setActualDiscountAmount(actualDiscountAmount);
|
||||
return info;
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
package com.ycwl.basic.pricing.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.ycwl.basic.pricing.dto.ProductItem;
|
||||
import com.ycwl.basic.pricing.entity.PriceBundleConfig;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.mapper.PriceBundleConfigMapper;
|
||||
import com.ycwl.basic.pricing.service.IPriceBundleService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 一口价套餐服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("pricingBundleServiceImpl")
|
||||
@RequiredArgsConstructor
|
||||
public class PriceBundleServiceImpl implements IPriceBundleService {
|
||||
|
||||
private final PriceBundleConfigMapper bundleConfigMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Override
|
||||
public boolean isBundleApplicable(List<ProductItem> products) {
|
||||
List<PriceBundleConfig> bundles = getActiveBundles();
|
||||
if (bundles.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> productTypes = new HashSet<>();
|
||||
for (ProductItem product : products) {
|
||||
productTypes.add(product.getProductType().getCode());
|
||||
}
|
||||
|
||||
for (PriceBundleConfig bundle : bundles) {
|
||||
if (isProductsMatchBundle(productTypes, bundle)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal getBundlePrice(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.getBundlePrice();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "active-bundles")
|
||||
public List<PriceBundleConfig> getActiveBundles() {
|
||||
return bundleConfigMapper.selectActiveBundles();
|
||||
}
|
||||
|
||||
private boolean isProductsMatchBundle(Set<String> productTypes, PriceBundleConfig bundle) {
|
||||
try {
|
||||
List<String> includedProducts = objectMapper.readValue(
|
||||
bundle.getIncludedProducts(), new TypeReference<List<String>>() {});
|
||||
|
||||
Set<String> requiredProducts = new HashSet<>(includedProducts);
|
||||
|
||||
if (bundle.getExcludedProducts() != null && !bundle.getExcludedProducts().isEmpty()) {
|
||||
List<String> excludedProducts = objectMapper.readValue(
|
||||
bundle.getExcludedProducts(), new TypeReference<List<String>>() {});
|
||||
|
||||
for (String excludedProduct : excludedProducts) {
|
||||
if (productTypes.contains(excludedProduct)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return productTypes.containsAll(requiredProducts);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("解析一口价配置失败: bundleId={}", bundle.getId(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,238 @@
|
||||
package com.ycwl.basic.pricing.service.impl;
|
||||
|
||||
import com.ycwl.basic.pricing.dto.*;
|
||||
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||
import com.ycwl.basic.pricing.entity.PriceTierConfig;
|
||||
import com.ycwl.basic.pricing.enums.ProductType;
|
||||
import com.ycwl.basic.pricing.exception.PriceCalculationException;
|
||||
import com.ycwl.basic.pricing.service.ICouponService;
|
||||
import com.ycwl.basic.pricing.service.IPriceBundleService;
|
||||
import com.ycwl.basic.pricing.service.IPriceCalculationService;
|
||||
import com.ycwl.basic.pricing.service.IProductConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 价格计算服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("pricingCalculationServiceImpl")
|
||||
@RequiredArgsConstructor
|
||||
public class PriceCalculationServiceImpl implements IPriceCalculationService {
|
||||
|
||||
private final IProductConfigService productConfigService;
|
||||
private final ICouponService couponService;
|
||||
private final IPriceBundleService bundleService;
|
||||
|
||||
@Override
|
||||
public PriceCalculationResult calculatePrice(PriceCalculationRequest request) {
|
||||
if (request.getProducts() == null || request.getProducts().isEmpty()) {
|
||||
throw new PriceCalculationException("商品列表不能为空");
|
||||
}
|
||||
|
||||
// 计算商品价格和原价
|
||||
PriceDetails priceDetails = calculateProductsPriceWithOriginal(request.getProducts());
|
||||
BigDecimal totalAmount = priceDetails.getTotalAmount();
|
||||
BigDecimal originalTotalAmount = priceDetails.getOriginalTotalAmount();
|
||||
|
||||
List<DiscountDetail> discountDetails = new ArrayList<>();
|
||||
|
||||
// 添加限时立减折扣(如果原价 > 实际价格)
|
||||
BigDecimal limitedTimeDiscount = originalTotalAmount.subtract(totalAmount);
|
||||
if (limitedTimeDiscount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
discountDetails.add(DiscountDetail.createLimitedTimeDiscount(limitedTimeDiscount));
|
||||
}
|
||||
|
||||
// 检查一口价优惠
|
||||
BigDecimal bundlePrice = bundleService.getBundlePrice(request.getProducts());
|
||||
if (bundlePrice != null && bundlePrice.compareTo(totalAmount) < 0) {
|
||||
BigDecimal bundleDiscount = totalAmount.subtract(bundlePrice);
|
||||
discountDetails.add(DiscountDetail.createBundleDiscount(bundleDiscount));
|
||||
totalAmount = bundlePrice;
|
||||
log.info("使用一口价: {}, 优惠: {}", bundlePrice, bundleDiscount);
|
||||
}
|
||||
|
||||
PriceCalculationResult result = new PriceCalculationResult();
|
||||
result.setOriginalAmount(originalTotalAmount); // 原总价
|
||||
result.setSubtotalAmount(priceDetails.getTotalAmount()); // 商品小计
|
||||
result.setProductDetails(request.getProducts());
|
||||
|
||||
// 处理优惠券
|
||||
BigDecimal couponDiscountAmount = BigDecimal.ZERO;
|
||||
if (Boolean.TRUE.equals(request.getAutoUseCoupon()) && request.getUserId() != null) {
|
||||
CouponInfo bestCoupon = couponService.selectBestCoupon(
|
||||
request.getUserId(), request.getProducts(), totalAmount);
|
||||
|
||||
if (bestCoupon != null && bestCoupon.getActualDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||
result.setUsedCoupon(bestCoupon);
|
||||
couponDiscountAmount = bestCoupon.getActualDiscountAmount();
|
||||
discountDetails.add(DiscountDetail.createCouponDiscount(bestCoupon.getCouponName(), couponDiscountAmount));
|
||||
}
|
||||
}
|
||||
|
||||
// 计算总优惠金额
|
||||
BigDecimal totalDiscountAmount = discountDetails.stream()
|
||||
.map(DiscountDetail::getDiscountAmount)
|
||||
.reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
// 按排序排列折扣明细
|
||||
discountDetails.sort(Comparator.comparing(DiscountDetail::getSortOrder));
|
||||
|
||||
result.setDiscountAmount(totalDiscountAmount);
|
||||
result.setDiscountDetails(discountDetails);
|
||||
result.setFinalAmount(originalTotalAmount.subtract(totalDiscountAmount));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private BigDecimal calculateProductsPrice(List<ProductItem> products) {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
|
||||
for (ProductItem product : products) {
|
||||
BigDecimal itemPrice = calculateSingleProductPrice(product);
|
||||
product.setUnitPrice(itemPrice);
|
||||
|
||||
BigDecimal subtotal = itemPrice.multiply(BigDecimal.valueOf(product.getPurchaseCount()));
|
||||
product.setSubtotal(subtotal);
|
||||
|
||||
totalAmount = totalAmount.add(subtotal);
|
||||
}
|
||||
|
||||
return totalAmount.setScale(2, RoundingMode.HALF_UP);
|
||||
}
|
||||
|
||||
private PriceDetails calculateProductsPriceWithOriginal(List<ProductItem> products) {
|
||||
BigDecimal totalAmount = BigDecimal.ZERO;
|
||||
BigDecimal originalTotalAmount = BigDecimal.ZERO;
|
||||
|
||||
for (ProductItem product : products) {
|
||||
// 计算实际价格和原价
|
||||
ProductPriceInfo priceInfo = calculateSingleProductPriceWithOriginal(product);
|
||||
|
||||
product.setUnitPrice(priceInfo.getActualPrice());
|
||||
product.setOriginalPrice(priceInfo.getOriginalPrice());
|
||||
|
||||
BigDecimal subtotal = priceInfo.getActualPrice().multiply(BigDecimal.valueOf(product.getPurchaseCount()));
|
||||
BigDecimal originalSubtotal = priceInfo.getOriginalPrice().multiply(BigDecimal.valueOf(product.getPurchaseCount()));
|
||||
|
||||
product.setSubtotal(subtotal);
|
||||
|
||||
totalAmount = totalAmount.add(subtotal);
|
||||
originalTotalAmount = originalTotalAmount.add(originalSubtotal);
|
||||
}
|
||||
|
||||
return new PriceDetails(
|
||||
totalAmount.setScale(2, RoundingMode.HALF_UP),
|
||||
originalTotalAmount.setScale(2, RoundingMode.HALF_UP)
|
||||
);
|
||||
}
|
||||
|
||||
private BigDecimal calculateSingleProductPrice(ProductItem product) {
|
||||
ProductType productType = product.getProductType();
|
||||
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||
|
||||
// 优先使用基于product_id的阶梯定价
|
||||
PriceTierConfig tierConfig = productConfigService.getTierConfig(
|
||||
productType.getCode(), productId, product.getProductSubType(), product.getQuantity());
|
||||
|
||||
if (tierConfig != null) {
|
||||
log.debug("使用阶梯定价: productType={}, productId={}, quantity={}, price={}",
|
||||
productType.getCode(), productId, product.getQuantity(), tierConfig.getPrice());
|
||||
return tierConfig.getPrice();
|
||||
}
|
||||
|
||||
// 使用基于product_id的基础配置
|
||||
try {
|
||||
PriceProductConfig baseConfig = productConfigService.getProductConfig(productType.getCode(), productId);
|
||||
if (baseConfig != null) {
|
||||
if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) {
|
||||
return baseConfig.getBasePrice().multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
} else {
|
||||
return baseConfig.getBasePrice();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("未找到具体商品配置: productType={}, productId={}, 尝试使用通用配置",
|
||||
productType, productId);
|
||||
}
|
||||
|
||||
// 兜底:使用通用配置(向后兼容)
|
||||
List<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode());
|
||||
if (!configs.isEmpty()) {
|
||||
PriceProductConfig baseConfig = configs.get(0); // 使用第一个配置作为默认
|
||||
if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) {
|
||||
return baseConfig.getBasePrice().multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
} else {
|
||||
return baseConfig.getBasePrice();
|
||||
}
|
||||
}
|
||||
|
||||
throw new PriceCalculationException("无法计算商品价格: " + productType.getDescription() + ", productId: " + productId);
|
||||
}
|
||||
|
||||
private ProductPriceInfo calculateSingleProductPriceWithOriginal(ProductItem product) {
|
||||
ProductType productType = product.getProductType();
|
||||
String productId = product.getProductId() != null ? product.getProductId() : "default";
|
||||
|
||||
BigDecimal actualPrice;
|
||||
BigDecimal originalPrice = null;
|
||||
|
||||
// 优先使用基于product_id的阶梯定价
|
||||
PriceTierConfig tierConfig = productConfigService.getTierConfig(
|
||||
productType.getCode(), productId, product.getProductSubType(), product.getQuantity());
|
||||
|
||||
if (tierConfig != null) {
|
||||
actualPrice = tierConfig.getPrice();
|
||||
originalPrice = tierConfig.getOriginalPrice();
|
||||
log.debug("使用阶梯定价: productType={}, productId={}, quantity={}, price={}, originalPrice={}",
|
||||
productType.getCode(), productId, product.getQuantity(), actualPrice, originalPrice);
|
||||
} else {
|
||||
// 使用基于product_id的基础配置
|
||||
try {
|
||||
PriceProductConfig baseConfig = productConfigService.getProductConfig(productType.getCode(), productId);
|
||||
if (baseConfig != null) {
|
||||
actualPrice = baseConfig.getBasePrice();
|
||||
originalPrice = baseConfig.getOriginalPrice();
|
||||
|
||||
if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) {
|
||||
actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
if (originalPrice != null) {
|
||||
originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new PriceCalculationException("无法找到具体商品配置");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("未找到具体商品配置: productType={}, productId={}, 尝试使用通用配置",
|
||||
productType, productId);
|
||||
|
||||
// 兜底:使用通用配置(向后兼容)
|
||||
List<PriceProductConfig> configs = productConfigService.getProductConfig(productType.getCode());
|
||||
if (!configs.isEmpty()) {
|
||||
PriceProductConfig baseConfig = configs.getFirst(); // 使用第一个配置作为默认
|
||||
actualPrice = baseConfig.getBasePrice();
|
||||
originalPrice = baseConfig.getOriginalPrice();
|
||||
|
||||
if (productType == ProductType.PHOTO_PRINT || productType == ProductType.MACHINE_PRINT) {
|
||||
actualPrice = actualPrice.multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
if (originalPrice != null) {
|
||||
originalPrice = originalPrice.multiply(BigDecimal.valueOf(product.getQuantity()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new PriceCalculationException("无法计算商品价格: " + productType.getDescription() + ", productId: " + productId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ProductPriceInfo(actualPrice, originalPrice);
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
package com.ycwl.basic.pricing.service.impl;
|
||||
|
||||
import com.ycwl.basic.pricing.entity.*;
|
||||
import com.ycwl.basic.pricing.mapper.*;
|
||||
import com.ycwl.basic.pricing.service.IPricingManagementService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 价格管理服务实现(用于配置管理,手动处理时间字段)
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("pricingManagementServiceImpl")
|
||||
@RequiredArgsConstructor
|
||||
public class PricingManagementServiceImpl implements IPricingManagementService {
|
||||
|
||||
private final PriceProductConfigMapper productConfigMapper;
|
||||
private final PriceTierConfigMapper tierConfigMapper;
|
||||
private final PriceCouponConfigMapper couponConfigMapper;
|
||||
private final PriceCouponClaimRecordMapper couponClaimRecordMapper;
|
||||
private final PriceBundleConfigMapper bundleConfigMapper;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createProductConfig(PriceProductConfig config) {
|
||||
config.setCreatedTime(LocalDateTime.now());
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
productConfigMapper.insertProductConfig(config);
|
||||
return config.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateProductConfig(PriceProductConfig config) {
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
return productConfigMapper.updateProductConfig(config) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createTierConfig(PriceTierConfig config) {
|
||||
config.setCreatedTime(LocalDateTime.now());
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
tierConfigMapper.insertTierConfig(config);
|
||||
return config.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateTierConfig(PriceTierConfig config) {
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
return tierConfigMapper.updateTierConfig(config) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createCouponConfig(PriceCouponConfig config) {
|
||||
config.setCreatedTime(LocalDateTime.now());
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
couponConfigMapper.insertCoupon(config);
|
||||
return config.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateCouponConfig(PriceCouponConfig config) {
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
return couponConfigMapper.updateCoupon(config) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createCouponClaimRecord(PriceCouponClaimRecord record) {
|
||||
record.setClaimTime(LocalDateTime.now());
|
||||
record.setCreatedTime(LocalDateTime.now());
|
||||
record.setUpdatedTime(LocalDateTime.now());
|
||||
couponClaimRecordMapper.insertClaimRecord(record);
|
||||
return record.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateCouponClaimRecord(PriceCouponClaimRecord record) {
|
||||
record.setUpdatedTime(LocalDateTime.now());
|
||||
return couponClaimRecordMapper.updateClaimRecord(record) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Long createBundleConfig(PriceBundleConfig config) {
|
||||
config.setCreatedTime(LocalDateTime.now());
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
bundleConfigMapper.insertBundleConfig(config);
|
||||
return config.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public boolean updateBundleConfig(PriceBundleConfig config) {
|
||||
config.setUpdatedTime(LocalDateTime.now());
|
||||
return bundleConfigMapper.updateBundleConfig(config) > 0;
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
package com.ycwl.basic.pricing.service.impl;
|
||||
|
||||
import com.ycwl.basic.pricing.entity.PriceProductConfig;
|
||||
import com.ycwl.basic.pricing.entity.PriceTierConfig;
|
||||
import com.ycwl.basic.pricing.exception.ProductConfigNotFoundException;
|
||||
import com.ycwl.basic.pricing.mapper.PriceProductConfigMapper;
|
||||
import com.ycwl.basic.pricing.mapper.PriceTierConfigMapper;
|
||||
import com.ycwl.basic.pricing.service.IProductConfigService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 商品配置管理服务实现
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("pricingProductConfigServiceImpl")
|
||||
@RequiredArgsConstructor
|
||||
public class ProductConfigServiceImpl implements IProductConfigService {
|
||||
|
||||
private final PriceProductConfigMapper productConfigMapper;
|
||||
private final PriceTierConfigMapper tierConfigMapper;
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "product-config", key = "#productType")
|
||||
public List<PriceProductConfig> getProductConfig(String productType) {
|
||||
return productConfigMapper.selectByProductType(productType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "product-config", key = "#productType + '_' + #productId")
|
||||
public PriceProductConfig getProductConfig(String productType, String productId) {
|
||||
PriceProductConfig config = productConfigMapper.selectByProductTypeAndId(productType, productId);
|
||||
if (config == null) {
|
||||
throw new ProductConfigNotFoundException("商品配置未找到: " + productType + ", productId: " + productId);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tier-config", key = "#productType + '_' + #productId + '_' + (#productSubType ?: 'default') + '_' + #quantity")
|
||||
public PriceTierConfig getTierConfig(String productType, String productId, String productSubType, Integer quantity) {
|
||||
PriceTierConfig config = tierConfigMapper.selectByProductTypeAndQuantity(productType, productId, productSubType, quantity);
|
||||
if (config == null) {
|
||||
log.warn("阶梯定价配置未找到: productType={}, productId={}, productSubType={}, quantity={}",
|
||||
productType, productId, productSubType, quantity);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Deprecated
|
||||
public PriceTierConfig getTierConfig(String productType, String productSubType, Integer quantity) {
|
||||
// 兼容旧接口,使用默认productId
|
||||
return getTierConfig(productType, "default", productSubType, quantity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "active-product-configs")
|
||||
public List<PriceProductConfig> getActiveProductConfigs() {
|
||||
return productConfigMapper.selectActiveConfigs();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tier-configs", key = "#productType")
|
||||
public List<PriceTierConfig> getTierConfigs(String productType) {
|
||||
return tierConfigMapper.selectByProductType(productType);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = "tier-configs", key = "#productType + '_' + #productId")
|
||||
public List<PriceTierConfig> getTierConfigs(String productType, String productId) {
|
||||
return tierConfigMapper.selectByProductTypeAndId(productType, productId);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user