Browse Source

补录订单,相关流程修复

wanghao 2 tháng trước cách đây
mục cha
commit
1771e8f0d2

+ 10 - 0
commons/src/main/java/com/jzg/commons/entity/dto/AgreementContactAddParam.java

@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @Schema(description = "外部协议表新增dto")
@@ -69,4 +70,13 @@ public class AgreementContactAddParam {
     @Schema(description = "对接管理人id")
     @NotNull(message = "请选择协议管理人")
     private String custodianId;
+
+    @Schema(description = "交强入口手续费比例")
+    private BigDecimal jqEntranceFeeRatio;
+
+    @Schema(description = "商业入口手续费比例")
+    private BigDecimal syEntranceFeeRatio;
+
+    @Schema(description = "驾意入口手续费比例")
+    private BigDecimal jyEntranceFeeRatio;
 }

+ 14 - 0
commons/src/main/java/com/jzg/commons/entity/dto/AgreementContactEditParam.java

@@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotNull;
 import jakarta.validation.constraints.Size;
 import lombok.Data;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @Schema(description = "外部协议表修改dto")
@@ -71,4 +72,17 @@ public class AgreementContactEditParam {
     @Schema(description = "api类型")
     @NotNull(message = "请选择api类型")
     private Integer apiType;
+
+    @Schema(description = "交强入口手续费比例")
+    private BigDecimal jqEntranceFeeRatio;
+
+    @Schema(description = "商业入口手续费比例")
+    private BigDecimal syEntranceFeeRatio;
+
+    @Schema(description = "驾意入口手续费比例")
+    private BigDecimal jyEntranceFeeRatio;
+
+
+
+
 }

+ 1 - 1
commons/src/main/java/com/jzg/commons/entity/dto/PtlAgreementCostExternalAdd.java

