1、新增组织支持自动过期的购买记录;2、软件购买定价规则自动展示1~10月的价格和参考定价中年的展示;3、产品管理和定价管理适配新的需求入参;

This commit is contained in:
gaoqr
2025-09-28 17:45:06 +08:00
parent 836b61ac6b
commit 7250861d0e
28 changed files with 322 additions and 308 deletions
@@ -474,6 +474,7 @@ public class ErrorCodeConstants {
public static final ErrorCode RECHARGE_ACTIVE_NO_EXIST = new ErrorCode(1_002_045_010, "当前赠送金活动不存在");
public static final ErrorCode PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT = new ErrorCode(1_002_045_011, "不支持的时长单位,请检查该定价规则的时长单位");
public static final ErrorCode ORG_PRODUCT_PURCHASE_WAIT_PAY_ERROR = new ErrorCode(1_002_045_012, "组织下存在当前产品的待支付记录,请确认或取消支付后再次发起软件购买");
public static final ErrorCode PRODUCT_PRICE_NULL_ERROR = new ErrorCode(1_002_045_013, "产品建议价格为空,请联系客服");
//=========== 广告相关 1-002-046-000 ============
public static final ErrorCode ADVERTISEMENT_POSITION_NO_EXIST = new ErrorCode(1_002_046_001, "广告所选放置位置不存在");
@@ -33,31 +33,30 @@ public class ProductsController {
@PutMapping("")
@Operation(summary = "新建产品")
public CommonResult<Long> createProducts(@RequestParam("productName") String productName) {
return success(productsService.create(productName));
public CommonResult<Long> createProducts(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
return success(productsService.create(updateReqVO));
}
@PostMapping("")
@Operation(summary = "更新产品")
public CommonResult<Boolean> updateProducts(@Valid @RequestBody ProductSaveReqVO updateReqVO) {
ProductsDO productsDO = BeanUtils.toBean(updateReqVO, ProductsDO.class);
productsService.update(productsDO);
productsService.update(updateReqVO);
return success(true);
}
@PostMapping("/freeze")
@Operation(summary = "下架产品")
public CommonResult<Boolean> freezeProducts(@Valid Long id) {
ProductsDO productsDO = new ProductsDO().setId(id).setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());
productsService.update(productsDO);
ProductSaveReqVO updateReqVO = new ProductSaveReqVO().setId(id).setStatus(ProductStatusEnum.PRODUCT_DELIS.getStatus());
productsService.update(updateReqVO);
return success(true);
}
@PostMapping("/melt")
@Operation(summary = "上架产品")
public CommonResult<Boolean> meltProducts(@Valid Long id) {
ProductsDO productsDO = new ProductsDO().setId(id).setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus());
productsService.update(productsDO);
ProductSaveReqVO updateReqVO = new ProductSaveReqVO().setId(id).setStatus(ProductStatusEnum.PRODUCT_UPDATES.getStatus());
productsService.update(updateReqVO);
return success(true);
}
@@ -74,8 +74,8 @@ public class ProductsDetailController {
@Parameter(name = "organId", description = "组织编号", required = true, example = "1")
@Parameter(name = "productId", description = "产品编号", required = true, example = "1")
public CommonResult<List<ProductDetailsRespVO>> getOrgProductDetailList(@RequestParam("organId") Long organId, @RequestParam("productId") Long productId) {
List<ProductsDetailDO> pageResult = purchaseService.getOrgProductDetailList(organId, productId);
return success(BeanUtils.toBean(pageResult, ProductDetailsRespVO.class));
List<ProductDetailsRespVO> pageResult = purchaseService.getOrgProductDetailList(organId, productId);
return success(pageResult);
}
}
@@ -6,6 +6,7 @@ import com.cf.imes.module.system.validation.products.ProductStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 产品表 products Response VO")
@@ -34,4 +35,7 @@ public class ProductRespVO {
@ExcelProperty("编辑时间")
private LocalDateTime updateTime;
@Schema(description = "建议价格")
@ExcelProperty("建议价格")
private BigDecimal price;
}
@@ -2,11 +2,17 @@ package com.cf.imes.module.system.controller.admin.funds.products.vo.product;
import com.cf.imes.module.system.validation.products.ProductStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Digits;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import jakarta.validation.constraints.NotEmpty;
import java.math.BigDecimal;
@Schema(description = "管理后台 - 产品表 product新增/修改 Request VO")
@Data
public class ProductSaveReqVO {
@@ -19,6 +25,13 @@ public class ProductSaveReqVO {
@Size(max = 64, message = "产品名称长度不能超过64")
private String productName;
@Schema(description = "产品建议价格")
@NotNull(message = "产品建议价格不能为空")
@DecimalMin(value = "0.01", message = "产品建议价格必须大于零")
@DecimalMax(value = "100000", message = "产品建议价格不能超过十万")
@Digits(integer = 6, fraction = 2, message = "产品建议价格格式不正确")
private BigDecimal price;
@Schema(description = "产品状态", requiredMode = Schema.RequiredMode.REQUIRED)
@ProductStatus
private Integer status;
@@ -25,7 +25,7 @@ public class ProductDetailsRespVO {
private Integer purchaseType;
@Schema(description = "购买时长", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String duration;
private Integer duration;
@Schema(description = "产品时长单位,0:年、1:月", example = "1")
private Integer durationUnit;
@@ -1,8 +1,7 @@
package com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails;
import com.cf.imes.framework.common.validation.NumberValid;
import com.cf.imes.module.system.enums.products.ProductsPurchaseTypeEnum;
import com.cf.imes.module.system.validation.products.ProductDurationUnitValid;
import com.cf.imes.module.system.validation.products.ProductType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.DecimalMax;
import jakarta.validation.constraints.DecimalMin;
@@ -24,46 +23,28 @@ public class ProductDetailsSaveReqVO {
private Long productId;
@Schema(description = "购买方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "购买方式不能为空")
@ProductType
private Integer purchaseType;
private Integer purchaseType = ProductsPurchaseTypeEnum.DURATION.getStatus();
@Schema(description = "产品时间,格式:时长值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String duration;
@Schema(description = "产品时长单位,0:年、1:月", example = "1")
@Schema(description = "产品时长单位,0:年", example = "1")
@ProductDurationUnitValid
private Integer durationUnit;
@Schema(description = "产品产量", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private BigDecimal output;
@Schema(description = "赠送时间,格式:时长值 - 时间单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String giftDuration;
@Schema(description = "赠送时长单位,0:年、1:月", example = "1")
@ProductDurationUnitValid
private Integer giftDurationUnit;
@Schema(description = "赠送产量", requiredMode = Schema.RequiredMode.REQUIRED, example = "")
private BigDecimal giftOutput;
@Schema(description = "产品原价", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "产品原价不能为空")
@NumberValid(name = "产品原价")
private BigDecimal originalPrice;
@Schema(description = "折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.8")
@DecimalMax(value = "100", message = "折扣必须小于等于100")
@DecimalMin(value = "0", message = "折扣必须大于等于0")
@Digits(integer = 3, fraction = 0, message = "折扣必须是0 ~ 100间的整数")
@NotNull(message = "折扣不能为空")
private BigDecimal discount;
@Schema(description = "产品现价", requiredMode = Schema.RequiredMode.REQUIRED, example = "80")
@NotNull(message = "产品现价不能为空")
@NumberValid(name = "产品现价")
private BigDecimal currentPrice;
@Schema(description = "是否特惠", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean isDiscounted;
@@ -1,6 +1,5 @@
package com.cf.imes.module.system.controller.admin.funds.purchase.vo;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import lombok.AllArgsConstructor;
import lombok.Data;
@@ -18,9 +17,4 @@ public class OrgEarliestPurchaseProductRespVO {
* 购买记录
*/
private PurchaseRecordDO purchaseRecordDO;
/**
* 产品定价规则
*/
private ProductsDetailDO productsDetailDO;
}
@@ -116,7 +116,7 @@ public class OrganController {
Long organId = bean.getId();
// 查询当次列表组织下的所有购买记录
List<OrganPagePurchaseRespVO> orgPurchaseList = BeanUtil.copyToList(purchaseService.getOrgPurchaseList(Set.of(organId)), OrganPagePurchaseRespVO.class);
List<OrganPagePurchaseRespVO> orgPurchaseList = BeanUtil.copyToList(purchaseService.getOrgPurchaseNotInitialList(Set.of(organId)), OrganPagePurchaseRespVO.class);
// 查询购买记录下的延期记录
orgPurchaseList.forEach(p -> {
List<ProductDelayRespVO> delayRecordsByPurchaseId = productDelayService.getRecordsByPurchaseId(p.getId(), organId);
@@ -143,7 +143,7 @@ public class OrganController {
Set<Long> organIds = bean.getList().stream().map(OrganRespVO::getId).collect(Collectors.toSet());
// 查询当次列表组织下的所有购买记录
List<PurchaseRecordDO> orgPurchaseList = purchaseService.getOrgPurchaseList(organIds);
List<PurchaseRecordDO> orgPurchaseList = purchaseService.getOrgPurchaseNotInitialList(organIds);
// 匹配到对应的组织下
bean.getList().forEach(e -> {
List<OrganPagePurchaseRespVO> purchaseRespVOList = orgPurchaseList.stream()
@@ -8,7 +8,6 @@ import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
@Schema(description = "管理后台 - 组织创建购买产品 Request VO")
@Data
@@ -17,18 +16,10 @@ public class OrganCreateProductPurchaseReqVO {
@NotNull(message = "产品编号不能为空", groups = {OrganSaveCreateGroup.class})
private Long productId;
@Schema(description = "定价规则编号", example = "1")
@NotNull(message = "定价规则编号不能为空", groups = {OrganSaveCreateGroup.class})
private Long productDetailsId;
@Schema(description = "产品购买金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "产品购买金额不能为空", groups = {OrganSaveCreateGroup.class})
@DecimalMin(value = "0.01", message = "产品购买金额必须大于零")
@DecimalMax(value = "100000", message = "产品购买金额不能超过十万")
@Digits(integer = 6, fraction = 2, message = "产品购买金额格式不正确")
private BigDecimal price;
@Schema(description = "产品有效时间")
@NotNull(message = "产品有效时间不能为空", groups = {OrganSaveCreateGroup.class})
private LocalDate productExpireDate;
}
@@ -3,7 +3,6 @@ package com.cf.imes.module.system.controller.admin.pay.vo;
import com.cf.imes.module.system.validation.pay.PayProductChannelCodeValid;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@@ -23,6 +22,10 @@ public class PayProductOrderUnifiedReqVO {
@NotNull(message = "产品定价规则编号不能为空")
private Long productDetailsId;
@Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "产品ID不能为空")
private Long productId;
@Schema(description = "产品支付渠道编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@PayProductChannelCodeValid
@NotNull(message = "产品支付渠道编码不能为空")
@@ -6,6 +6,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.cf.imes.framework.mybatis.core.dataobject.BaseDO;
import lombok.*;
import java.math.BigDecimal;
/**
* 产品表 products DO
@@ -47,6 +49,8 @@ public class ProductsDO extends BaseDO {
*/
private Integer softwareId;
/**
* 建议价格
*/
private BigDecimal price;
}
@@ -56,36 +56,16 @@ public class ProductsDetailDO extends BaseDO {
@TableField("`output`")
private BigDecimal output;
/**
* 赠送时间
*/
private Integer giftDuration;
/**
* 赠送时间单位
*/
private Integer giftDurationUnit;
/**
* 赠送产量
*/
private BigDecimal giftOutput;
/**
* 产品原价
*/
private BigDecimal originalPrice;
/**
* 折扣
*/
private BigDecimal discount;
/**
* 产品现价
*/
private BigDecimal currentPrice;
/**
* 是否特惠: 0否1是
*/
@@ -22,12 +22,9 @@ public interface ProductsDetailMapper extends BaseMapperX<ProductsDetailDO> {
.eqIfPresent(ProductsDetailDO::getPurchaseType, reqVO.getPurchaseType())
.eqIfPresent(ProductsDetailDO::getProductId, reqVO.getProductId())
.eqIfPresent(ProductsDetailDO::getDuration, reqVO.getDuration())
.eqIfPresent(ProductsDetailDO::getGiftDuration, reqVO.getGiftDuration())
.eqIfPresent(ProductsDetailDO::getOutput, reqVO.getOutput())
.eqIfPresent(ProductsDetailDO::getGiftOutput, reqVO.getGiftOutput())
.eqIfPresent(ProductsDetailDO::getOriginalPrice, reqVO.getOriginalPrice())
.eqIfPresent(ProductsDetailDO::getDiscount, reqVO.getDiscount())
.eqIfPresent(ProductsDetailDO::getCurrentPrice, reqVO.getCurrentPrice())
.eqIfPresent(ProductsDetailDO::getIsDiscounted, reqVO.getIsDiscounted())
.eqIfPresent(ProductsDetailDO::getDeleted, false)
);
@@ -45,6 +45,7 @@ public interface PurchaseRecordMapper extends BaseMapperX<PurchaseRecordDO> {
.eqIfPresent(PurchaseRecordDO::getPurchaseDuration, pageReqVO.getPurchaseDuration())
.betweenIfPresent(PurchaseRecordDO::getCreateTime, pageReqVO.getCreateTime())
.likeIfPresent(PurchaseRecordDO::getCreator, pageReqVO.getCreator())
.ne(PurchaseRecordDO::getInitial, true)
.orderByDesc(PurchaseRecordDO::getCreateTime));
}
@@ -63,6 +64,7 @@ public interface PurchaseRecordMapper extends BaseMapperX<PurchaseRecordDO> {
.eq(PurchaseRecordDO::getOrganId, SecurityFrameworkUtils.getUserOrganId())
.eq(PurchaseRecordDO::getDeleted, false)
.in(PurchaseRecordDO::getIsInvocing, InvoiceStatusEnum.INVOICABLE.getCode(), InvoiceStatusEnum.INVOICINGFAILED.getCode())
.ne(PurchaseRecordDO::getInitial, true)
.orderByDesc(PurchaseRecordDO::getCreateTime)
);
@@ -109,6 +111,7 @@ public interface PurchaseRecordMapper extends BaseMapperX<PurchaseRecordDO> {
.eqIfPresent(PurchaseRecordDO::getOrganId, pageReqVO.getOrganId())
.eqIfPresent(PurchaseRecordDO::getCreator, pageReqVO.getOperator())
.eq(PurchaseRecordDO::getDeleted, false)
.ne(PurchaseRecordDO::getInitial, true)
.orderByDesc(PurchaseRecordDO::getCreateTime)
.select(PurchaseRecordDO::getId, PurchaseRecordDO::getOrganId, PurchaseRecordDO::getTotalAmount, PurchaseRecordDO::getAccountBalanceSpent, PurchaseRecordDO::getAlipaySpent,
PurchaseRecordDO::getWechatSpent, PurchaseRecordDO::getGiftMoneySpent, PurchaseRecordDO::getCreator, PurchaseRecordDO::getCreateTime)
@@ -10,4 +10,6 @@ public class PayConstants {
public static final String PAY_ORDER_PREFIX_FORMAT = "P%dO%s"; // 第三方支付流水号格式
public static final String PAY_PRODUCT_DETAIL_BUSINESSNO_PREFIX_FORMAT = "P%dD%s"; // 产品购买流水号格式
public static final String PAY_PRODUCT_DETAIL_AUTO_GENERATE_MONTH_ID_FORMAT = "1759029544%d"; // 1~12月定价规则自动生成ID格式
}
@@ -40,10 +40,6 @@ public enum ProductDurationUnitEnum implements IntArrayValuable, BaseEnum {
return Objects.equals(YEAR.code, name);
}
public static boolean isMonth(String name) {
return Objects.equals(MONTH.code, name);
}
public static ProductDurationUnitEnum fromStatus(Integer status) {
return Arrays.stream(values())
.filter(bean -> Objects.equals(bean.code, status))
@@ -61,4 +57,13 @@ public enum ProductDurationUnitEnum implements IntArrayValuable, BaseEnum {
ProductDurationUnitEnum unit = fromStatus(status);
return unit != null ? unit.getDesc() : null;
}
/**
* 获取需要筛选的类型:年
*
* @return
*/
public static ProductDurationUnitEnum[] validArr() {
return new ProductDurationUnitEnum[]{YEAR};
}
}
@@ -187,16 +187,11 @@ public class OrganAmountServiceImpl implements OrganAmountService {
// 查询组织最晚还有效的产品购买记录
OrgEarliestPurchaseProductRespVO orgEarliestProductDetail = purchaseService.getOrgEarliestProductDetail(organId);
if (ObjectUtil.isNotNull(orgEarliestProductDetail)) {
// 购买记录
// 查询对应购买记录
PurchaseRecordDO purchaseRecordDO = orgEarliestProductDetail.getPurchaseRecordDO();
// 购买记录关联的定价规则
ProductsDetailDO productsDetailDO = orgEarliestProductDetail.getProductsDetailDO();
organAmountRespVO.setProductEfficientDate(LocalDateTimeUtil.format(purchaseRecordDO.getEndTime(), NORM_DATETIME_FORMATTER));
organAmountRespVO.setDuration(productsDetailDO.getDuration());
organAmountRespVO.setDurationUnit(productsDetailDO.getDurationUnit());
organAmountRespVO.setGiftDuration(productsDetailDO.getGiftDuration());
organAmountRespVO.setGiftDurationUnit(productsDetailDO.getGiftDurationUnit());
organAmountRespVO.setDuration(purchaseRecordDO.getPurchaseDuration());
organAmountRespVO.setDurationUnit(purchaseRecordDO.getPurchaseDurationUnit());
}
// 查询可开票金额
@@ -8,7 +8,7 @@ import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import jakarta.validation.Valid;
import java.util.List;
import java.math.BigDecimal;
/**
* 产品明细表 product_details Service 接口
@@ -67,4 +67,12 @@ public interface ProductsDetailService {
* @param productId
*/
ProductsDO validateProductExist(Long productId);
/**
* 获取产品1年定价的折扣
*
* @param productId
* @return
*/
BigDecimal getProductDetailOneYearDiscount(Long productId);
}
@@ -1,11 +1,14 @@
package com.cf.imes.module.system.service.funds.products;
import com.alibaba.nacos.common.utils.StringUtils;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.exception.util.ServiceExceptionUtil;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.mybatis.core.query.LambdaUpdateWrapperX;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
@@ -19,8 +22,8 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;
@@ -49,14 +52,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
if (ProductsPurchaseTypeEnum.DURATION.getStatus().equals(createReqVO.getPurchaseType())) {
// 校验时长
checkDuration(createReqVO.getDuration(), createReqVO.getDurationUnit());
// 校验赠送时长
String giftDuration = createReqVO.getGiftDuration();
if (StringUtils.isNotEmpty(giftDuration)) {
checkDuration(giftDuration, createReqVO.getGiftDurationUnit());
}
// 校验现价计算是否正确
calculateDiscount(createReqVO.getOriginalPrice(), createReqVO.getCurrentPrice(), createReqVO.getDiscount());
// 查询是否有重复的定价
checkProductDetails(createReqVO);
@@ -81,14 +77,6 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
if (updateReqVO.getPurchaseType().equals(ProductsPurchaseTypeEnum.DURATION.getStatus())) {
// 校验时长
checkDuration(updateReqVO.getDuration(), updateReqVO.getDurationUnit());
// 校验赠送时长
String giftDuration = updateReqVO.getGiftDuration();
if (StringUtils.isNotEmpty(giftDuration)) {
checkDuration(giftDuration, updateReqVO.getGiftDurationUnit());
}
// 校验现价计算是否正确
calculateDiscount(updateReqVO.getOriginalPrice(), updateReqVO.getCurrentPrice(), updateReqVO.getDiscount());
// 校验同时长是否还有其他的定价规则
checkProductDetails(updateReqVO);
@@ -97,7 +85,12 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
throw exception(PRODUCTS_OUTPUT_NOT_SUPPORT);
}
productDetailsMapper.updateById(BeanUtils.toBean(updateReqVO, ProductsDetailDO.class));
productDetailsMapper.update(new LambdaUpdateWrapper<ProductsDetailDO>()
.eq(ProductsDetailDO::getId, updateReqVO.getId())
.set(ProductsDetailDO::getDiscount, updateReqVO.getDiscount())
.set(ProductsDetailDO::getDuration, updateReqVO.getDuration())
.set(ProductsDetailDO::getDurationUnit, updateReqVO.getDurationUnit())
);
}
@Override
@@ -228,22 +221,16 @@ public class ProductsDetailServiceImpl implements ProductsDetailService {
}
}
/**
* 计算折扣是否正确
*
* @param originalPrice 原价
* @param currentPrice 当前价格
* @param discount 折扣(百分比)
*/
private void calculateDiscount(BigDecimal originalPrice, BigDecimal currentPrice, BigDecimal discount) {
// 校验当前价格是否正确
// 计算期望现价 = 原价 * 折扣 /100(保留2位小数)
BigDecimal expectedPrice = originalPrice.multiply(discount).divide(new BigDecimal(100)).setScale(2, RoundingMode.DOWN);
if (currentPrice.compareTo(expectedPrice) != 0) {
throw ServiceExceptionUtil.exception(PRODUCTS_DETAIL_CURRENTPRICE_CALCULATE_ERROR, expectedPrice);
@Override
public BigDecimal getProductDetailOneYearDiscount(Long productId) {
ProductsDetailDO productsDetailDO = productDetailsMapper.selectOne(new LambdaQueryWrapper<ProductsDetailDO>()
.eq(ProductsDetailDO::getProductId, productId)
.eq(ProductsDetailDO::getDuration, 1)
.eq(ProductsDetailDO::getDurationUnit, ProductDurationUnitEnum.YEAR.getCode()));
if (ObjectUtil.isNotNull(productsDetailDO)) {
return productsDetailDO.getDiscount();
} else {
return BigDecimal.valueOf(100);
}
}
}
@@ -2,6 +2,7 @@ package com.cf.imes.module.system.service.funds.products;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import jakarta.validation.Valid;
@@ -16,17 +17,17 @@ public interface ProductsService {
/**
* 创建产品
*
* @param productName 产品名称
* @param saveReqVO
* @return 编号
*/
Long create(@Valid String productName);
Long create(ProductSaveReqVO saveReqVO);
/**
* 更新产品
*
* @param updateReqVO 更新信息
*/
void update(@Valid ProductsDO updateReqVO);
void update(ProductSaveReqVO updateReqVO);
/**
* 删除产品
@@ -3,8 +3,11 @@ package com.cf.imes.module.system.service.funds.products;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.cf.imes.framework.mybatis.core.query.LambdaUpdateWrapperX;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsMapper;
import com.cf.imes.module.system.enums.products.ProductStatusEnum;
@@ -32,12 +35,14 @@ public class ProductsServiceImpl implements ProductsService {
private ProductsMapper productsMapper;
@Override
public Long create(String productName) {
public Long create(ProductSaveReqVO createReqVO) {
String productName = createReqVO.getProductName();
// 校验产品名称唯一
validateProductNameUnique(null, productName);
ProductsDO productsDO = new ProductsDO()
.setProductName(productName)
.setPrice(createReqVO.getPrice())
.setStatus(ProductStatusEnum.PRODUCT_WAITING_UPDATES.getStatus())
.setSoftwareId(0);
productsMapper.insert(productsDO);
@@ -47,7 +52,7 @@ public class ProductsServiceImpl implements ProductsService {
@Override
public void update(ProductsDO updateReqVO) {
public void update(ProductSaveReqVO updateReqVO) {
Long updateReqVOId = updateReqVO.getId();
String productName = updateReqVO.getProductName();
// 目标状态
@@ -63,9 +68,14 @@ public class ProductsServiceImpl implements ProductsService {
if (ProductStatusEnum.PRODUCT_UPDATES.getStatus().equals(status) && !ProductStatusEnum.isProductDelis(updateReqVOStatus)) {
throw exception(PRODUCTS_NO_UPDATE);
}
productsMapper.updateById(updateReqVO);
ProductsDO productsDO = BeanUtils.toBean(updateReqVO, ProductsDO.class);
productsMapper.updateById(productsDO);
productsMapper.update(new LambdaUpdateWrapperX<ProductsDO>()
.eq(ProductsDO::getId, updateReqVOId)
.setIfPresent(ProductsDO::getProductName, productName)
.setIfPresent(ProductsDO::getPrice, updateReqVO.getPrice())
.set(ProductsDO::getStatus, updateReqVOStatus)
);
}
@@ -2,13 +2,13 @@ package com.cf.imes.module.system.service.funds.purchase;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.TotalSalesAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.SalesDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.organ.vo.organ.OrganCreateProductPurchaseReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import java.math.BigDecimal;
@@ -82,12 +82,12 @@ public interface PurchaseService {
void createOrgInitProductPurchaseRecord(Long organId, List<OrganCreateProductPurchaseReqVO> productPurchaseReqVOS);
/**
* 查询组织下的产品购买记录
* 查询组织下的非首购的产品购买记录
*
* @param organIds
* @return
*/
List<PurchaseRecordDO> getOrgPurchaseList(Set<Long> organIds);
List<PurchaseRecordDO> getOrgPurchaseNotInitialList(Set<Long> organIds);
/**
* 查询销售总额
@@ -131,7 +131,7 @@ public interface PurchaseService {
* @param organId
* @return
*/
List<ProductsDetailDO> getOrgProductDetailList(Long organId, Long productId);
List<ProductDetailsRespVO> getOrgProductDetailList(Long organId, Long productId);
/**
* 作废历史组织产品购买记录
@@ -13,6 +13,7 @@ import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.TotalSalesAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.OrgEarliestPurchaseProductRespVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseRecordRespVO;
@@ -23,7 +24,6 @@ import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDO;
import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.mysql.funds.products.ProductsDetailMapper;
import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper;
import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum;
import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum;
@@ -46,7 +46,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCT_PRICE_NULL_ERROR;
import static com.cf.imes.module.system.enums.pay.PayConstants.PAY_PRODUCT_DETAIL_AUTO_GENERATE_MONTH_ID_FORMAT;
/**
* @author 软件购买接口 实现类
@@ -58,9 +59,6 @@ public class PurchaseServiceImpl implements PurchaseService {
@Resource
private PurchaseRecordMapper purchaseRecordMapper;
@Resource
private ProductsDetailMapper productsDetailMapper;
@Resource
private ProductsDetailService productsDetailService;
@@ -139,7 +137,7 @@ public class PurchaseServiceImpl implements PurchaseService {
// 延期记录是按自增id逆序的,第一条的延期后时间作为产品到期时间
purchaseRecordDO.setEndTime(recordsByPurchaseId.get(0).getDelayTime());
}
return new OrgEarliestPurchaseProductRespVO(purchaseRecordDO, productsDetailMapper.selectById(purchaseRecordDO.getProductDetailsId()));
return new OrgEarliestPurchaseProductRespVO(purchaseRecordDO);
}
return null;
}
@@ -152,53 +150,35 @@ public class PurchaseServiceImpl implements PurchaseService {
@Override
public void createOrgInitProductPurchaseRecord(Long organId, List<OrganCreateProductPurchaseReqVO> productPurchaseReqVOS) {
for (OrganCreateProductPurchaseReqVO purchaseReqVO : productPurchaseReqVOS) {
// 1、校验定价
ProductsDetailDO productsDetailDO = productsDetailService.validateProductDetailsExists(purchaseReqVO.getProductDetailsId());
// 2、校验产品
Long productId = productsDetailDO.getProductId();
// 1、校验产品
Long productId = purchaseReqVO.getProductId();
ProductsDO productsDO = productsDetailService.validateProductExist(productId);
// 3、创建购买记录
// 2、创建购买记录
// 输入的价格
BigDecimal price = purchaseReqVO.getPrice();
// 月均单价
BigDecimal monthAverageAmount;
// 计算后总价
BigDecimal totalAmount = price;
/**
* ①:首次订购满1年:
* 总价 = 输入金额
* 月均单价 = 输入金额 / (12 * duration)
* ②:不满1年按月份计算
* 总价 = 输入金额
* 月均单价 = 输入金额 / duration
*/
Integer duration = productsDetailDO.getDuration();
switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
case YEAR -> monthAverageAmount = price.divide(new BigDecimal(duration * 12), 2, RoundingMode.DOWN);
case MONTH -> monthAverageAmount = price.divide(new BigDecimal(duration), 2, RoundingMode.DOWN);
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
// 首次订购默认是1年的价格,月均单价/10(赠送两个月)
BigDecimal monthAverageAmount = price.divide(new BigDecimal(10), 2, RoundingMode.DOWN);
long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId();
LocalDate now = LocalDate.now();
PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder()
.id(purchaseId)
.organId(organId)
.productId(productId)
.productName(productsDO.getProductName())
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(duration)
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(LocalDate.now())
.endTime(purchaseReqVO.getProductExpireDate())
.productDetailsId(0L)
.purchaseDuration(1)
.purchaseDurationUnit(ProductDurationUnitEnum.YEAR.getCode())
.startTime(now)
// 组织创建时的套餐购买默认过期
.endTime(now.minusDays(1))
.paymentMethod(PayProductChannelCodeEnum.ORGAN_CREATE.getCode())
.totalAmount(totalAmount)
.totalAmount(price)
.monthAverageAmount(monthAverageAmount)
.originalAmount(price)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
.initial(true)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build();
@@ -207,10 +187,11 @@ public class PurchaseServiceImpl implements PurchaseService {
}
@Override
public List<PurchaseRecordDO> getOrgPurchaseList(Set<Long> organIds) {
public List<PurchaseRecordDO> getOrgPurchaseNotInitialList(Set<Long> organIds) {
return purchaseRecordMapper.selectList(new LambdaQueryWrapper<PurchaseRecordDO>()
.in(PurchaseRecordDO::getOrganId, organIds)
.eq(PurchaseRecordDO::getStatus, PurchaseRecordStatusEnum.ACTIVE.getStatus())
.ne(PurchaseRecordDO::getInitial, true)
.eq(PurchaseRecordDO::getDeleted, false));
}
@@ -265,9 +246,9 @@ public class PurchaseServiceImpl implements PurchaseService {
}
@Override
public List<ProductsDetailDO> getOrgProductDetailList(Long organId, Long productId) {
public List<ProductDetailsRespVO> getOrgProductDetailList(Long organId, Long productId) {
// 校验产品
productsDetailService.validateProductExist(productId);
ProductsDO productsDO = productsDetailService.validateProductExist(productId);
// 查询产品下的所有定价规则
ProductDetailsPageReqVO pageReqVO = new ProductDetailsPageReqVO();
@@ -278,64 +259,110 @@ public class PurchaseServiceImpl implements PurchaseService {
// 查询组织下的首次订购产品记录
PurchaseRecordDO orgInitialPurchaseRecord = getOrgInitialPurchaseRecord(organId, productId);
if (ObjectUtil.isNull(orgInitialPurchaseRecord)) {
return page.getList();
return getInitialProductDetailList(productsDO, page.getList());
}
List<ProductsDetailDO> resultList = new ArrayList<>();
for (ProductsDetailDO detailDO : page.getList()) {
ProductsDetailDO newDetailDO = BeanUtils.toBean(detailDO, ProductsDetailDO.class);
List<ProductDetailsRespVO> resultList = new ArrayList<>();
// 月均单价
BigDecimal monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount();
// 月均单价
BigDecimal monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount();
// 1年定价
BigDecimal totalAmount = orgInitialPurchaseRecord.getTotalAmount();
// 定价中有的,以年数量*组织定价*折扣展示
List<ProductDetailsRespVO> productDetailRespVOList = new ArrayList<>();
for (ProductsDetailDO detailDO : page.getList()) {
ProductDetailsRespVO newDetailRespVO = BeanUtils.toBean(detailDO, ProductDetailsRespVO.class);
// 折扣
BigDecimal discount = detailDO.getDiscount();
// 需要支付的总价
BigDecimal totalAmount;
// 重新计算购买价格
switch (ProductDurationUnitEnum.fromStatus(detailDO.getDurationUnit())) {
case YEAR -> totalAmount = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(detailDO.getDuration()))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
case MONTH -> {
int months = detailDO.getDuration();
if (months >= 12) {
// 转换成年 + 剩余月
int years = months / 12;
int remainMonths = months % 12;
case YEAR -> {
Integer duration = detailDO.getDuration();
// 原价
BigDecimal originalPrice = totalAmount.multiply(BigDecimal.valueOf(duration));
newDetailRespVO.setOriginalPrice(originalPrice);
// 折扣价格
newDetailRespVO.setCurrentPrice(originalPrice.multiply(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN));
productDetailRespVOList.add(newDetailRespVO);
}
default -> {
}
}
}
// 默认展示1到10月的价格
for (int i = 1; i <= 10; i++) {
ProductDetailsRespVO detailRespVO = new ProductDetailsRespVO();
detailRespVO.setId(Long.parseLong(String.format(PAY_PRODUCT_DETAIL_AUTO_GENERATE_MONTH_ID_FORMAT, i)));
detailRespVO.setProductId(productId);
detailRespVO.setDuration(i);
detailRespVO.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode());
// 价格:月均单价*月数量
BigDecimal price = monthAverageAmount.multiply(BigDecimal.valueOf(i));
// 有一年的折扣要把月价格做折算
detailRespVO.setCurrentPrice(price);
detailRespVO.setOriginalPrice(price);
resultList.add(detailRespVO);
}
resultList.addAll(productDetailRespVOList);
// 整年部分:用年逻辑(10 个月价)
BigDecimal yearPart = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(years))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
return resultList;
}
// 剩余月部分:用月逻辑
BigDecimal monthPart = remainMonths == 0
? BigDecimal.ZERO
: monthAverageAmount
.multiply(new BigDecimal(remainMonths))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
/**
* 拼接没有首次购买记录的定价方式列表
*
* @param productsDO
* @param productsDetailDOS
* @return
*/
private List<ProductDetailsRespVO> getInitialProductDetailList(ProductsDO productsDO, List<ProductsDetailDO> productsDetailDOS) {
List<ProductDetailsRespVO> resultList = new ArrayList<>();
// 没有首次购买记录的产品,展示的总价是产品建议价格
// 1年定价
BigDecimal totalAmount = productsDO.getPrice();
if(ObjectUtil.isNull(totalAmount)) {
throw new ServiceException(PRODUCT_PRICE_NULL_ERROR);
}
// 月均单价
BigDecimal monthAverageAmount = totalAmount.divide(new BigDecimal(10), 2, RoundingMode.DOWN);
totalAmount = yearPart.add(monthPart);
} else {
// 小于 12 个月直接按月逻辑
totalAmount = monthAverageAmount
.multiply(new BigDecimal(months))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
}
List<ProductDetailsRespVO> productDetailRespVOList = new ArrayList<>();
for (ProductsDetailDO detailDO : productsDetailDOS) {
ProductDetailsRespVO newDetailRespVO = BeanUtils.toBean(detailDO, ProductDetailsRespVO.class);
// 折扣
BigDecimal discount = detailDO.getDiscount();
// 重新计算购买价格
switch (ProductDurationUnitEnum.fromStatus(detailDO.getDurationUnit())) {
case YEAR -> {
Integer duration = detailDO.getDuration();
// 原价
BigDecimal originalPrice = totalAmount.multiply(BigDecimal.valueOf(duration));
newDetailRespVO.setOriginalPrice(originalPrice);
// 折扣价格
newDetailRespVO.setCurrentPrice(originalPrice.multiply(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.DOWN));
productDetailRespVOList.add(newDetailRespVO);
}
default -> {
}
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
newDetailDO.setCurrentPrice(totalAmount);
newDetailDO.setOriginalPrice(totalAmount);
resultList.add(newDetailDO);
}
// 默认展示1到10月的价格
for (int i = 1; i <= 10; i++) {
ProductDetailsRespVO detailRespVO = new ProductDetailsRespVO();
detailRespVO.setId(Long.parseLong(String.format(PAY_PRODUCT_DETAIL_AUTO_GENERATE_MONTH_ID_FORMAT, i)));
detailRespVO.setProductId(productsDO.getId());
detailRespVO.setDuration(i);
detailRespVO.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode());
// 价格:月均单价*月数量
BigDecimal price = monthAverageAmount.multiply(BigDecimal.valueOf(i));
// 有一年的折扣要把月价格做折算
detailRespVO.setCurrentPrice(price);
detailRespVO.setOriginalPrice(price);
resultList.add(detailRespVO);
}
resultList.addAll(productDetailRespVOList);
return resultList;
}
@@ -38,6 +38,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.product.Prod
import com.cf.imes.module.system.dal.dataobject.funds.delay.ProductDelayRecordDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.pay.PayOrderRechargeDetailDO;
import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum;
import com.cf.imes.module.system.service.funds.delay.ProductDelayService;
import com.cf.imes.module.system.service.funds.purchase.PurchaseService;
import com.cf.imes.module.system.controller.admin.pay.vo.PayProductOrderUnifiedReqVO;
@@ -111,6 +112,9 @@ public class PayOrderServiceImpl implements PayOrderService {
// 外部payNo正则
private static final Pattern pattern = Pattern.compile("P(\\d+)O");
// 自动生成定价id正则
private static final Pattern productDetailIdPattern = Pattern.compile("^1759029544(\\d+)$");
@Resource
private PayHandlerFactory payHandlerFactory;
@@ -169,10 +173,12 @@ public class PayOrderServiceImpl implements PayOrderService {
ValidationUtils.validate(validator, payProductOrderUnifiedReqVO);
// 校验产品和产品明细
ProductsDetailDO productsDetailDO = productsDetailService.validateProductDetailsExists(payProductOrderUnifiedReqVO.getProductDetailsId());
Long productId = productsDetailDO.getProductId();
Long productId = payProductOrderUnifiedReqVO.getProductId();
ProductsDO productsDO = productsDetailService.validateProductExist(productId);
// 解析产品定价明细
ProductsDetailDO productsDetailDO = analyzeProductDetail(productId, payProductOrderUnifiedReqVO.getProductDetailsId());
// 检查机构产品购买锁
String purchaseLockKey = String.format(RedisKeyConstants.PRODUCT_PURCHASE_LOCK_KEY, organId, productId);
boolean lockResult = redisLockUtil.redissonLock(purchaseLockKey, chenfengCacheProperties.getLockWaitTime(), chenfengCacheProperties.getLockTimeout());
@@ -250,6 +256,30 @@ public class PayOrderServiceImpl implements PayOrderService {
}
}
/**
* 解析定价规则,年从数据库取,月就手动拼
*
* @param productDetailId
* @return
*/
private ProductsDetailDO analyzeProductDetail(Long productId, Long productDetailId) {
// productDetailId符合规则PPAG%d
Matcher matcher = productDetailIdPattern.matcher(String.valueOf(productDetailId));
if (matcher.find()) {
// 取PPAG后的数字,是订购月份
String monthDuration = matcher.group(1);
ProductsDetailDO monthDetail = new ProductsDetailDO();
monthDetail.setId(productDetailId);
monthDetail.setDuration(Integer.parseInt(monthDuration));
monthDetail.setDurationUnit(ProductDurationUnitEnum.MONTH.getCode());
// 获取产品下的1年的折扣
monthDetail.setDiscount(productsDetailService.getProductDetailOneYearDiscount(productId));
return monthDetail;
} else {
return productsDetailService.validateProductDetailsExists(productDetailId);
}
}
/**
* 处理前序购买记录:
* 1、需要第三方支付,当前购买记录状态为待支付,等第三方回调/超期轮训处理到的时候标记为成功还是失败
@@ -94,6 +94,9 @@ public class PayProductProcessor {
// 本地订购产品的月均单价
private BigDecimal monthAverageAmount = BigDecimal.ZERO;
// 原始价格
private BigDecimal originalPrice;
public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, PreviouProductDelayRespVO previouProductDelayRespVO,
OrganAmountDO organAmountDO, Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties) {
this.productsDO = productsDO;
@@ -104,8 +107,7 @@ public class PayProductProcessor {
previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord();
// 上一次订购产品的结束时间进行计算
endTime = previousPurchaseRecord.getEndTime();
endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit(),
productsDetailDO.getGiftDuration(), productsDetailDO.getGiftDurationUnit());
endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit());
}
this.organAmountDO = organAmountDO;
this.organId = organId;
@@ -125,9 +127,7 @@ public class PayProductProcessor {
if (initialPurchaseExist) {
price = computePrice(orgInitialPurchaseRecord, productsDetailDO);
} else {
price = productsDetailDO.getCurrentPrice();
// 首次订购计算月均单价
monthAverageAmount = computeInitialProductPayMonthAverageAmount(price);
price = computeInitialProductPayAmount();
// 首次购买记录是空,那么当前就是首次订购
initialized = true;
}
@@ -184,13 +184,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.accountBalanceSpent(price)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
@@ -244,13 +242,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.accountBalanceSpent(price)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
@@ -301,13 +297,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.alipaySpent(price)
.initial(initialized)
@@ -340,13 +334,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.wechatSpent(price)
.initial(initialized)
@@ -384,13 +376,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.isInvocing(InvoiceStatusEnum.INVOICABLE.getCode())
.initial(initialized)
@@ -464,13 +454,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
@@ -543,13 +531,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
@@ -624,13 +610,11 @@ public class PayProductProcessor {
.productDetailsId(productsDetailDO.getId())
.purchaseDuration(productsDetailDO.getDuration())
.purchaseDurationUnit(productsDetailDO.getDurationUnit())
.giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
.totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice())
.originalAmount(originalPrice)
.monthAverageAmount(monthAverageAmount)
.initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
@@ -723,12 +707,9 @@ public class PayProductProcessor {
*
* @param duration
* @param durationUnit
* @param giftDuration
* @param giftDurationUnit
* @return
*/
private LocalDate calculateEndTime(Integer duration, Integer durationUnit,
Integer giftDuration, Integer giftDurationUnit) {
private LocalDate calculateEndTime(Integer duration, Integer durationUnit) {
LocalDate today = LocalDate.now();
// 1、 计算延期后的实际有效期
LocalDate maxDelayEnd = endTime;
@@ -758,11 +739,6 @@ public class PayProductProcessor {
endTime = addTime(purchaseStart, duration, durationUnit);
}
// 加赠送时间
if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) {
endTime = addTime(endTime, giftDuration, giftDurationUnit);
}
// 5. 扣掉延期天数
long delayDays = 0L;
if (CollUtil.isNotEmpty(delayRespVOList)) {
@@ -889,7 +865,7 @@ public class PayProductProcessor {
}
/**
* 计算产品价格
* 计算有首购记录的产品价格
*
* @param orgInitialPurchaseRecord 首次购买记录
* @param productsDetailDO 本次购买产品的定价规则
@@ -899,52 +875,35 @@ public class PayProductProcessor {
// 月均单价
monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount();
// 首购的定价
BigDecimal initialPurchaseRecordTotalAmount = orgInitialPurchaseRecord.getTotalAmount();
// 定价规则的折扣
BigDecimal discount = productsDetailDO.getDiscount();
BigDecimal totalAmount;
/**
* ①:续费订购满1年:
* 总价 = 月均单价 * 10 * duration * 折扣 /100,保留两位小数
* 原始价格 = 订购总价 * duration
* 现价 = 原始价格 * 折扣 /100,保留两位小数
* ②:不满1年按月份计算
* 总价 = 月均单价 * duration * 折扣 /100,保留两位小数
* 原始价格 = 月均单价 * duration
* 现价 = 原始价格 * 折扣 /100,保留两位小数
*/
switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
case YEAR -> totalAmount = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(productsDetailDO.getDuration()))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
case YEAR -> {
originalPrice = initialPurchaseRecordTotalAmount
.multiply(BigDecimal.valueOf(productsDetailDO.getDuration()));
totalAmount = originalPrice
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
}
case MONTH -> {
int months = productsDetailDO.getDuration();
if (months >= 12) {
// 转换成年 + 剩余月
int years = months / 12;
int remainMonths = months % 12;
// 整年部分:用年逻辑(10 个月价)
BigDecimal yearPart = monthAverageAmount
.multiply(new BigDecimal(10))
.multiply(new BigDecimal(years))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
// 剩余月部分:用月逻辑
BigDecimal monthPart = remainMonths == 0
? BigDecimal.ZERO
: monthAverageAmount
.multiply(new BigDecimal(remainMonths))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
totalAmount = yearPart.add(monthPart);
} else {
// 小于 12 个月直接按月逻辑
totalAmount = monthAverageAmount
.multiply(new BigDecimal(months))
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
}
originalPrice = monthAverageAmount
.multiply(BigDecimal.valueOf(productsDetailDO.getDuration()));
totalAmount = originalPrice
.multiply(discount)
.divide(new BigDecimal(100), 2, RoundingMode.DOWN);
}
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
@@ -953,17 +912,37 @@ public class PayProductProcessor {
}
/**
* 计算首次订购的月均单
* 计算首次订购的价
*/
private BigDecimal computeInitialProductPayMonthAverageAmount(BigDecimal price) {
private BigDecimal computeInitialProductPayAmount() {
// 订购数量
Integer duration = productsDetailDO.getDuration();
BigDecimal monthAverageAmount;
// 产品订购价格
BigDecimal orgPurchasePrice = productsDO.getPrice();
// 折扣
BigDecimal discount = productsDetailDO.getDiscount();
BigDecimal totalAmount;
switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) {
case YEAR -> monthAverageAmount = price.divide(new BigDecimal(duration * 12), 2, RoundingMode.DOWN);
case MONTH -> monthAverageAmount = price.divide(new BigDecimal(duration), 2, RoundingMode.DOWN);
case YEAR -> {
// 原价
originalPrice = orgPurchasePrice.multiply(BigDecimal.valueOf(duration));
// 折扣价格 = 原价 * 折扣
totalAmount = originalPrice.multiply(discount);
// 月均单价 = 折扣价格 / 10
monthAverageAmount = originalPrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN);
}
case MONTH -> {
// 月均单价 = 订购价格 / 10
monthAverageAmount = orgPurchasePrice.divide(new BigDecimal(10), 2, RoundingMode.DOWN);
// 原价
originalPrice = monthAverageAmount.multiply(BigDecimal.valueOf(duration));
// 折扣价格 = 原价 * 折扣
totalAmount = originalPrice.multiply(discount);
}
default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT);
}
return monthAverageAmount;
return totalAmount;
}
}
@@ -20,7 +20,7 @@ public class ProductDurationUnitValidator implements ConstraintValidator<Product
if (ObjectUtil.isNull(value)) {
return true;
}
for (ProductDurationUnitEnum unitEnum : ProductDurationUnitEnum.values()) {
for (ProductDurationUnitEnum unitEnum : ProductDurationUnitEnum.validArr()) {
if (Objects.equals(unitEnum.getCode(), value)) {
return true;
}
@@ -39,7 +39,7 @@
END
) AS invoiced
from purchase_record
where organ_id = #{organId} and deleted = false;
where organ_id = #{organId} and deleted = false and initial != true;
</select>
<select id="getInvoicableAmount" resultType="java.math.BigDecimal">
@@ -58,7 +58,7 @@
sum(total_amount - gift_money_spent) as amount,
sum(gift_money_spent) as giftAmount
from purchase_record
where deleted = false;
where deleted = false and initial != true;
</select>
<select id="getTotalRechargeUsageAmount" resultType="java.math.BigDecimal">