Просмотр исходного кода

Merge remote-tracking branch 'origin/dev_jzg_lipf_v20260622' into dev_jzg_lipf_v20260622

lipf 1 месяц назад
Родитель
Сommit
ee676b0eb6
10 измененных файлов с 530 добавлено и 24 удалено
  1. 28 0
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/component/HuaNongCrawlerRequestComponent.java
  2. 1 0
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/constant/enums/RequestUrl.java
  3. 11 0
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/entity/request/HuaNongCrawlerFindBusinessesRequestRequest.java
  4. 73 0
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/entity/request/HuaNongCrawlerQueryReviveOpinionRequest.java
  5. 99 0
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/entity/response/HuaNongCrawlerQueryReviveOpinionResponse.java
  6. 306 15
      tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/service/impl/HuaNongCrawlerRequestImpl.java
  7. 9 6
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java
  8. 1 1
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsOrdersServiceImpl.java
  9. 1 1
      tenant/insurance/quotation-zhongan/src/main/java/com/jzg/quotation/zhongan/crawler/component/ZhongAnCrawlerCacheComponent.java
  10. 1 1
      tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/entity/Request/ZmCrawlerQuotedPriceRequest.java

+ 28 - 0
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/component/HuaNongCrawlerRequestComponent.java

@@ -449,6 +449,34 @@ public class HuaNongCrawlerRequestComponent {
         return post(FIND_BUSINESSES, request, HuaNongCrawlerFindBusinessesResponse.class, headers);
     }
 
