From fa24cdf1eed8399337f03e1521da7c23a5963ffd Mon Sep 17 00:00:00 2001 From: gaoqr <13665037151@163.com> Date: Wed, 17 Sep 2025 17:09:52 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E5=88=9B=E5=BB=BA=E7=BB=84=E7=BB=87?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=B4=AD=E4=B9=B0=E8=AE=B0=E5=BD=95=E6=A0=87?= =?UTF-8?q?=E8=AE=B0=E4=B8=BA=E9=A6=96=E6=AC=A1=E8=B4=AD=E4=B9=B0=EF=BC=9B?= =?UTF-8?q?2=E3=80=81=E6=96=B0=E5=A2=9E=E8=BD=AF=E4=BB=B6=E8=B4=AD?= =?UTF-8?q?=E4=B9=B0=E5=B1=95=E7=A4=BA=E5=AE=9A=E4=BB=B7=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=EF=BC=9B3=E3=80=81=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E8=BD=AF=E4=BB=B6=E8=B4=AD=E4=B9=B0=E4=BB=A5=E9=A6=96=E6=AC=A1?= =?UTF-8?q?=E8=B4=AD=E4=B9=B0=E6=97=B6=E8=AE=A1=E7=AE=97=E7=9A=84=E6=9C=88?= =?UTF-8?q?=E5=9D=87=E5=8D=95=E4=BB=B7=E4=B8=BA=E5=9F=BA=E7=A1=80=E8=AE=A1?= =?UTF-8?q?=E7=AE=97=E6=80=BB=E4=BB=B7=E6=B5=81=E7=A8=8B=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../system/enums/ErrorCodeConstants.java | 3 + .../organamount/vo/OrganAmountRespVO.java | 4 +- .../products/ProductsDetailController.java | 17 ++- .../productDetails/ProductDetailsRespVO.java | 2 +- .../OrganCreateProductPurchaseReqVO.java | 4 + .../admin/organ/vo/organ/OrganSaveReqVO.java | 4 +- .../funds/products/ProductsDetailDO.java | 4 +- .../funds/purchase/PurchaseRecordDO.java | 21 ++- .../funds/products/ProductsDetailMapper.java | 1 + .../products/ProductsDetailServiceImpl.java | 28 +++- .../funds/purchase/PurchaseService.java | 17 +++ .../funds/purchase/PurchaseServiceImpl.java | 143 +++++++++++++++++- .../service/organ/OrganServiceImpl.java | 3 + .../service/pay/PayOrderServiceImpl.java | 5 +- .../service/pay/PayProductProcessor.java | 111 ++++++++++++-- 15 files changed, 337 insertions(+), 30 deletions(-) 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 53d220ee4..1cf41f7f1 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 @@ -443,6 +443,7 @@ public class ErrorCodeConstants { public static final ErrorCode ORG_INVOICE_AGREE_FILE_NULL_ERROR = new ErrorCode(1_002_043_030, "确认开票发票附件不能为空,请上传后再提交"); public static final ErrorCode INVOICE_TITLE_ORG_EXISTS_ERROR = new ErrorCode(1_002_043_031, "组织下已存在发票抬头,请刷新后编辑抬头内容"); public static final ErrorCode INVOICE_TITLE_TAXPAYERID_EXISTS_ERROR = new ErrorCode(1_002_043_032, "纳税人识别号已存在,请检查"); + public static final ErrorCode PRODUCTS_NOT_NOLIST = new ErrorCode(1_002_043_033, "当前产品未上架"); //=========== 支付相关 1-002-048-000 ============ public static final ErrorCode PAY_CHANNEL_NOT_SUPPORT = new ErrorCode(1_002_048_000, "暂不支持的支付渠道,请使用其他支付渠道"); @@ -470,6 +471,8 @@ public class ErrorCodeConstants { public static final ErrorCode PRODUCTS_DURATION_INT_ERR = new ErrorCode(1_002_045_008, "产品定价规则:时长必须为整数且大于 0"); public static final ErrorCode PRODUCTS_OUTPUT_NOT_SUPPORT = new ErrorCode(1_002_045_009, "暂不支持创建购买方式为【产量】的定价规则"); 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 INITIAL_PRODUCTS_NOT_EXIST_ERROR = new ErrorCode(1_002_045_012, "产品首次购买记录获取失败,请检查组织数据"); //=========== 广告相关 1-002-046-000 ============ public static final ErrorCode ADVERTISEMENT_POSITION_NO_EXIST = new ErrorCode(1_002_046_001, "广告所选放置位置不存在"); diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java index 136396944..37dc36aed 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/organamount/vo/OrganAmountRespVO.java @@ -26,13 +26,13 @@ public class OrganAmountRespVO { * 正常时间,不加赠送时间 */ @Schema(description = "产品时长") - private String duration; + private Integer duration; @Schema(description = "产品时长单位") private Integer durationUnit; @Schema(description = "赠送时长") - private String giftDuration; + private Integer giftDuration; @Schema(description = "赠送时长单位") private Integer giftDurationUnit; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsDetailController.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsDetailController.java index 25c0fa871..d0456bd6c 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsDetailController.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/ProductsDetailController.java @@ -8,6 +8,7 @@ import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetai import com.cf.imes.module.system.controller.admin.funds.products.vo.productDetails.ProductDetailsSaveReqVO; import com.cf.imes.module.system.dal.dataobject.funds.products.ProductsDetailDO; import com.cf.imes.module.system.service.funds.products.ProductsDetailService; +import com.cf.imes.module.system.service.funds.purchase.PurchaseService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -17,6 +18,8 @@ import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import java.util.List; + import static com.cf.imes.framework.common.pojo.CommonResult.success; @Tag(name = "管理后台 - 产品明细管理") @@ -28,6 +31,9 @@ public class ProductsDetailController { @Resource private ProductsDetailService productDetailsService; + @Resource + private PurchaseService purchaseService; + @PutMapping("") @Operation(summary = "新建产品明细") public CommonResult createProductsDetails(@Valid @RequestBody ProductDetailsSaveReqVO createReqVO) { @@ -51,7 +57,7 @@ public class ProductsDetailController { @GetMapping("/{id}") @Operation(summary = "查询产品明细") - @Parameter(name = "id", description = "编号", required = true, example = "1024") + @Parameter(name = "id", description = "产品明细编号", required = true, example = "1024") public CommonResult getProductsDetail(@PathVariable("id") Long id) { return success(productDetailsService.get(id)); } @@ -63,4 +69,13 @@ public class ProductsDetailController { return success(BeanUtils.toBean(pageResult, ProductDetailsRespVO.class)); } + @GetMapping("/organ/purchase") + @Operation(summary = "查询产品明细分页") + @Parameter(name = "organId", description = "组织编号", required = true, example = "1") + @Parameter(name = "productId", description = "产品编号", required = true, example = "1") + public CommonResult> getOrgProductDetailList(@RequestParam("organId") Long organId, @RequestParam("productId") Long productId) { + List pageResult = purchaseService.getOrgProductDetailList(organId, productId); + return success(BeanUtils.toBean(pageResult, ProductDetailsRespVO.class)); + } + } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/productDetails/ProductDetailsRespVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/productDetails/ProductDetailsRespVO.java index ea90dba40..8487c7511 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/productDetails/ProductDetailsRespVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/funds/products/vo/productDetails/ProductDetailsRespVO.java @@ -18,7 +18,7 @@ public class ProductDetailsRespVO { private Long id; @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private String productId; + private Long productId; @Schema(description = "购买方式", requiredMode = Schema.RequiredMode.REQUIRED, example = "购买方式:0时长;1产量") @ProductType diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganCreateProductPurchaseReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganCreateProductPurchaseReqVO.java index 870523384..ff757e8e2 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganCreateProductPurchaseReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganCreateProductPurchaseReqVO.java @@ -13,6 +13,10 @@ import java.time.LocalDate; @Schema(description = "管理后台 - 组织创建购买产品 Request VO") @Data public class OrganCreateProductPurchaseReqVO { + @Schema(description = "产品编号",example = "1") + @NotNull(message = "产品编号不能为空") + private Long productId; + @Schema(description = "定价规则编号",example = "1") @NotNull(message = "定价规则编号不能为空") private Long productDetailsId; diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganSaveReqVO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganSaveReqVO.java index d946a921f..aee3834eb 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganSaveReqVO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/controller/admin/organ/vo/organ/OrganSaveReqVO.java @@ -4,6 +4,7 @@ import com.cf.imes.framework.common.validation.Mobile; import com.cf.imes.framework.common.validation.NumberValid; import com.cf.imes.module.system.validation.common.CommonStatus; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.Data; import jakarta.validation.constraints.*; @@ -112,6 +113,7 @@ public class OrganSaveReqVO { private Integer grayStatus; @Schema(description = "产品购买列表") - @Size(max = 10, message = "创建组织时最少可购买1个产品,最多可购买10个产品") + @Size(max = 10, message = "创建组织时最少需要购买1个产品,最多可购买10个产品") + @Valid private List productPurchaseReqVOS; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/products/ProductsDetailDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/products/ProductsDetailDO.java index 6568d421e..962e9140a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/products/ProductsDetailDO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/products/ProductsDetailDO.java @@ -43,7 +43,7 @@ public class ProductsDetailDO extends BaseDO { /** * 正常时间,不加赠送时间 */ - private String duration; + private Integer duration; /** * 产品时长单位 @@ -59,7 +59,7 @@ public class ProductsDetailDO extends BaseDO { /** * 赠送时间 */ - private String giftDuration; + private Integer giftDuration; /** * 赠送时间单位 diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java index 1057df473..0a559151a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/dataobject/funds/purchase/PurchaseRecordDO.java @@ -64,7 +64,7 @@ public class PurchaseRecordDO extends BaseDO { /** * 购买时长 */ - private String purchaseDuration; + private Integer purchaseDuration; /** * 购买时长单位 @@ -74,7 +74,7 @@ public class PurchaseRecordDO extends BaseDO { /** * 赠送时长 */ - private String giftDuration; + private Integer giftDuration; /** * 赠送时间单位 @@ -105,6 +105,16 @@ public class PurchaseRecordDO extends BaseDO { */ private BigDecimal totalAmount; + /** + * 原始价格 + */ + private BigDecimal originalAmount; + + /** + * 月均单价 + */ + private BigDecimal monthAverageAmount; + /** * 现金余额支出 @@ -144,12 +154,13 @@ public class PurchaseRecordDO extends BaseDO { */ private String currencyUnit; - - /** * 是否开票 */ private Integer isInvocing; - + /** + * 是否首次订购 + */ + private Boolean initial; } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/products/ProductsDetailMapper.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/products/ProductsDetailMapper.java index ca0c42019..7d4e1076a 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/products/ProductsDetailMapper.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/dal/mysql/funds/products/ProductsDetailMapper.java @@ -29,6 +29,7 @@ public interface ProductsDetailMapper extends BaseMapperX { .eqIfPresent(ProductsDetailDO::getDiscount, reqVO.getDiscount()) .eqIfPresent(ProductsDetailDO::getCurrentPrice, reqVO.getCurrentPrice()) .eqIfPresent(ProductsDetailDO::getIsDiscounted, reqVO.getIsDiscounted()) + .eqIfPresent(ProductsDetailDO::getDeleted, false) ); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java index ade2378bb..dc2a12229 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/products/ProductsDetailServiceImpl.java @@ -44,7 +44,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService { public Long create(ProductDetailsSaveReqVO createReqVO) { // 校验是否有这个产品 - validateProductExist(createReqVO.getProductId()); + validateProductExistAndNotOnList(createReqVO.getProductId()); if (ProductsPurchaseTypeEnum.DURATION.getStatus().equals(createReqVO.getPurchaseType())) { // 校验时长 @@ -74,7 +74,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService { @Override public void update(ProductDetailsSaveReqVO updateReqVO) { - validateProductExist(updateReqVO.getProductId()); + validateProductExistAndNotOnList(updateReqVO.getProductId()); validateProductDetailsExists(updateReqVO.getId()); @@ -105,7 +105,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService { ProductsDetailDO productsDetailDO = validateProductDetailsExists(id); - validateProductExist(productsDetailDO.getProductId()); + validateProductExistAndNotOnList(productsDetailDO.getProductId()); productDetailsMapper.deleteById(id); @@ -164,7 +164,7 @@ public class ProductsDetailServiceImpl implements ProductsDetailService { /** - * 产品是否存在和时效性校验 + * 产品是否存在 * * @param productId */ @@ -176,6 +176,26 @@ public class ProductsDetailServiceImpl implements ProductsDetailService { AssertUtils.notEmpty(productsDO, PRODUCTS_NO_EXIST); + if (!productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())) { + throw exception(PRODUCTS_NOT_NOLIST); + } + + return productsDO; + } + + /** + * 校验产品存在并且未上架 + * + * @param productId + * @return + */ + private ProductsDO validateProductExistAndNotOnList(Long productId) { + ProductsDO productsDO = productsMapper.selectOne(new LambdaQueryWrapperX() + .eq(ProductsDO::getId, productId) + .eq(ProductsDO::getDeleted, false)); + + AssertUtils.notEmpty(productsDO, PRODUCTS_NO_EXIST); + if (productsDO.getStatus().equals(ProductStatusEnum.PRODUCT_UPDATES.getStatus())) { throw exception(PRODUCTS_DETAIL_NO_UPDATE); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java index 70e291005..aed950873 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseService.java @@ -8,6 +8,7 @@ import com.cf.imes.module.system.controller.admin.funds.purchase.vo.PurchaseReco 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; @@ -99,4 +100,20 @@ public interface PurchaseService { * @return */ PageResult getManageSalesDetailsPage(SalesDetailsPageReqVO pageReqVO); + + /** + * 获取组织初始产品购买记录 + * + * @param organId + * @return + */ + PurchaseRecordDO getOrgInitialPurchaseRecord(Long organId, Long productId); + + /** + * 获取组织续费定价规则列表 + * + * @param organId + * @return + */ + List getOrgProductDetailList(Long organId, Long productId); } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java index ff15c1b82..5e592aed0 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/funds/purchase/PurchaseServiceImpl.java @@ -5,10 +5,14 @@ import cn.hutool.core.util.IdUtil; 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.ServiceException; +import com.cf.imes.framework.common.pojo.PageParam; import com.cf.imes.framework.common.pojo.PageResult; +import com.cf.imes.framework.common.util.Assert.AssertUtils; import com.cf.imes.framework.common.util.date.LocalDateTimeUtils; import com.cf.imes.framework.common.util.object.BeanUtils; 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.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,6 +27,7 @@ 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; +import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; 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.funds.products.ProductsDetailService; @@ -33,12 +38,16 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Set; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.INITIAL_PRODUCTS_NOT_EXIST_ERROR; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT; + /** * @author 软件购买接口 实现类 */ @@ -125,6 +134,53 @@ public class PurchaseServiceImpl implements PurchaseService { ProductsDO productsDO = productsDetailService.validateProductExist(productId); // 3、创建购买记录 + + // 输入的价格 + BigDecimal price = purchaseReqVO.getPrice(); + // 月均单价 + BigDecimal monthAverageAmount; + // 计算后总价 + BigDecimal totalAmount; + /** + * ①:首次订购满1年: + * 总价 = 输入金额 / 12 * (10 * duration) + * 月均单价 = 输入金额 / (10 * duration) + * ②:不满1年按月份计算 + * 总价 = 输入金额 + * 月均单价 = 输入金额 / duration + */ + Integer duration = productsDetailDO.getDuration(); + switch (ProductDurationUnitEnum.fromStatus(productsDetailDO.getDurationUnit())) { + case YEAR -> { + totalAmount = price.divide(new BigDecimal(1.2), 2, RoundingMode.DOWN); + monthAverageAmount = price.divide(new BigDecimal(duration * 12), 2, RoundingMode.DOWN); + } + case MONTH -> { + if (duration >= 12) { + // 转换成年 + 剩余月 + int years = duration / 12; + int remainMonths = duration % 12; + + // 整年部分(走年折扣逻辑) + BigDecimal yearAmount = price + .multiply(new BigDecimal(years)) + .divide(new BigDecimal("1.2"), 2, RoundingMode.DOWN); + + // 剩余月份部分(走月逻辑) + BigDecimal monthAmount = remainMonths == 0 + ? BigDecimal.ZERO + : price.multiply(new BigDecimal(remainMonths)) + .divide(new BigDecimal(12), 2, RoundingMode.DOWN); + + totalAmount = yearAmount.add(monthAmount); + } else { + // 月份 + totalAmount = price; + } + monthAverageAmount = price.divide(new BigDecimal(duration), 2, RoundingMode.DOWN); + } + default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); + } long purchaseId = IdUtil.getSnowflake(mybatisIdProperties.getWorkerId(), mybatisIdProperties.getDatacenterId()).nextId(); PurchaseRecordDO purchaseRecordDO = PurchaseRecordDO.builder() .id(purchaseId) @@ -132,15 +188,18 @@ public class PurchaseServiceImpl implements PurchaseService { .productId(productId) .productName(productsDO.getProductName()) .productDetailsId(productsDetailDO.getId()) - .purchaseDuration(productsDetailDO.getDuration()) + .purchaseDuration(duration) .purchaseDurationUnit(productsDetailDO.getDurationUnit()) .giftDuration(productsDetailDO.getGiftDuration()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(LocalDate.now()) .endTime(purchaseReqVO.getProductExpireDate()) .paymentMethod(PayProductChannelCodeEnum.ORGAN_CREATE.getCode()) - .totalAmount(purchaseReqVO.getPrice()) + .totalAmount(totalAmount) + .monthAverageAmount(monthAverageAmount) + .originalAmount(price) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) + .initial(true) .build(); createRecord(purchaseRecordDO); } @@ -183,4 +242,84 @@ public class PurchaseServiceImpl implements PurchaseService { } return new PageResult<>(resultList, salesDetailPage.getTotal()); } + + @Override + public PurchaseRecordDO getOrgInitialPurchaseRecord(Long organId, Long productId) { + return purchaseRecordMapper.selectOne(new LambdaQueryWrapper() + .eq(PurchaseRecordDO::getOrganId, organId) + .eq(PurchaseRecordDO::getProductId, productId) + .eq(PurchaseRecordDO::getInitial, true)); + } + + @Override + public List getOrgProductDetailList(Long organId, Long productId) { + // 校验产品 + productsDetailService.validateProductExist(productId); + + // 查询产品下的所有定价规则 + ProductDetailsPageReqVO pageReqVO = new ProductDetailsPageReqVO(); + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + pageReqVO.setProductId(productId); + PageResult page = productsDetailService.getPage(pageReqVO); + + // 查询组织下的首次订购产品记录 + PurchaseRecordDO orgInitialPurchaseRecord = getOrgInitialPurchaseRecord(organId, productId); + AssertUtils.notEmpty(orgInitialPurchaseRecord, INITIAL_PRODUCTS_NOT_EXIST_ERROR); + + List resultList = new ArrayList<>(); + for (ProductsDetailDO detailDO : page.getList()) { + ProductsDetailDO newDetailDO = BeanUtils.toBean(detailDO, ProductsDetailDO.class); + + // 月均单价 + BigDecimal monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount(); + // 折扣 + 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; + + // 整年部分:用年逻辑(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); + } + } + default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); + } + newDetailDO.setCurrentPrice(totalAmount); + resultList.add(newDetailDO); + } + + return resultList; + } } diff --git a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java index 273f70ad4..d6ac962c3 100644 --- a/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java +++ b/cf-module-system/cf-module-system-biz/src/main/java/com/cf/imes/module/system/service/organ/OrganServiceImpl.java @@ -74,6 +74,7 @@ import org.springframework.validation.annotation.Validated; import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.util.*; +import java.util.stream.Collectors; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.cf.imes.framework.common.util.json.JsonUtils.parseArray; @@ -252,6 +253,8 @@ public class OrganServiceImpl implements OrganService { // 创建产品购买记录 List productPurchaseReqVOS = createReqVO.getProductPurchaseReqVOS(); AssertUtils.notEmpty(productPurchaseReqVOS, ORGAN_CREATE_PRODUCTLIST_EMPTY_ERROR); + // 根据productId去重,初次订购每个产品只能有一种订购 + productPurchaseReqVOS = productPurchaseReqVOS.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(OrganCreateProductPurchaseReqVO::getProductId))), ArrayList::new)); purchaseService.createOrgInitProductPurchaseRecord(organId, productPurchaseReqVOS); return tenant.getId(); 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 4226e50ab..47888c082 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 @@ -155,8 +155,11 @@ public class PayOrderServiceImpl implements PayOrderService { // 查询组织余额信息 OrganAmountDO organAmount = organAmountService.validOrganAmount(organId); + // 查询组织产品的首次购买记录 + PurchaseRecordDO orgInitialPurchaseRecord = purchaseService.getOrgInitialPurchaseRecord(organId, productsDO.getId()); + // 根据购买类型做价格处理 - PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, organAmount, organId, payConfig, mybatisIdProperties); + PayProductProcessor payProductProcessor = new PayProductProcessor(productsDO, productsDetailDO, orgInitialPurchaseRecord, organAmount, organId, payConfig, mybatisIdProperties); ProductProcessorRespVO productProcessorRespVO = payProductProcessor.processPayment(PayProductChannelCodeEnum.fromType(payProductOrderUnifiedReqVO.getProductChannelCode())); // 记录入库,如果有外部支付发起请求二维码 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 76afefd69..eef4c3f68 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 @@ -3,6 +3,7 @@ package com.cf.imes.module.system.service.pay; import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.IdUtil; +import cn.hutool.core.util.ObjectUtil; import com.cf.imes.framework.common.exception.ServiceException; import com.cf.imes.framework.common.util.date.LocalDateTimeUtils; import com.cf.imes.framework.pay.config.ChenfengPayConfig; @@ -23,15 +24,17 @@ import com.cf.imes.module.system.enums.pay.PayProductChannelCodeEnum; import com.cf.imes.module.system.enums.pay.ProductDurationUnitEnum; import com.cf.imes.module.system.enums.pay.TradeTypeEnum; import com.cf.imes.module.system.framework.snowflake.config.MybatisIdProperties; -import org.apache.commons.lang3.StringUtils; import java.math.BigDecimal; +import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.INITIAL_PRODUCTS_NOT_EXIST_ERROR; import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_AVAILABLE_BALANCE_INSUFFICIENT; import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_BALANCE_INSUFFICIENT; import static com.cf.imes.module.system.enums.ErrorCodeConstants.PAY_PRODUCT_GIFT_BALANCE_INSUFFICIENT; +import static com.cf.imes.module.system.enums.ErrorCodeConstants.PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT; /** * 产品购买类型处理器 @@ -45,6 +48,8 @@ public class PayProductProcessor { private ProductsDetailDO productsDetailDO; + private PurchaseRecordDO orgInitialPurchaseRecord; + private OrganAmountDO organAmountDO; private Long organId; @@ -53,10 +58,14 @@ public class PayProductProcessor { private MybatisIdProperties mybatisIdProperties; - public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, OrganAmountDO organAmountDO, + public PayProductProcessor(ProductsDO productsDO, ProductsDetailDO productsDetailDO, PurchaseRecordDO orgInitialPurchaseRecord, OrganAmountDO organAmountDO, Long organId, ChenfengPayConfig payConfig, MybatisIdProperties mybatisIdProperties) { this.productsDO = productsDO; this.productsDetailDO = productsDetailDO; + if (ObjectUtil.isNull(orgInitialPurchaseRecord)) { + throw new ServiceException(INITIAL_PRODUCTS_NOT_EXIST_ERROR); + } + this.orgInitialPurchaseRecord = orgInitialPurchaseRecord; this.organAmountDO = organAmountDO; this.organId = organId; this.payConfig = payConfig; @@ -70,7 +79,7 @@ public class PayProductProcessor { */ public ProductProcessorRespVO processPayment(PayProductChannelCodeEnum payType) { // 需要支付的价格 - BigDecimal price = productsDetailDO.getCurrentPrice(); + BigDecimal price = computePrice(orgInitialPurchaseRecord, productsDetailDO); // 当前组织的余额 BigDecimal balance = organAmountDO.getAmount(); // 当前组织的赠送金 @@ -129,6 +138,8 @@ public class PayProductProcessor { .startTime(LocalDate.now()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .accountBalanceSpent(price) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .build(); @@ -183,6 +194,8 @@ public class PayProductProcessor { .startTime(LocalDate.now()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .accountBalanceSpent(price) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .build(); @@ -235,6 +248,8 @@ public class PayProductProcessor { .startTime(LocalDate.now()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .alipaySpent(price) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), @@ -268,6 +283,8 @@ public class PayProductProcessor { .startTime(LocalDate.now()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_ONLY.getCode()) .totalAmount(price) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .wechatSpent(price) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), @@ -306,6 +323,8 @@ public class PayProductProcessor { .startTime(LocalDate.now()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) .totalAmount(price) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .isInvocing(InvoiceStatusEnum.INVOICABLE.getCode()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), @@ -378,8 +397,10 @@ public class PayProductProcessor { .giftDuration(productsDetailDO.getGiftDuration()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(LocalDate.now()) - .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) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), @@ -450,8 +471,10 @@ public class PayProductProcessor { .giftDuration(productsDetailDO.getGiftDuration()) .giftDurationUnit(productsDetailDO.getGiftDurationUnit()) .startTime(LocalDate.now()) - .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) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), @@ -527,6 +550,8 @@ public class PayProductProcessor { .startTime(LocalDate.now()) .paymentMethod(PayProductChannelCodeEnum.BALANCE_AND_GIFT.getCode()) .totalAmount(price) + .originalAmount(productsDetailDO.getCurrentPrice()) + .monthAverageAmount(orgInitialPurchaseRecord.getMonthAverageAmount()) .build(); purchaseRecordDO.setEndTime(calculateEndTime(purchaseRecordDO.getStartTime(), purchaseRecordDO.getPurchaseDuration(), purchaseRecordDO.getPurchaseDurationUnit(), @@ -618,17 +643,17 @@ public class PayProductProcessor { * @param giftDurationUnit * @return */ - private static LocalDate calculateEndTime(LocalDate startTime, String duration, Integer durationUnit, - String giftDuration, Integer giftDurationUnit) { + private static LocalDate calculateEndTime(LocalDate startTime, Integer duration, Integer durationUnit, + Integer giftDuration, Integer giftDurationUnit) { // 加正常时间 - if (StringUtils.isNotEmpty(duration) && durationUnit != null) { - startTime = addTime(startTime, Integer.parseInt(duration), durationUnit); + if (ObjectUtil.isNotNull(duration) && durationUnit != null) { + startTime = addTime(startTime, duration, durationUnit); } // 加赠送时间 - if (StringUtils.isNotEmpty(giftDuration) && giftDurationUnit != null) { - startTime = addTime(startTime, Integer.parseInt(giftDuration), giftDurationUnit); + if (ObjectUtil.isNotNull(giftDuration) && giftDurationUnit != null) { + startTime = addTime(startTime, giftDuration, giftDurationUnit); } return startTime; @@ -653,4 +678,68 @@ public class PayProductProcessor { } } + /** + * 计算产品价格 + * + * @param orgInitialPurchaseRecord 首次购买记录 + * @param productsDetailDO 本次购买产品的定价规则 + * @return + */ + private BigDecimal computePrice(PurchaseRecordDO orgInitialPurchaseRecord, ProductsDetailDO productsDetailDO) { + // 月均单价 + BigDecimal monthAverageAmount = orgInitialPurchaseRecord.getMonthAverageAmount(); + + // 定价规则的折扣 + BigDecimal discount = productsDetailDO.getDiscount(); + + BigDecimal totalAmount; + /** + * ①:续费订购满1年: + * 总价 = 月均单价 * 10 * duration * 折扣 /100,保留两位小数 + * ②:不满1年按月份计算 + * 总价 = 月均单价 * 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 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); + } + } + default -> throw new ServiceException(PRODUCTS_DETAIL_DURATION_UNIT_NOT_SUPPORT); + } + + return totalAmount; + } + }