@@ -37,7 +37,7 @@ public class PtlAgreementCostExternalAdd {
     @Schema(description = "0-申请审核1-审核通过2-审核未通过")
     private String auditStatus;
 
-    @Schema(description = "是否现询")
+    @Schema(description = "是否现询(0:是,1:否)")
     private String isInquiry;
 
     @Schema(description = "费用描述")

+ 73 - 0
commons/src/main/java/com/jzg/commons/entity/quote/vo/orders/InsFeeCalculateRequest.java

@@ -0,0 +1,73 @@
+package com.jzg.commons.entity.quote.vo.orders;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 费用计算请求参数。
+ * 入口手续费比例从协议(PtlAgreement)自动获取,
+ * 其余费用比例(入口/加投/出口/出口留点)由调用方传入。
+ * @author wh
+ * @date 2026/6/27
+ */
+@Data
+@Schema(description = "费用计算请求")
+public class InsFeeCalculateRequest {
+
+    @Schema(description = "协议ID(用于获取税率因子)", required = true)
+    private String agreementId;
+    @Schema(description = "业务员手机号", required = true)
+    private String mobile;
+
+    // ── 保费 ──
+    @Schema(description = "交强险保费", example = "0")
+    private BigDecimal jqPremium;
+
+    @Schema(description = "商业险保费", example = "0")
+    private BigDecimal syPremium;
+
+    @Schema(description = "驾意险保费", example = "0")
+    private BigDecimal jyPremium;
+
+    // ── 入口比例(百分比,来自 PtlAgreementCostType)──
+    @Schema(description = "交强险入口比例(百分比)", example = "0")
+    private BigDecimal jqInlet;
+
+    @Schema(description = "商业险入口比例(百分比)", example = "0")
+    private BigDecimal syInlet;
+
+    @Schema(description = "驾意险入口比例(百分比)", example = "0")
+    private BigDecimal jyInlet;
+
+    // ── 加投比例(百分比)──
+    @Schema(description = "交强险加投比例(百分比)", example = "0")
+    private BigDecimal jqAddThrow;
+
+    @Schema(description = "商业险加投比例(百分比)", example = "0")
+    private BigDecimal syAddThrow;
+
+    @Schema(description = "驾意险加投比例(百分比)", example = "0")
+    private BigDecimal jyAddThrow;
+
+    // ── 出口比例(百分比)──
+    @Schema(description = "交强险出口比例(百分比)", example = "0")
+    private BigDecimal jqExport;
+
+    @Schema(description = "商业险出口比例(百分比)", example = "0")
+    private BigDecimal syExport;
+
+    @Schema(description = "驾意险出口比例(百分比)", example = "0")
+    private BigDecimal jyExport;
+
+    // ── 出口留点比例(百分比)──
+    @Schema(description = "交强险出口留点比例(百分比)", example = "0")
+    private BigDecimal jqExportKeepPointRatio;
+
+    @Schema(description = "商业险出口留点比例(百分比)", example = "0")
+    private BigDecimal syExportKeepPointRatio;
+
+    @Schema(description = "驾意险出口留点比例(百分比)", example = "0")
+    private BigDecimal jyExportKeepPointRatio;
+}

+ 30 - 0
commons/src/main/java/com/jzg/commons/entity/quote/vo/orders/InsFeeCalculateResponse.java

@@ -0,0 +1,30 @@
+package com.jzg.commons.entity.quote.vo.orders;
+
+import com.jzg.commons.entity.po.InsFeeOrders;
+import com.jzg.commons.entity.vo.PtlAgreementCostRatioVo;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 费用计算响应结果
+ *
+ * @author lipf
+ * @date 2026/6/27
+ */
+@Data
+@NoArgsConstructor
+@Schema(description = "费用计算响应")
+public class InsFeeCalculateResponse {
+
+    @Schema(description = "费用订单明细")
+    private InsFeeOrders feeOrdersVo;
+
+    @Schema(description = "协议费用比例")
+    private PtlAgreementCostRatioVo costRatio;
+
+    public InsFeeCalculateResponse(InsFeeOrders feeOrders, PtlAgreementCostRatioVo costRatio) {
+        this.feeOrdersVo = feeOrders;
+        this.costRatio = costRatio;
+    }
+}

+ 9 - 0
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/controller/InsOrdersController.java

@@ -18,6 +18,8 @@ import com.jzg.commons.entity.po.InsFeeOrders;
 import com.jzg.commons.entity.quote.vo.KindInfoVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.entity.quote.vo.aggregated.QuoteEchoVo;
+import com.jzg.commons.entity.quote.vo.orders.InsFeeCalculateRequest;
+import com.jzg.commons.entity.quote.vo.orders.InsFeeCalculateResponse;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersAdd;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersEnd;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersVo;
@@ -74,6 +76,13 @@ public class InsOrdersController extends BaseController {
         return insOrdersService.saveOrders(ordersAdd);
     }
 
+    @Operation(summary = "计算补录单费用")
+    @PostMapping("cvalculateFees")
+    public HttpResult<InsFeeCalculateResponse> cvalculateFees(@RequestBody InsFeeCalculateRequest request) {
+        return insOrdersService.cvalculateFees(request);
+    }
+
+
     @OperatorTrajectory(moduleName = "车险订单/补录订单修改)",mapperClass = InsOrdersMapper.class,entityClass = InsOrdersAdd.class,isRecorded = "insOrders.isTemporarily")
     @Operation(summary = "录入订单修改")
     @PostMapping("updateOrders")

+ 5 - 0
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/InsOrdersService.java

@@ -10,6 +10,7 @@ import com.jzg.commons.entity.enums.MessageCodeEnum;
 import com.jzg.commons.entity.finance.vo.BatchModifyVo;
 import com.jzg.commons.entity.orders.po.InsOrders;
 import com.jzg.commons.entity.orders.po.InsOrdersCarInfo;
+
 import com.jzg.commons.entity.orders.po.InsOrdersCosts;
 import com.jzg.commons.entity.orders.vo.HistoryQuoteQueryVo;
 import com.jzg.commons.entity.orders.vo.HistoryQuoteResultVo;
@@ -25,6 +26,8 @@ import com.jzg.commons.entity.vo.OrdersListVo;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersAdd;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersEnd;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersVo;
+import com.jzg.commons.entity.quote.vo.orders.InsFeeCalculateRequest;
+import com.jzg.commons.entity.quote.vo.orders.InsFeeCalculateResponse;
 import com.jzg.quotation.summary.entity.vo.*;
 
 import java.lang.reflect.InvocationTargetException;
@@ -293,4 +296,6 @@ public interface InsOrdersService extends IService<InsOrders> {
      * @date 2026/6/18 9:49
      */
     BigDecimal obtainTaxRate(String orderNo);
+
+    HttpResult<InsFeeCalculateResponse> cvalculateFees(InsFeeCalculateRequest request);
 }

+ 371 - 11
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsOrdersServiceImpl.java

@@ -36,6 +36,8 @@ import com.jzg.commons.entity.quote.vo.aggregated.QuoteEchoVo;
 import com.jzg.commons.entity.quote.vo.aggregated.QuoteResultsVo;
 import com.jzg.commons.entity.quote.vo.aggregated.UnderwritingResultsVo;
 import com.jzg.commons.entity.quote.vo.image.InsuranceImageVo;
+import com.jzg.commons.entity.quote.vo.orders.InsFeeCalculateRequest;
+import com.jzg.commons.entity.quote.vo.orders.InsFeeCalculateResponse;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersAdd;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersEnd;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersVo;
@@ -50,6 +52,7 @@ import com.jzg.quotation.commons.client.PartnerDistributionClient;
 import com.jzg.quotation.commons.client.PlatformClient;
 import com.jzg.quotation.commons.utils.BeanCopyUtil;
 import com.jzg.quotation.commons.utils.SnowFlakeUtil;
+import com.jzg.quotation.summary.aop.aspect.QuoteVerificationAspect;
 import com.jzg.quotation.summary.consumer.template.OrderMessageTemplate;
 import com.jzg.quotation.summary.entity.bo.DataValueBo;
 import com.jzg.quotation.summary.entity.vo.*;
@@ -73,6 +76,7 @@ import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.net.URL;
 import java.time.Instant;
 import java.time.LocalDate;
@@ -529,17 +533,21 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
                 .eq(InsFeeOrders::getOrderNo, insOrdersVo.getInsOrders().getId()));
         // 比例信息 分销员信息
         if (insFeeOrders != null) {
-            HttpResult<SysUserJzgInfo> firstDistributor = orgClient.getUserInfoById(insFeeOrders.getFirstDistributorId());
-            HttpResult<SysUserJzgInfo> secondDistributor = orgClient.getUserInfoById(insFeeOrders.getSecondDistributorId());
-            HttpResult<SysUserJzgInfo> thirdDistributor = orgClient.getUserInfoById(insFeeOrders.getThirdDistributorId());
-            if (firstDistributor.getData() != null) {
-                insFeeOrders.setFirstDistributorName(firstDistributor.getData().getName());
-            }
-            if (secondDistributor.getData() != null) {
-                insFeeOrders.setSecondDistributorName(secondDistributor.getData().getName());
-            }
-            if (thirdDistributor.getData() != null) {
-                insFeeOrders.setThirdDistributorName(thirdDistributor.getData().getName());
+            if(insFeeOrders.getFirstDistributorId() != null && insFeeOrders.getSecondDistributorId() != null &&
+                    insFeeOrders.getThirdDistributorId() != null){
+                HttpResult<SysUserJzgInfo> firstDistributor = orgClient.getUserInfoById(insFeeOrders.getFirstDistributorId());
+                HttpResult<SysUserJzgInfo> secondDistributor = orgClient.getUserInfoById(insFeeOrders.getSecondDistributorId());
+                HttpResult<SysUserJzgInfo> thirdDistributor = orgClient.getUserInfoById(insFeeOrders.getThirdDistributorId());
+                if (firstDistributor.getData() != null) {
+                    insFeeOrders.setFirstDistributorName(firstDistributor.getData().getName());
+                }
+                if (secondDistributor.getData() != null) {
+                    insFeeOrders.setSecondDistributorName(secondDistributor.getData().getName());
+                }
+                if (thirdDistributor.getData() != null) {
+                    insFeeOrders.setThirdDistributorName(thirdDistributor.getData().getName());
+                }
+
             }
             insOrdersVo.setCostRatio(new PtlAgreementCostRatioVo(insFeeOrders));
             insOrdersVo.setFeeOrdersVo(new InsFeeOrdersVo(insFeeOrders));
@@ -1698,6 +1706,358 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
         return taxFactor;
     }
 
+    /**
+     * 计算保单费用(应收/应付/手续费/跟单费/留点费/出口费等)。
+     * 本方法为纯计算接口,不需要已有订单,直接根据保费和费用比例计算费用明细。
+     * 入口手续费比例从协议(PtlAgreement)自动获取,其余费用比例由调用方传入。
+     *
+     * @param request 保费 + 各险种费用比例(入口/加投/出口/出口留点)
+     * @return InsFeeCalculateResponse(费用明细 + 协议费用比例VO)
+     */
+    @Override
+    public HttpResult<InsFeeCalculateResponse> cvalculateFees(InsFeeCalculateRequest request) {
+        String agreementId = request.getAgreementId();
+        // 保费 & 所有比例字段 null → 0
+        BigDecimal jqPremium  = nvl(request.getJqPremium());
+        BigDecimal syPremium  = nvl(request.getSyPremium());
+        BigDecimal jyPremium  = nvl(request.getJyPremium());
+        BigDecimal sumPremium = jqPremium.add(syPremium).add(jyPremium);
+        BigDecimal jqInlet    = nvl(request.getJqInlet());
+        BigDecimal syInlet    = nvl(request.getSyInlet());
+        BigDecimal jyInlet    = nvl(request.getJyInlet());
+        BigDecimal jqAddThrow = nvl(request.getJqAddThrow());
+        BigDecimal syAddThrow = nvl(request.getSyAddThrow());
+        BigDecimal jyAddThrow = nvl(request.getJyAddThrow());
+        BigDecimal jqExport   = nvl(request.getJqExport());
+        BigDecimal syExport   = nvl(request.getSyExport());
+        BigDecimal jyExport   = nvl(request.getJyExport());
+        BigDecimal jqEKP      = nvl(request.getJqExportKeepPointRatio());
+        BigDecimal syEKP      = nvl(request.getSyExportKeepPointRatio());
+        BigDecimal jyEKP      = nvl(request.getJyExportKeepPointRatio());
+
+        // 1. 查询协议(同时获取入口手续费比例 + 税率因子,一次查询)
+        PtlAgreement ptlAgreement = orgClient.getByAgreementId(agreementId);
+        if (ptlAgreement == null) {
+            throw new ServiceException(String.format("协议不存在,协议编号=%s", agreementId));
+        }
+        // 入口手续费比例(来自协议 PtlAgreement)
+        BigDecimal jqRatio = ptlAgreement.getJqEntranceFeeRatio();
+        BigDecimal syRatio = ptlAgreement.getSyEntranceFeeRatio();
+        BigDecimal jyRatio = ptlAgreement.getJyEntranceFeeRatio();
+        // 税率因子(协议含税→1,不含税→1.06)
+        BigDecimal taxFactor = resolveTaxFactor(ptlAgreement);
+
+        // 2. 从请求构建各险种费用类型配置(入口/加投/出口/出口留点)
+        PtlAgreementCostTypeVo jqCostType = buildCostType(jqInlet, jqAddThrow, jqExport, jqEKP);
+        PtlAgreementCostTypeVo syCostType = buildCostType(syInlet, syAddThrow, syExport, syEKP);
+        PtlAgreementCostTypeVo jyCostType = buildCostType(jyInlet, jyAddThrow, jyExport, jyEKP);
+
+        // 3. 入口手续费比例上限控制
+        jqRatio = capRatio(jqRatio, jqCostType);
+        syRatio = capRatio(syRatio, syCostType);
+        jyRatio = capRatio(jyRatio, jyCostType);
+
+        // 4. 跟单费比例 = 入口比例 - 手续费比例
+        BigDecimal jqFollow = calcFollowRatio(jqCostType, jqRatio);
+        BigDecimal syFollow = calcFollowRatio(syCostType, syRatio);
+        BigDecimal jyFollow = calcFollowRatio(jyCostType, jyRatio);
+
+        // 5. 组装 InsFeeOrders
+        InsFeeOrders feeOrders = new InsFeeOrders();
+        feeOrders.setAgreementId(agreementId);
+        feeOrders.setJqPremium(jqPremium);
+        feeOrders.setSyPremium(syPremium);
+        feeOrders.setJyPremium(jyPremium);
+        feeOrders.setTotalPremium(sumPremium);
+        feeOrders.setJqFollowRatio(jqFollow);
+        feeOrders.setSyFollowRatio(syFollow);
+        feeOrders.setJyFollowRatio(jyFollow);
+
+        // 6. 各险种费用计算(应收/应付/手续费/跟单费/加投费/留点)
+        calcInsuranceType(feeOrders, jqPremium, jqCostType, jqRatio, jqFollow, taxFactor, "jq");
+        calcInsuranceType(feeOrders, syPremium, syCostType, syRatio, syFollow, taxFactor, "sy");
+        calcInsuranceType(feeOrders, jyPremium, jyCostType, jyRatio, jyFollow, taxFactor, "jy");
+
+        // 7. 保存各比例(小数)到 feeOrders
+        // 保存各比例到 feeOrders
+        feeOrders.setJqCommissionRatio(div100(ptlAgreement.getJqEntranceFeeRatio()));
+        feeOrders.setSyCommissionRatio(div100(ptlAgreement.getSyEntranceFeeRatio()));
+        feeOrders.setJyCommissionRatio(div100(ptlAgreement.getJyEntranceFeeRatio()));
+        feeOrders.setJqInletRatio(div100(jqInlet));
+        feeOrders.setSyInletRatio(div100(syInlet));
+        feeOrders.setJyInletRatio(div100(jyInlet));
+        feeOrders.setJqFollowRatio(nvl(feeOrders.getJqFollowRatio()));
+        feeOrders.setSyFollowRatio(nvl(feeOrders.getSyFollowRatio()));
+        feeOrders.setJyFollowRatio(nvl(feeOrders.getJyFollowRatio()));
+        feeOrders.setJqAdditionalRatio(div100(jqAddThrow));
+        feeOrders.setSyAdditionalRatio(div100(syAddThrow));
+        feeOrders.setJyAdditionalRatio(div100(jyAddThrow));
+        feeOrders.setJqKeepPointRatio(nvl(feeOrders.getJqKeepPointRatio()));
+        feeOrders.setSyKeepPointRatio(nvl(feeOrders.getSyKeepPointRatio()));
+        feeOrders.setJyKeepPointRatio(nvl(feeOrders.getJyKeepPointRatio()));
+        feeOrders.setJqExportRatio(div100(jqExport));
+        feeOrders.setSyExportRatio(div100(syExport));
+        feeOrders.setJyExportRatio(div100(jyExport));
+        feeOrders.setJqExportKeepPointRatio(div100(jqEKP));
+        feeOrders.setSyExportKeepPointRatio(div100(syEKP));
+        feeOrders.setJyExportKeepPointRatio(div100(jyEKP));
+
+        // 计算分销费用(返回分销精度差值)
+        BigDecimal distributionDiff = calcDistributionFee(sumPremium, feeOrders,request.getMobile());
+
+        // 8. 总应收/总应付
+        feeOrders.setTotalReceivablePremium(feeOrders.getJqReceivablePremium()
+                .add(feeOrders.getSyReceivablePremium()).add(feeOrders.getJyReceivablePremium())
+                .setScale(5, RoundingMode.HALF_EVEN));
+        feeOrders.setTotalPayablePremium(feeOrders.getJqPayablePremium()
+                .add(feeOrders.getSyPayablePremium()).add(feeOrders.getJyPayablePremium())
+                .setScale(2, RoundingMode.DOWN));
+
+        // 9. 出口费用(setScale(2,DOWN) 截断)& 出口留点费用
+        BigDecimal jqEOrigin = feeOrders.getJqExportRatio().multiply(jqPremium);
+        BigDecimal jqE = jqEOrigin.setScale(2, RoundingMode.DOWN);
+        feeOrders.setJqExportPremium(jqE);
+        BigDecimal syEOrigin = feeOrders.getSyExportRatio().multiply(syPremium);
+        BigDecimal syE = syEOrigin.setScale(2, RoundingMode.DOWN);
+        feeOrders.setSyExportPremium(syE);
+        BigDecimal jyEOrigin = feeOrders.getJyExportRatio().multiply(jyPremium);
+        BigDecimal jyE = jyEOrigin.setScale(2, RoundingMode.DOWN);
+        feeOrders.setJyExportPremium(jyE);
+
+        BigDecimal jqK = feeOrders.getJqExportKeepPointRatio().multiply(jqPremium);
+        feeOrders.setJqExportKeepPointPremium(jqK);
+        BigDecimal syK = feeOrders.getSyExportKeepPointRatio().multiply(syPremium);
+        feeOrders.setSyExportKeepPointPremium(syK);
+        BigDecimal jyK = feeOrders.getJyExportKeepPointRatio().multiply(jyPremium);
+        feeOrders.setJyExportKeepPointPremium(jyK);
+
+        // 截断差值 = 出口精度损失 + 分销差值
+        List<BigDecimal> diffs = new ArrayList<>();
+        diffs.add(BigDecimalUtil.subtract(jqEOrigin, jqE));
+        diffs.add(BigDecimalUtil.subtract(syEOrigin, syE));
+        diffs.add(BigDecimalUtil.subtract(jyEOrigin, jyE));
+        diffs.add(distributionDiff);
+        feeOrders.setTotalExportDiffAmount(BigDecimalUtil.add(diffs));
+
+        feeOrders.setAvailableAmount(jqE.add(syE).add(jyE).subtract(jqK).subtract(syK).subtract(jyK)
+                .setScale(2, RoundingMode.HALF_UP));
+        feeOrders.setTotalExportPremium(jqE.add(syE).add(jyE));
+        feeOrders.setTotalExportKeepPointPremium(jqK.add(syK).add(jyK));
+
+        // 10. 构建比例VO
+        PtlAgreementCostRatioVo costRatio = new PtlAgreementCostRatioVo(feeOrders);
+
+        return HttpResult.ok(new InsFeeCalculateResponse(feeOrders, costRatio));
+    }
+
+    /** null-safe → BigDecimal.ZERO */
+    private static BigDecimal nvl(BigDecimal v) { return v != null ? v : BigDecimal.ZERO; }
+
+    /** 从请求参数构建费用类型配置VO */
+    private PtlAgreementCostTypeVo buildCostType(BigDecimal inlet, BigDecimal addThrow,
+                                                  BigDecimal export, BigDecimal exportKeepPointRatio) {
+        PtlAgreementCostTypeVo vo = new PtlAgreementCostTypeVo();
+        vo.setInlet(inlet);
+        vo.setAddThrow(addThrow);
+        vo.setExport(export);
+        vo.setExportKeepPointRatio(exportKeepPointRatio);
+        return vo;
+    }
+
+    /** 从已查询的协议中解析税率因子:withTax=0(不含税)→ 1.06,withTax=1(含税)→ 1 */
+    private BigDecimal resolveTaxFactor(PtlAgreement ptlAgreement) {
+        Integer withTax = ptlAgreement.getWithTax();
+        if (withTax == null) {
+            throw new ServiceException(String.format("协议未配置是否含税,协议编号=%s", ptlAgreement.getId()));
+        }
+        return withTax == 0 ? new BigDecimal("1.06") : BigDecimal.ONE;
+    }
+
+    // ===== 分销费用计算(复用 calcCosts 中的 calculateDistributionFee 逻辑)=====
+
+    /**
+     * 计算分销费用(与原 calcCosts 中 calculateDistributionFee 逻辑一致)。
+     * 区别:userId 取自当前登录用户 baseController.getUserId(),而非订单。
+     *
+     * @param totalPremium 总保费
+     * @param feeOrders    费用订单对象(用于设置分销字段)
+     * @param mobile    业务员手机号
+     * @return 分销精度差值(scale=5 - scale=2)
+     */
+    private BigDecimal calcDistributionFee(BigDecimal totalPremium, InsFeeOrders feeOrders,String mobile) {
+
+        HttpResult<SysUserJzgInfo> userResult = orgClient.getUserInfoByMobile(mobile);
+        if (userResult == null || userResult.getData() == null) {
+            return BigDecimal.ZERO;
+        }
+        String userId = userResult.getData().getUserId();
+
+        // 获取租户分销设置
+        HttpResult<SysDistributionSetting> settingResult = orgClient.getSettingBySystemCode();
+        if (settingResult == null || settingResult.getData() == null) {
+            return BigDecimal.ZERO;
+        }
+        SysDistributionSetting sysDistributionSetting = settingResult.getData();
+        // 判断业务员所属机构是否开启分销
+        String deptId = userResult.getData().getDeptId();
+        HttpResult<SysDeptVo> deptResult = orgClient.deptGet(deptId);
+        if (deptResult == null || deptResult.getData() == null
+                || !"1".equals(deptResult.getData().getIsDistribution())) {
+            return BigDecimal.ZERO;
+        }
+        String level = sysDistributionSetting.getDistributionMode();
+        BigDecimal diffAmount = BigDecimal.ZERO;
+        switch (level) {
+            case "1":
+                Map<String, Object> lv1 = calcDistLevel1(feeOrders, totalPremium, userId, sysDistributionSetting);
+                if (lv1 != null) diffAmount = new BigDecimal(lv1.get("diffAmount").toString());
+                break;
+            case "2":
+                Map<String, Object> first = calcDistLevel1(feeOrders, totalPremium, userId, sysDistributionSetting);
+                if (first != null) {
+                    Map<String, Object> second = calcDistLevel2(feeOrders, totalPremium, first.get("id").toString(), sysDistributionSetting);
+                    if (second != null) diffAmount = new BigDecimal(second.get("diffAmount").toString());
+                    diffAmount = diffAmount.add(new BigDecimal(first.get("diffAmount").toString()));
+                }
+                break;
+            case "3":
+                Map<String, Object> l1 = calcDistLevel1(feeOrders, totalPremium, userId, sysDistributionSetting);
+                if (l1 != null) {
+                    Map<String, Object> l2 = calcDistLevel2(feeOrders, totalPremium, l1.get("id").toString(), sysDistributionSetting);
+                    if (l2 != null) {
+                        BigDecimal l3Diff = calcDistLevel3(feeOrders, totalPremium, l2.get("id").toString(), sysDistributionSetting);
+                        diffAmount = diffAmount.add(l3Diff);
+                        diffAmount = diffAmount.add(new BigDecimal(l2.get("diffAmount").toString()));
+                    }
+                    diffAmount = diffAmount.add(new BigDecimal(l1.get("diffAmount").toString()));
+                }
+                break;
+        }
+        return diffAmount;
+    }
+
+    private Map<String, Object> calcDistLevel1(InsFeeOrders o, BigDecimal totalPremium, String userId, SysDistributionSetting setting) {
+        HttpResult<EsmUserReferrer> ref = orgClient.getReferrer(userId);
+        if (ref == null || ref.getData() == null) return null;
+        EsmUserReferrer referrer = ref.getData();
+        HttpResult<SysUserJzgInfo> info = orgClient.getUserInfo(referrer.getReferrerId());
+        if (info == null || info.getData() == null || !"1".equals(info.getData().getIsDistribution())) {
+            o.setFirstDistributionFee(BigDecimal.ZERO);
+            o.setFirstDistributionRatio(BigDecimal.ZERO);
+            return null;
+        }
+        o.setFirstDistributorId(referrer.getReferrerId());
+        BigDecimal fee5 = totalPremium.multiply(setting.getCommissionRatioLevel1()).setScale(5, RoundingMode.DOWN);
+        o.setFirstDistributionFee(totalPremium.multiply(setting.getCommissionRatioLevel1()).setScale(2, RoundingMode.DOWN));
+        o.setFirstDistributionRatio(setting.getCommissionRatioLevel1());
+        Map<String, Object> map = new HashMap<>();
+        map.put("id", referrer.getReferrerId());
+        map.put("diffAmount", fee5.subtract(o.getFirstDistributionFee()));
+        return map;
+    }
+
+    private Map<String, Object> calcDistLevel2(InsFeeOrders o, BigDecimal totalPremium, String upperId, SysDistributionSetting setting) {
+        HttpResult<EsmUserReferrer> ref = orgClient.getReferrer(upperId);
+        if (ref == null || ref.getData() == null) return null;
+        EsmUserReferrer referrer = ref.getData();
+        HttpResult<SysUserJzgInfo> info = orgClient.getUserInfo(referrer.getReferrerId());
+        if (info == null || info.getData() == null || !"1".equals(info.getData().getIsDistribution())) {
+            o.setSecondDistributionFee(BigDecimal.ZERO);
+            o.setSecondDistributionRatio(BigDecimal.ZERO);
+            return null;
+        }
+        o.setSecondDistributorId(referrer.getReferrerId());
+        BigDecimal fee5 = totalPremium.multiply(setting.getCommissionRatioLevel2()).setScale(5, RoundingMode.DOWN);
+        o.setSecondDistributionFee(totalPremium.multiply(setting.getCommissionRatioLevel2()).setScale(2, RoundingMode.DOWN));
+        o.setSecondDistributionRatio(setting.getCommissionRatioLevel2());
+        Map<String, Object> map = new HashMap<>();
+        map.put("id", referrer.getReferrerId());
+        map.put("diffAmount", fee5.subtract(o.getSecondDistributionFee()));
+        return map;
+    }
+
+    private BigDecimal calcDistLevel3(InsFeeOrders o, BigDecimal totalPremium, String upperId, SysDistributionSetting setting) {
+        HttpResult<EsmUserReferrer> ref = orgClient.getReferrer(upperId);
+        if (ref == null || ref.getData() == null) return BigDecimal.ZERO;
+        EsmUserReferrer referrer = ref.getData();
+        HttpResult<SysUserJzgInfo> info = orgClient.getUserInfo(referrer.getReferrerId());
+        if (info == null || info.getData() == null || !"1".equals(info.getData().getIsDistribution())) {
+            o.setThirdDistributionFee(BigDecimal.ZERO);
+            o.setThirdDistributionRatio(BigDecimal.ZERO);
+            return BigDecimal.ZERO;
+        }
+        o.setThirdDistributorId(referrer.getReferrerId());
+        BigDecimal fee5 = totalPremium.multiply(setting.getCommissionRatioLevel3()).setScale(5, RoundingMode.DOWN);
+        o.setThirdDistributionFee(totalPremium.multiply(setting.getCommissionRatioLevel3()).setScale(2, RoundingMode.DOWN));
+        o.setThirdDistributionRatio(setting.getCommissionRatioLevel3());
+        return fee5.subtract(o.getThirdDistributionFee());
+    }
+
+    // ===== 费用计算辅助方法 =====
+
+    /** 百分比转小数 */
+    private static BigDecimal div100(BigDecimal pct) {
+        return pct != null ? pct.divide(BigDecimal.valueOf(100), 10, RoundingMode.HALF_EVEN) : BigDecimal.ZERO;
+    }
+
+    /** 入口手续费比例上限控制:入口手续费比例 > 入口比例时强制取入口比例 */
+    private static BigDecimal capRatio(BigDecimal ratio, PtlAgreementCostTypeVo costType) {
+        if (costType == null || costType.getInlet() == null) return ratio != null ? ratio : BigDecimal.ZERO;
+        BigDecimal inlet = BigDecimalUtil.divide(costType.getInlet(), new BigDecimal(100)); // scale=5, HALF_EVEN
+        return BigDecimalUtil.subtract(ratio, inlet).compareTo(BigDecimal.ZERO) > 0 ? inlet : ratio;
+    }
+
+    /** 跟单费比例 = 入口比例 - 手续费比例 */
+    private static BigDecimal calcFollowRatio(PtlAgreementCostTypeVo costType, BigDecimal entranceFeeRatio) {
+        if (costType == null || costType.getInlet() == null || entranceFeeRatio == null) return BigDecimal.ZERO;
+        return div100(costType.getInlet()).subtract(entranceFeeRatio).setScale(5, RoundingMode.HALF_UP);
+    }
+
+    /** 单个险种费用计算 */
+    private static void calcInsuranceType(InsFeeOrders o, BigDecimal premium, PtlAgreementCostTypeVo ct,
+                                          BigDecimal commissionRatio, BigDecimal followRatio, BigDecimal taxFactor,
+                                          String type) {
+        if (ct == null || premium == null) {
+            if ("jq".equals(type)) { o.setJqReceivablePremium(BigDecimal.ZERO); o.setJqPayablePremium(BigDecimal.ZERO); }
+            else if ("sy".equals(type)) { o.setSyReceivablePremium(BigDecimal.ZERO); o.setSyPayablePremium(BigDecimal.ZERO); }
+            else { o.setJyReceivablePremium(BigDecimal.ZERO); o.setJyPayablePremium(BigDecimal.ZERO); }
+            return;
+        }
+        BigDecimal inlet   = div100(ct.getInlet());
+        BigDecimal addThrow = div100(ct.getAddThrow());
+        BigDecimal export  = div100(ct.getExport());
+
+        BigDecimal taxable = premium.divide(taxFactor, 5, RoundingMode.HALF_EVEN);
+        BigDecimal payable = premium.multiply(export.setScale(5, RoundingMode.DOWN)).setScale(2, RoundingMode.DOWN);
+        BigDecimal commission = taxable.multiply(commissionRatio).setScale(5, RoundingMode.HALF_EVEN);
+        BigDecimal addAmt = premium.multiply(addThrow).setScale(5, RoundingMode.HALF_EVEN);
+        BigDecimal receivable = taxable.multiply(commissionRatio.add(followRatio)).setScale(5, RoundingMode.HALF_EVEN);
+        receivable = receivable.setScale(5, RoundingMode.HALF_EVEN); // 与旧代码保持一致:二次setScale
+        BigDecimal follow = receivable.subtract(commission);
+        BigDecimal keepRate = BigDecimalUtil.subtract(BigDecimalUtil.add(BigDecimalUtil.divide(inlet, taxFactor), addThrow), export)
+                .setScale(5, RoundingMode.HALF_EVEN);
+        BigDecimal keepAmt = BigDecimalUtil.subtract(BigDecimalUtil.add(receivable, addAmt), payable)
+                .setScale(5, RoundingMode.HALF_EVEN);
+
+        switch (type) {
+            case "jq":
+                o.setJqReceivablePremium(receivable); o.setJqPayablePremium(payable);
+                o.setJqCommissionPremium(commission); o.setJqFollowPremium(follow);
+                o.setJqAdditionalPremium(addAmt); o.setJqKeepPointRatio(keepRate); o.setJqKeepPointPremium(keepAmt);
+                break;
+            case "sy":
+                o.setSyReceivablePremium(receivable); o.setSyPayablePremium(payable);
+                o.setSyCommissionPremium(commission); o.setSyFollowPremium(follow);
+                o.setSyAdditionalPremium(addAmt); o.setSyKeepPointRatio(keepRate); o.setSyKeepPointPremium(keepAmt);
+                break;
+            case "jy":
+                o.setJyReceivablePremium(receivable); o.setJyPayablePremium(payable);
+                o.setJyCommissionPremium(commission); o.setJyFollowPremium(follow);
+                o.setJyAdditionalPremium(addAmt); o.setJyKeepPointRatio(keepRate); o.setJyKeepPointPremium(keepAmt);
+                break;
+        }
+    }
+
     /**
      * 车险报价批量修改联系人等信息
      *

+ 5 - 1
tenant/insurance/quotation-summary/src/main/resources/mapper/InsOrdersMapper.xml

@@ -25,6 +25,8 @@
         <result property="signingTime" column="signing_time" jdbcType="VARCHAR"/>
         <result property="isTemporarily" column="is_temporarily" jdbcType="VARCHAR"/>
         <result property="realQuoteUserId" column="real_quote_user_id" jdbcType="VARCHAR"/>
+        <result property="dockingPersonPhone" column="docking_person_phone" jdbcType="VARCHAR"/>
+        <result property="dockingPerson" column="docking_person" jdbcType="VARCHAR"/>
     </resultMap>
     <!--    订单 车辆信息-->
     <resultMap id="carInfoMap" type="com.jzg.commons.entity.quote.vo.CarInfoVo">
@@ -647,7 +649,9 @@
         pay_link,
         ins_order_no,
         signing_time,
-        is_temporarily
+        is_temporarily,
+        docking_person,
+        docking_person_phone
         from ins_orders
         <where>
             and is_delete = 0

+ 34 - 14
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlAgreementCostExternalServiceImpl.java

@@ -49,10 +49,14 @@ public class PtlAgreementCostExternalServiceImpl extends ServiceImpl<PtlAgreemen
     @Transactional
     public HttpResult save(CostExternalAdd costExternalAdd) {
         try {
-            if (verifyAgreementCostExternal(costExternalAdd.getContactPerson(), costExternalAdd.getContactMobile(),null)){
-                throw new SystemException("对接人姓名和对接人手机号已存在");
-            }
             costExternalAdd.getPtlAgreementCostExternalAddList().forEach(ptlAgreementCostExternalAdd -> {
+                // 非现询不限制一条
+                if(!"1".equals(ptlAgreementCostExternalAdd.getIsInquiry())){
+                    if (verifyAgreementCostExternal(costExternalAdd.getContactPerson(), costExternalAdd.getContactMobile(),
+                            ptlAgreementCostExternalAdd.getProductId(), null) ) {
+                        throw new SystemException("对接人姓名和对接人手机号在同一险种下已存在现询记录");
+                    }
+                }
                 ptlAgreementCostExternalAdd.setPtlAgreementId(costExternalAdd.getPtlAgreementId());
                 ptlAgreementCostExternalAdd.setContactPerson(costExternalAdd.getContactPerson());
                 ptlAgreementCostExternalAdd.setContactMobile(costExternalAdd.getContactMobile());
@@ -66,20 +70,30 @@ public class PtlAgreementCostExternalServiceImpl extends ServiceImpl<PtlAgreemen
             return HttpResult.ok(ReturnInformation.SUCCESS_MESSAGE);
         } catch (Exception e) {
             log.error("保存失败: ", e);
-            return HttpResult.error(ReturnInformation.FAILURE_MESSAGE);
+            return HttpResult.error(e.getMessage());
         }
     }
 
 
-    public boolean verifyAgreementCostExternal(String contactPerson, String contactMobile,List<String> ids) {
-        if (baseMapper.selectCount(new LambdaQueryWrapper<>(PtlAgreementCostExternal.class)
+    /**
+     * 校验对接人+手机号+产品ID下是否已存在isInquiry=0的记录(同一产品只能有一个现询)
+     *
+     * @param contactPerson 对接人姓名
+     * @param contactMobile 对接人手机号
+     * @param productId     产品ID(险种)
+     * @param ids           排除的ID列表(更新时排除自身)
+     */
+    public boolean verifyAgreementCostExternal(String contactPerson, String contactMobile, String productId, List<String> ids) {
+        LambdaQueryWrapper<PtlAgreementCostExternal> wrapper = new LambdaQueryWrapper<>(PtlAgreementCostExternal.class)
                 .eq(PtlAgreementCostExternal::getContactPerson, contactPerson)
                 .eq(PtlAgreementCostExternal::getContactMobile, contactMobile)
-                .eq(PtlAgreementCostExternal::getIsDelete, Constants.NOT_DELETED)
-                .notIn(CollectionUtils.isNotEmpty(ids),PtlAgreementCostExternal::getId, ids) ) > 0 ){
-            return true;
+                .eq(PtlAgreementCostExternal::getProductId, productId)
+                .eq(PtlAgreementCostExternal::getIsInquiry, 0)
+                .eq(PtlAgreementCostExternal::getIsDelete, Constants.NOT_DELETED);
+        if (CollectionUtils.isNotEmpty(ids)) {
+            wrapper.notIn(PtlAgreementCostExternal::getId, ids);
         }
-        return false;
+        return baseMapper.selectCount(wrapper) > 0;
     }
 
 