+    /**
+     * 查询核保意见
+     *
+     * @param request 核保意见查询请求
+     * @param headers 请求头
+     * @return 核保意见
+     */
+    public HuaNongCrawlerQueryReviveOpinionResponse queryReviveOpinion(
+            HuaNongCrawlerQueryReviveOpinionRequest request, HttpHeaders headers) {
+        HuaNongCrawlerRequestBaseHead head = request.getHead();
+        head.setTransCode(QUERY_REVIVE_OPINION.getUri());
+        JSONObject json = JSONObject.from(request);
+        RequestEncryptor.processRequest(json);
+        String url = properties.getUrl() + "?" + QUERY_REVIVE_OPINION.getRequestSuffix();
+        ResponseEntity<String> responseEntity = requestComponent.stringPost(url, json, String.class, headers);
+        String responseBody = responseEntity.getBody();
+        HuaNongCrawlerQueryReviveOpinionResponse response = JSON.parseObject(
+                responseBody, HuaNongCrawlerQueryReviveOpinionResponse.class);
+        HuaNongCrawlerBaseResponseHead responseHead = Optional.ofNullable(response)
+                .map(HuaNongHeadResponse::getHead)
+                .orElseThrow(() -> new SystemException(QUERY_REVIVE_OPINION.getRequestDescription() + "异常"));
+        if (!"0".equals(responseHead.getReturnCode())) {
+            throw new SystemException(responseHead.getReturnMessage());
+        }
+        response.setRawResponseBody(responseBody);
+        return response;
+    }
+
     /**
      * 获取支付链接
      *

+ 1 - 0
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/constant/enums/RequestUrl.java

@@ -44,6 +44,7 @@ public enum RequestUrl implements CrawlerRequestUrlStandard {
     SUBMIT("Submit", "提交核保", "Submit"),
     // 状态查询
     FIND_BUSINESSES("FindBusinesses", "订单状态查询", "FindBusinesses"),
+    QUERY_REVIVE_OPINION("QUERYREVIVEOPINION", "查询核保意见", "QUERYREVIVEOPINION"),
     // 保单下载
     FTP_DOWNLOAD("FTPDOWLOAD", "保单下载", "ftpDowload.do"),
     DOCUMENTS_PRINT("DocumentsPrint", "非车险保单下载", "DocumentsPrint.do"),

+ 11 - 0
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/entity/request/HuaNongCrawlerFindBusinessesRequestRequest.java

@@ -126,6 +126,17 @@ public class HuaNongCrawlerFindBusinessesRequestRequest implements HuaNongHeadRe
     @JsonProperty("expireDateEnd")
     private String expireDateEnd;
 
+    public static HuaNongCrawlerFindBusinessesRequestRequest forProposalStatus(
+            String token, String loginCom, String comCode, String proposalNo, String recordingTime) {
+        HuaNongCrawlerFindBusinessesRequestRequest request =
+                new HuaNongCrawlerFindBusinessesRequestRequest(token, loginCom, proposalNo, recordingTime);
+        request.setBusinessType("2");
+        request.setQueryStatus(List.of("0"));
+        request.setComCode(comCode);
+        request.setProposalNo(proposalNo);
+        return request;
+    }
+
     public HuaNongCrawlerFindBusinessesRequestRequest(String token, String loginCom, String policyNo, String recordingTime) {
         this.head = new HuaNongCrawlerRequestBaseHead(token);
         this.loginCom = loginCom;

+ 73 - 0
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/entity/request/HuaNongCrawlerQueryReviveOpinionRequest.java

@@ -0,0 +1,73 @@
+package com.jzg.quotation.huanong.crawler.entity.request;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * 华农财险核保意见查询请求
+ */
+@NoArgsConstructor
+@Data
+public class HuaNongCrawlerQueryReviveOpinionRequest implements HuaNongHeadRequest {
+
+    @Serial
+    private static final long serialVersionUID = 8309830212240733031L;
+
+    @JsonProperty("head")
+    private HuaNongCrawlerRequestBaseHead head;
+
+    @JsonProperty("operateType")
+    private String operateType;
+
+    @JsonProperty("contractMain")
+    private List<ContractMainDTO> contractMain;
+
+    @JsonProperty("loginCom")
+    private String loginCom;
+
+    public HuaNongCrawlerQueryReviveOpinionRequest(String token, String loginCom,
+                                                    String ciPolicyNumber, String biPolicyNumber) {
+        this.head = new HuaNongCrawlerRequestBaseHead(token);
+        this.operateType = "-2";
+        this.loginCom = loginCom;
+        this.contractMain = new ArrayList<>();
+
+        Set<String> contractNumbers = new LinkedHashSet<>();
+        addContractNumber(contractNumbers, ciPolicyNumber);
+        addContractNumber(contractNumbers, biPolicyNumber);
+        contractNumbers.forEach(contractNo -> this.contractMain.add(new ContractMainDTO(contractNo)));
+    }
+
+    private static void addContractNumber(Set<String> contractNumbers, String contractNo) {
+        if (contractNo != null && !contractNo.trim().isEmpty()) {
+            contractNumbers.add(contractNo.trim());
+        }
+    }
+
+    @NoArgsConstructor
+    @Data
+    public static class ContractMainDTO {
+
+        @JsonProperty("businessType")
+        private String businessType;
+
+        @JsonProperty("contractNo")
+        private String contractNo;
+
+        @JsonProperty("remark")
+        private String remark;
+
+        public ContractMainDTO(String contractNo) {
+            this.businessType = "2";
+            this.contractNo = contractNo;
+            this.remark = "";
+        }
+    }
+}

+ 99 - 0
tenant/insurance/quotation-huanong/src/main/java/com/jzg/quotation/huanong/crawler/entity/response/HuaNongCrawlerQueryReviveOpinionResponse.java

