1、产品订购、延期、续期等日期计算优化;2、优化延期记录同步异步对于备注和已支付状态的处理;

This commit is contained in:
gaoqr
2025-09-26 15:58:48 +08:00
parent 628087d26c
commit e71e4e660a
10 changed files with 247 additions and 84 deletions
@@ -491,7 +491,7 @@ public class ErrorCodeConstants {
//=========== 产品延期管理 1-002-049-000 ============ //=========== 产品延期管理 1-002-049-000 ============
public static final ErrorCode PRODUCT_PURCHASE_NO_EXIST = new ErrorCode(1_002_049_001, "当前购买记录不存在,请检查"); public static final ErrorCode PRODUCT_PURCHASE_NO_EXIST = new ErrorCode(1_002_049_001, "当前购买记录不存在,请检查");
public static final ErrorCode PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR = new ErrorCode(1_002_049_002, "当前订购的产品延期时间{}超过续费时间{},请调整续费时长"); public static final ErrorCode PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR = new ErrorCode(1_002_049_002, "当前产品订购有效期{}超过当前日期,请调整订购时长");
//=========== 手工调账管理 1-002-050-000 ============ //=========== 手工调账管理 1-002-050-000 ============
public static final ErrorCode RECHARGE_GIFT_MEANWHILE_EMPTY_ERROR = new ErrorCode(1_002_050_001, "充值金额、赠送金额不能同时为空"); public static final ErrorCode RECHARGE_GIFT_MEANWHILE_EMPTY_ERROR = new ErrorCode(1_002_050_001, "充值金额、赠送金额不能同时为空");
@@ -22,8 +22,12 @@ import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT
* @since 2025/9/15 14:45 * @since 2025/9/15 14:45
*/ */
@Data @Data
@Schema(description = "管理后台 - 组织账户明细 Response VO") @Schema(description = "管理后台 - 产品延期记录 Response VO")
public class ProductDelayRespVO { public class ProductDelayRespVO {
@Schema(description = "延期记录编号")
@ExcelIgnore
private Long id;
@Schema(description = "组织名称") @Schema(description = "组织名称")
@ExcelProperty("组织名称") @ExcelProperty("组织名称")
private String organName; private String organName;
@@ -1,5 +1,6 @@
package com.cf.imes.module.system.controller.admin.funds.products.vo.product; package com.cf.imes.module.system.controller.admin.funds.products.vo.product;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO; import com.cf.imes.module.system.dal.dataobject.funds.organamount.OrganAmountDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO; import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO; import com.cf.imes.module.system.dal.dataobject.incomeandexpense.IncomeExpenseDetailsDO;
@@ -7,6 +8,8 @@ import com.cf.imes.module.system.dal.dataobject.pay.PayOrderDO;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import java.util.List;
/** /**
* @author Gqr * @author Gqr
* @since 2025/8/14 15:22 * @since 2025/8/14 15:22
@@ -33,4 +36,9 @@ public class ProductProcessorRespVO {
* 组织资金 * 组织资金
*/ */
private OrganAmountDO organAmountDO; private OrganAmountDO organAmountDO;
/**
* 延期记录列表
*/
private List<ProductDelayRespVO> delayRespVOList;
} }
@@ -5,6 +5,7 @@ import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductD
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelaySaveReqVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelaySaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.delay.ProductDelayRecordDO;
import java.util.List; import java.util.List;
@@ -48,6 +49,14 @@ public interface ProductDelayService {
*/ */
PreviouProductDelayRespVO getActiveRecordsByProductId(Long productId, Long organId); PreviouProductDelayRespVO getActiveRecordsByProductId(Long productId, Long organId);
/**
* 批量更新购买记录下的延期记录
*
* @param list 需要更新的延期列表
* @param organId 组织id
* @param paid 是否要更新为已支付
*/
void updatePurchaseDelayRecordsPaidAndRemark(List<ProductDelayRecordDO> list, Long organId, Boolean paid);
/** /**
* 更新购买记录下的延期记录为已支付 * 更新购买记录下的延期记录为已支付
@@ -56,4 +65,12 @@ public interface ProductDelayService {
* @param organId * @param organId
*/ */
void updatePurchaseDelayRecordsPaid(Long purchaseId, Long organId); void updatePurchaseDelayRecordsPaid(Long purchaseId, Long organId);
/**
* 重置购买记录下的延期记录的备注
*
* @param purchaseId
* @param organId
*/
void resetPurchaseDelayRecordsRemark(Long purchaseId, Long organId);
} }
@@ -9,6 +9,7 @@ import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils; import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.object.BeanUtils; 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.LambdaQueryWrapperX;
import com.cf.imes.framework.mybatis.core.query.LambdaUpdateWrapperX;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.PreviouProductDelayRespVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO; import com.cf.imes.module.system.controller.admin.funds.delay.vo.ProductDelayRespVO;
@@ -176,7 +177,7 @@ public class ProductDelayServiceImpl implements ProductDelayService {
.eq(ProductDelayRecordDO::getOrganId, organId) .eq(ProductDelayRecordDO::getOrganId, organId)
.eq(ProductDelayRecordDO::getPurchaseId, purchaseRecordDO.getId()) .eq(ProductDelayRecordDO::getPurchaseId, purchaseRecordDO.getId())
.eq(ProductDelayRecordDO::getDeleted, false) .eq(ProductDelayRecordDO::getDeleted, false)
.select(ProductDelayRecordDO::getDelayDuration, ProductDelayRecordDO::getDelayDurationUnit, ProductDelayRecordDO::getDelayTime, ProductDelayRecordDO::getEndTime) .select(ProductDelayRecordDO::getDelayDuration, ProductDelayRecordDO::getDelayDurationUnit, ProductDelayRecordDO::getDelayTime, ProductDelayRecordDO::getEndTime, ProductDelayRecordDO::getId)
); );
if(CollUtil.isNotEmpty(recordDOS)) { if(CollUtil.isNotEmpty(recordDOS)) {
resultList = BeanUtil.copyToList(recordDOS, ProductDelayRespVO.class); resultList = BeanUtil.copyToList(recordDOS, ProductDelayRespVO.class);
@@ -186,6 +187,20 @@ public class ProductDelayServiceImpl implements ProductDelayService {
return previouProductDelayRespVO; return previouProductDelayRespVO;
} }
@Override
public void updatePurchaseDelayRecordsPaidAndRemark(List<ProductDelayRecordDO> list, Long organId, Boolean paid) {
if (CollUtil.isEmpty(list)) {
return;
}
for (ProductDelayRecordDO recordDO : list) {
productDelayRecordMapper.update(new LambdaUpdateWrapperX<ProductDelayRecordDO>()
.eq(ProductDelayRecordDO::getId, recordDO.getId())
.eq(ProductDelayRecordDO::getOrganId, organId)
.setIfPresent(ProductDelayRecordDO::getPaid, paid)
.setIfPresent(ProductDelayRecordDO::getRemark, recordDO.getRemark()));
}
}
@Override @Override
public void updatePurchaseDelayRecordsPaid(Long purchaseId, Long organId) { public void updatePurchaseDelayRecordsPaid(Long purchaseId, Long organId) {
productDelayRecordMapper.update(new LambdaUpdateWrapper<ProductDelayRecordDO>() productDelayRecordMapper.update(new LambdaUpdateWrapper<ProductDelayRecordDO>()
@@ -193,4 +208,12 @@ public class ProductDelayServiceImpl implements ProductDelayService {
.eq(ProductDelayRecordDO::getOrganId, organId) .eq(ProductDelayRecordDO::getOrganId, organId)
.set(ProductDelayRecordDO::getPaid, true)); .set(ProductDelayRecordDO::getPaid, true));
} }
@Override
public void resetPurchaseDelayRecordsRemark(Long purchaseId, Long organId) {
productDelayRecordMapper.update(new LambdaUpdateWrapper<ProductDelayRecordDO>()
.eq(ProductDelayRecordDO::getPurchaseId, purchaseId)
.eq(ProductDelayRecordDO::getOrganId, organId)
.set(ProductDelayRecordDO::getRemark, null));
}
} }
@@ -1,5 +1,6 @@
package com.cf.imes.module.system.service.pay; package com.cf.imes.module.system.service.pay;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
@@ -34,6 +35,7 @@ import com.cf.imes.framework.pay.core.enums.order.PayOrderStatusRespEnum;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils; import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganRechargeAmountRespVO; import com.cf.imes.module.system.controller.admin.funds.organamount.vo.OrganRechargeAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO; import com.cf.imes.module.system.controller.admin.funds.products.vo.product.ProductProcessorRespVO;
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.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.pay.PayOrderRechargeDetailDO; import com.cf.imes.module.system.dal.dataobject.pay.PayOrderRechargeDetailDO;
import com.cf.imes.module.system.service.funds.delay.ProductDelayService; import com.cf.imes.module.system.service.funds.delay.ProductDelayService;
@@ -259,14 +261,19 @@ public class PayOrderServiceImpl implements PayOrderService {
private void processPreviosPurchaseRecord(PreviouProductDelayRespVO previouProductDelayRespVO, PayOrderDO payOrder) { private void processPreviosPurchaseRecord(PreviouProductDelayRespVO previouProductDelayRespVO, PayOrderDO payOrder) {
// 前序购买记录 // 前序购买记录
PurchaseRecordDO previouPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); PurchaseRecordDO previouPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord();
// 前序购买记录的延期记录
List<ProductDelayRecordDO> productDelayRecordDOS = BeanUtil.copyToList(previouProductDelayRespVO.getDelayRespVOList(), ProductDelayRecordDO.class);
if (ObjectUtil.isNotNull(previouPurchaseRecord)) { if (ObjectUtil.isNotNull(previouPurchaseRecord)) {
Long previousId = previouPurchaseRecord.getId(); Long previousId = previouPurchaseRecord.getId();
Long organId = previouPurchaseRecord.getOrganId(); Long organId = previouPurchaseRecord.getOrganId();
if (ObjectUtil.isNull(payOrder)) { if (ObjectUtil.isNull(payOrder)) {
// 作废历史的购买记录 // 作废历史的购买记录
purchaseService.invalidHistoryOrganProductPurchaseRecord(organId, previousId); purchaseService.invalidHistoryOrganProductPurchaseRecord(organId, previousId);
// 更新前置购买记录下的延期记录为已支付 // 更新前置购买记录下的延期记录
productDelayService.updatePurchaseDelayRecordsPaid(previousId, organId); productDelayService.updatePurchaseDelayRecordsPaidAndRemark(productDelayRecordDOS, organId, true);
} else {
// 更新前置购买记录下的延期记录
productDelayService.updatePurchaseDelayRecordsPaidAndRemark(productDelayRecordDOS, organId, null);
} }
} }
} }
@@ -730,6 +737,12 @@ public class PayOrderServiceImpl implements PayOrderService {
incomeExpenseDetailsDO.setUpdater(record.getUpdater()); incomeExpenseDetailsDO.setUpdater(record.getUpdater());
incomeExpenseService.createDetail(incomeExpenseDetailsDO); incomeExpenseService.createDetail(incomeExpenseDetailsDO);
} }
// 4、重置前序产品购买记录下延期记录的备注
Long previousId = record.getPreviousId();
if (ObjectUtil.isNotNull(previousId)) {
productDelayService.resetPurchaseDelayRecordsRemark(previousId, organId);
}
} }
} }
@@ -842,7 +855,7 @@ public class PayOrderServiceImpl implements PayOrderService {
respVO.setAccountBalanceSpent(zero); respVO.setAccountBalanceSpent(zero);
respVO.setGiftMoneySpent(zero); respVO.setGiftMoneySpent(zero);
} }
OrganizationDO organ = organService.getOrgan(detailDO.getOrganId()); OrganizationDO organ = organService.getOrganWithDeleted(detailDO.getOrganId());
if (ObjectUtil.isNotNull(organ)) { if (ObjectUtil.isNotNull(organ)) {
respVO.setOrganName(organ.getName()); respVO.setOrganName(organ.getName());
} }
@@ -30,12 +30,14 @@ import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum;
import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum; import com.cf.imes.module.system.enums.pay.PurchaseRecordStatusEnum;
import com.cf.imes.module.system.enums.pay.TradeTypeEnum; import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties; import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties;
import com.cf.imes.module.system.service.pay.dto.DateIntervalDTO;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@@ -86,6 +88,9 @@ public class PayProductProcessor {
// 本地订购产品的开始时间 // 本地订购产品的开始时间
private LocalDate startTime = LocalDate.now(); private LocalDate startTime = LocalDate.now();
// 产品结束时间
private LocalDate endTime;
// 本地订购产品的月均单价 // 本地订购产品的月均单价
private BigDecimal monthAverageAmount = BigDecimal.ZERO; private BigDecimal monthAverageAmount = BigDecimal.ZERO;
@@ -97,8 +102,10 @@ public class PayProductProcessor {
if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) { if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) {
delayRespVOList = previouProductDelayRespVO.getDelayRespVOList(); delayRespVOList = previouProductDelayRespVO.getDelayRespVOList();
previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord();
// 如果有前置购买记录,那么新的开始时间是上一次订购的结束时间 // 上一次订购产品的结束时间进行计算
startTime = previousPurchaseRecord.getEndTime(); endTime = previousPurchaseRecord.getEndTime();
endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit(),
productsDetailDO.getGiftDuration(), productsDetailDO.getGiftDurationUnit());
} }
this.organAmountDO = organAmountDO; this.organAmountDO = organAmountDO;
this.organId = organId; this.organId = organId;
@@ -180,6 +187,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -189,9 +197,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 新增收支明细 // 新增收支明细
// 现金余额减去购买金额 // 现金余额减去购买金额
BigDecimal newBalance = balance.subtract(price); BigDecimal newBalance = balance.subtract(price);
@@ -212,7 +217,7 @@ public class PayProductProcessor {
// 更新余额 // 更新余额
organAmountDO.setAmount(newBalance); organAmountDO.setAmount(newBalance);
organAmountDO.setGiftAmount(giftAmount); organAmountDO.setGiftAmount(giftAmount);
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO); return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO, delayRespVOList);
} }
/** /**
@@ -238,6 +243,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -247,9 +253,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 新增收支明细 // 新增收支明细
// 赠送金余额减去购买金额 // 赠送金余额减去购买金额
BigDecimal newGift = gift.subtract(price); BigDecimal newGift = gift.subtract(price);
@@ -270,7 +273,7 @@ public class PayProductProcessor {
// 更新余额 // 更新余额
organAmountDO.setAmount(amount); organAmountDO.setAmount(amount);
organAmountDO.setGiftAmount(newGift); organAmountDO.setGiftAmount(newGift);
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO); return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO, delayRespVOList);
} }
/** /**
@@ -293,6 +296,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -301,9 +305,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.PENDING.getStatus()) .status(PurchaseRecordStatusEnum.PENDING.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 记录前序购买记录id // 记录前序购买记录id
if (ObjectUtil.isNotNull(previousPurchaseRecord)) { if (ObjectUtil.isNotNull(previousPurchaseRecord)) {
purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId());
@@ -311,7 +312,7 @@ public class PayProductProcessor {
// 新增第三方支付订单 // 新增第三方支付订单
PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.ALIPAY_PC); PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.ALIPAY_PC);
return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null); return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null, delayRespVOList);
} }
/** /**
@@ -334,6 +335,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -342,9 +344,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.PENDING.getStatus()) .status(PurchaseRecordStatusEnum.PENDING.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 记录前序购买记录id // 记录前序购买记录id
if (ObjectUtil.isNotNull(previousPurchaseRecord)) { if (ObjectUtil.isNotNull(previousPurchaseRecord)) {
purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId());
@@ -352,7 +351,7 @@ public class PayProductProcessor {
// 新增第三方支付订单 // 新增第三方支付订单
PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.WECHAT_NATIVE); PayOrderDO payOrderDO = goExternalPay(purchaseId, price, PayChannelCodeEnum.WECHAT_NATIVE);
return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null); return new ProductProcessorRespVO(purchaseRecordDO, null, payOrderDO, null, delayRespVOList);
} }
/** /**
@@ -380,6 +379,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -388,9 +388,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 先用赠送金 // 先用赠送金
if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) {
@@ -432,7 +429,7 @@ public class PayProductProcessor {
// 更新余额 // 更新余额
organAmountDO.setAmount(newAmounta); organAmountDO.setAmount(newAmounta);
organAmountDO.setGiftAmount(newGift); organAmountDO.setGiftAmount(newGift);
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO); return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, null, organAmountDO, delayRespVOList);
} }
/** /**
@@ -458,6 +455,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode()) .paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.BALANCE_AND_GIFT_AND_WECHAT.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -465,9 +463,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 记录前序购买记录id // 记录前序购买记录id
if (ObjectUtil.isNotNull(previousPurchaseRecord)) { if (ObjectUtil.isNotNull(previousPurchaseRecord)) {
purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId());
@@ -513,7 +508,7 @@ public class PayProductProcessor {
// 更新余额 // 更新余额
organAmountDO.setAmount(newAmounta); organAmountDO.setAmount(newAmounta);
organAmountDO.setGiftAmount(giftAmount); organAmountDO.setGiftAmount(giftAmount);
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO); return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList);
} }
/** /**
@@ -539,6 +534,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode()) .paymentMethod(PayChannelCodeEnum.ALIPAY_PC.equals(payChannelCodeEnum) ? PayProductChannelCodeEnum.GIFT_AND_ALIPAY.getCode() : PayProductChannelCodeEnum.GIFT_AND_WECHAT.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -546,9 +542,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 记录前序购买记录id // 记录前序购买记录id
if (ObjectUtil.isNotNull(previousPurchaseRecord)) { if (ObjectUtil.isNotNull(previousPurchaseRecord)) {
purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId());
@@ -594,7 +587,7 @@ public class PayProductProcessor {
// 更新余额 // 更新余额
organAmountDO.setAmount(amount); organAmountDO.setAmount(amount);
organAmountDO.setGiftAmount(newGift); organAmountDO.setGiftAmount(newGift);
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO); return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList);
} }
/** /**
@@ -622,6 +615,7 @@ public class PayProductProcessor {
.giftDuration(productsDetailDO.getGiftDuration()) .giftDuration(productsDetailDO.getGiftDuration())
.giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit())
.startTime(startTime) .startTime(startTime)
.endTime(endTime)
.paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode())
.totalAmount(price) .totalAmount(price)
.originalAmount(productsDetailDO.getCurrentPrice()) .originalAmount(productsDetailDO.getCurrentPrice())
@@ -629,9 +623,6 @@ public class PayProductProcessor {
.initial(initialized) .initial(initialized)
.status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus())
.build(); .build();
purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(),
purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(),
purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit()));
// 记录前序购买记录id // 记录前序购买记录id
if (ObjectUtil.isNotNull(previousPurchaseRecord)) { if (ObjectUtil.isNotNull(previousPurchaseRecord)) {
purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId());
@@ -685,7 +676,7 @@ public class PayProductProcessor {
// 更新余额 // 更新余额
organAmountDO.setAmount(newAmounta); organAmountDO.setAmount(newAmounta);
organAmountDO.setGiftAmount(newGift); organAmountDO.setGiftAmount(newGift);
return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO); return new ProductProcessorRespVO(purchaseRecordDO, incomeExpenseDetailsDO, payOrderDO, organAmountDO, delayRespVOList);
} }
/** /**
@@ -718,16 +709,34 @@ public class PayProductProcessor {
/** /**
* 计算结束时间 * 计算结束时间
* *
* @param startTime
* @param duration * @param duration
* @param durationUnit * @param durationUnit
* @param giftDuration * @param giftDuration
* @param giftDurationUnit * @param giftDurationUnit
* @return * @return
*/ */
private LocalDate calculateEndTime(LocalDate startTime, Integer duration, Integer durationUnit, private LocalDate calculateEndTime(Integer duration, Integer durationUnit,
Integer giftDuration, Integer giftDurationUnit) { Integer giftDuration, Integer giftDurationUnit) {
LocalDate endTime = startTime; LocalDate today = LocalDate.now();
// 1、 计算延期后的实际有效期
LocalDate maxDelayEnd = endTime;
if (CollUtil.isNotEmpty(delayRespVOList)) {
// 找到延期记录中最大的 delayTime 作为最终有效期
maxDelayEnd = delayRespVOList.stream()
.map(ProductDelayRespVO::getDelayTime)
.max(LocalDate::compareTo)
.orElse(endTime);
}
// 2、 判断是否过期
if (maxDelayEnd.isBefore(today)) {
// 已过期,从今天开始
startTime = today;
} else {
// 未过期,从上次结束时间继续
startTime = today.isBefore(maxDelayEnd) ? today : maxDelayEnd;;
}
// 加正常时间 // 加正常时间
if (ObjectUtil.isNotNull(duration) && durationUnit != null) { if (ObjectUtil.isNotNull(duration) && durationUnit != null) {
endTime = addTime(startTime, duration, durationUnit); endTime = addTime(startTime, duration, durationUnit);
@@ -735,17 +744,115 @@ public class PayProductProcessor {
// 加赠送时间 // 加赠送时间
if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) { if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) {
endTime = addTime(startTime, giftDuration, giftDurationUnit); endTime = addTime(endTime, giftDuration, giftDurationUnit);
} }
// 5. 扣掉延期天数
long delayDays = 0L;
if (CollUtil.isNotEmpty(delayRespVOList)) { if (CollUtil.isNotEmpty(delayRespVOList)) {
// 根据延期列表计算新的结束时间 delayDays = calculateDelayDays(startTime, delayRespVOList);
endTime = calculateDelayEndTime(startTime, endTime, delayRespVOList); }
if (delayDays > 0) {
endTime = endTime.minusDays(delayDays);
}
if (endTime.isBefore(today)) {
throw ServiceExceptionUtil.exception(PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR, LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN));
} }
return endTime; return endTime;
} }
/**
* 计算总延期天数(合并区间,避免重叠/不连续)
*/
private static long calculateDelayDays(LocalDate startTime, List<ProductDelayRespVO> delayRecords) {
// 1. 生成延期区间并打上备注
List<DateIntervalDTO> intervals = buildIntervals(startTime, delayRecords);
if (CollUtil.isEmpty(intervals)) {
return 0L;
}
// 2. 合并区间
List<DateIntervalDTO> merged = mergeIntervals(intervals);
// 3. 计算天数
return calculateTotalDays(merged);
}
/**
* 根据延期记录生成扣减区间,同时写入备注
*/
private static List<DateIntervalDTO> buildIntervals(LocalDate startTime, List<ProductDelayRespVO> delayRecords) {
List<DateIntervalDTO> intervals = new ArrayList<>();
for (ProductDelayRespVO record : delayRecords) {
LocalDate delayStart = record.getEndTime().plusDays(1);
LocalDate delayEnd = record.getDelayTime();
if (delayEnd.isBefore(delayStart)) {
record.setRemark("无扣减");
continue; // 异常数据,忽略
}
LocalDate intervalEnd;
if (!delayEnd.isAfter(startTime)) {
// 情况1:延期结束在计算结束之前 → 整段都扣
intervalEnd = delayEnd;
} else {
// 情况2:延期结束晚于计算结束 → 扣到 startTime 为止
intervalEnd = startTime;
}
long deductDays = ChronoUnit.DAYS.between(delayStart, intervalEnd) + 1;
if (deductDays > 0) {
record.setRemark("扣减" + deductDays + "");
intervals.add(new DateIntervalDTO(delayStart, intervalEnd));
} else {
record.setRemark("无扣减");
}
}
return intervals;
}
/**
* 合并区间(避免重叠/相邻)
*/
private static List<DateIntervalDTO> mergeIntervals(List<DateIntervalDTO> intervals) {
intervals.sort(Comparator.comparing(DateIntervalDTO::getStart));
List<DateIntervalDTO> merged = new ArrayList<>();
DateIntervalDTO current = intervals.get(0);
for (int i = 1; i < intervals.size(); i++) {
DateIntervalDTO next = intervals.get(i);
if (!next.getStart().isAfter(current.getEnd().plusDays(1))) {
// 重叠或相邻 → 合并
current.setEnd(current.getEnd().isAfter(next.getEnd()) ? current.getEnd() : next.getEnd());
} else {
merged.add(current);
current = next;
}
}
merged.add(current);
return merged;
}
/**
* 计算合并区间的总天数
*/
private static long calculateTotalDays(List<DateIntervalDTO> merged) {
long totalDays = 0L;
for (DateIntervalDTO interval : merged) {
totalDays += ChronoUnit.DAYS.between(interval.getStart(), interval.getEnd()) + 1;
}
return totalDays;
}
/** /**
* 基于时长和单位累加time * 基于时长和单位累加time
* *
@@ -765,44 +872,6 @@ public class PayProductProcessor {
} }
} }
/**
* 根据延期列表中最早的产品结束时间和最晚的延期后时间计算新的结束时间
*
* @param startTime 订购的开始时间
* @param endTime 订购的结束时间
* @param delayRecordsList 延期列表
* @return
*/
private LocalDate calculateDelayEndTime(LocalDate startTime, LocalDate endTime, List<ProductDelayRespVO> delayRecordsList) {
// 筛选出延期列表结束时间排序最早和最晚的
ProductDelayRespVO earliestDelayVO = delayRecordsList.stream().min(Comparator.comparing(ProductDelayRespVO::getEndTime)).get();
ProductDelayRespVO lastestDelayVO = delayRecordsList.stream().max(Comparator.comparing(ProductDelayRespVO::getDelayTime)).get();
// 延期结束时间超过续费结束时间,提示重新选择续费时长
LocalDate delayEndTime = earliestDelayVO.getEndTime();
if (delayEndTime.isAfter(endTime)) {
throw ServiceExceptionUtil.exception(PRODUCT_PURCHASE_ENDTIME_BEFORE_DELAYTIME_ERROR,
LocalDateTimeUtil.format(delayEndTime, DatePattern.NORM_DATE_PATTERN),
LocalDateTimeUtil.format(endTime, DatePattern.NORM_DATE_PATTERN));
}
// 延期区间: [原结束日+1, 延期结束日]
LocalDate delayStart = delayEndTime.plusDays(1);
LocalDate delayEnd = lastestDelayVO.getDelayTime();
// 购买区间: [startTime, endTime]
LocalDate overlapStart = startTime.isAfter(delayStart) ? startTime : delayStart;
LocalDate overlapEnd = endTime.isBefore(delayEnd) ? endTime : delayEnd;
long overlapDays = 0;
if (!overlapEnd.isBefore(overlapStart)) {
overlapDays = ChronoUnit.DAYS.between(overlapStart, overlapEnd) + 1;
}
// 从 endTime 扣掉重合天数
return endTime.minusDays(overlapDays);
}
/** /**
* 计算产品价格 * 计算产品价格
* *
@@ -0,0 +1,25 @@
package com.cf.imes.module.system.service.pay.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDate;
/**
* 延期记录记录重合区域DTO
* @author Gqr
* @since 2025/9/26 9:30
*/
@Data
@AllArgsConstructor
public class DateIntervalDTO {
/**
* 开始时间
*/
LocalDate start;
/**
* 结束时间
*/
LocalDate end;
}
@@ -205,6 +205,8 @@ chenfeng:
- /admin-api/system/product-details/organ/purchase - /admin-api/system/product-details/organ/purchase
- /admin-api/system/purchase - /admin-api/system/purchase
- /admin-api/system/pay/product - /admin-api/system/pay/product
- /admin-api/system/recharge-active/page
- /admin-api/system/pay/recharge
ignore-tables: ignore-tables:
- system_organization - system_organization
- system_tenant_package - system_tenant_package
@@ -221,6 +221,8 @@ chenfeng:
- /admin-api/system/product-details/organ/purchase - /admin-api/system/product-details/organ/purchase
- /admin-api/system/purchase - /admin-api/system/purchase
- /admin-api/system/pay/product - /admin-api/system/pay/product
- /admin-api/system/recharge-active/page
- /admin-api/system/pay/recharge
ignore-tables: ignore-tables:
- system_organization - system_organization
- system_tenant_package - system_tenant_package