@@ -174,11 +188,17 @@ public class PtlAgreementCostExternalServiceImpl extends ServiceImpl<PtlAgreemen
     @Override
     public HttpResult updateList(CostExternalEnd costExternalEnd) {
         try {
-            List<String> collect = costExternalEnd.getPtlAgreementCostExternalAddList().stream().map(PtlAgreementCostExternalEnd::getId).collect(Collectors.toList());
-            if (verifyAgreementCostExternal(costExternalEnd.getContactPerson(), costExternalEnd.getContactMobile(),collect)){
-                throw new SystemException("对接人姓名和对接人手机号已存在");
-            }
             costExternalEnd.getPtlAgreementCostExternalAddList().forEach(agreementCostExternalEnd -> {
+                List<String> excludeIds = agreementCostExternalEnd.getId() != null
+                        ? Collections.singletonList(agreementCostExternalEnd.getId()) : null;
+                // 非现询不限制条数
+                if(!"1".equals(agreementCostExternalEnd.getIsInquiry())){
+                    if (verifyAgreementCostExternal(costExternalEnd.getContactPerson(), costExternalEnd.getContactMobile(),
+                            agreementCostExternalEnd.getProductId(), excludeIds)) {
+                        throw new SystemException("对接人姓名和对接人手机号在同一险种下已存在现询记录");
+                    }
+                }
+
                 agreementCostExternalEnd.setPtlAgreementId(costExternalEnd.getPtlAgreementId());
                 agreementCostExternalEnd.setContactPerson(costExternalEnd.getContactPerson());
                 agreementCostExternalEnd.setContactMobile(costExternalEnd.getContactMobile());