diff --git a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java index 15f2190f1..7e9579067 100644 --- a/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java +++ b/cf-module-system/cf-module-system-api/src/main/java/com/cf/imes/module/system/enums/ErrorCodeConstants.java @@ -491,7 +491,7 @@ public class ErrorCodeConstants { //=========== 产品延期管理 1-002-049-000 ============ 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 ============ public static final ErrorCode RECHARGE_GIFT_MEANWHILE_EMPTY_ERROR = new ErrorCode(1_002_050_001, "充值金额、赠送金额不能同时为空"); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java index 02bd9b9e8..20294f7e6 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/delay/vo/ProductDelayRespVO.java @@ -22,8 +22,12 @@ import static com.cf.imes.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT * @since 2025/9/15 14:45 */ @Data -@Schema(description = "管理后台 - 组织账户明细 Response VO") +@Schema(description = "管理后台 - 产品延期记录 Response VO") public class ProductDelayRespVO { + @Schema(description = "延期记录编号") + @ExcelIgnore + private Long id; + @Schema(description = "组织名称") @ExcelProperty("组织名称") private String organName; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java index 238b45178..9498a502b 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/product/ProductProcessorRespVO.java @@ -1,5 +1,6 @@ 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.purchase.PurchaseRecordDO; 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.Data; +import java.util.List; + /** * @author Gqr * @since 2025/8/14 15:22 @@ -33,4 +36,9 @@ public class ProductProcessorRespVO { * 组织资金 */ private OrganAmountDO organAmountDO; + + /** + * 延期记录列表 + */ + private List delayRespVOList; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java index f563e7268..6c3179712 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayService.java @@ -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.ProductDelayRespVO; 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; @@ -48,6 +49,14 @@ public interface ProductDelayService { */ PreviouProductDelayRespVO getActiveRecordsByProductId(Long productId, Long organId); + /** + * 批量更新购买记录下的延期记录 + * + * @param list 需要更新的延期列表 + * @param organId 组织id + * @param paid 是否要更新为已支付 + */ + void updatePurchaseDelayRecordsPaidAndRemark(List list, Long organId, Boolean paid); /** * 更新购买记录下的延期记录为已支付 @@ -56,4 +65,12 @@ public interface ProductDelayService { * @param organId */ void updatePurchaseDelayRecordsPaid(Long purchaseId, Long organId); + + /** + * 重置购买记录下的延期记录的备注 + * + * @param purchaseId + * @param organId + */ + void resetPurchaseDelayRecordsRemark(Long purchaseId, Long organId); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java index 73784a9c2..c867e96b4 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/delay/ProductDelayServiceImpl.java @@ -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.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.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.ProductDelayRespVO; @@ -176,7 +177,7 @@ public class ProductDelayServiceImpl implements ProductDelayService { .eq(ProductDelayRecordDO::getOrganId, organId) .eq(ProductDelayRecordDO::getPurchaseId, purchaseRecordDO.getId()) .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)) { resultList = BeanUtil.copyToList(recordDOS, ProductDelayRespVO.class); @@ -186,6 +187,20 @@ public class ProductDelayServiceImpl implements ProductDelayService { return previouProductDelayRespVO; } + @Override + public void updatePurchaseDelayRecordsPaidAndRemark(List list, Long organId, Boolean paid) { + if (CollUtil.isEmpty(list)) { + return; + } + for (ProductDelayRecordDO recordDO : list) { + productDelayRecordMapper.update(new LambdaUpdateWrapperX() + .eq(ProductDelayRecordDO::getId, recordDO.getId()) + .eq(ProductDelayRecordDO::getOrganId, organId) + .setIfPresent(ProductDelayRecordDO::getPaid, paid) + .setIfPresent(ProductDelayRecordDO::getRemark, recordDO.getRemark())); + } + } + @Override public void updatePurchaseDelayRecordsPaid(Long purchaseId, Long organId) { productDelayRecordMapper.update(new LambdaUpdateWrapper() @@ -193,4 +208,12 @@ public class ProductDelayServiceImpl implements ProductDelayService { .eq(ProductDelayRecordDO::getOrganId, organId) .set(ProductDelayRecordDO::getPaid, true)); } + + @Override + public void resetPurchaseDelayRecordsRemark(Long purchaseId, Long organId) { + productDelayRecordMapper.update(new LambdaUpdateWrapper() + .eq(ProductDelayRecordDO::getPurchaseId, purchaseId) + .eq(ProductDelayRecordDO::getOrganId, organId) + .set(ProductDelayRecordDO::getRemark, null)); + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java index e8f712864..f6bb70407 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayOrderServiceImpl.java @@ -1,5 +1,6 @@ package com.cf.imes.module.system.service.pay; +import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.IdUtil; 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.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.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.service.funds.delay.ProductDelayService; @@ -259,14 +261,19 @@ public class PayOrderServiceImpl implements PayOrderService { private void processPreviosPurchaseRecord(PreviouProductDelayRespVO previouProductDelayRespVO, PayOrderDO payOrder) { // 前序购买记录 PurchaseRecordDO previouPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); + // 前序购买记录的延期记录 + List productDelayRecordDOS = BeanUtil.copyToList(previouProductDelayRespVO.getDelayRespVOList(), ProductDelayRecordDO.class); if (ObjectUtil.isNotNull(previouPurchaseRecord)) { Long previousId = previouPurchaseRecord.getId(); Long organId = previouPurchaseRecord.getOrganId(); if (ObjectUtil.isNull(payOrder)) { // 作废历史的购买记录 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()); 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.setGiftMoneySpent(zero); } - OrganizationDO organ = organService.getOrgan(detailDO.getOrganId()); + OrganizationDO organ = organService.getOrganWithDeleted(detailDO.getOrganId()); if (ObjectUtil.isNotNull(organ)) { respVO.setOrganName(organ.getName()); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java index 4afc1ab46..ca83d85f7 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/PayProductProcessor.java @@ -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.TradeTypeEnum; 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.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -86,6 +88,9 @@ public class PayProductProcessor { // 本地订购产品的开始时间 private LocalDate startTime = LocalDate.now(); + // 产品结束时间 + private LocalDate endTime; + // 本地订购产品的月均单价 private BigDecimal monthAverageAmount = BigDecimal.ZERO; @@ -97,8 +102,10 @@ public class PayProductProcessor { if (ObjectUtil.isNotNull(previouProductDelayRespVO.getPreviouPurchaseRecord())) { delayRespVOList = previouProductDelayRespVO.getDelayRespVOList(); previousPurchaseRecord = previouProductDelayRespVO.getPreviouPurchaseRecord(); - // 如果有前置购买记录,那么新的开始时间是上一次订购的结束时间 - startTime = previousPurchaseRecord.getEndTime(); + // 上一次订购产品的结束时间进行计算 + endTime = previousPurchaseRecord.getEndTime(); + endTime = calculateEndTime(productsDetailDO.getDuration(), productsDetailDO.getDurationUnit(), + productsDetailDO.getGiftDuration(), productsDetailDO.getGiftDurationUnit()); } this.organAmountDO = organAmountDO; this.organId = organId; @@ -180,6 +187,7 @@ public class PayProductProcessor { .giftDuration(productsDetailDO.getGiftDuration()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(startTime) + .endTime(endTime) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) .originalAmount(productsDetailDO.getCurrentPrice()) @@ -189,9 +197,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 新增收支明细 // 现金余额减去购买金额 BigDecimal newBalance = balance.subtract(price); @@ -212,7 +217,7 @@ public class PayProductProcessor { // 更新余额 organAmountDO.setAmount(newBalance); 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()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(startTime) + .endTime(endTime) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) .originalAmount(productsDetailDO.getCurrentPrice()) @@ -247,9 +253,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 新增收支明细 // 赠送金余额减去购买金额 BigDecimal newGift = gift.subtract(price); @@ -270,7 +273,7 @@ public class PayProductProcessor { // 更新余额 organAmountDO.setAmount(amount); 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()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(startTime) + .endTime(endTime) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) .originalAmount(productsDetailDO.getCurrentPrice()) @@ -301,9 +305,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.PENDING.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 记录前序购买记录id if (ObjectUtil.isNotNull(previousPurchaseRecord)) { purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); @@ -311,7 +312,7 @@ public class PayProductProcessor { // 新增第三方支付订单 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()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(startTime) + .endTime(endTime) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) .originalAmount(productsDetailDO.getCurrentPrice()) @@ -342,9 +344,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.PENDING.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 记录前序购买记录id if (ObjectUtil.isNotNull(previousPurchaseRecord)) { purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); @@ -352,7 +351,7 @@ public class PayProductProcessor { // 新增第三方支付订单 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()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(startTime) + .endTime(endTime) .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) .totalAmount(price) .originalAmount(productsDetailDO.getCurrentPrice()) @@ -388,9 +388,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 先用赠送金 if (gift != null && gift.compareTo(BigDecimal.ZERO) > 0) { @@ -432,7 +429,7 @@ public class PayProductProcessor { // 更新余额 organAmountDO.setAmount(newAmounta); 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()) .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()) @@ -465,9 +463,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 记录前序购买记录id if (ObjectUtil.isNotNull(previousPurchaseRecord)) { purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); @@ -513,7 +508,7 @@ public class PayProductProcessor { // 更新余额 organAmountDO.setAmount(newAmounta); 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()) .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()) @@ -546,9 +542,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 记录前序购买记录id if (ObjectUtil.isNotNull(previousPurchaseRecord)) { purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); @@ -594,7 +587,7 @@ public class PayProductProcessor { // 更新余额 organAmountDO.setAmount(amount); 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()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(startTime) + .endTime(endTime) .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) .totalAmount(price) .originalAmount(productsDetailDO.getCurrentPrice()) @@ -629,9 +623,6 @@ public class PayProductProcessor { .initial(initialized) .status(PurchaseRecordStatusEnum.ACTIVE.getStatus()) .build(); - purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), - purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), - purchaseRecordDO.getGiftDuration(), purchaseRecordDO.getGiftDurationUnit())); // 记录前序购买记录id if (ObjectUtil.isNotNull(previousPurchaseRecord)) { purchaseRecordDO.setPreviousId(previousPurchaseRecord.getId()); @@ -685,7 +676,7 @@ public class PayProductProcessor { // 更新余额 organAmountDO.setAmount(newAmounta); 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 durationUnit * @param giftDuration * @param giftDurationUnit * @return */ - private LocalDate calculateEndTime(LocalDate startTime, Integer duration, Integer durationUnit, + private LocalDate calculateEndTime(Integer duration, Integer durationUnit, 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) { endTime = addTime(startTime, duration, durationUnit); @@ -735,17 +744,115 @@ public class PayProductProcessor { // 加赠送时间 if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) { - endTime = addTime(startTime, giftDuration, giftDurationUnit); + endTime = addTime(endTime, giftDuration, giftDurationUnit); } + // 5. 扣掉延期天数 + long delayDays = 0L; if (CollUtil.isNotEmpty(delayRespVOList)) { - // 根据延期列表计算新的结束时间 - endTime = calculateDelayEndTime(startTime, endTime, delayRespVOList); + delayDays = calculateDelayDays(startTime, 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; } + /** + * 计算总延期天数(合并区间,避免重叠/不连续) + */ + private static long calculateDelayDays(LocalDate startTime, List delayRecords) { + // 1. 生成延期区间并打上备注 + List intervals = buildIntervals(startTime, delayRecords); + if (CollUtil.isEmpty(intervals)) { + return 0L; + } + + // 2. 合并区间 + List merged = mergeIntervals(intervals); + + // 3. 计算天数 + return calculateTotalDays(merged); + } + + + /** + * 根据延期记录生成扣减区间,同时写入备注 + */ + private static List buildIntervals(LocalDate startTime, List delayRecords) { + List 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 mergeIntervals(List intervals) { + intervals.sort(Comparator.comparing(DateIntervalDTO::getStart)); + + List 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 merged) { + long totalDays = 0L; + for (DateIntervalDTO interval : merged) { + totalDays += ChronoUnit.DAYS.between(interval.getStart(), interval.getEnd()) + 1; + } + return totalDays; + } + /** * 基于时长和单位累加time * @@ -765,44 +872,6 @@ public class PayProductProcessor { } } - /** - * 根据延期列表中最早的产品结束时间和最晚的延期后时间计算新的结束时间 - * - * @param startTime 订购的开始时间 - * @param endTime 订购的结束时间 - * @param delayRecordsList 延期列表 - * @return - */ - private LocalDate calculateDelayEndTime(LocalDate startTime, LocalDate endTime, List 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); - } - /** * 计算产品价格 * diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/dto/DateIntervalDTO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/dto/DateIntervalDTO.java new file mode 100644 index 000000000..ff94bb2f2 --- /dev/null +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/pay/dto/DateIntervalDTO.java @@ -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; +} diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml index 86111a279..0e0d6673e 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/application-dev.yaml @@ -205,6 +205,8 @@ chenfeng: - /admin-api/system/product-details/organ/purchase - /admin-api/system/purchase - /admin-api/system/pay/product + - /admin-api/system/recharge-active/page + - /admin-api/system/pay/recharge ignore-tables: - system_organization - system_tenant_package diff --git a/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml b/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml index db7ab3d46..542a9577c 100644 --- a/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml +++ b/cf-module-system/cf-module-system-biz/src/main/resources/application-local.yaml @@ -221,6 +221,8 @@ chenfeng: - /admin-api/system/product-details/organ/purchase - /admin-api/system/purchase - /admin-api/system/pay/product + - /admin-api/system/recharge-active/page + - /admin-api/system/pay/recharge ignore-tables: - system_organization - system_tenant_package