Преглед изворни кода

Merge branch 'master' of http://39.101.143.165:8090/785834757/jzg

lipf пре 1 месец
родитељ
комит
4a870860b1

+ 45 - 0
tenant/insurance/quotation-dadi/src/main/java/com/jzg/quotation/dadi/crawler/build/DaDiCrawlerQuoteResultVoBuild.java

@@ -6,6 +6,7 @@ import com.jzg.commons.constants.dict.InsOrderStatusEnum;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.entity.quote.vo.RiskInfoVo;
 import com.jzg.commons.entity.quote.vo.aggregated.QuoteResultsVo;
+import com.jzg.quotation.commons.constant.enums.quote.RenewalEnum;
 import com.jzg.quotation.commons.utils.DateTimeUtil;
 import com.jzg.quotation.dadi.crawler.entity.response.DaDiQuoteResponse;
 import com.jzg.quotation.dadi.crawler.entity.response.DaDiVehiclePricResponse;
@@ -14,6 +15,7 @@ import org.apache.http.util.TextUtils;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * @author Liub
@@ -65,6 +67,14 @@ public class DaDiCrawlerQuoteResultVoBuild {
 
         // 保司评分数据(用于规则引擎匹配)
         QuoteResultsVo.ScoreVo scoreVo = new QuoteResultsVo.ScoreVo();
+        DaDiQuoteResponse.InsurancePlanDTO insurancePlan = daDiQuoteResponse.getInsurancePlan();
+        // 出险次数、续保标志
+        if (Objects.nonNull(insurancePlan)) {
+            scoreVo.setCiClaims(insurancePlan.getClaimTimes());
+            scoreVo.setBiClaims(insurancePlan.getClaimTimes());
+            scoreVo.setJqRenewal("0".equals(insurancePlan.getTrafficRenewalFlag()) ? String.valueOf(RenewalEnum.Transfer.getCode()) : String.valueOf(RenewalEnum.Renewal.getCode()));
+            scoreVo.setSyRenewal("0".equals(insurancePlan.getBusinessRenewalFlag()) ? String.valueOf(RenewalEnum.Transfer.getCode()) : String.valueOf(RenewalEnum.Renewal.getCode()));
+        }
         JSONArray scoreJson = new JSONArray();
         // 上年保费
         if (daDiQuoteResponse.getLastPremium() != null) {
@@ -108,6 +118,41 @@ public class DaDiCrawlerQuoteResultVoBuild {
                     .fluentPut("scoreKey", "xcarscore")
                     .fluentPut("score", daDiQuoteResponse.getXcarscore()));
         }
+        // 保险计划评分(出险次数、承保年数、续保标志、违章等级等)
+        if (Objects.nonNull(insurancePlan)) {
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "出险次数")
+                    .fluentPut("scoreKey", "claimTimes")
+                    .fluentPut("score", insurancePlan.getClaimTimes()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "承保年数")
+                    .fluentPut("scoreKey", "insureYears")
+                    .fluentPut("score", insurancePlan.getInsureYears()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "交强续保标志")
+                    .fluentPut("scoreKey", "trafficRenewalFlag")
+                    .fluentPut("score", insurancePlan.getTrafficRenewalFlag()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "交强续保类型")
+                    .fluentPut("scoreKey", "trafficRenewalType")
+                    .fluentPut("score", insurancePlan.getTrafficRenewalType()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "商业续保标志")
+                    .fluentPut("scoreKey", "businessRenewalFlag")
+                    .fluentPut("score", insurancePlan.getBusinessRenewalFlag()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "商业续保类型")
+                    .fluentPut("scoreKey", "businessRenewalType")
+                    .fluentPut("score", insurancePlan.getBusinessRenewalType()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "违章等级")
+                    .fluentPut("scoreKey", "violationLevel")
+                    .fluentPut("score", insurancePlan.getViolationLevel()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "商业险折扣率")
+                    .fluentPut("scoreKey", "cDiscountRate")
+                    .fluentPut("score", insurancePlan.getCDiscountRate()));
+        }
         scoreVo.setScoreJson(scoreJson);
         quoteResultsVo.setScoreVo(scoreVo);
 

+ 60 - 11
tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/build/GuoRenCrawlerQuoteResultsVoBuild.java

@@ -7,6 +7,7 @@ import com.jzg.commons.constants.quote.enums.quote.KindCode;
 import com.jzg.commons.entity.quote.vo.KindInfoVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.entity.quote.vo.aggregated.QuoteResultsVo;
+import com.jzg.quotation.guoren.crawler.entity.dto.CiInsureDemandDTO;
 import com.jzg.quotation.guoren.crawler.entity.response.GuoRenCrawlerBaseResponse;
 import com.jzg.quotation.guoren.crawler.entity.response.GuoRenCrawlerCalculationPremiumData;
 import com.jzg.quotation.guoren.crawler.entity.response.GuoRenCrawlerProposalNoSaveData;