@@ -0,0 +1,99 @@
+package com.jzg.quotation.huanong.crawler.entity.response;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 华农财险核保意见查询响应
+ */
+@NoArgsConstructor
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class HuaNongCrawlerQueryReviveOpinionResponse implements HuaNongHeadResponse {
+
+    @Serial
+    private static final long serialVersionUID = 2210550128695116894L;
+
+    @JsonProperty("head")
+    private HuaNongCrawlerBaseResponseHead head;
+
+    @JsonProperty("appliResult")
+    private List<AppliResultDTO> appliResult;
+
+    @JsonIgnore
+    private String rawResponseBody;
+
+    public List<OpinionDTO> getOpinions() {
+        List<OpinionDTO> opinions = new ArrayList<>();
+        if (appliResult == null) {
+            return opinions;
+        }
+        for (AppliResultDTO result : appliResult) {
+            if (result == null || result.getUwtionInfo() == null
+                    || result.getUwtionInfo().getData() == null
+                    || result.getUwtionInfo().getData().getData() == null) {
+                continue;
+            }
+            opinions.addAll(result.getUwtionInfo().getData().getData());
+        }
+        return opinions;
+    }
+
+    @NoArgsConstructor
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class AppliResultDTO {
+
+        @JsonProperty("uwtionInfo")
+        private UwtionInfoDTO uwtionInfo;
+    }
+
+    @NoArgsConstructor
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class UwtionInfoDTO {
+
+        @JsonProperty("data")
+        private OpinionDataDTO data;
+    }
+
+    @NoArgsConstructor
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class OpinionDataDTO {
+
+        @JsonProperty("data")
+        private List<OpinionDTO> data;
+    }
+
+    @NoArgsConstructor
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class OpinionDTO {
+
+        @JsonProperty("flag")
+        private String flag;
+
+        @JsonProperty("sHTime")
+        private String sHTime;
+
+        @JsonProperty("notionCode")
+        private String notionCode;
+
+        @JsonProperty("currentGradeName")
+        private String currentGradeName;
+
+        @JsonProperty("operateUserName")
+        private String operateUserName;
+
+        @JsonProperty("handleText")
+        private String handleText;
+    }
+}

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

@@ -687,11 +687,11 @@ public class HuaNongCrawlerRequestImpl implements HuaNongCrawlerRequest {
             String ciPolicyNumber = huaNongCrawlerOrder.getCiPolicyNumber();
             String biPolicyNumber = huaNongCrawlerOrder.getBiPolicyNumber();
 
-            // 获取支付链接
+           /* // 获取支付链接
             String payUrl = huaNongCrawlerRequestComponent.getPayUrl(ciPolicyNumber, biPolicyNumber, username);
-            log.info("支付链接获取完成: {}", payUrl);
+            log.info("支付链接获取完成: {}", payUrl);*/
             // 返回转人工结果
-            return convertToUnderwritingResultsVo(manualSubmit, payUrl);
+            return convertToUnderwritingResultsVo(submit, "");
         }
 
         // 正常核保:获取支付链接
@@ -782,7 +782,9 @@ public class HuaNongCrawlerRequestImpl implements HuaNongCrawlerRequest {
                     underwritingResultsVo.setSyApplyNo(contractNo);
                 }
                 // 状态转换:以第一个为准
-                if (underwritingResultsVo.getUnderwritingStatus() == null) {
+                if ("9".equals(status)) {
+                    underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.STAFF_PROCESS);
+                }else if("8".equals(status)){
                     underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
                 }
                 // 拼接核保意见
@@ -966,20 +968,309 @@ public class HuaNongCrawlerRequestImpl implements HuaNongCrawlerRequest {
         return downloadPolicyResultVo;
     }
 