@@ -72,26 +73,74 @@ public final class GuoRenCrawlerQuoteResultsVoBuild {
 
 
         QuoteResultsVo.ScoreVo scoreVo = new QuoteResultsVo.ScoreVo();
-        scoreVo.setLossRation(calculationPremium.getBc().getAutoPriceOutputXOMRespVo().getStrBillELR2());
-        scoreVo.setJqLossRation(calculationPremium.getBc().getAutoPriceOutputXOMRespVo().getStrBillCTPELR2());
-        scoreVo.setSyLossRation(calculationPremium.getBc().getAutoPriceOutputXOMRespVo().getStrBillCOMELR2());
-        scoreVo.setJqRenewal(Integer.parseInt(calculationPremium.getBc().getPrpTitemCar().getOriginalRouteRenewalFlagci()) + 1 + "");
-        scoreVo.setSyRenewal(Integer.parseInt(calculationPremium.getBc().getPrpTitemCar().getOriginalRouteRenewalFlagbi()) + 1 + "");
+        // 赔付率(总/交强/商业)
+        Optional.ofNullable(calculationPremium.getBc())
+                .map(GuoRenCrawlerCalculationPremiumData.BcDTO::getAutoPriceOutputXOMRespVo)
+                .ifPresent(autoPriceOutputXOMRespVo -> {
+                    scoreVo.setLossRation(autoPriceOutputXOMRespVo.getStrBillELR2());
+                    scoreVo.setJqLossRation(autoPriceOutputXOMRespVo.getStrBillCTPELR2());
+                    scoreVo.setSyLossRation(autoPriceOutputXOMRespVo.getStrBillCOMELR2());
+                });
+        // 续保/转保标志
+        Optional.ofNullable(calculationPremium.getBc())
+                .map(GuoRenCrawlerCalculationPremiumData.BcDTO::getPrpTitemCar)
+                .ifPresent(prpTitemCar -> {
+                    scoreVo.setJqRenewal(Integer.parseInt(prpTitemCar.getOriginalRouteRenewalFlagci()) + 1 + "");
+                    scoreVo.setSyRenewal(Integer.parseInt(prpTitemCar.getOriginalRouteRenewalFlagbi()) + 1 + "");
+                });
+        // 交强险出险次数
+        Optional.ofNullable(calculationPremium.getCi())
+                .map(GuoRenCrawlerCalculationPremiumData.CiDTO::getCiInsureDemand)
+                .map(CiInsureDemandDTO::getClaimTimes)
+                .ifPresent(scoreVo::setCiClaims);
+        // 商业险出险次数
+        Optional.ofNullable(calculationPremium.getBi())
+                .map(GuoRenCrawlerCalculationPremiumData.BiDTO::getCiInsureDemand)
+                .map(CiInsureDemandDTO::getClaimTimes)
+                .ifPresent(scoreVo::setBiClaims);
 
         // 保司评分
         JSONArray scoreJson = new JSONArray();
         //光博评分
-        scoreJson.add(new JSONObject()
-                .fluentPut("scoreName", "交强光博分")
-                .fluentPut("scoreKey", "jqScore")
-                .fluentPut("score", calculationPremium.getCi().getPrpTmainRate().getBillcomeGroup()));
-
-        if (calculationPremium.getBi().getPrpTmainRate() != null) {
+        Optional.ofNullable(calculationPremium.getCi())
+                .map(GuoRenCrawlerCalculationPremiumData.CiDTO::getPrpTmainRate)
+                .map(GuoRenCrawlerCalculationPremiumData.CiDTO.PrpTmainRateDTO::getBillcomeGroup)
+                .ifPresent(billcomeGroup -> scoreJson.add(new JSONObject()
+                        .fluentPut("scoreName", "交强光博分")
+                        .fluentPut("scoreKey", "jqScore")
+                        .fluentPut("score", billcomeGroup)));
+
+        if (calculationPremium.getBi() != null && calculationPremium.getBi().getPrpTmainRate() != null) {
             scoreJson.add(new JSONObject()
                     .fluentPut("scoreName", "商业光博分")
                     .fluentPut("scoreKey", "syScore")
                     .fluentPut("score", calculationPremium.getBi().getPrpTmainRate().getBillcomeGroup()));
         }
+        // 保司评分(阳光评分/全保评分/责任险评分/交强险评分/推荐折扣评分)
+        Optional.ofNullable(calculationPremium.getBc())
+                .map(GuoRenCrawlerCalculationPremiumData.BcDTO::getAutoPriceOutputXOMRespVo)
+                .ifPresent(autoPriceOutputXOMRespVo -> {
+                    scoreJson.add(new JSONObject()
+                            .fluentPut("scoreName", "阳光评分")
+                            .fluentPut("scoreKey", "sunlightScore")
+                            .fluentPut("score", autoPriceOutputXOMRespVo.getSunlightScore()));
+                    scoreJson.add(new JSONObject()
+                            .fluentPut("scoreName", "全保评分")
+                            .fluentPut("scoreKey", "fullCoverageScore")
+                            .fluentPut("score", autoPriceOutputXOMRespVo.getFullCoverageScore()));
+                    scoreJson.add(new JSONObject()
+                            .fluentPut("scoreName", "责任险评分")
+                            .fluentPut("scoreKey", "liabilityOnlyScore")
+                            .fluentPut("score", autoPriceOutputXOMRespVo.getLiabilityOnlyScore()));
+                    scoreJson.add(new JSONObject()
+                            .fluentPut("scoreName", "交强险评分")
+                            .fluentPut("scoreKey", "ctpOnlyScore")
+                            .fluentPut("score", autoPriceOutputXOMRespVo.getCtpOnlyScore()));
+                    scoreJson.add(new JSONObject()
+                            .fluentPut("scoreName", "推荐折扣评分")
+                            .fluentPut("scoreKey", "recommenDiscountScore")
+                            .fluentPut("score", autoPriceOutputXOMRespVo.getRecommenDiscountScore()));
+                });
         // 重试成功时,将提前记录的重复投保信息放入评分字段
         if (duplicateMessage != null) {
             scoreJson.add(new JSONObject()

+ 46 - 0
tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/build/HbCrawlerQuoteResultVoBuild.java

@@ -9,6 +9,7 @@ import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.entity.quote.vo.RiskInfoVo;
 import com.jzg.commons.entity.quote.vo.aggregated.QuoteResultsVo;
 import com.jzg.commons.util.StringUtils;
+import com.jzg.quotation.commons.constant.enums.quote.RenewalEnum;
 import com.jzg.quotation.commons.utils.DateTimeUtil;
 import com.jzg.quotation.hengbang.crawler.entity.response.HbCrawlerProposalResponse;
 import lombok.extern.slf4j.Slf4j;
@@ -57,6 +58,31 @@ public final class HbCrawlerQuoteResultVoBuild {
         quoteResultsVo.setKindInfoVo(buildKindInfoList(proposalResponse, quoteVo));
 
         QuoteResultsVo.ScoreVo scoreVo = new QuoteResultsVo.ScoreVo();
+        HbCrawlerProposalResponse.ForceCarInsureRiskDTO forceCarInsureRisk = proposalResponse.getForceCarInsureRisk();
+        HbCrawlerProposalResponse.BusiCarInsureRiskDTO busiCarInsureRisk = proposalResponse.getBusiCarInsureRisk();
+        // 交强险赔付率、出险次数
+        if (Objects.nonNull(forceCarInsureRisk)) {
+            scoreVo.setJqLossRation(forceCarInsureRisk.getBillctpelr());
+            scoreVo.setCiClaims(forceCarInsureRisk.getClaimTimes());
+        }
+        // 商业险赔付率、出险次数
+        if (Objects.nonNull(busiCarInsureRisk)) {
+            scoreVo.setSyLossRation(busiCarInsureRisk.getBillcomelr());
+            scoreVo.setBiClaims(busiCarInsureRisk.getClaimTimes());
+        }
+        // 总赔付率
+        if (Objects.nonNull(forceCarInsureRisk)) {
+            scoreVo.setLossRation(forceCarInsureRisk.getBillelr());
+        } else if (Objects.nonNull(busiCarInsureRisk)) {
+            scoreVo.setLossRation(busiCarInsureRisk.getBillelr());
+        }
+        // 续保/转保标志(0:转保,其他:续保)
+        HbCrawlerProposalResponse.CarInsureMainDTO carInsureMain = proposalResponse.getCarInsureMain();
+        if (Objects.nonNull(carInsureMain) && StringUtils.isNotBlank(carInsureMain.getRenewind())) {
+            String renewind = carInsureMain.getRenewind();
+            scoreVo.setJqRenewal(String.valueOf("0".equals(renewind) ? RenewalEnum.Transfer.getCode() : RenewalEnum.Renewal.getCode()));
+            scoreVo.setSyRenewal(String.valueOf("0".equals(renewind) ? RenewalEnum.Transfer.getCode() : RenewalEnum.Renewal.getCode()));
+        }
         // 保司评分
         JSONArray scoreJson = new JSONArray();
         if (proposalResponse.getForceLiability() != null && proposalResponse.getCarInsureLiabilityList().size() > 0) {
@@ -70,6 +96,26 @@ public final class HbCrawlerQuoteResultVoBuild {
             scoreJson.add(new JSONObject().fluentPut("scoreName", "交强光博分").fluentPut("scoreKey", "score").fluentPut("score", proposalResponse.getBusiCarInsureRisk().getBillelr2()));
 
         }
+        // 保司评分列表
+        if (CollUtil.isNotEmpty(proposalResponse.getCarInsureScoreList())) {
+            for (HbCrawlerProposalResponse.CarInsureScoreDTO carInsureScoreDTO : proposalResponse.getCarInsureScoreList()) {
+                scoreJson.add(new JSONObject()
+                        .fluentPut("scoreName", carInsureScoreDTO.getScoringType())
+                        .fluentPut("scoreKey", carInsureScoreDTO.getScoringType())
+                        .fluentPut("score", carInsureScoreDTO.getScoring()));
+            }
+        }
+        // 违章评分、承保年数
+        if (Objects.nonNull(forceCarInsureRisk)) {
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "违章评分")
+                    .fluentPut("scoreKey", "trafficViolationValue")
+                    .fluentPut("score", forceCarInsureRisk.getTrafficViolationValue()));
+            scoreJson.add(new JSONObject()
+                    .fluentPut("scoreName", "承保年数")
+                    .fluentPut("scoreKey", "insureYears")
+                    .fluentPut("score", forceCarInsureRisk.getInsureYears()));
+        }
         // 重试成功时,将提前记录的重复投保信息放入评分字段
         if (duplicateMessage != null) {
             scoreJson.add(new JSONObject().fluentPut("scoreName", "重复投保信息").fluentPut("scoreKey", "duplicateInfo").fluentPut("score", duplicateMessage));

+ 1 - 1
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/service/impl/HuaNongCrawlerRequestImpl.java

@@ -1067,7 +1067,7 @@ public class HuaNongCrawlerRequestImpl implements HuaNongCrawlerRequest {
         Optional<HuaNongCrawlerQueryReviveOpinionResponse.OpinionDTO> manualOpinion =
                 findOpinion(opinionResponse, "0");
         if (manualOpinion.isPresent()) {
-            setOrderStatus(result, InsOrderStatusEnum.STAFF_PROCESS);
+            setOrderStatus(result, InsOrderStatusEnum.AUDIT_FAIL);
             result.setErrorMsg(formatOpinion(manualOpinion.get(), false));
             return result;
         }

+ 10 - 0
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/mapper/InsOrdersMapper.java

@@ -43,6 +43,16 @@ public interface InsOrdersMapper extends BaseMapper<InsOrders> {
 
     List<OrderListVo> queryOrderListAll(@Param("insOrdersParam") InsOrdersParamVO insOrdersParam);
 
+    /**
+     * 手动分页版:按车牌+状态分组去重(每组取最新一条),返回当前页数据
+     */
+    List<OrderListVo> queryOrderListDistinct(@Param("insOrdersParam") InsOrdersParamVO insOrdersParam, @Param("offset") long offset, @Param("size") long size);
+
+    /**
+     * 手动分页版:统计按车牌+状态分组去重后的总条数
+     */
+    Long countOrderListDistinct(@Param("insOrdersParam") InsOrdersParamVO insOrdersParam);
+
     List<HistoryQuoteResultVo> getHistoryQuote(@Param("userName") String userName);
 
     Page<HistoryQuoteResultVo> getHistoryQuoteList(@Param("page") Page page, @Param("params") HistoryQuoteQueryVo historyQuoteQueryVo, @Param("userName") String userName);

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

@@ -80,6 +80,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Collectors;
 
 @RequiredArgsConstructor
@@ -1038,7 +1039,21 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
             jzgInfoId = sysUserJzgInfo.getId();
             insOrdersParam.setJzgInfoId(jzgInfoId);
         }
-        Page<OrderListVo> orderListVoPage = insOrdersMapper.queryOrderList(insOrdersParam.getPage(), insOrdersParam);
+        Page<OrderListVo> orderListVoPage;
+        if (baseController.getUserSystemCode().equals("APP")) {
+            // 手动分页:按车牌+状态分组去重,先统计总数再取当前页(避免 MP 自动 count 解析窗口函数报错)
+            Page<OrderListVo> page = insOrdersParam.getPage();
+            long current = page.getCurrent();
+            long size = page.getSize();
+            long offset = (current - 1) * size;
+            long total = insOrdersMapper.countOrderListDistinct(insOrdersParam);
+            List<OrderListVo> records = insOrdersMapper.queryOrderListDistinct(insOrdersParam, offset, size);
+            orderListVoPage = new Page<>(current, size, total);
+            orderListVoPage.setRecords(records != null ? records : java.util.Collections.emptyList());
+        } else {
+            // 非 APP 走原 MP 自动分页查询
+            orderListVoPage = insOrdersMapper.queryOrderList(insOrdersParam.getPage(), insOrdersParam);
+        }
         // modify by lipf, 2026年4月29日11:06:02 解决业务员类型缺失的问题
         List<OrderListVo> records = orderListVoPage.getRecords();
         if (CollUtil.isNotEmpty(records)) {
@@ -1071,14 +1086,27 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
         if (CollUtil.isEmpty(insOrders)) {
             return;
         }
+        Map<String, String> logoMap = new ConcurrentHashMap<>();
         for (OrderQuotationCompany insOrder : insOrders) {
-            HttpResult<EsmInsCompany> esmInsCompanyHttpResult = platformClient.findLogoById(insOrder.getCompanyId());
-            if (esmInsCompanyHttpResult.getCode() == 200) {
-                insOrder.setLogo(esmInsCompanyHttpResult.getData().getLogo());
+            String companyId = insOrder.getCompanyId();
+            if (StrUtil.isBlank(companyId)) {
+                continue;
+            }
+            String logo = logoMap.get(companyId);
+            if (StrUtil.isBlank(logo)) {
+                HttpResult<EsmInsCompany> result = platformClient.findLogoById(companyId);
+                if (result.getCode() == 200 && result.getData() != null) {
+                    logo = result.getData().getLogo();
+                    if (StrUtil.isNotBlank(logo)) {
+                        logoMap.put(companyId, logo);
+                    }
+                }
+            }
+            if (StrUtil.isNotBlank(logo)) {
+                insOrder.setLogo(logo);
             }
         }
 
-
         Map<String, Map<String, List<OrderQuotationCompany>>> groupMap = insOrders.stream().collect(Collectors.groupingBy(OrderQuotationCompany::getQuoteNo, Collectors.groupingBy(OrderQuotationCompany::getOrderStatus)));
         for (OrderListVo orders : orderList) {
             String orderNo = orders.getNo();

+ 405 - 0
tenant/insurance/quotation-summary/src/main/resources/mapper/InsOrdersMapper.xml

@@ -1233,6 +1233,411 @@
         order by io.create_time desc
     </select>
 
+    <!-- 手动分页版:按车牌+状态分组去重(每组取最新一条),返回当前页数据;不触发 MP 自动 count -->
+    <select id="queryOrderListDistinct" resultType="com.jzg.commons.entity.orders.vo.OrderListVo">
+        SELECT
+        io.id as order_no,
+        io.quote_no as no,
+        io.company_id,
+        io.company_name,
+        io.product_id as productCode,
+        io.product_name as productName,
+        io.order_status,
+        io.create_time,
+        io.create_by,
+        io.error_message,
+        ioci.license_no,
+        ioci.vin_no,
+        owner_u.name as owner_name,
+        owner_u.mobile as owner_mobile,
+        policy_u.name as policy_name,
+        policy_u.mobile as policy_mobile,
+        insur_u.name as insur_name,
+        insur_u.mobile as insur_mobile,
+        iorci.start_date as ci_start_date,
+        iorbi.start_date as bi_start_date,
+        iorco.sum_premium as sumPremium,
+        iorco.jq_premium as ciPremium,
+        iorco.sy_premium as biPremium,
+        iorco.jy_premium as niPremium,
+        iorco.tax_premium as vvTax,
+        su.username as recorder,
+        suji.name as salesmanInfo,
+        suji.id as jzgInfoId,
+        suji.type_attr as salesmanType,
+        pa.agreement_name as agreementName,
+        sd.name as deptName,
+        io.pay_link as paymentLink,
+        ioa.status as adjustStatus,
+        ioa.id as adjustId,
+        ioa1.after_premium as adjustPremium
+        FROM (
+            SELECT dedup.order_id
+            FROM (
+                SELECT
+                    io.id AS order_id,
+                    io.create_time,
+                    ROW_NUMBER() OVER(PARTITION BY COALESCE(ioci.license_no, io.id), io.order_status ORDER BY io.create_time DESC) AS rn
+                FROM
+                ins_orders io
+                LEFT JOIN ins_orders_car_info ioci ON ioci.order_no = io.id and ioci.is_delete = 0
+                LEFT JOIN
+                ins_orders_car_user_info owner_u on owner_u.order_no = io.id and owner_u.policy_person_type = 1 and owner_u.is_delete = 0
+                left JOIN
+                ins_orders_car_user_info policy_u on policy_u.order_no = io.id and policy_u.policy_person_type = 2 and policy_u.is_delete = 0
+                left JOIN
+                ins_orders_car_user_info insur_u on insur_u.order_no = io.id and insur_u.policy_person_type = 3 and insur_u.is_delete = 0
+                left join
+                ins_orders_risk iorci on iorci.order_no = io.id and iorci.risk_code = '0507' and iorci.is_delete = 0
+                left join
+                ins_orders_risk iorbi on iorbi.order_no = io.id and iorbi.risk_code = '0510' and iorbi.is_delete = 0
+                LEFT JOIN
+                ins_orders_costs iorco on iorco.order_no = io.id and iorco.is_delete = 0
+                left join
+                sys_user su on su.mobile = io.real_quote_user_id and su.is_delete = 0
+                left join
+                sys_user_jzg_info suji on suji.user_id = su.id and suji.system_code = #{insOrdersParam.systemCode} and suji.is_delete = 0
+                left join
+                sys_dept sd on sd.id = suji.dept_id and sd.is_delete = 0
+                left join
+                ptl_agreement pa on pa.id = io.agreement_id and pa.is_delete = 0
+                left join
+                (
+                SELECT *
+                FROM (
+                SELECT t.*,
+                ROW_NUMBER() OVER(PARTITION BY t.order_no ORDER BY t.create_time DESC) AS rn
+                FROM ins_order_adjust t where t.is_delete = 0
+                ) x WHERE x.rn = 1
+                ) ioa ON io.id = ioa.order_no
+                left join
+                (
+                SELECT *
+                FROM (
+                SELECT t.*,
+                ROW_NUMBER() OVER(PARTITION BY t.order_no ORDER BY t.create_time DESC) AS rn
+                FROM ins_order_adjust t where t.status = 2 and t.is_delete = 0
+                ) x WHERE x.rn = 1
+                ) ioa1 ON io.id = ioa1.order_no
+                <where>
+                    and io.is_delete = 0  and io.parent_id != '0'
+                    <if test="insOrdersParam.jzgInfoId != null and insOrdersParam.jzgInfoId != ''">
+                        and io.real_quote_user_id in (select bbb.mobile from sys_user bbb where bbb.id in ( select ss.user_id
+                        from sys_user_jzg_info ss where 1 =1 and (ss.leader_id = #{insOrdersParam.jzgInfoId} or ss.id
+                        =#{insOrdersParam.jzgInfoId}) and ss.is_delete = 0 ) and bbb.is_delete = 0 )
+                    </if>
+                    <if test="insOrdersParam.orderNo != null and insOrdersParam.orderNo != ''">
+                        AND io.id = #{insOrdersParam.orderNo}
+                    </if>
+                    <if test="insOrdersParam.licenseNo != null and insOrdersParam.licenseNo != ''">
+                        AND ioci.license_no = #{insOrdersParam.licenseNo}
+                    </if>
+                    <if test="insOrdersParam.orderStatus!= null and insOrdersParam.orderStatus!= ''">
+                        AND io.order_status = #{insOrdersParam.orderStatus}
+                    </if>
+                    <if test="insOrdersParam.adjustStatus!= null and insOrdersParam.adjustStatus!= ''">
+                        AND ioa.status = #{insOrdersParam.adjustStatus}
+                    </if>
+                    <if test="insOrdersParam.salesman!= null">
+                        AND io.real_quote_user_id = #{insOrdersParam.salesman}
+                    </if>
+                    <if test="insOrdersParam.companyId != null and insOrdersParam.companyId != ''">
+                        AND io.company_id = #{insOrdersParam.companyId}
+                    </if>
+                    <if test="insOrdersParam.startDate != null and insOrdersParam.startDate != ''and insOrdersParam.endDate != null and insOrdersParam.endDate != ''">
+                        AND io.create_time BETWEEN #{insOrdersParam.startDate} and #{insOrdersParam.endDate}
+                    </if>
+                    <if test="insOrdersParam.companyName != null and insOrdersParam.companyName != ''">
+                        AND io.company_name = #{insOrdersParam.companyName}
+                    </if>
+                    <if test="insOrdersParam.productCode != null and insOrdersParam.productCode != ''">
+                        AND io.product_id = #{insOrdersParam.productCode}
+                    </if>
+                    <if test="insOrdersParam.productName != null and insOrdersParam.productName != ''">
+                        AND io.product_name = #{insOrdersParam.productName}
+                    </if>
+                    <if test="insOrdersParam.vinNo != null and insOrdersParam.vinNo != ''">
+                        AND ioci.vin_no = #{insOrdersParam.vinNo}
+                    </if>
+                    <if test="insOrdersParam.ownerName != null and insOrdersParam.ownerName != ''">
+                        AND owner_u.name = #{insOrdersParam.ownerName}
+                    </if>
+                    <if test="insOrdersParam.ownerMobile != null and insOrdersParam.ownerMobile != ''">
+                        AND owner_u.mobile = #{insOrdersParam.ownerMobile}
+                    </if>
+                    <if test="insOrdersParam.policyName != null and insOrdersParam.policyName != ''">
+                        AND policy_u.name = #{insOrdersParam.policyName}
+                    </if>
+                    <if test="insOrdersParam.policyMobile != null and insOrdersParam.policyMobile != ''">
+                        AND policy_u.mobile = #{insOrdersParam.policyMobile}
+                    </if>
+                    <if test="insOrdersParam.insurName != null and insOrdersParam.insurName != ''">
+                        AND insur_u.name = #{insOrdersParam.insurName}
+                    </if>
+                    <if test="insOrdersParam.insurMobile != null and insOrdersParam.insurMobile != ''">
+                        AND insur_u.mobile = #{insOrdersParam.insurMobile}
+                    </if>
+                    <if test="insOrdersParam.ciStartDate != null">
+                        AND iorci.start_date = #{insOrdersParam.ciStartDate}
+                    </if>
+                    <if test="insOrdersParam.biStartDate != null">
+                        AND iorbi.start_date = #{insOrdersParam.biStartDate}
+                    </if>
+                    <if test="insOrdersParam.sumPremium != null">
+                        AND iorco.sum_premium = #{insOrdersParam.sumPremium}
+                    </if>
+                    <if test="insOrdersParam.ciPremium != null">
+                        AND iorco.jq_premium = #{insOrdersParam.ciPremium}
+                    </if>
+                    <if test="insOrdersParam.biPremium != null">
+                        AND iorco.sy_premium = #{insOrdersParam.biPremium}
+                    </if>
+                    <if test="insOrdersParam.niPremium != null">
+                        AND iorco.jy_premium = #{insOrdersParam.niPremium}
+                    </if>
+                    <if test="insOrdersParam.vvTax != null">
+                        AND iorco.tax_premium = #{insOrdersParam.vvTax}
+                    </if>
+                    <if test="insOrdersParam.salesmanInfo != null and insOrdersParam.salesmanInfo != ''">
+                        AND su.username = #{insOrdersParam.salesmanInfo}
+                    </if>
+                    <if test="insOrdersParam.salesmanType != null and insOrdersParam.salesmanType != ''">
+                        AND suji.type_attr = #{insOrdersParam.salesmanType}
+                    </if>
+                    <if test="insOrdersParam.agreementName != null and insOrdersParam.agreementName != ''">
+                        AND pa.agreement_name = #{insOrdersParam.agreementName}
+                    </if>
+                    <if test="insOrdersParam.deptId != null and insOrdersParam.deptId != ''">
+                        AND sd.id = #{insOrdersParam.deptId}
+                    </if>
+                    <if test="insOrdersParam.deptName != null and insOrdersParam.deptName != ''">
+                        AND sd.name = #{insOrdersParam.deptName}
+                    </if>
+                    <if test="insOrdersParam.paymentLink != null and insOrdersParam.paymentLink != ''">
+                        AND io.pay_link = #{insOrdersParam.paymentLink}
+                    </if>
+                    <if test="insOrdersParam.adjustId != null">
+                        AND ioa.id = #{insOrdersParam.adjustId}
+                    </if>
+                    <if test="insOrdersParam.adjustPremium != null">
+                        AND ioa1.after_premium = #{insOrdersParam.adjustPremium}
+                    </if>
+                    <if test="insOrdersParam.startSigningTime != null and insOrdersParam.startSigningTime != ''and insOrdersParam.endSigningTime != null and insOrdersParam.endSigningTime != ''">
+                        AND io.signing_time BETWEEN #{insOrdersParam.startSigningTime} and #{insOrdersParam.endSigningTime}
+                    </if>
+                    <if test="insOrdersParam.startPayTime != null and insOrdersParam.startPayTime != ''and insOrdersParam.endPayTime != null and insOrdersParam.endPayTime != ''">
+                        AND io.pay_time BETWEEN #{insOrdersParam.startPayTime} and #{insOrdersParam.endPayTime}
+                    </if>
+                </where>
+            ) dedup
+            WHERE dedup.rn = 1
+            ORDER BY dedup.create_time DESC
+            LIMIT #{offset}, #{size}
+        ) p
+        JOIN ins_orders io ON io.id = p.order_id
+        LEFT JOIN ins_orders_car_info ioci ON ioci.order_no = io.id and ioci.is_delete = 0
+        LEFT JOIN
+        ins_orders_car_user_info owner_u on owner_u.order_no = io.id and owner_u.policy_person_type = 1 and owner_u.is_delete = 0
+        left JOIN
+        ins_orders_car_user_info policy_u on policy_u.order_no = io.id and policy_u.policy_person_type = 2 and policy_u.is_delete = 0
+        left JOIN
+        ins_orders_car_user_info insur_u on insur_u.order_no = io.id and insur_u.policy_person_type = 3 and insur_u.is_delete = 0
+        left join
+        ins_orders_risk iorci on iorci.order_no = io.id and iorci.risk_code = '0507' and iorci.is_delete = 0
+        left join
+        ins_orders_risk iorbi on iorbi.order_no = io.id and iorbi.risk_code = '0510' and iorbi.is_delete = 0
+        LEFT JOIN
+        ins_orders_costs iorco on iorco.order_no = io.id and iorco.is_delete = 0
+        left join
+        sys_user su on su.mobile = io.real_quote_user_id and su.is_delete = 0
+        left join
+        sys_user_jzg_info suji on suji.user_id = su.id and suji.system_code = #{insOrdersParam.systemCode} and suji.is_delete = 0
+        left join
+        sys_dept sd on sd.id = suji.dept_id and sd.is_delete = 0
+        left join
+        ptl_agreement pa on pa.id = io.agreement_id and pa.is_delete = 0
+        left join
+        (
+        SELECT *
+        FROM (
+        SELECT t.*,
+        ROW_NUMBER() OVER(PARTITION BY t.order_no ORDER BY t.create_time DESC) AS rn
+        FROM ins_order_adjust t where t.is_delete = 0
+        ) x WHERE x.rn = 1
+        ) ioa ON io.id = ioa.order_no
+        left join
+        (
+        SELECT *
+        FROM (
+        SELECT t.*,
+        ROW_NUMBER() OVER(PARTITION BY t.order_no ORDER BY t.create_time DESC) AS rn
+        FROM ins_order_adjust t where t.status = 2 and t.is_delete = 0
+        ) x WHERE x.rn = 1
+        ) ioa1 ON io.id = ioa1.order_no
+        order by io.create_time desc
+    </select>
+
+    <!-- 手动分页版:统计按车牌+状态分组去重后的总条数 -->
+    <select id="countOrderListDistinct" resultType="java.lang.Long">
+        SELECT COUNT(*) FROM (
+            SELECT
+                io.id AS order_id,
+                ROW_NUMBER() OVER(PARTITION BY COALESCE(ioci.license_no, io.id), io.order_status ORDER BY io.create_time DESC) AS rn
+            FROM
+            ins_orders io
+            LEFT JOIN ins_orders_car_info ioci ON ioci.order_no = io.id and ioci.is_delete = 0
+            LEFT JOIN
+            ins_orders_car_user_info owner_u on owner_u.order_no = io.id and owner_u.policy_person_type = 1 and owner_u.is_delete = 0
+            left JOIN
+            ins_orders_car_user_info policy_u on policy_u.order_no = io.id and policy_u.policy_person_type = 2 and policy_u.is_delete = 0
+            left JOIN
+            ins_orders_car_user_info insur_u on insur_u.order_no = io.id and insur_u.policy_person_type = 3 and insur_u.is_delete = 0
+            left join
+            ins_orders_risk iorci on iorci.order_no = io.id and iorci.risk_code = '0507' and iorci.is_delete = 0
+            left join
+            ins_orders_risk iorbi on iorbi.order_no = io.id and iorbi.risk_code = '0510' and iorbi.is_delete = 0
+            LEFT JOIN
+            ins_orders_costs iorco on iorco.order_no = io.id and iorco.is_delete = 0
+            left join
+            sys_user su on su.mobile = io.real_quote_user_id and su.is_delete = 0
+            left join
+            sys_user_jzg_info suji on suji.user_id = su.id and suji.system_code = #{insOrdersParam.systemCode} and suji.is_delete = 0
+            left join
+            sys_dept sd on sd.id = suji.dept_id and sd.is_delete = 0
+            left join
+            ptl_agreement pa on pa.id = io.agreement_id and pa.is_delete = 0
+            left join
+            (
+            SELECT *
+            FROM (
+            SELECT t.*,
+            ROW_NUMBER() OVER(PARTITION BY t.order_no ORDER BY t.create_time DESC) AS rn
+            FROM ins_order_adjust t where t.is_delete = 0
+            ) x WHERE x.rn = 1
+            ) ioa ON io.id = ioa.order_no
+            left join
+            (
+            SELECT *
+            FROM (
+            SELECT t.*,
+            ROW_NUMBER() OVER(PARTITION BY t.order_no ORDER BY t.create_time DESC) AS rn
+            FROM ins_order_adjust t where t.status = 2 and t.is_delete = 0
+            ) x WHERE x.rn = 1
+            ) ioa1 ON io.id = ioa1.order_no
+            <where>
+                and io.is_delete = 0  and io.parent_id != '0'
+                <if test="insOrdersParam.jzgInfoId != null and insOrdersParam.jzgInfoId != ''">
+                    and io.real_quote_user_id in (select bbb.mobile from sys_user bbb where bbb.id in ( select ss.user_id
+                    from sys_user_jzg_info ss where 1 =1 and (ss.leader_id = #{insOrdersParam.jzgInfoId} or ss.id
+                    =#{insOrdersParam.jzgInfoId}) and ss.is_delete = 0 ) and bbb.is_delete = 0 )
+                </if>
+                <if test="insOrdersParam.orderNo != null and insOrdersParam.orderNo != ''">
+                    AND io.id = #{insOrdersParam.orderNo}
+                </if>
+                <if test="insOrdersParam.licenseNo != null and insOrdersParam.licenseNo != ''">
+                    AND ioci.license_no = #{insOrdersParam.licenseNo}
+                </if>
+                <if test="insOrdersParam.orderStatus!= null and insOrdersParam.orderStatus!= ''">
+                    AND io.order_status = #{insOrdersParam.orderStatus}
+                </if>
+                <if test="insOrdersParam.adjustStatus!= null and insOrdersParam.adjustStatus!= ''">
+                    AND ioa.status = #{insOrdersParam.adjustStatus}
+                </if>
+                <if test="insOrdersParam.salesman!= null">
+                    AND io.real_quote_user_id = #{insOrdersParam.salesman}
+                </if>
+                <if test="insOrdersParam.companyId != null and insOrdersParam.companyId != ''">
+                    AND io.company_id = #{insOrdersParam.companyId}
+                </if>
+                <if test="insOrdersParam.startDate != null and insOrdersParam.startDate != ''and insOrdersParam.endDate != null and insOrdersParam.endDate != ''">
+                    AND io.create_time BETWEEN #{insOrdersParam.startDate} and #{insOrdersParam.endDate}
+                </if>
+                <if test="insOrdersParam.companyName != null and insOrdersParam.companyName != ''">
+                    AND io.company_name = #{insOrdersParam.companyName}
+                </if>
+                <if test="insOrdersParam.productCode != null and insOrdersParam.productCode != ''">
+                    AND io.product_id = #{insOrdersParam.productCode}
+                </if>
+                <if test="insOrdersParam.productName != null and insOrdersParam.productName != ''">
+                    AND io.product_name = #{insOrdersParam.productName}
+                </if>
+                <if test="insOrdersParam.vinNo != null and insOrdersParam.vinNo != ''">
+                    AND ioci.vin_no = #{insOrdersParam.vinNo}
+                </if>
+                <if test="insOrdersParam.ownerName != null and insOrdersParam.ownerName != ''">
+                    AND owner_u.name = #{insOrdersParam.ownerName}
+                </if>
+                <if test="insOrdersParam.ownerMobile != null and insOrdersParam.ownerMobile != ''">
+                    AND owner_u.mobile = #{insOrdersParam.ownerMobile}
+                </if>
+                <if test="insOrdersParam.policyName != null and insOrdersParam.policyName != ''">
+                    AND policy_u.name = #{insOrdersParam.policyName}
+                </if>
+                <if test="insOrdersParam.policyMobile != null and insOrdersParam.policyMobile != ''">
+                    AND policy_u.mobile = #{insOrdersParam.policyMobile}
+                </if>
+                <if test="insOrdersParam.insurName != null and insOrdersParam.insurName != ''">
+                    AND insur_u.name = #{insOrdersParam.insurName}
+                </if>
+                <if test="insOrdersParam.insurMobile != null and insOrdersParam.insurMobile != ''">
+                    AND insur_u.mobile = #{insOrdersParam.insurMobile}
+                </if>
+                <if test="insOrdersParam.ciStartDate != null">
+                    AND iorci.start_date = #{insOrdersParam.ciStartDate}
+                </if>
+                <if test="insOrdersParam.biStartDate != null">
+                    AND iorbi.start_date = #{insOrdersParam.biStartDate}
+                </if>
+                <if test="insOrdersParam.sumPremium != null">
+                    AND iorco.sum_premium = #{insOrdersParam.sumPremium}
+                </if>
+                <if test="insOrdersParam.ciPremium != null">
+                    AND iorco.jq_premium = #{insOrdersParam.ciPremium}
+                </if>
+                <if test="insOrdersParam.biPremium != null">
+                    AND iorco.sy_premium = #{insOrdersParam.biPremium}
+                </if>
+                <if test="insOrdersParam.niPremium != null">
+                    AND iorco.jy_premium = #{insOrdersParam.niPremium}
+                </if>
+                <if test="insOrdersParam.vvTax != null">
+                    AND iorco.tax_premium = #{insOrdersParam.vvTax}
+                </if>
+                <if test="insOrdersParam.salesmanInfo != null and insOrdersParam.salesmanInfo != ''">
+                    AND su.username = #{insOrdersParam.salesmanInfo}
+                </if>
+                <if test="insOrdersParam.salesmanType != null and insOrdersParam.salesmanType != ''">
+                    AND suji.type_attr = #{insOrdersParam.salesmanType}
+                </if>
+                <if test="insOrdersParam.agreementName != null and insOrdersParam.agreementName != ''">
+                    AND pa.agreement_name = #{insOrdersParam.agreementName}
+                </if>
+                <if test="insOrdersParam.deptId != null and insOrdersParam.deptId != ''">
+                    AND sd.id = #{insOrdersParam.deptId}
+                </if>
+                <if test="insOrdersParam.deptName != null and insOrdersParam.deptName != ''">
+                    AND sd.name = #{insOrdersParam.deptName}
+                </if>
+                <if test="insOrdersParam.paymentLink != null and insOrdersParam.paymentLink != ''">
+                    AND io.pay_link = #{insOrdersParam.paymentLink}
+                </if>
+                <if test="insOrdersParam.adjustId != null">
+                    AND ioa.id = #{insOrdersParam.adjustId}
+                </if>
+                <if test="insOrdersParam.adjustPremium != null">
+                    AND ioa1.after_premium = #{insOrdersParam.adjustPremium}
+                </if>
+                <if test="insOrdersParam.startSigningTime != null and insOrdersParam.startSigningTime != ''and insOrdersParam.endSigningTime != null and insOrdersParam.endSigningTime != ''">
+                    AND io.signing_time BETWEEN #{insOrdersParam.startSigningTime} and #{insOrdersParam.endSigningTime}
+                </if>
+                <if test="insOrdersParam.startPayTime != null and insOrdersParam.startPayTime != ''and insOrdersParam.endPayTime != null and insOrdersParam.endPayTime != ''">
+                    AND io.pay_time BETWEEN #{insOrdersParam.startPayTime} and #{insOrdersParam.endPayTime}
+                </if>
+            </where>
+        ) dedup
+        WHERE dedup.rn = 1
+    </select>
+
     <select id="getHistoryQuote" resultType="com.jzg.commons.entity.orders.vo.HistoryQuoteResultVo">
         SELECT t.id as order_no,
                t.license_no

+ 61 - 5
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlAgreementDispatchServiceImpl.java

@@ -30,6 +30,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.util.*;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -181,6 +182,56 @@ public class PtlAgreementDispatchServiceImpl extends ServiceImpl<PtlAgreementDis
                     ));
             Map<String, PtlAreaLicense> areaLicenseMap = ptlAreaLicenseService.queryAllAreaLicenseMap();
 
+            // ===== 消除 N+1:提前批量查询所有依赖数据并构建索引 =====
+            // N+1-A:规则属性 attrCode → Attr(直接复用 L146 已查到的 undwrtRulesAttrList,不再逐条单查)
+            Map<String, PtlAgreementUndwrtRulesAttr> attrCode2RuleAttr = undwrtRulesAttrList.stream()
+                    .collect(Collectors.toMap(
+                            PtlAgreementUndwrtRulesAttr::getAttrCode,
+                            Function.identity(),
+                            (o1, o2) -> o1
+                    ));
+
+            // N+1-B:字典标签 dictId → List<DictLabal>(从 attrCode2RuleAttr 取所有非空 dictId,一次批量查)
+            Set<Integer> dictIdSet = attrCode2RuleAttr.values().stream()
+                    .map(PtlAgreementUndwrtRulesAttr::getDictId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toSet());
+            Map<Integer, List<PtlAgreementUndwrtRulesDictLabal>> dictId2Labels = new HashMap<>();
+            if (!dictIdSet.isEmpty()) {
+                List<PtlAgreementUndwrtRulesDictLabal> allLabels = ptlAgreementUndwrtRulesDictLabalService.list(
+                        new LambdaQueryWrapper<PtlAgreementUndwrtRulesDictLabal>()
+                                .in(PtlAgreementUndwrtRulesDictLabal::getDictId, dictIdSet));
+                dictId2Labels = allLabels.stream()
+                        .collect(Collectors.groupingBy(PtlAgreementUndwrtRulesDictLabal::getDictId));
+            }
+
+            // N+1-C:协议 agreementId → PtlAgreement(收集所有 specify_agreement 动作的 agreementId,一次批量查)
+            Set<String> agreementIdSet = new HashSet<>();
+            for (PtlAgreementDispatchAction action : ptlAgreementDispatchActions) {
+                if ("specify_agreement".equals(action.getActionCode()) && action.getActionJson() != null) {
+                    String agreementId = action.getActionJson().getString("agreementId");
+                    if (StrUtil.isNotBlank(agreementId)) {
+                        agreementIdSet.add(agreementId);
+                    }
+                }
+            }
+            // after_action 中的 specify_agreement 也要收集
+            afterActionsByCompany.values().stream().flatMap(List::stream).forEach(afterAction -> {
+                if ("specify_agreement".equals(afterAction.getActionCode()) && afterAction.getActionJson() != null) {
+                    String agreementId = afterAction.getActionJson().getString("agreementId");
+                    if (StrUtil.isNotBlank(agreementId)) {
+                        agreementIdSet.add(agreementId);
+                    }
+                }
+            });
+            Map<String, PtlAgreement> agreementById = new HashMap<>();
+            if (!agreementIdSet.isEmpty()) {
+                List<PtlAgreement> agreements = ptlAgreementService.listByIds(agreementIdSet);
+                agreementById = agreements.stream()
+                        .collect(Collectors.toMap(PtlAgreement::getId, Function.identity(), (a, b) -> a));
+            }
+            // ============================================================
+
             // ===== 收集所有 change_bs_account 动作的 accountId,批量查询一次(消除串行 RPC) =====
             List<String> accountIds = new ArrayList<>();
             for (PtlAgreementDispatchAction action : ptlAgreementDispatchActions) {
@@ -247,13 +298,14 @@ public class PtlAgreementDispatchServiceImpl extends ServiceImpl<PtlAgreementDis
 
                         attrlink.setMaxName(maxName);
                     }
-                    System.out.printf(attrlink.getAttrCode()+"\n");
-                    PtlAgreementUndwrtRulesAttr undwrtRuleAttrByAttrCode = ptlAgreementUndwrtRulesAttrService.getUndwrtRuleAttrByAttrCode(attrlink.getAttrCode());
+                    // 从索引中取规则属性(N+1-A 消除:原逐条 DB 查询改为 Map get)
+                    PtlAgreementUndwrtRulesAttr undwrtRuleAttrByAttrCode = attrCode2RuleAttr.get(attrlink.getAttrCode());
                     if(undwrtRuleAttrByAttrCode != null){
                         attrlink.setAttrCodeName(undwrtRuleAttrByAttrCode.getAttrName());
                         if(undwrtRuleAttrByAttrCode.getDictId() != null){
                             attrlink.setAttrCodeName(undwrtRuleAttrByAttrCode.getAttrName());
-                            List<PtlAgreementUndwrtRulesDictLabal> dictLabelsByTypeCode = ptlAgreementUndwrtRulesDictLabalService.getDictLabelsByTypeCode(undwrtRuleAttrByAttrCode.getDictId());
+                            // 从索引中取字典标签(N+1-B 消除:原逐条 DB 查询改为 Map get)
+                            List<PtlAgreementUndwrtRulesDictLabal> dictLabelsByTypeCode = dictId2Labels.getOrDefault(undwrtRuleAttrByAttrCode.getDictId(), Collections.emptyList());
 
                             PtlAgreementUndwrtRulesDictLabal ptlAgreementUndwrtRulesDictLabal = dictLabelsByTypeCode.stream().filter(x -> x.getCode().equals(attrlink.getMin())).findFirst().orElse(null);
                             if(ptlAgreementUndwrtRulesDictLabal != null){
@@ -275,7 +327,9 @@ public class PtlAgreementDispatchServiceImpl extends ServiceImpl<PtlAgreementDis
                         action.setActionName(sysDict.getLabel());
                         if("specify_agreement".equals(action.getActionCode())){
                             if(action.getActionJson() != null && !action.getActionJson().isEmpty()) {
-                                PtlAgreement byId = ptlAgreementService.getById(action.getActionJson().get("agreementId").toString());
+                                // N+1-C 消除:从批量查询的索引中取协议(原逐条 getById 改为 Map get)
+                                String agreementId = action.getActionJson().getString("agreementId");
+                                PtlAgreement byId = agreementById.get(agreementId);
                                 if(byId != null) {
                                     action.getActionJson().put("agreementName", byId.getAgreementName());
                                 }
@@ -321,7 +375,9 @@ public class PtlAgreementDispatchServiceImpl extends ServiceImpl<PtlAgreementDis
                     if("specify_agreement".equals(afterAction.getActionCode())){
                         //specify_agreement 指定协议
                         if(afterAction.getActionJson() != null) {
-                            PtlAgreement byId = ptlAgreementService.getById(afterAction.getActionJson().get("agreementId").toString());
+                            // N+1-C 消除:从批量查询的索引中取协议(原逐条 getById 改为 Map get)
+                            String agreementId = afterAction.getActionJson().getString("agreementId");
+                            PtlAgreement byId = agreementById.get(agreementId);
                             if(byId != null) {
                                 afterAction.getActionJson().put("agreementName", byId.getAgreementName());
                             }

+ 120 - 23
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlSchemeServiceImpl.java

@@ -363,7 +363,13 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
 
     @Override
     public boolean deleteScheme(String schemeId) {
-        return ptlSchemeInfoMapper.deleteById(schemeId) > 0;
+        PtlSchemeInfo ptlSchemeInfo = ptlSchemeInfoMapper.selectById(schemeId);
+        if(ptlSchemeInfo != null){
+            ptlSchemeMapper.deleteById(ptlSchemeInfo.getSchemeId());
+            ptlSchemeInfoMapper.deleteById(schemeId);
+            return true;
+        }
+        return false;
     }
 
     @Override
@@ -414,6 +420,14 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
 
     /**
      * 规则过滤保险公司
+     * <p>
+     * 主流程编排方法,按以下步骤执行:
+     * Step1: 根据权限获取所有保司
+     * Step2: 协议和账号状态过滤
+     * Step3: 业务员归属权限过滤
+     * Step4: 方案匹配 + 费用规则校验 + 过滤不匹配方案的保司
+     * Step5: 构建保司结果 VO + 调度规则校验
+     * Step6: 国任车主与投保人不一致时,返回车主约定T0028特约
      *
      * @param schemeRuleFilterVo 过滤保险公司的条件
      * @return List<PtlAgreementInsCompanyVo> 满足报价要求的保险公司以及其下的协议
@@ -425,18 +439,83 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
         String userSystemCode = baseController.getUserSystemCode();
         String systemCode = baseController.getSystemCode();
         log.info("租户匹配保司以及保司下的协议等信息。 schemeRuleFilterVo=[{}],userSystemCode=[{}],systemCode=[{}]", JSONUtil.toJsonStr(schemeRuleFilterVo), userSystemCode, systemCode);
+
+        long totalStart = System.currentTimeMillis();
+
+        // Step1: 根据权限获取所有保司
+        long step1Start = System.currentTimeMillis();
+        log.info("【性能监控】Step1: 根据权限获取所有保司 - 开始");
+        List<EsmInsCompany> companyList = getAuthorizedInsCompanies(systemCode);
+        log.info("【性能监控】Step1: 根据权限获取所有保司 - 结束,耗时[{}]ms", System.currentTimeMillis() - step1Start);
+
+        // Step2: 协议和账号状态过滤
+        long step2Start = System.currentTimeMillis();
+        log.info("【性能监控】Step2: 协议和账号状态过滤 - 开始");
+        filterByAgreementAndAccountStatus(companyList);
+        log.info("【性能监控】Step2: 协议和账号状态过滤 - 结束,耗时[{}]ms", System.currentTimeMillis() - step2Start);
+        log.info("协议/账号状态过滤完成,开始后续业务匹配");
+
+        // Step3: 业务员的归属权限是可选参数,不选的话不进行过滤
+        long step3Start = System.currentTimeMillis();
+        log.info("【性能监控】Step3: 业务员归属权限过滤 - 开始");
+        companyList = salespersonFilterCompanyList(schemeRuleFilterVo, companyList);
+        log.info("【性能监控】Step3: 业务员归属权限过滤 - 结束,耗时[{}]ms", System.currentTimeMillis() - step3Start);
+
+        log.info("根据权限获取到的保司个数是[{}].systemCode=[{}]", CollUtil.size(companyList), systemCode);
+        if (CollUtil.isEmpty(companyList)) {
+            log.info("租户[{}]没有可报价的保险公司,请到平台系统的【租户管理】-【功能配置】页面进行配置并且查看对应公司的协议,费用是否维护好", systemCode);
+            AssertionUtils.isFail(companyList.isEmpty(), "没有可报价的保险公司");
+        }
+
+        // Step4: 根据基本规则匹配方案 + 费用规则校验 + 过滤不匹配方案的保司
+        long step4Start = System.currentTimeMillis();
+        log.info("【性能监控】Step4: 方案匹配+费用规则校验+保司过滤 - 开始");
+        ValidateSchemeRuleVo validateSchemeRuleVo = matchSchemeAndFilterCompany(schemeRuleFilterVo, companyList);
+        log.info("【性能监控】Step4: 方案匹配+费用规则校验+保司过滤 - 结束,耗时[{}]ms", System.currentTimeMillis() - step4Start);
+
+        // Step5: 构建保司结果 VO + 调度规则校验
+        long step5Start = System.currentTimeMillis();
+        log.info("【性能监控】Step5: 构建保司结果VO+调度规则校验 - 开始");
+        List<PtlAgreementInsCompanyVo> resVo = buildResultVoAndApplyDispatch(companyList, validateSchemeRuleVo);
+        log.info("【性能监控】Step5: 构建保司结果VO+调度规则校验 - 结束,耗时[{}]ms", System.currentTimeMillis() - step5Start);
+
+        // Step6: 国任车主与投保人不一致时,返回车主约定T0028特约
+        long step6Start = System.currentTimeMillis();
+        log.info("【性能监控】Step6: 国任T0028特约处理 - 开始");
+        applyOwnerClauseForXDCX(resVo, schemeRuleFilterVo);
+        log.info("【性能监控】Step6: 国任T0028特约处理 - 结束,耗时[{}]ms", System.currentTimeMillis() - step6Start);
+
+        log.info("【性能监控】ruleFilterCompany 全流程结束,总耗时[{}]ms", System.currentTimeMillis() - totalStart);
+        return resVo;
+    }
+
+    /**
+     * Step1: 根据权限获取所有保司
+     *
+     * @param systemCode 系统编码
+     * @return 有权限的保司列表
+     */
+    private List<EsmInsCompany> getAuthorizedInsCompanies(String systemCode) {
         //根据权限获取所有保司
         HttpResult<List<EsmInsCompany>> companyListResult = esmInsCompanyClient.getUserCompany(systemCode);
         log.info("租户[{}]有权限的保司有[{}]个", systemCode, CollUtil.size(companyListResult.getData()));
         AssertionUtils.isFail(companyListResult.getCode() != 200, companyListResult.getMsg());
-        List<EsmInsCompany> companyList = companyListResult.getData();
+        return companyListResult.getData();
+    }
 
+    /**
+     * Step2: 协议和账号状态过滤
+     * <p>
+     * 过滤条件:
+     *   1. 协议必须启用    (isEnable == 0)
+     *   2. 协议必须生效中  (validStatus == "0")
+     *   3. 关联账号必须可用 (useStatus == "0") 且可报价 (quoteStatus == "0")
+     * 过滤后如果某保司没有任何有效协议,会被后续 removeIf 自动排除
+     *
+     * @param companyList 保司列表(原地修改)
+     */
+    private void filterByAgreementAndAccountStatus(List<EsmInsCompany> companyList) {
         // ========================= 协议和账号状态过滤 =========================
-        // 过滤条件:
-        //   1. 协议必须启用    (isEnable == 0)
-        //   2. 协议必须生效中  (validStatus == "0")
-        //   3. 关联账号必须可用 (useStatus == "0") 且可报价 (quoteStatus == "0")
-        // 过滤后如果某保司没有任何有效协议,会被下面的 removeIf 自动排除
         if (CollUtil.isNotEmpty(companyList)) {
             // 1. 收集所有需要校验的归属账号 id
             List<String> attributionIds = companyList.stream()
@@ -485,18 +564,17 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
                 company.setAgreements(validAgreements);
             }
         }
-        log.info("协议/账号状态过滤完成,开始后续业务匹配");
         // ====================================================================
+    }
 
-        // 业务员的归属权限是可选参数,不选的话不进行过滤
-        companyList = salespersonFilterCompanyList(schemeRuleFilterVo, companyList);
-
-        log.info("根据权限获取到的保司个数是[{}].systemCode=[{}]", CollUtil.size(companyList), systemCode);
-        if (CollUtil.isEmpty(companyList)) {
-            log.info("租户[{}]没有可报价的保险公司,请到平台系统的【租户管理】-【功能配置】页面进行配置并且查看对应公司的协议,费用是否维护好", systemCode);
-            AssertionUtils.isFail(companyList.isEmpty(), "没有可报价的保险公司");
-        }
-
+    /**
+     * Step4: 根据基本规则匹配方案 + 费用规则校验 + 过滤不匹配方案的保司
+     *
+     * @param schemeRuleFilterVo 过滤条件
+     * @param companyList 保司列表(原地修改,移除不匹配方案的保司及空协议保司)
+     * @return 方案规则校验 VO(含 quoteVo,供调度规则校验使用)
+     */
+    private ValidateSchemeRuleVo matchSchemeAndFilterCompany(SchemeRuleFilterVo schemeRuleFilterVo, List<EsmInsCompany> companyList) {
         //根据基本规则 能源、车辆类型、使用性质获取方案
         List<PtlScheme> ptlSchemes = ptlSchemeMapper.selectList(new LambdaQueryWrapper<PtlScheme>()
                 .eq(PtlScheme::getEnergyType, schemeRuleFilterVo.getCarInfo().getEnergyTypeCode().equals("0") ? "0" : "1")
@@ -529,6 +607,17 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
         Set<String> companyIdSet = ptlSchemes.stream().map(PtlScheme::getCompanyId).collect(Collectors.toSet());
         // 移除不匹配方案的保司,以及空协议的保司
         companyList.removeIf(company -> !companyIdSet.contains(company.getId()) || CollUtil.isEmpty(company.getAgreements()));
+        return validateSchemeRuleVo;
+    }
+
+    /**
+     * Step5: 构建保司结果 VO + 调度规则校验
+     *
+     * @param companyList 过滤后的保司列表
+     * @param validateSchemeRuleVo 方案规则校验 VO(提供 quoteVo)
+     * @return 保司结果 VO 列表
+     */
+    private List<PtlAgreementInsCompanyVo> buildResultVoAndApplyDispatch(List<EsmInsCompany> companyList, ValidateSchemeRuleVo validateSchemeRuleVo) {
         List<PtlAgreementInsCompanyVo> resVo = new ArrayList<>();
         companyList.forEach(company -> {
             PtlAgreementInsCompanyVo vo = new PtlAgreementInsCompanyVo();
@@ -540,12 +629,11 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
 
         ValidateDispatchRuleVo dispatchRuleVos = new ValidateDispatchRuleVo();
         dispatchRuleVos.setQuoteVo(validateSchemeRuleVo.getQuoteVo());
-        //2.协议调度规则数据拼接(一次批量查询所有保司,避免逐保司重复的全表/字典查询与串行 RPC)
+        //2.协议调度规则数据拼接(同项目内直接走 Service 调用,避免 Feign 自调用的 HTTP 往返与序列化开销)
         List<String> allCompanyIds = companyList.stream().map(EsmInsCompany::getId).collect(Collectors.toList());
         try {
-            HttpResult agreementDispatch = orgClient.getAgreementDispatchByCompanyIds(allCompanyIds);
-            if (agreementDispatch != null && agreementDispatch.getCode() == 200 && agreementDispatch.getData() != null) {
-                PtlAgreementDispatchVo dispatchVoResult = new ObjectMapper().convertValue(agreementDispatch.getData(), PtlAgreementDispatchVo.class);
+            PtlAgreementDispatchVo dispatchVoResult = ptlAgreementDispatchService.getAgreementDispatchByCompanyIds(allCompanyIds);
+            if (dispatchVoResult != null && CollUtil.isNotEmpty(dispatchVoResult.getDispatchVos())) {
                 List<ValidateDispatchRuleVo.DispatchAttrVo> dispatchAttrVos = new ArrayList<>();
                 for (PtlAgreementDispatchVo.DispatchVo vo : dispatchVoResult.getDispatchVos()) {
                     List<PtlAgreementDispatchRulesAttrLink> attrLinks = vo.getAttrLinks();
@@ -556,8 +644,8 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
                     dispatchAttrVo.setCompanyId(vo.getCompanyId());
                     dispatchAttrVo.setSectionGroups(grouped);
                     dispatchAttrVos.add(dispatchAttrVo);
-                    dispatchRuleVos.getDispatchAttrVos().addAll(dispatchAttrVos);
                 }
+                dispatchRuleVos.getDispatchAttrVos().addAll(dispatchAttrVos);
             }
         } catch (Exception e) {
             log.warn("批量查询协议调度异常", e);
@@ -599,6 +687,16 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
                 }
             });
         }
+        return resVo;
+    }
+
+    /**
+     * Step6: 国任车主与投保人不一致时,返回车主约定T0028特约
+     *
+     * @param resVo 保司结果 VO 列表
+     * @param schemeRuleFilterVo 过滤条件(含车主、投保人信息)
+     */
+    private void applyOwnerClauseForXDCX(List<PtlAgreementInsCompanyVo> resVo, SchemeRuleFilterVo schemeRuleFilterVo) {
         // 国任车主与投保人不一致时,返回车主约定T0028特约
         for (PtlAgreementInsCompanyVo company : resVo) {
             if ("XDCX".equals(company.getCode())) {
@@ -624,7 +722,6 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
                 break;
             }
         }
-        return resVo;
     }
     /**
      *  报价公司过滤入参统一校验