+    /**
+     * 查询华农订单状态。
+     *
+     * <p>先通过 FindBusinesses 定位当前投保单;待缴费状态直接生成支付链接,
+     * 其他状态继续查询核保意见,并转换为系统统一订单状态。</p>
+     *
+     * @param orderStatusVo 订单状态查询参数
+     * @return 系统统一订单状态、投保单号、保单号及审核信息
+     */
     @Override
     public OrderStatusResultVo queryOrderState(OrderStatusVo orderStatusVo) {
-        LoginInfoBO loginInfoDto = getLoginInfoDto(orderStatusVo.getOrder().getAttributionInformationVo());
-        HttpHeaders httpHeaders = new HttpHeaders();
+        // 查询依赖订单号和归属账号,缺少任一关键参数都无法调用华农接口
+        if (orderStatusVo == null || orderStatusVo.getOrder() == null
+                || orderStatusVo.getOrder().getAttributionInformationVo() == null
+                || !StringUtils.hasText(orderStatusVo.getOrder().getOrdersNo())) {
+            throw new SystemException("华农订单状态查询参数不完整");
+        }
+
+        // 获取华农登录信息和本地持久化的投保单、保单数据
+        AttributionInformationVo attributionInformationVo = orderStatusVo.getOrder().getAttributionInformationVo();
+        LoginInfoBO loginInfoDto = getLoginInfoDto(attributionInformationVo);
         HuaNongCrawlerOrder order = huaNongCrawlerOrderService.getById(orderStatusVo.getOrder().getOrdersNo());
-        String policyNo = StringUtils.hasText(order.getBiPolicyNumber()) ? order.getBiPolicyNumber() : order.getCiPolicyNumber();
-        HuaNongCrawlerFindBusinessesRequestRequest huaNongCrawlerFindBusinessesRequest
-                = new HuaNongCrawlerFindBusinessesRequestRequest(loginInfoDto.getToken(), loginInfoDto.getLoginComCode(), policyNo, order.getRecordingTime());
-        HuaNongCrawlerFindBusinessesResponse businesses = huaNongCrawlerRequestComponent.findBusinesses(huaNongCrawlerFindBusinessesRequest, httpHeaders);
-        OrderStatusResultVo vo = new OrderStatusResultVo();
-        vo.setOrderStatus(InsOrderStatusEnum.UNDERWRITED);
-        vo.setJqPolicyno(order.getCiPolicyNumber());
-        vo.setJyPolicyno(order.getNiInfos().get(0).getNiPolicyNumber());
-        return vo;
+        if (order == null) {
+            throw new SystemException("未找到华农本地订单信息");
+        }
+
+        // 先按字段真实语义回填三类投保单号和已存在的保单号
+        OrderStatusResultVo result = buildOrderStatusResult(order);
+
+        // FindBusinesses 每次按一个投保单号查询,优先商业险,其次交强险和非车险
+        String niPolicyNumber = getFirstNiNumber(order.getNiInfos(), false);
+        String proposalNo = firstNonBlank(order.getBiPolicyNumber(), order.getCiPolicyNumber(), niPolicyNumber);
+        if (!StringUtils.hasText(proposalNo)) {
+            setOrderStatus(result, InsOrderStatusEnum.AUDIT_ING);
+            result.setErrorMsg("华农订单状态查询失败:本地未保存投保单号");
+            return result;
+        }
+
+        // comCode 使用归属机构编码;未配置时回退到登录机构编码
+        String comCode = loginInfoDto.getParameters() != null
+                ? loginInfoDto.getParameters().getComCode() : null;
+        if (!StringUtils.hasText(comCode)) {
+            comCode = loginInfoDto.getLoginComCode();
+        }
+        // 按 Python 逻辑使用投保单模式查询未完成业务:businessType=2、queryStatus=0
+        HuaNongCrawlerFindBusinessesRequestRequest findRequest =
+                HuaNongCrawlerFindBusinessesRequestRequest.forProposalStatus(
+                        loginInfoDto.getToken(), loginInfoDto.getLoginComCode(), comCode,
+                        proposalNo, order.getRecordingTime());
+        HuaNongCrawlerFindBusinessesResponse businesses =
+                huaNongCrawlerRequestComponent.findBusinesses(findRequest, new HttpHeaders());
+
+        // 响应可能包含多条业务,必须用本地交强、商业、非车投保单号精确匹配当前订单
+        Set<String> proposalNumbers = collectProposalNumbers(order);
+        HuaNongCrawlerFindBusinessesResponse.BusinessDTO matchedBusiness =
+                findMatchedBusiness(businesses, proposalNumbers);
+        if (matchedBusiness == null) {
+            setOrderStatus(result, InsOrderStatusEnum.AUDIT_ING);
+            result.setErrorMsg(businesses == null || businesses.getBusiness() == null
+                    || businesses.getBusiness().isEmpty()
+                    ? "华农订单状态查询失败:未返回业务记录"
+                    : "华农订单状态查询失败:返回记录未匹配本地投保单号");
+            return result;
+        }
+
+        // 华农业务状态8表示核保已通过、可以缴费,不代表已经生成正式保单
+        if ("8".equals(matchedBusiness.getStatus())) {
+            setOrderStatus(result, InsOrderStatusEnum.PAY_PENDING);
+            result.setPaymentLink(buildPayUrl(order, attributionInformationVo.getUsername()));
+            return result;
+        }
+
+        // 非待缴费状态继续查询核保意见;与 Python 一致,仅提交交强和商业投保单号
+        HuaNongCrawlerQueryReviveOpinionRequest opinionRequest =
+                new HuaNongCrawlerQueryReviveOpinionRequest(
+                        loginInfoDto.getToken(), loginInfoDto.getLoginComCode(),
+                        order.getCiPolicyNumber(), order.getBiPolicyNumber());
+        if (opinionRequest.getContractMain().isEmpty()) {
+            setOrderStatus(result, InsOrderStatusEnum.STAFF_PROCESS);
+            result.setErrorMsg("审核中:缺少可用于核保意见查询的车险投保单号");
+            return result;
+        }
+
+        HuaNongCrawlerQueryReviveOpinionResponse opinionResponse =
+                huaNongCrawlerRequestComponent.queryReviveOpinion(opinionRequest, new HttpHeaders());
+
+        // flag=1 表示核保退回或失败,优先级高于其他审核结果
+        Optional<HuaNongCrawlerQueryReviveOpinionResponse.OpinionDTO> rejectedOpinion =
+                findOpinion(opinionResponse, "1");
+        if (rejectedOpinion.isPresent()) {
+            setOrderStatus(result, InsOrderStatusEnum.AUDIT_FAIL);
+            result.setErrorMsg(formatOpinion(rejectedOpinion.get(), true));
+            return result;
+        }
+
+        // flag=0 表示需要人工处理,保留审核人员返回的详细信息
+        Optional<HuaNongCrawlerQueryReviveOpinionResponse.OpinionDTO> manualOpinion =
+                findOpinion(opinionResponse, "0");
+        if (manualOpinion.isPresent()) {
+            setOrderStatus(result, InsOrderStatusEnum.STAFF_PROCESS);
+            result.setErrorMsg(formatOpinion(manualOpinion.get(), false));
+            return result;
+        }
+
+        // 未返回 flag 时,按 Python 规则同时检查 nodeStatus=0 和“同意承保”
+        if (isUnderwritingApproved(opinionResponse)) {
+            setOrderStatus(result, InsOrderStatusEnum.UNDERWRITED);
+            result.setPaymentLink(buildPayUrl(order, attributionInformationVo.getUsername()));
+            return result;
+        }
+
+        // 没有明确失败、人工或通过标记时,订单仍处于核保处理中
+        setOrderStatus(result, InsOrderStatusEnum.AUDIT_ING);
+        result.setErrorMsg("审核中");
+        return result;
+    }
+
+    /**
+     * 使用本地订单初始化状态查询结果,严格区分投保单号和正式保单号。
+     */
+    private OrderStatusResultVo buildOrderStatusResult(HuaNongCrawlerOrder order) {
+        OrderStatusResultVo result = new OrderStatusResultVo();
+        setOrderStatus(result, InsOrderStatusEnum.AUDIT_ING);
+        result.setJqApplyNo(normalize(order.getCiPolicyNumber()));
+        result.setSyApplyNo(normalize(order.getBiPolicyNumber()));
+        result.setJyApplyNo(getFirstNiNumber(order.getNiInfos(), false));
+        result.setJqPolicyno(normalize(order.getCiPolicyNo()));
+        result.setSyPolicyno(normalize(order.getBiPolicyNo()));
+        result.setJyPolicyno(getFirstNiNumber(order.getNiInfos(), true));
+        return result;
+    }
+
+    /**
+     * 获取第一条有效的非车险号码。
+     *
+     * @param niInfos 非车险信息列表
+     * @param policyNo true 获取正式保单号,false 获取投保单号
+     */
+    private String getFirstNiNumber(List<HuaNongCrawlerOrder.NiInfo> niInfos, boolean policyNo) {
+        if (niInfos == null) {
+            return null;
+        }
+        return niInfos.stream()
+                .filter(Objects::nonNull)
+                .map(niInfo -> policyNo ? niInfo.getNiPolicyNo() : niInfo.getNiPolicyNumber())
+                .map(this::normalize)
+                .filter(StringUtils::hasText)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 收集当前订单的全部投保单号,用于从华农业务列表中精确定位订单。
+     */
+    private Set<String> collectProposalNumbers(HuaNongCrawlerOrder order) {
+        Set<String> proposalNumbers = new LinkedHashSet<>();
+        addIfHasText(proposalNumbers, order.getCiPolicyNumber());
+        addIfHasText(proposalNumbers, order.getBiPolicyNumber());
+        if (order.getNiInfos() != null) {
+            order.getNiInfos().stream()
+                    .filter(Objects::nonNull)
+                    .map(HuaNongCrawlerOrder.NiInfo::getNiPolicyNumber)
+                    .forEach(number -> addIfHasText(proposalNumbers, number));
+        }
+        return proposalNumbers;
+    }
+
+    /**
+     * 标准化号码并加入集合,统一过滤 null、空串和重复号码。
+     */
+    private void addIfHasText(Set<String> numbers, String number) {
+        String normalized = normalize(number);
+        if (StringUtils.hasText(normalized)) {
+            numbers.add(normalized);
+        }
+    }
+
+    /**
+     * 按华农响应顺序查找第一条与本地投保单号匹配的业务记录。
+     */
+    private HuaNongCrawlerFindBusinessesResponse.BusinessDTO findMatchedBusiness(
+            HuaNongCrawlerFindBusinessesResponse response, Set<String> proposalNumbers) {
+        if (response == null || response.getBusiness() == null || proposalNumbers.isEmpty()) {
+            return null;
+        }
+        for (HuaNongCrawlerFindBusinessesResponse.BusinessDTO business : response.getBusiness()) {
+            if (business != null && isBusinessMatched(business, proposalNumbers)) {
+                return business;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 检查华农业务记录的 contractNo1 至 contractNo6 是否命中本地投保单号。
+     */
+    private boolean isBusinessMatched(HuaNongCrawlerFindBusinessesResponse.BusinessDTO business,
+                                      Set<String> proposalNumbers) {
+        return Arrays.asList(
+                        business.getContractNo1(), business.getContractNo2(), business.getContractNo3(),
+                        business.getContractNo4(), business.getContractNo5(), business.getContractNo6())
+                .stream()
+                .map(this::normalize)
+                .filter(StringUtils::hasText)
+                .anyMatch(proposalNumbers::contains);
+    }
+
+    /**
+     * 根据华农审核标志查找第一条意见:1 为失败,0 为人工处理。
+     */
+    private Optional<HuaNongCrawlerQueryReviveOpinionResponse.OpinionDTO> findOpinion(
+            HuaNongCrawlerQueryReviveOpinionResponse response, String flag) {
+        if (response == null) {
+            return Optional.empty();
+        }
+        return response.getOpinions().stream()
+                .filter(Objects::nonNull)
+                .filter(opinion -> flag.equals(opinion.getFlag()))
+                .findFirst();
+    }
+
+    /**
+     * 将核保意见转换为前端可直接展示的多行中文信息。
+     */
+    private String formatOpinion(HuaNongCrawlerQueryReviveOpinionResponse.OpinionDTO opinion,
+                                 boolean includeGrade) {
+        StringBuilder message = new StringBuilder();
+        appendOpinionLine(message, "审核时间", opinion.getSHTime());
+        appendOpinionLine(message, "审核信息", opinion.getNotionCode());
+        if (includeGrade) {
+            appendOpinionLine(message, "审核级别名称", opinion.getCurrentGradeName());
+        }
+        appendOpinionLine(message, "操作员名称", opinion.getOperateUserName());
+        appendOpinionLine(message, "详细审核信息", opinion.getHandleText());
+        return message.length() > 0 ? message.toString() : "审核状态已更新";
+    }
+
+    /**
+     * 仅追加有值的审核字段,避免结果中出现空行或 null 文本。
+     */
+    private void appendOpinionLine(StringBuilder message, String label, String value) {
+        if (!StringUtils.hasText(value)) {
+            return;
+        }
+        if (message.length() > 0) {
+            message.append('\n');
+        }
+        message.append(label).append(':').append(value);
+    }
+
+    /**
+     * 按 Python 判断规则确认核保通过:响应必须同时包含 nodeStatus=0 和“同意承保”。
+     */
+    private boolean isUnderwritingApproved(HuaNongCrawlerQueryReviveOpinionResponse response) {
+        if (response == null || !StringUtils.hasText(response.getRawResponseBody())) {
+            return false;
+        }
+        String responseBody = response.getRawResponseBody();
+        boolean approvedNode = Pattern.compile("\\\"nodeStatus\\\"\\s*:\\s*\\\"?0\\\"?")
+                .matcher(responseBody)
+                .find();
+        return approvedNode && responseBody.contains("同意承保");
+    }
+
+    /**
+     * 校验支付链接必要参数后,复用组件生成与 Python 一致的 Base64 支付地址。
+     */
+    private String buildPayUrl(HuaNongCrawlerOrder order, String username) {
+        if (!StringUtils.hasText(username)) {
+            throw new SystemException("华农支付链接生成失败:操作员账号为空");
+        }
+        if (!StringUtils.hasText(order.getCiPolicyNumber())
+                && !StringUtils.hasText(order.getBiPolicyNumber())) {
+            throw new SystemException("华农支付链接生成失败:交强险和商业险投保单号均为空");
+        }
+        return huaNongCrawlerRequestComponent.getPayUrl(
+                order.getCiPolicyNumber(), order.getBiPolicyNumber(), username);
+    }
+
+    /**
+     * 同步设置枚举状态及其字符串编码,避免两个返回字段不一致。
+     */
+    private void setOrderStatus(OrderStatusResultVo result, InsOrderStatusEnum status) {
+        result.setOrderStatus(status);
+        result.setOrderStatusStr(status.getCode());
+    }
+
+    /**
+     * 按业务优先级返回第一个非空号码。
+     */
+    private String firstNonBlank(String... values) {
+        return Arrays.stream(values)
+                .map(this::normalize)
+                .filter(StringUtils::hasText)
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 去除号码首尾空白,保留 null 语义供上层统一判空。
+     */
+    private String normalize(String value) {
+        return value == null ? null : value.trim();
     }
 
     /**

+ 9 - 6
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java

@@ -853,7 +853,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
             Map<String, Object> body = exchangeForm(url, Map.of("cmd", "submitZRG"), data, buildManualUwHeaders());
 
             UnderwritingResultsVo r = new UnderwritingResultsVo();
-            r.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
+            r.setUnderwritingStatus(InsOrderStatusEnum.AUDIT_FAIL);
             r.setCompanyOrderNo(str(body, "quotationno"));
             r.setJqApplyNo(str(body, "proposal_no_jq"));
             r.setSyApplyNo(str(body, "proposal_no_sy"));
@@ -872,7 +872,9 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                 return r;
             }
             int s = html.indexOf("原因"), e = html.lastIndexOf("。");
-            throw new SystemException("审核未通过:" + (s >= 0 && e > s ? html.substring(s, e) : "审核未通过"));
+            // throw new SystemException("审核未通过:" + (s >= 0 && e > s ? html.substring(s, e) : "审核未通过"));
+            r.setUnderwritingResult(s >= 0 && e > s ? html.substring(s, e) : "审核未通过");
+            return r;
         } catch (Exception e) { throw new SystemException(e.getMessage()); }
     }
 
@@ -1018,19 +1020,20 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         String body = post(ORIGIN + "/htcsp/DocumentPrintAction.do?cmd=electronicPolicyEndorseDownload",
                 buildDocumentHeaders(req.getCookie()), fd);
         HtCrawlerDownloadPolicyResponse res = JSONObject.parseObject(body, HtCrawlerDownloadPolicyResponse.class);
-        log.info("下载保单响应结果,{}", JSONObject.toJSONString(res));
+        log.info("华泰电子保单---订单号{},保单号{},下载保单响应结果,{}", order.getOrdersNo(), no, JSONObject.toJSONString(res));
         DownloadPolicyResultVo r = new DownloadPolicyResultVo();
         List<HtCrawlerDownloadPolicyResponse.FtpdownloadJson> ftpdownloadJson = res.getFtpdownloadJson();
         if(!ftpdownloadJson.isEmpty()) {
             if (jq) {
                 r.setJqUrl(ftpdownloadJson.get(0).getDownurl());
                 r.setJqFlagUrl(ftpdownloadJson.get(1).getBzdownurl());
-                log.info("下载交强保单地址:{}", r.getJqUrl());
+                log.info("华泰电子保单---订单号{},保单号{},下载交强保单地址:{}", order.getOrdersNo(), no, r.getJqUrl());
             } else {
                 r.setSyUrl(ftpdownloadJson.get(0).getDownurl());
-                log.info("下载商业保单地址:{}", r.getSyUrl());
+                log.info("华泰电子保单---订单号{},保单号{},下载商业保单地址:{}", order.getOrdersNo(), no, r.getSyUrl());
             }
         }
+        // 下载驾意险保单
         if (StringUtils.isNotBlank(order.getJyPolicyno())) {
             // 1. 构造请求头
             HttpHeaders headers = getHeaders();
@@ -1116,7 +1119,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         String jqApplication = "";
         String fcxApplication = "";
         if ("已通过核保已实收".equals(orderStatus)) {
-            orderStatusEnum = InsOrderStatusEnum.valueOf("PAY_PENDING");
+            orderStatusEnum = InsOrderStatusEnum.valueOf("UNDERWRITED"); // 已承保
             orderStatus = orderStatusEnum.getCode();
             List<Map<String, String>> fcxList = new ArrayList<>();
             // 1. 构建请求头 headers

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

@@ -1008,7 +1008,7 @@ public class InsOrdersServiceImpl extends ServiceImpl<InsOrdersMapper, InsOrders
                                 kind.setPremium(new BigDecimal(kindInfoVo.getPremium()));
                             }
                         }
-                        if (kind.getKindCode().equals("A")) {
+                        if (kind.getKindCode().equals("A") && quoteResponse.getCarActualValue() != null) {
                             kind.setAmount(new BigDecimal(String.valueOf(quoteResponse.getCarActualValue())));
                         }
                     });

+ 1 - 1
tenant/insurance/quotation-zhongan/src/main/java/com/jzg/quotation/zhongan/crawler/component/ZhongAnCrawlerCacheComponent.java

@@ -145,7 +145,7 @@ public class ZhongAnCrawlerCacheComponent {
 
             Headers postHeaders = postHeaderBuilder.build();
 
-            String urlPost = "https://www.yuanqingtech.com/dmapi/dm-innovation-front/api/config-info/findIssuingLink";
+            String urlPost = "https://czxx.yuanqingtech.com/dmapi/dm-innovation-front/api/config-info/findIssuingLink";
 
             FirstReqData reqData = new FirstReqData();
             reqData.setInviterAccountId(zhongAnId);

+ 1 - 1
tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/entity/Request/ZmCrawlerQuotedPriceRequest.java

@@ -1177,7 +1177,7 @@ public class ZmCrawlerQuotedPriceRequest extends BaseRequest {
                     }
                     this.expiryDate = riskInfoVo.getEndDate();
                     this.argueSolution = "1";
-                    if (riskInfoVo.isInstantFlag() || !riskInfoVo.getStartDate().endsWith("00:00:00")) {
+                    if (riskInfoVo.isInstantFlag() && !riskInfoVo.getStartDate().endsWith("00:00:00")) {
                         this.reinsuranceFlag = "1";
                     } else {
                         this.reinsuranceFlag = "0";