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

Merge remote-tracking branch 'origin/master'

wanghao 3 месяцев назад
Родитель
Сommit
7172459d3e
46 измененных файлов с 1762 добавлено и 715 удалено
  1. 4 0
      commons/src/main/java/com/jzg/commons/entity/finance/dto/InsPlyIncomeDto.java
  2. 5 0
      commons/src/main/java/com/jzg/commons/entity/finance/po/InsPlyIncome.java
  3. 19 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/FollowCompanyFeeVO.java
  4. 6 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/IncomeDataVO.java
  5. 5 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/InvoiceRecordResult.java
  6. 30 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/QueryFollowIncomeVo.java
  7. 5 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/ReceivableQueryVo.java
  8. 3 0
      commons/src/main/java/com/jzg/commons/entity/finance/vo/SettlementReportQueryVo.java
  9. 63 0
      commons/src/main/java/com/jzg/commons/entity/orders/vo/FeeAuditVo.java
  10. 12 0
      commons/src/main/java/com/jzg/commons/entity/po/InsFeeOrders.java
  11. 7 0
      commons/src/main/java/com/jzg/commons/entity/scheme/po/PtlSchemeInsuranceDrivingIntention.java
  12. 2 0
      commons/src/main/java/com/jzg/commons/entity/scheme/vo/PtlSchemeSaveVo.java
  13. 2 0
      commons/src/main/java/com/jzg/commons/entity/scheme/vo/SchemeResultVo.java
  14. 19 0
      commons/src/main/java/com/jzg/commons/util/BigDecimalUtil.java
  15. 189 38
      commons/src/main/java/com/jzg/commons/util/ImageUtil.java
  16. 69 17
      platform/src/main/java/com/jzg/controller/FileUploadController.java
  17. 73 19
      tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/service/impl/AnShengCrawlerRequestImpl.java
  18. 7 2
      tenant/insurance/quotation-chengtai/src/main/java/com/jzg/quotation/chengtai/api/service/impl/ChengTaiApiRequestImpl.java
  19. 2 1
      tenant/insurance/quotation-dajia/src/main/java/com/jzg/quotation/dajia/crawler/service/impl/DaJiaCrawlerQuoteProcessServiceImpl.java
  20. 1 1
      tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/entity/request/GuoRenCrawlerCalculationPremiumRequest.java
  21. 2 0
      tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/service/Impl/HengBangCrawlerRequestImpl.java
  22. 7 4
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/build/HuaTaiCrawlerQuoteResultsVoBuild.java
  23. 18 11
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerOrderStateRequest.java
  24. 6 6
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerSubmitRequest.java
  25. 23 0
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/po/HtCrawlerOrder.java
  26. 171 20
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java
  27. 4 3
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/aspect/QuoteVerificationAspect.java
  28. 29 34
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsFeeOrdersServiceImpl.java
  29. 8 3
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/build/TkCrawlerQuoteResultsBuild.java
  30. 1 1
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/model/api/TKEntranceFor000038Result.java
  31. 1 1
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/model/entity/TKOrderData.java
  32. 100 0
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/TKCommonUtil.java
  33. 103 114
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/TKHttpAPIService.java
  34. 341 258
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/TKSimpleService.java
  35. 33 29
      tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/impl/TKQuotationBaseServiceImpl.java
  36. 1 1
      tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/service/impl/TaiShanCrawlerQuoteProcessServiceImpl.java
  37. 2 2
      tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/service/impl/TaiShanCrawlerRequestImpl.java
  38. 31 21
      tenant/organization/src/main/java/com/jzg/organization/controller/ReceivableController.java
  39. 2 6
      tenant/organization/src/main/java/com/jzg/organization/mapper/ReceivableMapper.java
  40. 1 1
      tenant/organization/src/main/java/com/jzg/organization/service/ReceivableService.java
  41. 53 1
      tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlSchemeServiceImpl.java
  42. 248 70
      tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java
  43. 13 6
      tenant/organization/src/main/resources/mapper/InsFeeFollowOrderMapper.xml
  44. 3 1
      tenant/organization/src/main/resources/mapper/PtlSchemeInsuranceDrivingIntentionMapper.xml
  45. 5 1
      tenant/organization/src/main/resources/mapper/PtlSchemeMapper.xml
  46. 33 43
      tenant/organization/src/main/resources/mapper/ReceivableMapper.xml

+ 4 - 0
commons/src/main/java/com/jzg/commons/entity/finance/dto/InsPlyIncomeDto.java

@@ -58,6 +58,10 @@ public class InsPlyIncomeDto extends BaseModel {
     @TableField(exist = false)
     private String signYear;
 
+    @Schema(description = "签单月份")
+    @TableField(exist = false)
+    private String signMonth;
+
     @Schema(description = "签单日期 yyyy-mm-dd 格式")
     @TableField(exist = false)
     private String signDate;

+ 5 - 0
commons/src/main/java/com/jzg/commons/entity/finance/po/InsPlyIncome.java

@@ -10,6 +10,7 @@ import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 import java.io.Serial;
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 
 @EqualsAndHashCode(callSuper = true)
@@ -341,5 +342,9 @@ public class InsPlyIncome extends BaseModel {
     @ExcelIgnore
     private String jyTotalProportion;
 
+    @TableField(exist = false)
+    @Schema(description = "非车应收金额")
+    private BigDecimal jyTotalPremiums;
+
 
 }

+ 19 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/FollowCompanyFeeVO.java

@@ -15,6 +15,10 @@ public class FollowCompanyFeeVO {
 
     private String companyId;
 
+    private String year;
+
+    private String month;
+
     @Schema(description = "对接人")
     private String contactPerson;
 
@@ -47,4 +51,19 @@ public class FollowCompanyFeeVO {
     /** 累计应收  totalReceivable */
     private BigDecimal totalReceivable;
 
+    /**
+     * 开票记录id
+     */
+    private String invoiceId;
+
+    /**
+     * 结算记录id
+     */
+    private String settlementId;
+
+    /**
+     * 跟单id
+     */
+    private String followId;
+
 }

+ 6 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/IncomeDataVO.java

@@ -46,6 +46,12 @@ public class IncomeDataVO implements Serializable {
     private String contactPersonPhone;
 
 
+    /**
+     * 类型:1:手续费 2:跟单费 3:驾意手续费 4:驾意跟单费
+     */
+    private String invoiceType;
+
+
     @Data
     public static class SearchVO implements Serializable {
         @Serial

+ 5 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/InvoiceRecordResult.java

@@ -148,4 +148,9 @@ public class InvoiceRecordResult extends PageRequest {
      * 分支公司名称
      */
     private String partnerCompanyName;
+
+    /**
+     * 回款差额
+     */
+    private BigDecimal settlementPaymentDifference;
 }

+ 30 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/QueryFollowIncomeVo.java

@@ -0,0 +1,30 @@
+package com.jzg.commons.entity.finance.vo;
+
+import com.jzg.commons.core.base.PageRequest;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+public class QueryFollowIncomeVo  extends PageRequest implements Serializable {
+
+    @Schema(description = "开始年份")
+    private Integer startYear;
+
+    @Schema(description = "结束年份")
+    private Integer endYear;
+
+    @Schema(description = "租户id")
+    private String systemCode;
+
+    @Schema(description = "保险公司id")
+    private List<String> companyIdList;
+
+    @Schema(description = "保险公司id")
+    private String companyId;
+
+}

+ 5 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/ReceivableQueryVo.java

@@ -116,4 +116,9 @@ public class ReceivableQueryVo extends PageRequest {
     @Schema(description = "租户代码")
     private String systemCode;
 
+    /**
+     * 类型:1:手续费 2:跟单费 3:驾意手续费 4:驾意跟单费
+     */
+    private String invoiceType;
+
 }

+ 3 - 0
commons/src/main/java/com/jzg/commons/entity/finance/vo/SettlementReportQueryVo.java

@@ -16,6 +16,9 @@ public class SettlementReportQueryVo extends PageRequest {
     @Schema(description = "保险公司id")
     private String companyId;
 
+    @Schema(description = "保险公司id")
+    private List<String> companyIdList;
+
     @Schema(description = "对接人")
     private String contactPerson;
 

+ 63 - 0
commons/src/main/java/com/jzg/commons/entity/orders/vo/FeeAuditVo.java

@@ -2,6 +2,7 @@ package com.jzg.commons.entity.orders.vo;
 
 import com.alibaba.excel.annotation.ExcelIgnore;
 import com.alibaba.excel.annotation.ExcelProperty;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import com.jzg.commons.core.base.BaseModel;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -352,4 +353,66 @@ public class FeeAuditVo extends BaseModel {
     @ExcelProperty("权益相关:可用金额<可用金额=出口费用➖出口人留点费用>")
     @Schema(description = "权益相关:可用金额<可用金额=出口费用➖出口人留点费用>")
     private BigDecimal availableAmount;
+
+
+    @Schema(description = "一级分销费")
+    @ExcelProperty("一级分销费")
+    @TableField(exist = false)
+    private BigDecimal firstDistributionFee;
+
+//    @Schema(description = "一级分销员id")
+//    @ExcelProperty("订单号")
+//    @TableField(exist = false)
+//    private String firstDistributorId;
+//
+//    @Schema(description = "一级分销员")
+//    @ExcelProperty("订单号")
+//    @TableField(exist = false)
+//    private String firstDistributorName;
+
+    @Schema(description = "一级分销比例")
+    @ExcelProperty("一级分销比例")
+    @TableField(exist = false)
+    private BigDecimal firstDistributionRatio;
+
+    @Schema(description = "二级分销费")
+    @ExcelProperty("二级分销费")
+    @TableField(exist = false)
+    private BigDecimal secondDistributionFee;
+
+//    @Schema(description = "二级分销员id")
+//    @ExcelProperty("二级分销员id")
+//    @ExcelIgnore
+//    @TableField(exist = false)
+//    private String secondDistributorId;
+//
+//    @Schema(description = "二级分销员")
+//    @ExcelProperty("二级分销员")
+//    @TableField(exist = false)
+//    private String secondDistributorName;
+
+    @Schema(description = "二级分销比例")
+    @ExcelProperty("二级分销比例")
+    @TableField(exist = false)
+    private BigDecimal secondDistributionRatio;
+
+    @Schema(description = "三级分销费")
+    @ExcelProperty("三级分销费")
+    @TableField(exist = false)
+    private BigDecimal thirdDistributionFee;
+
+//    @Schema(description = "三级分销员id")
+//    @ExcelIgnore
+//    @TableField(exist = false)
+//    private String thirdDistributorId;
+//
+//    @Schema(description = "三级分销员")
+//    @ExcelProperty("三级分销员")
+//    @TableField(exist = false)
+//    private String thirdDistributorName;
+
+    @Schema(description = "三级分销比例")
+    @ExcelProperty("三级分销比例")
+    @TableField(exist = false)
+    private BigDecimal thirdDistributionRatio;
 }

+ 12 - 0
commons/src/main/java/com/jzg/commons/entity/po/InsFeeOrders.java

@@ -165,12 +165,24 @@ public class InsFeeOrders {
     @Schema(description = "交强出口比例")
     private BigDecimal jqExportRatio;
 
+    @TableField(exist = false)
+    @Schema(description = "交强出口比例(含分销比例)")
+    private BigDecimal jqExportRatioContainDistribution;
+
     @Schema(description = "商业险出口比例")
     private BigDecimal syExportRatio;
 
+    @TableField(exist = false)
+    @Schema(description = "商业险出口比例(含分销比例)")
+    private BigDecimal syExportRatioContainDistribution;
+
     @Schema(description = "驾意险出口比例")
     private BigDecimal jyExportRatio;
 
+    @TableField(exist = false)
+    @Schema(description = "驾意险出口比例(含分销比例)")
+    private BigDecimal jyExportRatioContainDistribution;
+
     @Schema(description = "交强出口费用")
     private BigDecimal jqExportPremium;
 

+ 7 - 0
commons/src/main/java/com/jzg/commons/entity/scheme/po/PtlSchemeInsuranceDrivingIntention.java

@@ -35,6 +35,13 @@ public class PtlSchemeInsuranceDrivingIntention {
     @Schema(description = "是否删除")
     @TableLogic
     private Integer isDelete;
+
+    @Schema(description = "最小座位数")
+    private Integer minSeatCount;
+
+    @Schema(description = "最大座位数")
+    private Integer maxSeatCount;
+
 }
 
 

+ 2 - 0
commons/src/main/java/com/jzg/commons/entity/scheme/vo/PtlSchemeSaveVo.java

@@ -285,6 +285,8 @@ public class PtlSchemeSaveVo {
                 ptlSchemeInsuranceDrivingIntention.setSelectedStatus(drivingIntention.getSelectedStatus());
                 ptlSchemeInsuranceDrivingIntention.setDrivingIntentionId(drivingIntention.getDrivingIntentionId());
                 ptlSchemeInsuranceDrivingIntention.setQuantity(drivingIntention.getQuantity());
+                ptlSchemeInsuranceDrivingIntention.setMinSeatCount(drivingIntention.getMinSeatCount());
+                ptlSchemeInsuranceDrivingIntention.setMaxSeatCount(drivingIntention.getMaxSeatCount());
                 ptlSchemeInsuranceDrivingIntentionList.add(ptlSchemeInsuranceDrivingIntention);
             });
         }

+ 2 - 0
commons/src/main/java/com/jzg/commons/entity/scheme/vo/SchemeResultVo.java

@@ -99,5 +99,7 @@ public class SchemeResultVo {
         private String premium;
         private String quantity;
         private String selectedStatus;
+        private Integer minSeatCount;
+        private Integer maxSeatCount;
     }
 }

+ 19 - 0
commons/src/main/java/com/jzg/commons/util/BigDecimalUtil.java

@@ -36,6 +36,25 @@ public class BigDecimalUtil {
         return val.negate();
     }
 
+    /**
+     *
+     * @author lipf
+     * @date 2026/7/1 14:58
+     */
+    public static BigDecimal add(BigDecimal ... v1s){
+        if (v1s == null || v1s.length == 0) {
+            return BigDecimal.ZERO;
+        }
+        BigDecimal sum = BigDecimal.ZERO;
+        for (BigDecimal v1 : v1s) {
+            if(v1 == null){
+                v1 = BigDecimal.ZERO;
+            }
+            sum = BigDecimalUtil.add(sum,v1);
+        }
+        return sum;
+    }
+
     /**
      * 提供精确的加法运算
      * @param v1 被加数

+ 189 - 38
commons/src/main/java/com/jzg/commons/util/ImageUtil.java

@@ -126,10 +126,16 @@ public class ImageUtil {
 
     /**
      * 图片压缩参数配置
+     * - MAX_WIDTH/MAX_HEIGHT: 图片最大尺寸限制,超过则按比例缩放
+     * - INITIAL_QUALITY: 初始压缩质量(0.0-1.0),值越高质量越好但文件越大
+     * - MIN_QUALITY: 最低压缩质量,达到此值后停止迭代
+     * - QUALITY_STEP: 每次迭代降低的质量步长
      */
     private static final int MAX_WIDTH = 1920;
     private static final int MAX_HEIGHT = 1080;
-    private static final float QUALITY = 0.7f;
+    private static final float INITIAL_QUALITY = 0.7f;
+    private static final float MIN_QUALITY = 0.1f;
+    private static final float QUALITY_STEP = 0.1f;
 
     /**
      * 判断文件是否为图片类型(通过 Content-Type 判断)
@@ -158,70 +164,175 @@ public class ImageUtil {
 
     /**
      * 图片压缩方法
+     * 
      * 压缩策略:
-     * 1. 如果图片尺寸超过 MAX_WIDTH x MAX_HEIGHT,先按比例缩放
-     * 2. 无论是否缩放,都进行质量压缩(压缩质量为 QUALITY)
-     * 3. 保持原图片格式,PNG 保持透明通道
+     * 1. 尺寸压缩:如果图片尺寸超过 MAX_WIDTH x MAX_HEIGHT,先按比例缩放至最大尺寸以内
+     * 2. 迭代质量压缩:从 INITIAL_QUALITY(0.7) 开始逐步降低质量,直到压缩后文件小于原图或达到 MIN_QUALITY(0.1)
+     * 3. 格式降级:如果PNG格式压缩无效,自动尝试转为JPEG格式进行有损压缩
+     * 4. 透明通道处理:转为JPEG时,将透明通道填充为白色背景
      *
      * @param file 上传的图片文件
-     * @return 压缩后的字节数组,压缩失败返回 null
+     * @return 压缩后的字节数组,压缩失败或压缩后更大则返回 null(调用方应使用原文件)
      */
     public static byte[] compressImage(MultipartFile file) {
+        // 参数校验:文件为空直接返回null
         if (file == null || file.isEmpty()) {
             return null;
         }
 
+        // 获取原图大小,用于后续压缩效果对比
+        long originalSize = file.getSize();
+
         try (InputStream is = file.getInputStream()) {
+            // 读取图片为BufferedImage对象
             BufferedImage originalImage = ImageIO.read(is);
             if (originalImage == null) {
+                log.error("无法读取图片,ImageIO.read返回null,可能是不支持的图片格式");
                 return null;
             }
 
+            // 获取图片原始尺寸
             int width = originalImage.getWidth();
             int height = originalImage.getHeight();
-
-            // 如果图片尺寸在限制范围内,直接进行质量压缩
-            if (width <= MAX_WIDTH && height <= MAX_HEIGHT) {
-                return compressWithQuality(originalImage, file.getOriginalFilename());
+            log.debug("图片原始尺寸: {} x {}, 文件大小: {} bytes", width, height, originalSize);
+
+            // 默认使用原图,如需缩放则替换
+            BufferedImage targetImage = originalImage;
+
+            // 步骤1:尺寸压缩 - 超过最大尺寸则按比例缩放
+            if (width > MAX_WIDTH || height > MAX_HEIGHT) {
+                // 计算缩放比例,保持宽高比,取最小比例确保不超过任一维度限制
+                double scale = Math.min((double) MAX_WIDTH / width, (double) MAX_HEIGHT / height);
+                int newWidth = (int) (width * scale);
+                int newHeight = (int) (height * scale);
+
+                // 创建缩放后的图片,使用双线性插值保证清晰度
+                targetImage = new BufferedImage(newWidth, newHeight, originalImage.getType());
+                Graphics2D g2d = targetImage.createGraphics();
+                g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
+                g2d.dispose();
+                log.debug("图片缩放后尺寸: {} x {}", newWidth, newHeight);
             }
 
-            // 计算缩放比例,保持宽高比
-            double scale = Math.min((double) MAX_WIDTH / width, (double) MAX_HEIGHT / height);
-            int newWidth = (int) (width * scale);
-            int newHeight = (int) (height * scale);
+            // 获取图片格式(从文件名解析)
+            String format = getImageFormat(file.getOriginalFilename());
+            
+            // 步骤2:迭代质量压缩,尝试不同质量档位
+            byte[] compressedBytes = compressWithIterativeQuality(targetImage, format, originalSize);
 
-            // 创建缩放后的图片,使用双线性插值算法保证清晰度
-            BufferedImage resizedImage = new BufferedImage(newWidth, newHeight, originalImage.getType());
-            Graphics2D g2d = resizedImage.createGraphics();
-            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-            g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null);
-            g2d.dispose();
+            // 校验压缩结果:必须小于原图且不为空才视为有效压缩
+            if (compressedBytes != null && compressedBytes.length < originalSize) {
+                log.debug("图片压缩成功,原图: {} bytes, 压缩后: {} bytes", originalSize, compressedBytes.length);
+                return compressedBytes;
+            }
 
-            // 对缩放后的图片进行质量压缩
-            return compressWithQuality(resizedImage, file.getOriginalFilename());
+            // 压缩无效,记录警告日志并返回null(调用方应使用原文件)
+            log.warn("图片压缩无效,压缩后大小({} bytes) >= 原图大小({} bytes)",
+                    compressedBytes != null ? compressedBytes.length : 0, originalSize);
+            return null;
         } catch (IOException e) {
-            log.error("图片压缩失败", e);
+            log.error("图片压缩失败,读取或处理图片时发生IO异常", e);
             return null;
         }
     }
 
     /**
-     * 图片质量压缩
-     * 使用 ImageWriter 进行高质量压缩,保持原格式
-     *
-     * @param image    待压缩的图片对象
-     * @param filename 原文件名,用于确定图片格式
-     * @return 压缩后的字节数组
+     * 迭代质量压缩
+     * 
+     * 核心算法:从 INITIAL_QUALITY(0.7) 开始逐步降低质量,每次降低 QUALITY_STEP(0.1),
+     * 直到压缩后文件小于原图或达到 MIN_QUALITY(0.1)。
+     * 
+     * 特殊处理:
+     * - 如果某质量档位压缩失败(返回null),继续尝试下一档位,不直接退出
+     * - 如果PNG格式所有质量档位均无效,自动尝试转为JPEG格式进行有损压缩
+     * 
+     * @param image        待压缩的图片对象(已完成尺寸缩放)
+     * @param format       图片格式(如 "jpg", "png")
+     * @param originalSize 原图大小(字节),用于判断压缩是否有效
+     * @return 压缩后的字节数组,若所有档位均无效则返回 null
+     */
+    private static byte[] compressWithIterativeQuality(BufferedImage image, String format, long originalSize) {
+        log.info("开始迭代压缩,格式: {}, 原图大小: {} bytes", format, originalSize);
+        
+        // 从初始质量开始迭代
+        float quality = INITIAL_QUALITY;
+
+        // 迭代循环:从高到低尝试不同质量档位
+        while (quality >= MIN_QUALITY) {
+            // 使用当前质量档位进行压缩
+            byte[] compressedBytes = compressWithQuality(image, format, quality);
+
+            // 压缩失败(返回null),继续尝试下一质量档位
+            if (compressedBytes == null) {
+                log.warn("compressWithQuality返回null, 格式: {}, 质量: {}", format, quality);
+                quality -= QUALITY_STEP;
+                continue;
+            }
+
+            // 记录当前压缩尝试结果
+            log.info("压缩尝试 - 格式: {}, 质量: {}, 压缩后大小: {} bytes", format, quality, compressedBytes.length);
+
+            // 判断压缩是否有效:压缩后大小 > 0 且 < 原图大小
+            if (compressedBytes.length > 0 && compressedBytes.length < originalSize) {
+                log.info("压缩成功,使用质量 {},压缩后大小 {} bytes", quality, compressedBytes.length);
+                return compressedBytes;
+            }
+
+            // 压缩无效,降低质量继续尝试
+            quality -= QUALITY_STEP;
+        }
+
+        // 所有质量档位均无效
+        log.warn("所有质量档位均无效,格式: {}", format);
+
+        // PNG格式特殊处理:尝试转为JPEG格式进行有损压缩
+        // JPEG的有损压缩通常能获得更好的压缩率,适合对透明度要求不高的场景
+        if ("png".equalsIgnoreCase(format)) {
+            log.info("尝试将PNG转为JPEG格式压缩");
+            byte[] jpegBytes = compressWithIterativeQuality(image, "jpg", originalSize);
+            if (jpegBytes != null && jpegBytes.length > 0 && jpegBytes.length < originalSize) {
+                log.info("PNG转JPEG压缩成功,原图: {} bytes, 压缩后: {} bytes", originalSize, jpegBytes.length);
+                return jpegBytes;
+            }
+            log.warn("PNG转JPEG压缩也失败");
+        }
+
+        // 所有压缩尝试均无效,返回null
+        return null;
+    }
+
+    /**
+     * 图片质量压缩(指定质量参数)
+     * 
+     * 使用 ImageWriter 进行高质量压缩,支持以下特性:
+     * 1. 透明通道处理:当输出格式为JPEG且原图有透明通道时,先转为白色背景的不透明图片
+     * 2. 压缩参数配置:通过 ImageWriteParam 设置显式压缩质量
+     * 3. 降级处理:如果 ImageWriter 不可用,使用 ImageIO.write 作为回退方案
+     * 
+     * @param image   待压缩的图片对象
+     * @param format  图片格式(如 "jpg", "png")
+     * @param quality 压缩质量(0.0-1.0),值越高质量越好但文件越大
+     * @return 压缩后的字节数组,所有方案均失败则返回 null
      */
-    private static byte[] compressWithQuality(BufferedImage image, String filename) {
+    private static byte[] compressWithQuality(BufferedImage image, String format, float quality) {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         try {
-            String format = getImageFormat(filename);
+            // 默认使用原图作为输出
+            BufferedImage outputImage = image;
+
+            // 特殊处理:JPEG格式不支持透明通道,需要先转为不透明图片
+            // 将透明区域填充为白色背景,避免透明部分显示为黑色
+            if ("jpg".equalsIgnoreCase(format) && image.getTransparency() != Transparency.OPAQUE) {
+                outputImage = convertToOpaque(image);
+            }
+
+            // 获取对应格式的 ImageWriter
             Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName(format);
 
-            // 如果找不到对应格式的 ImageWriter,使用默认方式写入
+            // 如果找不到 ImageWriter,使用 ImageIO.write 作为回退方案
             if (!writers.hasNext()) {
-                ImageIO.write(image, format, baos);
+                ImageIO.write(outputImage, format, baos);
                 return baos.toByteArray();
             }
 
@@ -229,29 +340,69 @@ public class ImageUtil {
             ImageWriter writer = writers.next();
             ImageWriteParam writeParam = writer.getDefaultWriteParam();
 
+            // 设置显式压缩模式和质量参数
             if (writeParam.canWriteCompressed()) {
                 writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
-                writeParam.setCompressionQuality(QUALITY);
+                writeParam.setCompressionQuality(quality);
             }
 
+            // 写入压缩后的图片数据
             writer.setOutput(ImageIO.createImageOutputStream(baos));
-            writer.write(null, new javax.imageio.IIOImage(image, null, null), writeParam);
+            writer.write(null, new javax.imageio.IIOImage(outputImage, null, null), writeParam);
             writer.dispose();
 
-            return baos.toByteArray();
+            // 获取压缩结果并记录日志
+            byte[] result = baos.toByteArray();
+            log.debug("compressWithQuality完成 - 格式: {}, 质量: {}, 结果大小: {} bytes", format, quality, result.length);
+            return result;
         } catch (IOException e) {
-            log.error("图片质量压缩失败", e);
+            // 压缩失败,记录错误日志并尝试回退方案
+            log.error("图片质量压缩失败,格式: {}, 质量: {}", format, quality, e);
             try {
-                // 压缩失败时回退到普通写入方式
-                ImageIO.write(image, getImageFormat(filename), baos);
+                // 回退:使用 ImageIO.write 直接写入(不设置压缩参数)
+                ImageIO.write(image, format, baos);
                 return baos.toByteArray();
             } catch (IOException ex) {
+                // 回退也失败,返回null
                 log.error("图片回退保存失败", ex);
                 return null;
             }
         }
     }
 
+    /**
+     * 将带透明通道的图片转换为不透明图片
+     * 
+     * JPEG格式不支持透明通道,当需要将PNG等带透明通道的图片转为JPEG时,
+     * 需要先将透明区域填充为白色背景,避免透明部分显示为黑色。
+     * 
+     * @param image 带透明通道的图片
+     * @return 不透明图片(TYPE_INT_RGB),透明区域已填充为白色
+     */
+    private static BufferedImage convertToOpaque(BufferedImage image) {
+        // 创建不透明的RGB图片(TYPE_INT_RGB不支持透明通道)
+        BufferedImage opaqueImage = new BufferedImage(
+                image.getWidth(),
+                image.getHeight(),
+                BufferedImage.TYPE_INT_RGB
+        );
+        
+        // 创建 Graphics2D 对象用于绘制
+        Graphics2D g2d = opaqueImage.createGraphics();
+        
+        // 步骤1:填充白色背景,覆盖透明区域
+        g2d.setColor(Color.WHITE);
+        g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
+        
+        // 步骤2:绘制原图,此时透明区域已被白色背景覆盖
+        g2d.drawImage(image, 0, 0, null);
+        
+        // 释放资源
+        g2d.dispose();
+        
+        return opaqueImage;
+    }
+
     /**
      * 根据文件名获取图片格式
      *

+ 69 - 17
platform/src/main/java/com/jzg/controller/FileUploadController.java

@@ -12,6 +12,7 @@ import com.jzg.commons.util.idgen.IdGenerate;
 import com.jzg.mapper.SysUploadFilesMapper;
 import io.minio.*;
 import io.swagger.v3.oas.annotations.Operation;
+import lombok.extern.slf4j.Slf4j;
 import org.jetbrains.annotations.NotNull;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
@@ -31,6 +32,7 @@ import java.time.LocalDateTime;
 
 @RestController
 @RequestMapping("/file")
+@Slf4j
 public class FileUploadController extends BaseController {
 
 
@@ -57,35 +59,72 @@ public class FileUploadController extends BaseController {
     public HttpResult uploadFile(@RequestParam("file") MultipartFile file) {
         System.out.printf("调用文件上传接口");
         try {
-            // 获取文件原始名称和扩展名
+            // 获取文件原始名称,用于后续生成对象名
             String originalFileName = file.getOriginalFilename();
+            // 获取文件扩展名(含点号,如 ".jpg"),用于判断文件类型和生成对象名
             String extension = getFileExtension(originalFileName);
+            // 获取文件原始大小,用于压缩效果对比
+            long originalSize = file.getSize();
+            // 获取文件的 Content-Type,用于判断是否为图片类型
+            String contentType = file.getContentType();
 
-            // 判断是否为图片文件,若是则进行压缩
-            // 双重校验:通过 Content-Type 和文件扩展名判断
+            // 压缩后的字节数组,初始为null表示未压缩或压缩失败
             byte[] compressedBytes = null;
-            if (ImageUtil.isImage(file.getContentType()) || ImageUtil.isImageExtension(extension)) {
+            // 压缩后的文件扩展名,默认使用原扩展名,PNG转JPEG时会更新
+            String compressedExtension = extension;
+            // 压缩后的 Content-Type,默认使用原类型,PNG转JPEG时会更新
+            String compressedContentType = contentType;
+
+            // 判断是否为图片文件(双重校验:Content-Type 和扩展名)
+            // 图片文件需要进行压缩处理,非图片文件直接上传
+            if (ImageUtil.isImage(contentType) || ImageUtil.isImageExtension(extension)) {
                 compressedBytes = ImageUtil.compressImage(file);
             }
 
-            // 根据是否压缩成功,计算文件的 MD5 和 ETag
-            // 注意:MD5/ETag 必须基于最终上传的文件内容计算,确保与 MinIO 存储一致
-            String fileMd5;
-            String fileEtag;
-            String objectName;
+            // 检查压缩结果:压缩成功且压缩后文件不为空
+            if (compressedBytes != null && compressedBytes.length > 0) {
+                // 打印压缩前后大小对比,便于调试和监控压缩效果
+                log.info("图片压缩前大小: {} bytes, 压缩后大小: {} bytes", originalSize, compressedBytes.length);
+
+                // 判断压缩是否有效:如果压缩后文件比原图大或相等,则视为压缩无效
+                // 此时回退到使用原文件上传,避免上传更大的文件
+                if (compressedBytes.length >= originalSize) {
+                    log.info("警告:压缩后图片({} bytes)比原图{} bytes)大,使用原图片上传", compressedBytes.length, originalSize);
+                    compressedBytes = null;
+                }
+            }
 
+            // 声明文件哈希值和对象名变量
+            String fileMd5;      // 文件MD5值,用于唯一标识文件
+            String fileEtag;     // 文件ETag值,用于MinIO文件校验
+            String objectName;   // MinIO中的对象名称
+
+            // 根据是否压缩成功,选择不同的处理路径
             if (compressedBytes != null && compressedBytes.length > 0) {
-                // 使用压缩后的字节数组计算哈希值
+                // 压缩成功:基于压缩后的字节数组计算哈希值
                 fileMd5 = FileUtils.getFileMD5(compressedBytes);
                 fileEtag = FileUtils.getFileETag(compressedBytes);
-                objectName = generateObjectName(originalFileName, fileMd5, extension);
+
+                // 特殊处理:当原文件是PNG格式,但压缩后实际是JPEG格式时
+                // 需要更新扩展名和Content-Type,确保文件类型与内容一致
+                // 这是因为ImageUtil在PNG压缩无效时会尝试转为JPEG格式
+                if (".png".equalsIgnoreCase(extension) && isJpegContent(compressedBytes)) {
+                    compressedExtension = ".jpg";
+                    compressedContentType = "image/jpeg";
+                    log.info("PNG图片转换为JPEG格式,扩展名从 {} 变为 {}", extension, compressedExtension);
+                }
+
+                // 使用压缩后的扩展名生成MinIO对象名
+                objectName = generateObjectName(originalFileName, fileMd5, compressedExtension);
             } else {
-                // 使用原始文件计算哈希值(非图片或压缩失败时回退)
+                // 压缩失败或非图片文件:基于原文件计算哈希值
                 fileMd5 = FileUtils.getFileMD5(file);
                 fileEtag = FileUtils.getFileETag(file);
+                // 使用原扩展名生成MinIO对象名
                 objectName = generateObjectName(originalFileName, fileMd5, extension);
             }
 
+//            // 检查文件是否已存在于MinIO(功能已注释,如需启用请取消注释)
 //            if (isFileExistsInMinIo(objectName, fileEtag)) {
 //                return HttpResult.ok(getFileFromDatabase(fileEtag,objectName, extension));
 //            }
@@ -93,8 +132,8 @@ public class FileUploadController extends BaseController {
             // 根据是否压缩成功,选择对应的上传方式
             SysUploadFiles sysUploadFiles;
             if (compressedBytes != null && compressedBytes.length > 0) {
-                // 上传压缩后的图片字节数组
-                sysUploadFiles = uploadFileToMinIo(compressedBytes, objectName, fileEtag, extension, file.getContentType());
+                // 上传压缩后的图片字节数组(使用更新后的扩展名和Content-Type)
+                sysUploadFiles = uploadFileToMinIo(compressedBytes, objectName, fileEtag, compressedExtension, compressedContentType);
             } else {
                 // 上传原始文件(非图片或压缩失败时回退)
                 sysUploadFiles = uploadFileToMinIo(file, objectName, fileEtag, extension);
@@ -106,6 +145,22 @@ public class FileUploadController extends BaseController {
         }
     }
 
+    /**
+     * 判断字节数组是否为JPEG格式的图片内容
+     * 通过检测文件魔数(Magic Number)来判断:JPEG的魔数是 FF D8 FF
+     *
+     * @param data 待检测的字节数组
+     * @return 是否为JPEG格式内容
+     */
+    private boolean isJpegContent(byte[] data) {
+        // 参数校验:数据为空或长度不足3字节,无法包含完整的JPEG魔数
+        if (data == null || data.length < 3) {
+            return false;
+        }
+        // JPEG格式魔数检测:FF D8 FF
+        return data[0] == (byte) 0xFF && data[1] == (byte) 0xD8 && data[2] == (byte) 0xFF;
+    }
+
     @PostMapping("/uploadUrl")
     @Operation(summary = "网络文件上传接口", description = "文件上传接口")
     public HttpResult<SysUploadFiles> uploadUrlFile(String urlString, String fileName) {
@@ -232,7 +287,6 @@ public class FileUploadController extends BaseController {
                 .stream(fileInputStream, file.getSize(), -1)
                 .contentType(file.getContentType())
                 .build());
-
         return insertSysUploadFiles(objectName, extension, objectWriteResponse);
     }
 
@@ -244,7 +298,6 @@ public class FileUploadController extends BaseController {
                 .stream(fileInputStream, fileBytes.length, -1)
                 .contentType(contentType)
                 .build());
-
         return insertSysUploadFiles(objectName, extension, objectWriteResponse);
     }
 
@@ -257,7 +310,6 @@ public class FileUploadController extends BaseController {
                 .stream(fileInputStream, contentLength, -1)
                 .contentType(contentType)
                 .build());
-
         return insertSysUploadFiles(objectName, extension, objectWriteResponse);
     }
 

+ 73 - 19
tenant/insurance/quotation-ansheng/src/main/java/com/jzg/quotation/ansheng/crawler/service/impl/AnShengCrawlerRequestImpl.java

@@ -39,14 +39,12 @@ import org.springframework.util.MultiValueMap;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.time.Year;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 
 @Slf4j
@@ -250,8 +248,8 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
         paymentLinkResultsVo = loopAcquisitionPaymentLink(paymentLinkVo);
         // 支付链接为空直接失败返回
         if (StringUtils.isEmpty(paymentLinkResultsVo.getPaymentLink())) {
-            underwritingResultsVo.setUnderwritingResult(applyApplicationResponse.getMessage());
-            underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.AUDIT_FAIL);
+            underwritingResultsVo.setUnderwritingResult("核保失败,转人工");
+            underwritingResultsVo.setUnderwritingStatus(InsOrderStatusEnum.STAFF_PROCESS);
             return underwritingResultsVo;
         }
         // 正常赋值逻辑
@@ -335,7 +333,7 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
             } else if (DepartmentPlanCode.PLAN_109.getCode().equals(dataDTO.getPlanCode()) || DepartmentPlanCode.PLAN_113.getCode().equals(dataDTO.getPlanCode())) {
                 anShengCrawlerOrder.setBiPolicyNo(dataDTO.getApplyPolicyNo());
             } else {
-                String jy = com.jzg.commons.util.StringUtils.isEmpty(dataDTO.getProductCode()) ? "" : dataDTO.getProductCode();
+                String jy = StringUtils.isEmpty(dataDTO.getProductCode()) ? "" : dataDTO.getProductCode();
                 AnShengCrawlerOrder.NiInfo niInfo = new AnShengCrawlerOrder.NiInfo();
                 niInfo.setNiPolicyNo(jy);
                 niInfoList.add(niInfo);
@@ -467,18 +465,74 @@ public class AnShengCrawlerRequestImpl implements AnShengCrawlerRequest {
 
     @Override
     public OrderStatusResultVo queryOrderState(OrderStatusVo orderStatusVo) {
-        // 获取订单信息
-        AnShengCrawlerOrder anShengCrawlerOrder = anShengCrawlerOrderService.getById(orderStatusVo.getOrder().getOrdersNo());
-        // 获取请求头
-        HttpHeaders httpHeaders = anShengCrawlerQuoteProcessService.getHttpHeaders(orderStatusVo.getOrder().getAttributionInformationVo());
-        // 归属信息
-        AnShengCrawlerGetUserDetailResponse.DataDTO.AgentDefineDTO userDetailResponse = anShengCrawlerQuoteProcessService.getUserDetailResponse(orderStatusVo.getOrder().getAgreementId(), httpHeaders);
-        // 查询订单状态
-        AnShengCrawlerComprehensiveQueryInsurenetRequest crawlerComprehensiveQueryInsurenet = new AnShengCrawlerComprehensiveQueryInsurenetRequest(userDetailResponse.getAgentCode(), userDetailResponse.getDepartmentCode(), anShengCrawlerOrder.getOrderNo(), anShengCrawlerOrder.getRecordingTime());
-        AnShengCrawlerComprehensiveQueryInsurenetResponse queryDocumentsInformationResponse = anShengCrawlerRequestComponent.comprehensiveQueryInsurenet(crawlerComprehensiveQueryInsurenet, httpHeaders);
-        List<AnShengCrawlerComprehensiveQueryInsurenetResponse.DataDTO.ListDTO> list = queryDocumentsInformationResponse.getData().getList();
-
-        return null;
+        OrderStatusResultVo resultVo = new OrderStatusResultVo();
+        // 默认状态:若后续流程未匹配到明确状态,则回退到原始状态
+        InsOrderStatusEnum defaultStatus = InsOrderStatusEnum.matchKey(orderStatusVo.getInsOrders().getOrderStatus());
+        resultVo.setOrderStatus(defaultStatus);
+        try {
+            // 1. 获取基础信息
+            AnShengCrawlerOrder crawlerOrder = anShengCrawlerOrderService.getById(orderStatusVo.getOrder().getOrdersNo());
+            HttpHeaders httpHeaders = anShengCrawlerQuoteProcessService.getHttpHeaders(orderStatusVo.getOrder().getAttributionInformationVo());
+            AnShengCrawlerGetUserDetailResponse.DataDTO.AgentDefineDTO userDetail = anShengCrawlerQuoteProcessService.getUserDetailResponse(orderStatusVo.getOrder().getAgreementId(), httpHeaders);
+            // 2. 查询订单状态
+            AnShengCrawlerComprehensiveQueryInsurenetRequest request = new AnShengCrawlerComprehensiveQueryInsurenetRequest(userDetail.getAgentCode(), userDetail.getDepartmentCode(), crawlerOrder.getOrderNo(), crawlerOrder.getRecordingTime());
+            AnShengCrawlerComprehensiveQueryInsurenetResponse response = anShengCrawlerRequestComponent.comprehensiveQueryInsurenet(request, httpHeaders);
+
+            if (response == null || response.getData() == null || response.getData().getList() == null) {
+                return resultVo;
+            }
+            Set<String> targetPolicyNos = Stream.of(crawlerOrder.getCiPolicyNo(), crawlerOrder.getBiPolicyNo())
+                    .filter(StringUtils::isNotEmpty)
+                    .collect(Collectors.toSet());
+            List<AnShengCrawlerComprehensiveQueryInsurenetResponse.DataDTO.ListDTO.PolicyConciseDetailsDTO> matchedDetails =
+                    response.getData().getList().stream()
+                            .map(AnShengCrawlerComprehensiveQueryInsurenetResponse.DataDTO.ListDTO::getPolicyConciseDetails)
+                            .filter(Objects::nonNull)
+                            .filter(details -> {
+                                Set<String> currentPolicyNos = details.stream()
+                                        .map(AnShengCrawlerComprehensiveQueryInsurenetResponse.DataDTO.ListDTO.PolicyConciseDetailsDTO::getApplyPolicyNo)
+                                        .collect(Collectors.toSet());
+                                return currentPolicyNos.equals(targetPolicyNos);
+                            })
+                            .findFirst()
+                            .orElse(null);
+            if (matchedDetails == null || matchedDetails.isEmpty()) {
+                return resultVo;
+            }
+            List<String> underwriteMarks = matchedDetails.stream().map(AnShengCrawlerComprehensiveQueryInsurenetResponse.DataDTO.ListDTO.PolicyConciseDetailsDTO::getUnderwriteMark).collect(Collectors.toList());
+            String baseMark = underwriteMarks.get(0);
+            boolean allMarksSame = underwriteMarks.stream().allMatch(mark -> Objects.equals(baseMark, mark));
+            if (!allMarksSame) {
+                return resultVo;
+            }
+            if ("9".equals(baseMark) || "2".equals(baseMark)) {
+                boolean isPayPending = "9".equals(baseMark);
+                // 获取支付链接
+                PaymentLinkVo paymentLinkVo = new PaymentLinkVo();
+                paymentLinkVo.setOrder(orderStatusVo.getOrder());
+                PaymentLinkResultsVo paymentLinkResultsVo = loopAcquisitionPaymentLink(paymentLinkVo);
+                resultVo.setOrderStatus(isPayPending ? InsOrderStatusEnum.PAY_PENDING : InsOrderStatusEnum.UNDERWRITED);
+                resultVo.setOrderStatusStr(isPayPending ? "4" : "5");
+                resultVo.setPaymentLink(paymentLinkResultsVo.getPaymentLink());
+                resultVo.setErrorMsg("");
+                for (AnShengCrawlerComprehensiveQueryInsurenetResponse.DataDTO.ListDTO.PolicyConciseDetailsDTO detail : matchedDetails) {
+                    String planCode = detail.getPlanCode();
+                    if ("105".equals(planCode)) {
+                        resultVo.setJqPolicyno(detail.getPolicyNo());
+                        resultVo.setJqApplyNo(detail.getApplyPolicyNo());
+                    } else if ("109".equals(planCode)) {
+                        resultVo.setSyPolicyno(detail.getPolicyNo());
+                        resultVo.setSyApplyNo(detail.getApplyPolicyNo());
+                    } else if ("P13".equals(planCode)) {
+                        resultVo.setJyPolicyno(detail.getPolicyNo());
+                        resultVo.setJyApplyNo(detail.getApplyPolicyNo());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("查询安盛订单状态异常, ordersNo: {}", orderStatusVo.getOrder().getOrdersNo(), e);
+        }
+        return resultVo;
     }
 
 }

+ 7 - 2
tenant/insurance/quotation-chengtai/src/main/java/com/jzg/quotation/chengtai/api/service/impl/ChengTaiApiRequestImpl.java

@@ -306,13 +306,18 @@ public class ChengTaiApiRequestImpl implements ChengTaiApiRequest {
                     break;
                 }
             }
+            if (premiumCalculationResponse.getHead().getErrorMessage().contains("投保查询校验信息已经校验成功") || premiumCalculationResponse.getHead().getErrorMessage().contains("投保查询校验信息不存在或已经校验成功")) {
+                premiumCalculationRequest.getBasePart().setCommRenewalAnswer("");
+                premiumCalculationRequest.getBasePart().setCompRenewalAnswer("");
+            }
             //处理订单号
-            if( premiumCalculationResponse.getCompRisk() != null && premiumCalculationResponse.getCompRisk().getFtrno() != null){
+            if(premiumCalculationResponse.getCompRisk() != null && premiumCalculationResponse.getCompRisk().getFtrno() != null){
                 premiumCalculationRequest.getBasePart().setOrderNumber(premiumCalculationResponse.getCompRisk().getFtrno().getOrderNumber());
             }
-            if( premiumCalculationResponse.getCommRisk() != null && premiumCalculationResponse.getCommRisk().getFtrno() != null){
+            if(premiumCalculationResponse.getCommRisk() != null && premiumCalculationResponse.getCommRisk().getFtrno() != null){
                 premiumCalculationRequest.getBasePart().setOrderNumber(premiumCalculationResponse.getCommRisk().getFtrno().getOrderNumber());
             }
+
         }
         return premiumCalculationResponse;
     }

+ 2 - 1
tenant/insurance/quotation-dajia/src/main/java/com/jzg/quotation/dajia/crawler/service/impl/DaJiaCrawlerQuoteProcessServiceImpl.java

@@ -61,7 +61,8 @@ public class DaJiaCrawlerQuoteProcessServiceImpl implements DaJiaCrawlerQuotePro
                 DaJiaCrawlerAuthenticateResponse daJiaCrawlerCaptchaResponse = DESEncryption.decryptStrToObject(response.getResult(), DaJiaCrawlerAuthenticateResponse.class);
                 // 校验token
                 String token = checkToken(daJiaCrawlerCaptchaResponse.getUserCode(), daJiaCrawlerCaptchaResponse.getIdToken());
-                return daJiaCrawlerCacheComponent.cacheAuthorization(token, daJiaCrawlerCaptchaResponse.getUserCode(), username);
+                String userCode = daJiaCrawlerCaptchaResponse.getUserCode();
+                return daJiaCrawlerCacheComponent.cacheAuthorization(token, com.jzg.commons.util.StringUtils.isNotEmpty(userCode) ? userCode : username, username);
             }
 
             String message = Optional.ofNullable(responseEntity.getBody())

+ 1 - 1
tenant/insurance/quotation-guoren/src/main/java/com/jzg/quotation/guoren/crawler/entity/request/GuoRenCrawlerCalculationPremiumRequest.java

@@ -283,7 +283,7 @@ public class GuoRenCrawlerCalculationPremiumRequest {
             this.isDeductible = 0;
             this.rate = 0;
             this.amount = kindInfoVo.getAmount();
-            this.unitAmount = Integer.parseInt(kindInfoVo.getUnitAmount());
+            this.unitAmount = StringUtils.isEmpty(kindInfoVo.getUnitAmount()) ? 0 : Integer.parseInt(kindInfoVo.getUnitAmount());
             this.premium = "";
             this.basePremium = "";
             this.benchMarkPremium = "";

+ 2 - 0
tenant/insurance/quotation-hengbang/src/main/java/com/jzg/quotation/hengbang/crawler/service/Impl/HengBangCrawlerRequestImpl.java

@@ -189,6 +189,8 @@ public class HengBangCrawlerRequestImpl implements HengBangCrawlerRequest {
         if (errorMsg.contains("该车是转保车,请输入对应的转保验证码")) {
             String responseBody = resJsonObj.getString("responseBody");
             HbCrawlerRecordResponse recordResponse = JSONObject.parseObject(responseBody, HbCrawlerRecordResponse.class);
+            recordResponse.getCheckcodeJq().replace("0","o");
+            recordResponse.getCheckcodeSy().replace("0","o");
             calculationRequest.setTransferVehicle(recordResponse);
 
             resJson = hbCrawlerRequestComponent.getCalculationResult(calculationRequest, headers);

+ 7 - 4
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/build/HuaTaiCrawlerQuoteResultsVoBuild.java

@@ -2,6 +2,7 @@ package com.jzg.quotation.huatai.crawler.build;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.jzg.commons.constants.ProductsType;
 import com.jzg.commons.constants.dict.InsOrderStatusEnum;
 import com.jzg.commons.entity.quote.vo.KindInfoVo;
 import com.jzg.commons.entity.quote.vo.RiskInfoVo;
@@ -9,6 +10,7 @@ import com.jzg.commons.entity.quote.vo.aggregated.QuoteResultsVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.entity.quote.vo.base.OrderBase;
 import com.jzg.commons.util.StringUtils;
+import com.jzg.quotation.commons.constant.enums.quote.RenewalEnum;
 import com.jzg.quotation.huatai.api.constant.enums.ContractEnum;
 import com.jzg.quotation.huatai.crawler.entity.Response.HtCrawlerQuoteResponse;
 
@@ -53,10 +55,11 @@ public class HuaTaiCrawlerQuoteResultsVoBuild {
                 .fluentPut("score",quoteResponseData.getApplicationdto().getBicategory()));
         quoteResultsVo.getScoreVo().setScoreJson(scoreJson);
 
-        //续保标志0否1是 (跟python 一致)
-        quoteResultsVo.getScoreVo().setJqRenewal("转入".equals(quoteResponseData.getApplicationdto().getCustombusinesstype_ci()) ? "0" : "1");
-        quoteResultsVo.getScoreVo().setSyRenewal("转入".equals(quoteResponseData.getApplicationdto().getCustombusinesstype_bi()) ? "0" : "1");
-
+        //续保标志
+        quoteResultsVo.getScoreVo().setJqRenewal("转入".equals(quoteResponseData.getApplicationdto().getCustombusinesstype_ci()) ? String.valueOf(RenewalEnum.Transfer.getCode()) : quoteResponseData.getApplicationdto().getCustombusinesstype_ci());
+        if (!ProductsType.PT_0330.getCode().equals(quoteVo.getOrder().getProductId())) {
+            quoteResultsVo.getScoreVo().setSyRenewal("转入".equals(quoteResponseData.getApplicationdto().getCustombusinesstype_bi()) ? String.valueOf(RenewalEnum.Transfer.getCode()) : quoteResponseData.getApplicationdto().getCustombusinesstype_ci());
+        }
         quoteResultsVo.setQuoteStatusEnum(InsOrderStatusEnum.WAIT_AUDIT);
         return quoteResultsVo;
     }

+ 18 - 11
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerOrderStateRequest.java

@@ -5,6 +5,7 @@ import lombok.Data;
 
 import java.io.Serial;
 import java.io.Serializable;
+import java.time.LocalDate;
 
 /**
  * @description: 华泰财险 查询订单状态 请求参数
@@ -20,9 +21,13 @@ public class HtCrawlerOrderStateRequest implements Serializable {
         this.quotationNo = quotationRelaNo;
         this.comcode = parameters.getComCode();
     }
-
+    private String telemarketnum = "";
     private String telemarketFlag = "0";
-    private String renewalTwoSwitch = "1";
+    private String renewalTwoSwitch = "";
+    private String insuredName = "";
+    private String certiCode = "";
+    private String flag = "";
+    private String status = "";
     /**
      * 报价单号
      */
@@ -30,20 +35,20 @@ public class HtCrawlerOrderStateRequest implements Serializable {
     /**
      * 车牌号
      */
-    private String licensePlateNo;
+    private String licensePlateNo = "";
     /**
      * 开始时间
      */
-    private String createDate1;
+    private String createDate1 = LocalDate.now().minusDays(30).toString();
     /**
      * 结束时间
      */
-    private String createDate2;
+    private String createDate2 = LocalDate.now().toString();
     private String comcode;
     /**
      * 出单系统:集成
      */
-    private String Single_system = "8";
+    private String Single_system = "10000";
     /**
      * 状态:有效
      */
@@ -56,10 +61,12 @@ public class HtCrawlerOrderStateRequest implements Serializable {
      * 业务类型:全部
      */
     private String businesstype = "0";
-    private String iFlag = "3";
-    private Integer VENUS_PAGE_NO_KEY = 1;
-    private Integer VENUS_PAGE_COUNT_KEY = 1;
-    private Integer VENUS_PAGE_SIZE_KEY = 15;
-    private Integer VENUS_PAGE_NO_KEY_INPUT = 1;
+    private String agentno_fdl = "";
+    private String agentno_dl = "";
+    private String iFlag = "2";
+    private String VENUS_PAGE_NO_KEY = "1";
+    private String VENUS_PAGE_COUNT_KEY = "476";
+    private String VENUS_PAGE_SIZE_KEY = "15";
+    private String VENUS_PAGE_NO_KEY_INPUT = "1";
 
 }

+ 6 - 6
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerSubmitRequest.java

@@ -29,7 +29,7 @@ public class HtCrawlerSubmitRequest implements Serializable {
         List<InsOrdersCarUserInfo> insOrdersCarUserInfoList = underwritingVo.getInsOrdersCarUserInfoList();
 
         InsOrdersCarUserInfo ownerInfo = null;
-        InsOrdersCarUserInfo applicantInfo = null;
+        InsOrdersCarUserInfo policyInfo = null;
         InsOrdersCarUserInfo insuredInfo = null;
 
         for (InsOrdersCarUserInfo userInfo : insOrdersCarUserInfoList) {
@@ -38,7 +38,7 @@ public class HtCrawlerSubmitRequest implements Serializable {
                     ownerInfo = userInfo;
                     break;
                 case 2:
-                    applicantInfo = userInfo;
+                    policyInfo = userInfo;
                     break;
                 case 3:
                     insuredInfo = userInfo;
@@ -130,10 +130,10 @@ public class HtCrawlerSubmitRequest implements Serializable {
             clauseDTOS.add(clauseDTO);
         }
 
-        boolean isOwnerSameAsInsured = (ownerInfo != null && insuredInfo != null &&
-                ownerInfo.getName().equals(insuredInfo.getName()));
+        boolean isOwnerSameAsPolicy = (ownerInfo != null && policyInfo != null &&
+                ownerInfo.getName().equals(policyInfo.getName()));
 
-        if (isOwnerSameAsInsured) {
+        if (isOwnerSameAsPolicy) {
             this.iisCarOwner = "Y";
             this.beibaorenId = "Y";
         }
@@ -147,7 +147,7 @@ public class HtCrawlerSubmitRequest implements Serializable {
         jxClause.setReadonly("1");
         clauseDTOS.add(jxClause);
 
-        if (!isOwnerSameAsInsured && ownerInfo != null) {
+        if (!isOwnerSameAsPolicy && ownerInfo != null) {
             ClauseDTO ownerClause = new ClauseDTO();
             ownerClause.setChosedE("T0028");
             ownerClause.setClausename("车主约定");

+ 23 - 0
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/po/HtCrawlerOrder.java

@@ -33,4 +33,27 @@ public class HtCrawlerOrder {
      * 核保额外需要的参数  商业评分
      */
     private String biCategory;
+    /**
+     * 交强险保单号
+     */
+    private String jqPolicyno;
+
+    /**
+     * 商业保单号
+     */
+    private String syPolicyno;
+
+    /**
+     * 驾意保单号
+     */
+    private String jyPolicyno;
+    /**
+     * 支付链接
+     */
+    private String paymentLink;
+    /**
+     * 投保人姓名
+     */
+    private String policyName;
+
 }

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

@@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.ObjectReader;
 import com.jzg.commons.constants.ProductsType;
 import com.jzg.commons.constants.dict.InsOrderStatusEnum;
+import com.jzg.commons.entity.orders.po.InsOrdersCarUserInfo;
 import com.jzg.commons.entity.po.jyxSetting;
 import com.jzg.commons.entity.quote.vo.AccidentalDrivingVo;
 import com.jzg.commons.entity.quote.vo.AttributionInformationVo;
@@ -48,8 +49,10 @@ import com.jzg.quotation.huatai.crawler.util.CryptoUtil;
 import com.jzg.quotation.huatai.crawler.util.UploadUtil;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
 import org.jsoup.select.Elements;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
@@ -865,15 +868,24 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
     private UnderwritingResultsVo handlePassedUnderwriting(HtCrawlerOrder order, HtCrawlerConfigureParameters params, UnderwritingVo vo, Map<String, Object> map) {
         HtCrawlerPaymentLinkResponse pl = queryPaymentLink(order, params, new PaymentLinkVo());
-        log.info("paymentLinkResponse: {}", JSONObject.toJSONString(pl));
-        vo.getInsOrders().setPayLink(pl.getImg());
-        vo.getInsOrders().setAuditTime(LocalDateTime.now());
-        vo.getInsOrders().setCompanyOrderNo(order.getQuotationRelaNo());
+        log.info("handlePassedUnderwriting_paymentLinkResponse: {}", JSONObject.toJSONString(pl));
         UnderwritingResultsVo r = new UnderwritingResultsVo();
         r.setPaymentLink(pl.getImg());
         r.setJqPolicyno(map.get("jqProposalno") == null ? "" : map.get("jqProposalno").toString());
         r.setSyPolicyno(map.get("syProposalno") == null ? "" : map.get("syProposalno").toString());
         r.setUnderwritingStatus(InsOrderStatusEnum.PAY_PENDING);
+
+        order.setJqPolicyno(r.getJqPolicyno());
+        order.setSyPolicyno(r.getSyPolicyno());
+        InsOrdersCarUserInfo insOrdersCarUserInfo = null;
+        for (InsOrdersCarUserInfo userInfo : vo.getInsOrdersCarUserInfoList()) {
+            if(userInfo.getPolicyPersonType() == 2) {
+                insOrdersCarUserInfo = userInfo;
+                order.setPolicyName(insOrdersCarUserInfo.getName());
+                break;
+            }
+        }
+        orderService.update(order);
         return r;
     }
 
@@ -887,6 +899,8 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                 HtCrawlerConfigureParameters.class, attr.getConfigInfo());
         HtCrawlerPaymentLinkResponse pl = queryPaymentLink(order, params, vo);
         log.info("paymentLinkResponse: {}", JSONObject.toJSONString(pl));
+        order.setPaymentLink(pl.getImg());
+        orderService.update(order);
         return new PaymentLinkResultsVo(true, pl.getImg(), pl.getErrorInfo());
     }
 
@@ -975,12 +989,12 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
         doLoginCookie(attr);
         String productId = vo.getOrder().getProductId();
-        String companyOrderNo = vo.getOrder().getCompanyOrderNo();
+        HtCrawlerOrder order = orderService.findByOrdersNo(vo.getOrder().getOrdersNo());
         return ProductsType.PT_0330.getCode().equals(productId)
-                ? downloadPolicy(companyOrderNo, true) : downloadPolicy(companyOrderNo, false);
+                ? downloadPolicy(vo, order, order.getJqPolicyno(), true) : downloadPolicy(vo, order, order.getSyPolicyno(), false);
     }
 
-    private DownloadPolicyResultVo downloadPolicy(String no, boolean jq) {
+    private DownloadPolicyResultVo downloadPolicy(DownloadPolicyVo vo,HtCrawlerOrder order, String no, boolean jq) {
         MultiValueMap<String, String> fd = new LinkedMultiValueMap<>();
         fd.add("businessno", no);
         fd.add("riskcode", jq ? "1002" : "1251");
@@ -994,32 +1008,169 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         String body = post(BASE_URL + "/htcsp/DocumentPrintAction.do?cmd=electronicPolicyEndorseDownload",
                 buildDocumentHeaders(req.getCookie()), fd);
         HtCrawlerDownloadPolicyResponse res = JSONObject.parseObject(body, HtCrawlerDownloadPolicyResponse.class);
+        log.info("下载保单响应结果,{}", JSONObject.toJSONString(res));
         DownloadPolicyResultVo r = new DownloadPolicyResultVo();
-        for (var ftp : res.getFtpdownloadJson()) {
-            if (jq) { r.setJqUrl(ftp.getDownurl()); r.setJqFlagUrl(ftp.getBzdownurl()); }
-            else { r.setSyUrl(ftp.getDownurl()); }
-            log.info("下载{}保单地址:{}", jq ? "交强" : "商业", ftp.getDownurl());
+        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());
+            } else {
+                r.setSyUrl(ftpdownloadJson.get(0).getDownurl());
+                log.info("下载商业保单地址:{}", r.getSyUrl());
+            }
+        }
+        if (StringUtils.isNotBlank(order.getJyPolicyno())) {
+            // 1. 构造请求头
+            HttpHeaders headers = getHeaders();
+
+            // 2. url + query params(cmd=perDocumentPrint)
+            String baseUrl = "https://cspxp.pc.ehuatai.com";
+            String path = "/htcsp/DocumentPrintAction.do";
+            UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(baseUrl + path);
+            uriBuilder.queryParam("cmd", "perDocumentPrint");
+            String fullUrl = uriBuilder.toUriString();
+
+            // 3. 构造POST表单data(MultiValueMap 对应 form-data)
+            MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
+            formData.add("businessno", order.getJyPolicyno() + ",");
+            formData.add("registeredxml", "");
+            formData.add("printSysflag", "7,");
+            AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
+            HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
+                    HtCrawlerConfigureParameters.class, attr.getConfigInfo());
+            // Businesscompanynodecode 传两个相同comCode,表单同一个key传多次
+            formData.add("Businesscompanynodecode", params.getComCode());
+            formData.add("Businesscompanynodecode", params.getComCode());
+            formData.add("productcode", "E007");
+            formData.add("noncarPrintflag", "10");
+            formData.add("documenttype", "3");
+            formData.add("documenttypecode", "");
+            formData.add("documentstartcode", "");
+            // peopleinfo 取 tb_uname
+            String tbUname = order.getPolicyName();
+            formData.add("payrefname", tbUname);
+
+            // 4. 发起POST 请求(复用类内已有的post工具方法)
+            String responseText = post(fullUrl, headers, formData);
+            log.info("feiche response:{}", responseText);
+
+            // 5. 解析返回JSON
+            Map<String, Object> respMap = null;
+            try {
+                respMap = MAPPER.readValue(responseText, new TypeReference<Map<String, Object>>() {});
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException(e);
+            }
+            String printUrl = String.valueOf(respMap.get("printurl"));
+            // 替换 & 为 @
+            printUrl = printUrl.replace("&", "@");
+            // 拼接固定前缀赋值成员变量
+            String jyxPolicyUrl = "http://39.98.41.104:8088/ht/ht_bddownload?values=" + printUrl;
+            r.setJyUrl(jyxPolicyUrl);
         }
         return r;
     }
 
+    @NotNull
+    private HttpHeaders getHeaders() {
+        HttpHeaders headers = new HttpHeaders();
+        headers.set("Accept", "application/json, text/javascript, */*; q=0.01");
+        headers.set("Accept-Language", "zh-CN,zh;q=0.9");
+        headers.set("Connection", "keep-alive");
+        headers.set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
+        headers.set("Origin", "https://cspxp.pc.ehuatai.com");
+        headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36");
+        headers.set("X-Requested-With", "XMLHttpRequest");
+        headers.set("Cookie", req.getCookie());
+        return headers;
+    }
+
     // ====================== 查询订单状态 ======================
     @Override
     public OrderStatusResultVo queryOrderState(OrderStatusVo vo) {
         AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
+        doLoginCookie(attr);
         HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
                 HtCrawlerConfigureParameters.class, attr.getConfigInfo());
         HtCrawlerOrder order = orderService.findByOrdersNo(vo.getOrder().getOrdersNo());
         String html = req.queryOrderStatus(new HtCrawlerOrderStateRequest(params, order.getQuotationRelaNo()), req.getHeaders());
-        Document doc = Jsoup.parse(html);
-        Elements tds = doc.select("table[id='table'] tbody tr td");
-        // TODO: 完善字段映射
-        tds.get(1).firstElementChild().text(); // 客户名称
-        tds.get(2).firstElementChild().text(); // 保险起保日期
-        tds.get(3).firstElementChild().text(); // 车牌号
-        tds.get(4).firstElementChild().text(); // 报价单创建日期
-        tds.get(7).firstElementChild().text(); // 订单状态
-        return null;
+        String regex = "<div style=\"display: inline-block;width:50px;text-align: center; \">(.*?)</div>";
+        Pattern pattern = Pattern.compile(regex);
+        Matcher matcher = pattern.matcher(html);
+        String orderStatus = null;
+        if (matcher.find()) orderStatus = matcher.group(1);
+        InsOrderStatusEnum orderStatusEnum = null;
+        String syApplication = "";
+        String jqApplication = "";
+        String fcxApplication = "";
+        if ("已通过核保已实收".equals(orderStatus)) {
+            orderStatusEnum = InsOrderStatusEnum.valueOf("QUOTE_ING");
+            orderStatus = orderStatusEnum.getCode();
+            List<Map<String, String>> fcxList = new ArrayList<>();
+            // 1. 构建请求头 headers
+            HttpHeaders headers = getHttpHeaders();
+
+            // 2. 拼接URL + GET参数 params
+            String baseUrl = "https://cspxp.pc.ehuatai.com/htcsp/DocumentPrintAction.do";
+            MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
+            map.add("cmd", "queryDocumentPrintList");
+            map.add("printType", "1");
+            map.add("quotationno", order.getQuotationRelaNo());
+            map.add("licenseno", vo.getCarInfo().getLicenseNo());
+
+            // 3. 发起GET请求(复用项目已有cookieUtils工具,跳过证书校验同verify=False)
+            String htmlResp = get(baseUrl, headers, map);
+
+            // 4. Jsoup解析HTML,查找所有type=checkbox的input
+            Document soup = Jsoup.parse(htmlResp);
+            Elements inputElements = soup.select("input[type=checkbox]");
+
+            // 循环遍历input标签
+            for (Element item : inputElements) {
+                String businessno = item.attr("businessno");
+                String riskcode = item.attr("riskcode");
+                log.info("riskcode: {}, businessno: {}", riskcode, businessno);
+
+                if ("1251".equals(riskcode)) {
+                    syApplication = businessno;
+                } else if ("1002".equals(riskcode)) {
+                    jqApplication = businessno;
+                } else {
+                    fcxApplication = businessno;
+                    Map<String, String> fcxItem = new HashMap<>();
+                    fcxItem.put("policy", "");
+                    fcxItem.put("application", businessno);
+                    fcxList.add(fcxItem);
+                }
+            }
+        }
+        OrderStatusResultVo orderStatusResultVo = new OrderStatusResultVo();
+        //将订单状态转换为枚举类型
+        orderStatusResultVo.setOrderStatus(orderStatusEnum);
+        orderStatusResultVo.setOrderStatusStr(orderStatus);
+
+        orderStatusResultVo.setJqPolicyno( StringUtils.isBlank(jqApplication) ? order.getJqPolicyno() : jqApplication);
+        orderStatusResultVo.setSyPolicyno( StringUtils.isBlank(syApplication) ? order.getSyPolicyno() : syApplication);
+        orderStatusResultVo.setJyPolicyno( StringUtils.isBlank(fcxApplication) ? order.getJyPolicyno() : fcxApplication);
+        orderStatusResultVo.setPaymentLink(order.getPaymentLink());
+        return orderStatusResultVo;
+    }
+
+    @NotNull
+    private HttpHeaders getHttpHeaders() {
+        HttpHeaders headers = new HttpHeaders();
+        headers.set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
+        headers.set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8");
+        headers.set("Cache-Control", "no-cache");
+        headers.set("Connection", "keep-alive");
+        headers.set("Pragma", "no-cache");
+        headers.set("Referer", "https://cspxp.pc.ehuatai.com/htcsp/QuotationQueryAction.do?cmd=quotationListAll");
+        headers.set("Upgrade-Insecure-Requests", "1");
+        headers.set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36");
+        headers.set("Cookie", req.getCookie()); // 塞入cookie
+        return headers;
     }
 
     // ====================== Headers 统一构建 ======================

+ 4 - 3
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/aspect/QuoteVerificationAspect.java

@@ -454,9 +454,10 @@ public class QuoteVerificationAspect {
         BigDecimal keepPointRateCal  = BigDecimalUtil.subtract(BigDecimalUtil.add(BigDecimalUtil.divide(inletPercent,taxFactor),addThrowPercent),exportPercent)
                 .setScale(5,RoundingMode.HALF_EVEN);
         keepPointRate.accept(insFeeOrders, keepPointRateCal);
-        // 设置留点比例和留点费用
-        BigDecimal keepPointAmount  = BigDecimalUtil.subtract(BigDecimalUtil.add(receivable,addAmount),payable)
-                .setScale(5,RoundingMode.HALF_EVEN);
+        // 设置留点比例和留点费用, 费用这块, 留点金额按照 保费* 留点比例计算得到的吧。然后,直接保留5位,多出来的直接截断 2026年7月3日11:47:08
+//        BigDecimal keepPointAmount  = BigDecimalUtil.subtract(BigDecimalUtil.add(receivable,addAmount),payable)
+//                .setScale(5,RoundingMode.HALF_EVEN);
+        BigDecimal keepPointAmount = BigDecimalUtil.multiply(premium,keepPointRateCal).setScale(5,RoundingMode.DOWN);
         keepPointPremium.accept(insFeeOrders,keepPointAmount);
     }
 

+ 29 - 34
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsFeeOrdersServiceImpl.java

@@ -116,28 +116,6 @@ public class InsFeeOrdersServiceImpl extends ServiceImpl<InsFeeOrdersMapper, Ins
         // 获取费用信息
         InsFeeOrders insFeeOrders = insFeeOrdersMapper.selectById(insOrders.getId());
 
-        // add by lipf, 2026年6月23日18:32:24 按照财务和田智沟通后的结果,当入口手续费比例大于入口比例的时候,强制设置入口手续费比例为入口比例。
-//        BigDecimal jqInletRatio = insFeeOrders.getJqInletRatio();
-//        BigDecimal syInletRatio = insFeeOrders.getSyInletRatio();
-//        BigDecimal jyInletRatio = insFeeOrders.getJyInletRatio();
-//
-//        BigDecimal jqCommissionRatio = insFeeOrders.getJqCommissionRatio();
-//        BigDecimal syCommissionRatio = insFeeOrders.getSyCommissionRatio();
-//        BigDecimal jyCommissionRatio = insFeeOrders.getJyCommissionRatio();
-//
-//        if(BigDecimalUtil.subtract(jqCommissionRatio,jqInletRatio).compareTo(BigDecimal.ZERO)>0){
-//            insFeeOrders.setJqCommissionRatio(jqInletRatio);
-//            insFeeOrders.setJqFollowRatio(BigDecimal.ZERO);
-//        }
-//        if(BigDecimalUtil.subtract(syCommissionRatio,syInletRatio).compareTo(BigDecimal.ZERO)>0){
-//            insFeeOrders.setSyCommissionRatio(syInletRatio);
-//            insFeeOrders.setSyFollowRatio(BigDecimal.ZERO);
-//        }
-//        if(BigDecimalUtil.subtract(jyCommissionRatio,jyInletRatio).compareTo(BigDecimal.ZERO)>0){
-//            insFeeOrders.setJyCommissionRatio(jyInletRatio);
-//            insFeeOrders.setJyFollowRatio(BigDecimal.ZERO);
-//        }
-
         // 关键:获取是否含税标识
         Integer withTax = ptlAgreement.getWithTax(); // 0-否 1-是
 
@@ -153,19 +131,19 @@ public class InsFeeOrdersServiceImpl extends ServiceImpl<InsFeeOrdersMapper, Ins
         // 1. 交强险
         calculatePremiumByRule(insFeeOrders, "jq", jqPremium,
                 insFeeOrders.getJqCommissionRatio(), insFeeOrders.getJqInletRatio(), insFeeOrders.getJqFollowRatio(),
-                insFeeOrders.getJqAdditionalRatio(), insFeeOrders.getJqKeepPointRatio(), insFeeOrders.getJqExportRatio(), insFeeOrders.getJqExportKeepPointRatio(),
+                insFeeOrders.getJqAdditionalRatio(), insFeeOrders.getJqKeepPointRatio(), insFeeOrders.getJqExportRatio(), insFeeOrders.getJqExportKeepPointRatio(),insFeeOrders.getJqExportRatioContainDistribution(),
                 withTax, taxRate, SCALE);
 
         // 2. 商业险
         calculatePremiumByRule(insFeeOrders, "sy", syPremium,
                 insFeeOrders.getSyCommissionRatio(), insFeeOrders.getSyInletRatio(), insFeeOrders.getSyFollowRatio(),
-                insFeeOrders.getSyAdditionalRatio(), insFeeOrders.getSyKeepPointRatio(), insFeeOrders.getSyExportRatio(), insFeeOrders.getSyExportKeepPointRatio(),
+                insFeeOrders.getSyAdditionalRatio(), insFeeOrders.getSyKeepPointRatio(), insFeeOrders.getSyExportRatio(), insFeeOrders.getSyExportKeepPointRatio(),insFeeOrders.getSyExportRatioContainDistribution(),
                 withTax, taxRate, SCALE);
 
         // 3. 驾乘险
         calculatePremiumByRule(insFeeOrders, "jy", jyPremium,
                 insFeeOrders.getJyCommissionRatio(), insFeeOrders.getJyInletRatio(), insFeeOrders.getJyFollowRatio(),
-                insFeeOrders.getJyAdditionalRatio(), insFeeOrders.getJyKeepPointRatio(), insFeeOrders.getJyExportRatio(), insFeeOrders.getJyExportKeepPointRatio(),
+                insFeeOrders.getJyAdditionalRatio(), insFeeOrders.getJyKeepPointRatio(), insFeeOrders.getJyExportRatio(), insFeeOrders.getJyExportKeepPointRatio(),insFeeOrders.getJyExportRatioContainDistribution(),
                 withTax, taxRate, SCALE);
 
         // 重新统计总应收、总应付
@@ -203,7 +181,7 @@ public class InsFeeOrdersServiceImpl extends ServiceImpl<InsFeeOrdersMapper, Ins
             InsFeeOrders insFeeOrders, String type,
             BigDecimal premium,
             BigDecimal commissionRatio, BigDecimal inletRatio, BigDecimal followRatio,
-            BigDecimal addThrowRatio, BigDecimal keepPointRatio, BigDecimal exportRatio, BigDecimal exportKeepPointRatio,
+            BigDecimal addThrowRatio, BigDecimal keepPointRatio, BigDecimal exportRatio, BigDecimal exportKeepPointRatio, BigDecimal exportRatioContainDistribution,
             Integer withTax, BigDecimal taxRate, int scale) {
 
         // 计算基数
@@ -228,8 +206,19 @@ public class InsFeeOrdersServiceImpl extends ServiceImpl<InsFeeOrdersMapper, Ins
         BigDecimal exportKeepPoint = premium.multiply(exportKeepPointRatio).setScale(scale, RoundingMode.DOWN);
         // 留点比例直接乘, modify by lipf ,留点比例在infFeeOrder中计算得到。这里直接使用 2026年6月16日17:32:09, 为了保持金额一致,留点比例不适用百分比乘积,而是使用金额相减
         // BigDecimal keepPoint = premium.multiply(keepPointRatio).setScale(scale, RoundingMode.HALF_EVEN);
-        BigDecimal keepPoint = BigDecimalUtil.subtract(BigDecimalUtil.add(inlet,addThrow),export);
-        log.info("计算留点:inlet=[{}],addThrow=[{}].export=[{}]", inlet, addThrow, export);
+        //BigDecimal keepPoint = BigDecimalUtil.subtract(BigDecimalUtil.add(inlet,addThrow),export);
+        // modify by lipf 2026年7月1日10:07:18 计算留点的时候,将分销费用移除
+//        BigDecimal distributionFee = BigDecimalUtil.add(insFeeOrders.getFirstDistributionFee(), insFeeOrders.getSecondDistributionFee(), insFeeOrders.getThirdDistributionFee());
+//        log.info("分销总费用:distributionFee=【{}】",distributionFee);
+//        BigDecimal keepPoint = BigDecimalUtil.subtract(BigDecimalUtil.add(inlet,addThrow),BigDecimalUtil.add(export, distributionFee));
+//        log.info("留点费用:inlet=[{}],addThrow=[{}],export=[{}], distributionFee=[{}]mkeepPoint=[{}]", inlet, addThrow, export, distributionFee, keepPoint);
+        // modify by lipf 财务要求,计算留点的时候,出口费用,按照两位进行截断,
+//        BigDecimal exportContainDistribute = premium.multiply(exportRatioContainDistribution).setScale(2, RoundingMode.DOWN);
+//        BigDecimal keepPoint = BigDecimalUtil.subtract(BigDecimalUtil.add(inlet,addThrow),exportContainDistribute);
+
+        // 设置留点比例和留点费用, 费用这块, 留点金额按照 保费* 留点比例计算得到的吧。然后,直接保留5位,多出来的直接截断 2026年7月3日11:47:08
+        BigDecimal keepPoint = BigDecimalUtil.multiply(premium,keepPointRatio).setScale(5,RoundingMode.DOWN);
+        log.info("计算留点:inlet=[{}],addThrow=[{}].exportContainDistribute=[{}]", inlet, addThrow, keepPointRatio);
 
         // 赋值到实体(自动匹配 jq / sy / jy)
         if ("jq".equals(type)) {
@@ -405,15 +394,15 @@ public class InsFeeOrdersServiceImpl extends ServiceImpl<InsFeeOrdersMapper, Ins
             jyCommissionRatio = jyInletRatio;
         }
 
-        // 手续费
+        // 手续费比例
         insFeeOrders.setJqCommissionRatio(jqCommissionRatio);
         insFeeOrders.setSyCommissionRatio(syCommissionRatio);
         insFeeOrders.setJyCommissionRatio(jyCommissionRatio);
-        // 入口费用
+        // 入口费用比例
         insFeeOrders.setJqInletRatio(jqInletRatio);
         insFeeOrders.setSyInletRatio(syInletRatio);
         insFeeOrders.setJyInletRatio(jyInletRatio);
-        // 跟单费
+        // 跟单费比例
         insFeeOrders.setJqFollowRatio(jqFollowRatio);
         insFeeOrders.setSyFollowRatio(syFollowRatio);
         insFeeOrders.setJyFollowRatio(jyFollowRatio);
@@ -432,9 +421,15 @@ public class InsFeeOrdersServiceImpl extends ServiceImpl<InsFeeOrdersMapper, Ins
         insFeeOrders.setSyExportRatio(syExportRatio);
         insFeeOrders.setJyExportRatio(jyExportRatio);
         // 需求:入口 + 加投 - 出口 = 留点
-        BigDecimal jqKeepPointRatio = (jqInletRatio.divide(taxFactor, 5, RoundingMode.HALF_EVEN)).add(jqAdditionalRatio).subtract(jqExportRatio);
-        BigDecimal syKeepPointRatio = (syInletRatio.divide(taxFactor, 5, RoundingMode.HALF_EVEN)).add(syAdditionalRatio).subtract(syExportRatio);
-        BigDecimal jyKeepPointRatio = (jyInletRatio.divide(taxFactor, 5, RoundingMode.HALF_EVEN)).add(jyAdditionalRatio).subtract(jyExportRatio);
+        BigDecimal distributionRatio = BigDecimalUtil.divide(BigDecimalUtil.add(insFeeOrders.getFirstDistributionRatio(), insFeeOrders.getSecondDistributionRatio(), insFeeOrders.getThirdDistributionRatio()),new BigDecimal(100));
+        insFeeOrders.setJqExportRatioContainDistribution(BigDecimalUtil.add(jqExportRatio,distributionRatio));
+        insFeeOrders.setSyExportRatioContainDistribution(BigDecimalUtil.add(syExportRatio,distributionRatio) );
+        insFeeOrders.setJyExportRatioContainDistribution(BigDecimalUtil.add(jyExportRatio,distributionRatio) );
+        BigDecimal jqKeepPointRatio = (jqInletRatio.divide(taxFactor, 5, RoundingMode.HALF_EVEN)).add(jqAdditionalRatio).subtract(BigDecimalUtil.add(jqExportRatio,distributionRatio));
+        log.info("计算留点比例。 distributionRatio=[{}],jqInletRatio=[{}],jqAdditionalRatio=[{}],jqExportRatio=[{}]", distributionRatio,jqInletRatio,jqAdditionalRatio,jqExportRatio);
+
+        BigDecimal syKeepPointRatio = (syInletRatio.divide(taxFactor, 5, RoundingMode.HALF_EVEN)).add(syAdditionalRatio).subtract(BigDecimalUtil.add(syExportRatio,distributionRatio));
+        BigDecimal jyKeepPointRatio = (jyInletRatio.divide(taxFactor, 5, RoundingMode.HALF_EVEN)).add(jyAdditionalRatio).subtract(BigDecimalUtil.add(jyExportRatio,distributionRatio));
         // 留点比例
         insFeeOrders.setJqKeepPointRatio(jqKeepPointRatio);
         insFeeOrders.setSyKeepPointRatio(syKeepPointRatio);

+ 8 - 3
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/build/TkCrawlerQuoteResultsBuild.java

@@ -24,6 +24,7 @@ import com.jzg.quotation.taikang.crawler.model.entity.TKPremium;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.io.Serializable;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
@@ -127,11 +128,15 @@ public class TkCrawlerQuoteResultsBuild {
         BigDecimal bi = premium.getSyPremium() == null ? BigDecimal.ZERO : premium.getSyPremium();
         BigDecimal jyx = premium.getJyxPremium() == null ? BigDecimal.ZERO : premium.getJyxPremium();
         BigDecimal vv = preciseInitResult.getCarTax().getSumPayment() == null ? BigDecimal.ZERO : preciseInitResult.getCarTax().getSumPayment();
-        BigDecimal total = ci.add(bi).add(jyx).add(vv);
+        BigDecimal tlt = new BigDecimal(0);
+        if (CollectionUtils.isNotEmpty(preciseInitResult.getNonCarProductList()) && "1".equals(preciseInitResult.getNonCarProductList().get(0).getIsSelect())) {
+            tlt = preciseInitResult.getNonCarProductList().get(0).getPremium() == null ? BigDecimal.ZERO : new BigDecimal(preciseInitResult.getNonCarProductList().get(0).getPremium());
+        }
+        BigDecimal total = ci.add(bi).add(jyx).add(vv).add(tlt);
         quoteResultsVo.getCosts().setSumPremium(total.toPlainString());
         // 折扣
-        quoteResultsVo.getScoreVo().setJqAdjustRate(preciseInitResult.getCiDisCount() != null ? preciseInitResult.getCiDisCount().toString() : null);
-        quoteResultsVo.getScoreVo().setSyAdjustRate(preciseInitResult.getBzDisCount() != null ? preciseInitResult.getBzDisCount().toString() : null);
+        quoteResultsVo.getScoreVo().setJqAdjustRate(preciseInitResult.getBzDisCount() != null ? preciseInitResult.getBzDisCount().toString() : null);
+        quoteResultsVo.getScoreVo().setSyAdjustRate(preciseInitResult.getCiDisCount() != null ? preciseInitResult.getCiDisCount().toString() : null);
         // 交强续保
         quoteResultsVo.getScoreVo().setJqRenewal(convertRenewalFlag(preciseInitResult.getFlag().getRenewalBZFlag()));
         // 商业续保

+ 1 - 1
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/model/api/TKEntranceFor000038Result.java

@@ -171,7 +171,7 @@ public class TKEntranceFor000038Result extends UnknownFieldEntity {
     private String proxyFlag;
 
     /**
-     * 验车标志
+     * 验车标志 0表示此单无需验车,1表示需要验车
      */
     private String checkCarFlag;
 

+ 1 - 1
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/model/entity/TKOrderData.java

@@ -29,7 +29,7 @@ public class TKOrderData extends UnknownFieldEntity {
     private List<TKOrderGwNonAutoCheckInfo> gwNonAutoCheckInfoList;
 
     /**
-     * 核保标志
+     * 核保标志 3表示自核通过,9表示人工审核
      */
     private String underWriteFlag;
 

+ 100 - 0
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/TKCommonUtil.java

@@ -1,7 +1,20 @@
 package com.jzg.quotation.taikang.crawler.service;
 
+import com.jzg.commons.constants.quote.enums.quote.CarKindCode;
+import com.jzg.commons.constants.quote.enums.quote.VehicleUseCode;
+import com.jzg.commons.entity.quote.vo.CarInfoVo;
+import com.jzg.commons.entity.quote.vo.CustomerInfoVo;
+import com.jzg.commons.util.IDCardUtils;
+import com.jzg.quotation.taikang.crawler.model.entity.TKCar;
+import com.jzg.quotation.taikang.crawler.model.entity.TKCarModel;
+import com.jzg.quotation.taikang.crawler.model.entity.TKCarOwnerVo;
+import org.apache.commons.lang3.StringUtils;
+
+import java.math.BigDecimal;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 
 public class TKCommonUtil {
 
@@ -10,6 +23,19 @@ public class TKCommonUtil {
      */
     private static final List<Character> firstChar = Arrays.asList('A', 'B', 'C', 'D', 'J', 'K', 'L', 'Q', 'R', 'S', 'V', 'Y');
 
+    /**
+     * 车辆使用性质代码映射表:公司系统代码 -> 保司系统代码
+     */
+    public static final Map<String, String> VEHICLE_USE_CODE_MAP = Map.of(
+            VehicleUseCode.VEHICLE_USE_CODE_05.getCode(), "8A",
+            VehicleUseCode.VEHICLE_USE_CODE_06.getCode(), "8C",
+            VehicleUseCode.VEHICLE_USE_CODE_07.getCode(), "8B",
+            VehicleUseCode.VEHICLE_USE_CODE_01.getCode(), "9",
+            VehicleUseCode.VEHICLE_USE_CODE_02.getCode(), "9",
+            VehicleUseCode.VEHICLE_USE_CODE_03.getCode(), "9",
+            VehicleUseCode.VEHICLE_USE_CODE_04.getCode(), "9"
+    );
+
     /**
      * 是否为军队的车牌号
      *
@@ -41,4 +67,78 @@ public class TKCommonUtil {
 
         return false;
     }
+
+    /**
+     * 根据车辆大类代码解析保司车辆类型
+     * <p>
+     * 将公司系统的车辆大类代码转换为保司系统的车辆类型代码
+     *
+     * @param carKindCode 车辆大类代码
+     * @return 保司车辆类型代码 (A/D)
+     */
+    public static String resolveCarType(String carKindCode) {
+        if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A0.getCode())) {
+            return "A";
+        } else if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A1.getCode())) {
+            return "D";
+        } else {
+            throw new RuntimeException("未知车辆大类:" + carKindCode);
+        }
+    }
+
+    /**
+     * 获取字符串中第一个小数点之前的部分
+     * <p>
+     * 用于处理排量、购车价等需要去掉小数部分的场景
+     *
+     * @param value 原始字符串
+     * @return 小数点之前的部分,若没有小数点则返回原字符串
+     */
+    public static String substringBeforeDot(String value) {
+        if (StringUtils.isBlank(value)) {
+            return value;
+        }
+        int dotIndex = value.indexOf(".");
+        return dotIndex > 0 ? value.substring(0, dotIndex) : value;
+    }
+
+    /**
+     * 构建车主信息对象
+     * <p>
+     * 根据公司系统的车主信息构建保司系统的车主信息对象
+     *
+     * @param ownersInfo 公司系统车主信息(可为null)
+     * @return 保司系统车主信息对象
+     */
+    public static TKCarOwnerVo buildCarOwnerVo(CustomerInfoVo ownersInfo) {
+        TKCarOwnerVo carOwnerVo = new TKCarOwnerVo();
+        Optional.ofNullable(ownersInfo)
+                .ifPresent(info -> {
+                    carOwnerVo.setCarOwnerNature("1");
+                    carOwnerVo.setCertificateType("01");
+                    carOwnerVo.setId(info.getIdentifyNumber());
+                    carOwnerVo.setName(info.getName());
+                    carOwnerVo.setBirthDate(info.getBirthday());
+                    carOwnerVo.setSerialno(BigDecimal.ZERO);
+                    carOwnerVo.setSex(IDCardUtils.getSex(info.getIdentifyNumber()));
+                    carOwnerVo.setInsuredAddress("");
+                });
+        return carOwnerVo;
+    }
+
+    /**
+     * 构建车辆基本信息
+     * <p>
+     * 设置车辆的基础属性:燃油类型、车型名称、票据日期等
+     *
+     * @param car        车辆信息对象
+     * @param tkCarModel 车型信息
+     */
+    public static void buildCarBasicInfo(TKCar car, TKCarModel tkCarModel) {
+        car.setFuelType(tkCarModel.getFuelType());
+        car.setModelName(tkCarModel.getModelName());
+        car.setBillDate("");
+        car.setCarBookingTime("");
+        car.setBillNo("");
+    }
 }

+ 103 - 114
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/TKHttpAPIService.java

@@ -1,12 +1,12 @@
 package com.jzg.quotation.taikang.crawler.service;
 
 import cn.hutool.core.bean.BeanUtil;
-import com.jzg.commons.constants.quote.enums.quote.CarKindCode;
-import com.jzg.commons.constants.quote.enums.quote.VehicleUseCode;
+
 import com.jzg.commons.entity.quote.vo.CarInfoVo;
 import com.jzg.commons.entity.quote.vo.CustomerInfoVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.commons.exception.SystemException;
+
 import com.jzg.quotation.commons.filter.CarModelsFilterUtils;
 import com.jzg.quotation.taikang.crawler.CodeUtil;
 import com.jzg.quotation.taikang.crawler.JsonUtil;
@@ -395,137 +395,126 @@ public class TKHttpAPIService {
     }
 
     /**
-     * 09 交管车辆类型校验
+     * 交管车辆类型校验
+     * <p>
+     * 该方法负责构建车辆类型校验参数,调用保司接口进行交管车辆类型校验。
+     * <p>
+     * 主要处理逻辑:
+     * 1. 参数初始化:设置渠道代码、订单ID和车型信息
+     * 2. 车主信息构建:从公司系统参数提取车主信息
+     * 3. 投保人信息构建:初始化投保人、被保险人、联系人信息
+     * 4. 车辆信息构建:设置车辆基本信息、类型、过户状态、使用性质等
+     * 5. 请求组装:组装完整的校验参数并调用保司接口
      *
-     * @return
+     * @param session                登录会话信息
+     * @param commonQuoteVo          公司系统报价参数
+     * @param mainOrderId            主订单ID
+     * @param carModelinfoQueryResult 车型查询结果
+     * @return 校验结果
      */
     public String carModelinfoCheckVehicleType(TKLoginSession session, QuoteVo commonQuoteVo, String mainOrderId, CarModelinfoQueryResult carModelinfoQueryResult) {
-        CarModelinfoCheckVehicleTypeParam carModelinfoCheckVehicleTypeParam = new CarModelinfoCheckVehicleTypeParam();
-        carModelinfoCheckVehicleTypeParam.setChannelCode("OffWeb");
-        carModelinfoCheckVehicleTypeParam.setOrderId(mainOrderId);
-        TKCarModel tkCarModel = CarModelsFilterUtils.to(carModelinfoQueryResult.getCarModel(), commonQuoteVo.getCarInfoVo());
+        // ========== 1. 参数初始化 ==========
+
+        CarModelinfoCheckVehicleTypeParam param = new CarModelinfoCheckVehicleTypeParam();
+        param.setChannelCode("OffWeb");
+        param.setOrderId(mainOrderId);
+
+        // 获取车型信息并设置必要字段
+        TKCarModel tkCarModel = CarModelsFilterUtils.to(
+                carModelinfoQueryResult.getCarModel(),
+                commonQuoteVo.getCarInfoVo()
+        );
         tkCarModel.setVehicleStyleUniqueId("");
-        CarModelinfoQueryResult.CarInfoDto carInfoDto = carModelinfoQueryResult.getCarInfoDto();
-
-        // 车辆所有人信息
-        TKCarOwnerVo carOwnerVo = new TKCarOwnerVo();
-        CustomerInfoVo commonCustomersInfo = commonQuoteVo.getOwnersInfo();
-        if (commonCustomersInfo != null) {
-            carOwnerVo.setCarOwnerNature("1"); // todo
-            carOwnerVo.setCertificateType("01");  // 身份证
-            carOwnerVo.setId(commonCustomersInfo.getIdentifyNumber());
-            carOwnerVo.setName(commonCustomersInfo.getName());
-            carOwnerVo.setBirthDate(commonCustomersInfo.getBirthday());
-            carOwnerVo.setSerialno(BigDecimal.ZERO);
-            carOwnerVo.setSex(commonCustomersInfo.getGender()); // todo
-            carOwnerVo.setInsuredAddress("");
-        }
 
+        // ========== 2. 车主信息构建 ==========
+
+        TKCarOwnerVo carOwnerVo = TKCommonUtil.buildCarOwnerVo(commonQuoteVo.getOwnersInfo());
+
+        // ========== 3. 投保人信息构建 ==========
 
-        // 投保人信息
         SaveBasicInfoParam.InsureInfo insureInfo = new SaveBasicInfoParam.InsureInfo();
         insureInfo.setAppliInfo(new SaveBasicInfoParam.AppliInfo());
         insureInfo.setInsuredInfo(new SaveBasicInfoParam.InsuredInfo());
         insureInfo.setContacts(new SaveBasicInfoParam.Contacts());
 
-        // 车辆信息
-        TKCar car = new TKCar();
-        car.setFuelType(tkCarModel.getFuelType()); // todo 燃油类型
-        car.setModelName(tkCarModel.getModelName()); // todo
-        car.setBillDate(""); // 页面上没有输入框,可能是北京地区才用到
-        car.setCarBookingTime(""); // 页面上没有输入框,原注释为“上海地区开具发票日期”,但感觉是车辆时间。
-        car.setBillNo(""); // 页面上没有输入框,发票号
-
-        CarInfoVo commonCarInfoVo = commonQuoteVo.getCarInfoVo();
-
-        if (commonCarInfoVo != null) {
-            // 车辆大类
-            String carKindCode = commonCarInfoVo.getCarKindCode();
-
-            if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A0.getCode())) {
-                car.setCarType("A");
-            } else if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A1.getCode())) {
-                car.setCarType("D");
-            } else {
-                throw new RuntimeException("未知车辆大类:" + carKindCode);
-            }
+        // ========== 4. 车辆信息构建 ==========
 
-            CodeUtil.autoSetter(car::setLicenseNo, commonCarInfoVo::getLicenseNo, tkCarModel::getLicenseNo);
-            car.setVinNo(commonCarInfoVo.getVinNo());
-            car.setEngineNo(commonCarInfoVo.getEngineNo());
-            car.setEnrollDate(commonCarInfoVo.getRegisterDate());
-            CodeUtil.autoSetter(car::setTransferDate, commonCarInfoVo::getTransferDate, null);
-            CodeUtil.autoSetter(car::setPurchasePrice, commonCarInfoVo::getPurchasePrice, tkCarModel::getReplacementValue);
-            CodeUtil.autoSetter(car::setSelectedSeat, commonCarInfoVo::getSeatCount, () -> tkCarModel.getVehicleJingyou().getSeat());
-            CodeUtil.autoSetter(car::setExhaustScale, commonCarInfoVo::getDisplacement, tkCarModel::getDisplacement);  // 排量
-            CodeUtil.autoSetter(car::setBrandName, commonCarInfoVo::getBrandName, tkCarModel::getModelName); // todo 与 tkCarModel.getModelName() 重复
-
-            car.setSeatFlag(1); // todo
-            car.setExhaustScale(tkCarModel.getDisplacement().substring(0, tkCarModel.getDisplacement().indexOf(".")));
-            car.setPurchasePrice(tkCarModel.getPurchasePrice().substring(0, tkCarModel.getPurchasePrice().indexOf(".")));
-
-            Double tonCount = commonCarInfoVo.getTonCountDouble();
-            if (tonCount != null && tonCount > 2) {
-                // 好像主要是货车使用此字段
-                car.setTonnageFlag("1");
-            } else {
-                car.setTonnageFlag("0");
-            }
-
-            if (commonCarInfoVo.isTransfer()) {
-                car.setChgOwnerFlag("1"); // 是过户
-                car.setTransferDate(commonCarInfoVo.getTransferDate()); // 过户时间
-            } else {
-                car.setChgOwnerFlag("0"); // 不是过户
-                car.setTransferDate("");
-            }
-
-            String vehicleUseCode = commonCarInfoVo.getVehicleUseCode();
-
-            if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_05.getCode())) {
-                // 05 家庭自用 = 8A 家庭自用
-                car.setUseNatureCodeCode("8A"); // 页面上选择的家庭自用、非营业企业、、、、
-            } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_06.getCode())) {
-                // 06 非营业企业 = 8C 非营业党政机关事业团体
-                car.setUseNatureCodeCode("8C");
-            } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_07.getCode())) {
-                // 07 非营业企业 = 8B 非营业企业
-                car.setUseNatureCodeCode("8B");
-            } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_01.getCode())) {
-                // 01 营业出租租赁 = 9 营业
-                car.setUseNatureCodeCode("9");
-            } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_02.getCode())) {
-                // 02 营业城市公交 = 9 营业
-                car.setUseNatureCodeCode("9");
-            } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_03.getCode())) {
-                // 03 营业公路客运 = 9 营业
-                car.setUseNatureCodeCode("9");
-            } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_04.getCode())) {
-                // 04 营业货运 = 9 营业
-                car.setUseNatureCodeCode("9");
-            } else {
-                // 默认 8B 非营业企业
-                car.setUseNatureCodeCode("8B");
-            }
-            TKSimpleService.setLicenseTypeByLicenseNo(car, car.getLicenseNo());
-        }
+        TKCar car = new TKCar();
 
-        String passengerSeat = this.carTypePassengerSeat(session.getToken(), session.getTplatformId(), session.getFromid(), session.getComCode());
+        // 设置车辆基本信息
+        TKCommonUtil.buildCarBasicInfo(car, tkCarModel);
+
+        // 使用 Optional 处理车辆信息的空值情况
+        Optional.ofNullable(commonQuoteVo.getCarInfoVo())
+                .ifPresent(commonCarInfoVo -> {
+                    // 设置车辆大类(通过辅助方法解析)
+                    car.setCarType(TKCommonUtil.resolveCarType(commonCarInfoVo.getCarKindCode()));
+
+                    // 使用 autoSetter 设置可选字段(优先使用公司系统值,其次使用车型查询值)
+                    CodeUtil.autoSetter(car::setLicenseNo, commonCarInfoVo::getLicenseNo, tkCarModel::getLicenseNo);
+                    CodeUtil.autoSetter(car::setTransferDate, commonCarInfoVo::getTransferDate, null);
+                    CodeUtil.autoSetter(car::setPurchasePrice, commonCarInfoVo::getPurchasePrice, tkCarModel::getReplacementValue);
+                    CodeUtil.autoSetter(car::setSelectedSeat, commonCarInfoVo::getSeatCount, () -> tkCarModel.getVehicleJingyou().getSeat());
+                    CodeUtil.autoSetter(car::setExhaustScale, commonCarInfoVo::getDisplacement, tkCarModel::getDisplacement);
+                    CodeUtil.autoSetter(car::setBrandName, commonCarInfoVo::getBrandName, tkCarModel::getModelName);
+
+                    // 设置固定字段和从车型提取的字段(去掉小数部分)
+                    car.setSeatFlag(1);
+                    car.setExhaustScale(TKCommonUtil.substringBeforeDot(tkCarModel.getDisplacement()));
+                    car.setPurchasePrice(TKCommonUtil.substringBeforeDot(tkCarModel.getPurchasePrice()));
+                    car.setVinNo(commonCarInfoVo.getVinNo());
+                    car.setEngineNo(commonCarInfoVo.getEngineNo());
+                    car.setEnrollDate(commonCarInfoVo.getRegisterDate());
+
+                    // 设置吨位标志(两吨以上货车标记为1)
+                    car.setTonnageFlag(Optional.ofNullable(commonCarInfoVo.getTonCountDouble())
+                            .filter(ton -> ton > 2)
+                            .map(ton -> "1")
+                            .orElse("0"));
+
+                    // 设置过户标志和过户时间
+                    if (commonCarInfoVo.isTransfer()) {
+                        car.setChgOwnerFlag("1");
+                        car.setTransferDate(commonCarInfoVo.getTransferDate());
+                    } else {
+                        car.setChgOwnerFlag("0");
+                        car.setTransferDate("");
+                    }
+
+                    // 设置车辆使用性质(通过映射表转换,默认8B非营业企业)
+                    String vehicleUseCode = commonCarInfoVo.getVehicleUseCode();
+                    car.setUseNatureCodeCode(TKCommonUtil.VEHICLE_USE_CODE_MAP.getOrDefault(vehicleUseCode, "8B"));
+
+                    // 设置车牌类型
+                    TKSimpleService.setLicenseTypeByLicenseNo(car, car.getLicenseNo());
+                });
+
+        // 根据渠道调整车辆类型
+        String passengerSeat = this.carTypePassengerSeat(
+                session.getToken(),
+                session.getTplatformId(),
+                session.getFromid(),
+                session.getComCode()
+        );
+        Optional.ofNullable(carType(passengerSeat, car.getCarType(), carOwnerVo.getCarOwnerNature()))
+                .ifPresent(car::setCarType);
+
+        // 设置车型列表和品牌名称
+        car.setCarModel(List.of(tkCarModel));
+        car.setBrandName(tkCarModel.getModelName());
 
-        String carType = carType(passengerSeat, car.getCarType(), carOwnerVo.getCarOwnerNature());
+        // ========== 5. 请求组装与调用 ==========
 
-        if (carType != null) {
-            car.setCarType(carType);
-        }
-        car.setCarModel(Arrays.asList(tkCarModel));
-        car.setBrandName(tkCarModel.getModelName());
+        // 将 TKCar 转换为接口所需的 CarInfoVo 类型
         CarModelinfoCheckVehicleTypeParam.CarInfoVo carInfoVo = BeanUtil.copyProperties(car, CarModelinfoCheckVehicleTypeParam.CarInfoVo.class);
-        carModelinfoCheckVehicleTypeParam.setCarInfoVo(carInfoVo);
-
+        param.setCarInfoVo(carInfoVo);
 
+        // 设置请求头并调用接口
         HttpHeaders httpHeaders = new HttpHeaders();
         httpHeaders.add("tkCarRequestToken", session.getToken());
-        StringResponse response = client.request(HttpURL.carModelinfoCheckVehicleType, carModelinfoCheckVehicleTypeParam, httpHeaders, StringResponse.class);
+        StringResponse response = client.request(HttpURL.carModelinfoCheckVehicleType, param, httpHeaders, StringResponse.class);
+
+        // 校验响应并返回结果
         checkResponseUnknownField(response);
         return response.getData();
     }

+ 341 - 258
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/TKSimpleService.java

@@ -1,10 +1,8 @@
 package com.jzg.quotation.taikang.crawler.service;
 
-import com.jzg.commons.constants.quote.enums.quote.CarKindCode;
-import com.jzg.commons.constants.quote.enums.quote.VehicleUseCode;
 import com.jzg.commons.entity.orders.po.InsOrdersCarUserInfo;
 import com.jzg.commons.entity.quote.vo.*;
-import com.jzg.commons.util.IDCardUtils;
+
 import com.jzg.quotation.commons.filter.CarModelsFilterUtils;
 import com.jzg.quotation.taikang.crawler.CodeUtil;
 import com.jzg.quotation.taikang.crawler.model.TKLoginSession;
@@ -19,7 +17,9 @@ import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
 import java.util.*;
+import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -29,6 +29,62 @@ public class TKSimpleService {
     @Resource
     private TKHttpAPIService httpService;
 
+    /**
+     * 车船税减免标志映射表:公司系统标志 -> 保司系统标志
+     */
+    private static final Map<String, String> TAX_RELIEF_FLAG_MAP = Map.of(
+            "1", "T",
+            "2", "E",
+            "3", "C",
+            "4", "P"
+    );
+
+    /**
+     * 通用空值检查与初始化方法
+     * <p>
+     * 如果 getter 返回值为 null,则通过 constructor 创建新实例并通过 setter 设置
+     *
+     * @param getter      获取当前值的函数
+     * @param setter      设置新值的函数
+     * @param constructor 创建新实例的函数
+     * @param <T>         对象类型
+     * @return 非空的目标对象
+     */
+    private <T> T ensureNonNull(Supplier<T> getter, Consumer<T> setter, Supplier<T> constructor) {
+        T value = getter.get();
+        if (value == null) {
+            value = constructor.get();
+            setter.accept(value);
+        }
+        return value;
+    }
+
+    /**
+     * 重置驾意险信息为"不投保"状态
+     *
+     * @param driverAccidentVo 驾意险信息对象
+     */
+    private void resetDriverAccidentVo(TKDriverAccidentVo driverAccidentVo) {
+        driverAccidentVo.setIsSelect("0");
+        driverAccidentVo.setQuantity(0);
+        driverAccidentVo.setQuantityCname("不投保");
+        driverAccidentVo.setPremium(BigDecimal.ZERO);
+        driverAccidentVo.setNonCarStartDate(null);
+        driverAccidentVo.setNonCarEndDate(null);
+    }
+
+    /**
+     * 重置非车险产品信息为"不投保"状态
+     *
+     * @param nonCarProductListDTO 非车险产品列表项
+     */
+    private void resetNonCarProduct(PreciseInitResult.NonCarProductListDTO nonCarProductListDTO) {
+        nonCarProductListDTO.setIsSelect("0");
+        nonCarProductListDTO.setPremium("0");
+        nonCarProductListDTO.setStartDate(null);
+        nonCarProductListDTO.setEndDate(null);
+    }
+
     public TKLoginSession login(String username, String password) {
         IdentityTokenResult identityTokenResult = httpService.identityToken();
 
@@ -274,15 +330,8 @@ public class TKSimpleService {
         carInfoVo.setOperateFlag("6");
         carInfoVo.setLicenseNo(commonCarInfoVo.getLicenseNo());
 
-        // 车辆大类
-        String carKindCode = commonCarInfoVo.getCarKindCode();
-        if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A0.getCode())) {
-            carInfoVo.setCarType("A");
-        } else if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A1.getCode())) {
-            carInfoVo.setCarType("D");
-        } else {
-            throw new RuntimeException("未知车辆大类:" + carKindCode);
-        }
+        // 车辆大类(通过辅助方法解析)
+        carInfoVo.setCarType(TKCommonUtil.resolveCarType(commonCarInfoVo.getCarKindCode()));
 
         setLicenseTypeByLicenseNo(carInfoVo, carInfoVo.getLicenseNo());
         carInfoVo.setLocalCarFlag(null); // todo 车型查询时没有传这个参数,暂时不知什么地方用到。
@@ -325,16 +374,8 @@ public class TKSimpleService {
         carInfoVo.setVinNo(commonCarInfoVo.getVinNo());
         carInfoVo.setVehicleModel(commonCarInfoVo.getBrandName());
 
-        // 车辆大类
-        String carKindCode = commonCarInfoVo.getCarKindCode();
-
-        if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A0.getCode())) {
-            carInfoVo.setCarType("A");
-        } else if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A1.getCode())) {
-            carInfoVo.setCarType("D");
-        } else {
-            throw new RuntimeException("未知车辆大类:" + carKindCode);
-        }
+        // 车辆大类(通过辅助方法解析)
+        carInfoVo.setCarType(TKCommonUtil.resolveCarType(commonCarInfoVo.getCarKindCode()));
 
         carInfoVo.setLocalCarFlag(null); // todo 车型查询时没有传这个参数,暂时不知什么地方用到。
 
@@ -379,11 +420,7 @@ public class TKSimpleService {
         if (StringUtils.isBlank(commonCarInfoVo.getVinNo())) {
             return null;
         }
-        TKCarOwnerVo carOwnerVo = new TKCarOwnerVo();
-        carOwnerVo.setCarOwnerNature("1"); // todo
-        carOwnerVo.setCertificateType("01");  // 身份证
-        carOwnerVo.setId(ownersInfo.getIdentifyNumber());
-        carOwnerVo.setName(ownersInfo.getName());
+        TKCarOwnerVo carOwnerVo = TKCommonUtil.buildCarOwnerVo(ownersInfo);
 
         TKCarInfoVo carInfoVo = new TKCarInfoVo();
         TKCarModel tkCarModel = CarModelsFilterUtils.to(tkCarModels, commonCarInfoVo);
@@ -414,16 +451,8 @@ public class TKSimpleService {
         carInfoVo.setExhaustScale(tkCarModel.getDisplacement());
         carInfoVo.setBillNo("");
         carInfoVo.setCarCheckVo(new TKCarInfoVo.CarCheckVo("", 1, ""));
-        // 车辆大类
-        String carKindCode = commonCarInfoVo.getCarKindCode();
-
-        if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A0.getCode())) {
-            carInfoVo.setCarType("A");
-        } else if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A1.getCode())) {
-            carInfoVo.setCarType("D");
-        } else {
-            throw new RuntimeException("未知车辆大类:" + carKindCode);
-        }
+        // 车辆大类(通过辅助方法解析)
+        carInfoVo.setCarType(TKCommonUtil.resolveCarType(commonCarInfoVo.getCarKindCode()));
 
         carInfoVo.setLocalCarFlag(null); // todo 车型查询时没有传这个参数,暂时不知什么地方用到。
         CarModelinfoQueryParam carModelinfoQueryParam = new CarModelinfoQueryParam();
@@ -588,157 +617,148 @@ public class TKSimpleService {
 
     /**
      * 保存车险投保单
+     * <p>
+     * 该方法负责将公司系统的投保单参数转换为泰康保司系统的参数,
+     * 并调用保司接口保存投保单信息。
+     * <p>
+     * 主要处理逻辑:
+     * 1. 参数准备:获取全局固定参数和车型信息
+     * 2. 车主信息构建:从公司系统参数提取车主信息
+     * 3. 投保人信息构建:初始化投保人、被保险人、联系人信息
+     * 4. 车辆信息构建:设置车辆基本信息、类型、过户状态、使用性质等
+     * 5. 车船税信息构建:设置车船税代缴标志
+     * 6. 其他信息构建:设置查询标志、OCR标志等
+     * 7. 请求组装:组装完整的保存参数并调用保司接口
      *
-     * @param session
-     * @param mainOrderId
-     * @param commonQuoteVo
-     * @param tkAreaInfoVo
-     * @param carModelinfoQueryResult
+     * @param session                 登录会话信息
+     * @param mainOrderId             主订单ID
+     * @param commonQuoteVo           公司系统报价参数
+     * @param tkAreaInfoVo            区域信息(含代理人信息)
+     * @param carModelinfoQueryResult 车型查询结果
      */
     public void orderCreate(TKLoginSession session,
                             String mainOrderId,
                             QuoteVo commonQuoteVo,
                             TKAreaInfoVo tkAreaInfoVo,
                             CarModelinfoQueryResult carModelinfoQueryResult) {
+        // 从报价参数中提取车辆信息
         CarInfoVo commonCarInfoVo = commonQuoteVo.getCarInfoVo();
-        // 全局固定参数
+
+        // ========== 1. 参数准备 ==========
+
+        // 全局固定参数:从登录会话中获取
         String tplatformId = session.getTplatformId();
         String tplatLoginKey = session.getTplatLoginKey();
         String fromId = session.getFromid();
 
-        // 找到车型,后面要使用
-        TKCarModel tkCarModel = CarModelsFilterUtils.to(carModelinfoQueryResult.getCarModel(), commonQuoteVo.getCarInfoVo());
+        // 获取车型信息并设置必要字段
+        TKCarModel tkCarModel = CarModelsFilterUtils.to(
+                carModelinfoQueryResult.getCarModel(),
+                commonQuoteVo.getCarInfoVo()
+        );
         tkCarModel.setVehicleStyleUniqueId("");
         tkCarModel.setLicenseNo(commonCarInfoVo.getLicenseNo());
-        CarModelinfoQueryResult.CarInfoDto carInfoDto = carModelinfoQueryResult.getCarInfoDto();
 
-        // 车辆所有人信息
-        TKCarOwnerVo carOwnerVo = new TKCarOwnerVo();
-        CustomerInfoVo commonCustomersInfo = commonQuoteVo.getOwnersInfo();
-        if (commonCustomersInfo != null) {
-            carOwnerVo.setCarOwnerNature("1"); // todo
-            carOwnerVo.setCertificateType("01");  // 身份证
-            carOwnerVo.setId(commonCustomersInfo.getIdentifyNumber());
-            carOwnerVo.setName(commonCustomersInfo.getName());
-            carOwnerVo.setBirthDate(commonCustomersInfo.getBirthday());
-            carOwnerVo.setSerialno(BigDecimal.ZERO);
-            carOwnerVo.setSex(IDCardUtils.getSex(commonCustomersInfo.getIdentifyNumber()));
-            carOwnerVo.setInsuredAddress("");
-        }
+        // ========== 2. 车主信息构建 ==========
+
+        TKCarOwnerVo carOwnerVo = TKCommonUtil.buildCarOwnerVo(commonQuoteVo.getOwnersInfo());
+
+        // ========== 3. 投保人信息构建 ==========
 
-        // 投保人信息
         SaveBasicInfoParam.InsureInfo insureInfo = new SaveBasicInfoParam.InsureInfo();
         insureInfo.setAppliInfo(new SaveBasicInfoParam.AppliInfo());
         insureInfo.setInsuredInfo(new SaveBasicInfoParam.InsuredInfo());
         insureInfo.setContacts(new SaveBasicInfoParam.Contacts());
 
-        // 车辆信息
+        // ========== 4. 车辆信息构建 ==========
+
         TKCar car = new TKCar();
-        car.setFuelType(tkCarModel.getFuelType());
-        car.setModelName(tkCarModel.getModelName());
-        car.setBillDate(""); // 页面上没有输入框,可能是北京地区才用到
-        car.setCarBookingTime(""); // 页面上没有输入框,原注释为“上海地区开具发票日期”,但感觉是车辆时间。
-        car.setBillNo(""); // 页面上没有输入框,发票号
-        car.setColorCode("010"); // 默认黑色
+
+        // 设置车辆基本信息
+        TKCommonUtil.buildCarBasicInfo(car, tkCarModel);
+        car.setColorCode("010");
         car.setCuringQuality(tkCarModel.getVehicleWeight());
         car.setVehicleType(tkCarModel.getVehicleType());
         car.setVehicleTypeCname(tkCarModel.getVehicleTypeCname());
 
-        // 车辆大类
-        String carKindCode = commonCarInfoVo.getCarKindCode();
-        if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A0.getCode())) {
-            car.setCarType("A");
-        } else if (carKindCode.equals(CarKindCode.CAR_KIND_CODE_A1.getCode())) {
-            car.setCarType("D");
-        } else {
-            throw new RuntimeException("未知车辆大类:" + carKindCode);
-        }
+        // 设置车辆大类(通过辅助方法解析)
+        car.setCarType(TKCommonUtil.resolveCarType(commonCarInfoVo.getCarKindCode()));
 
+        // 使用 autoSetter 设置可选字段(优先使用公司系统值,其次使用车型查询值)
         CodeUtil.autoSetter(car::setLicenseNo, commonCarInfoVo::getLicenseNo, tkCarModel::getLicenseNo);
         CodeUtil.autoSetter(car::setTransferDate, commonCarInfoVo::getTransferDate, null);
         CodeUtil.autoSetter(car::setPurchasePrice, commonCarInfoVo::getPurchasePrice, tkCarModel::getReplacementValue);
         CodeUtil.autoSetter(car::setSelectedSeat, commonCarInfoVo::getSeatCount, () -> tkCarModel.getVehicleJingyou().getSeat());
-        CodeUtil.autoSetter(car::setExhaustScale, commonCarInfoVo::getDisplacement, tkCarModel::getDisplacement);  // 排量
-        CodeUtil.autoSetter(car::setBrandName, commonCarInfoVo::getBrandName, tkCarModel::getModelName); // todo 与 tkCarModel.getModelName() 重复
+        CodeUtil.autoSetter(car::setExhaustScale, commonCarInfoVo::getDisplacement, tkCarModel::getDisplacement);
+        CodeUtil.autoSetter(car::setBrandName, commonCarInfoVo::getBrandName, tkCarModel::getModelName);
 
-        car.setSeatFlag(1); // todo
-        car.setExhaustScale(tkCarModel.getDisplacement().substring(0, tkCarModel.getDisplacement().indexOf(".")));
-        car.setPurchasePrice(tkCarModel.getPurchasePrice().substring(0, tkCarModel.getPurchasePrice().indexOf(".")));
+        // 设置固定字段和从车型提取的字段(去掉小数部分)
+        car.setSeatFlag(1);
+        car.setExhaustScale(TKCommonUtil.substringBeforeDot(tkCarModel.getDisplacement()));
+        car.setPurchasePrice(TKCommonUtil.substringBeforeDot(tkCarModel.getPurchasePrice()));
         car.setVinNo(commonCarInfoVo.getVinNo());
         car.setEngineNo(commonCarInfoVo.getEngineNo());
         car.setEnrollDate(commonCarInfoVo.getRegisterDate());
 
-        Double tonCount = commonCarInfoVo.getTonCountDouble();
-        if (tonCount != null && tonCount > 2) {
-            // 好像主要是货车使用此字段
-            car.setTonnageFlag("1");
-        } else {
-            car.setTonnageFlag("0");
-        }
+        // 设置吨位标志(两吨以上货车标记为1)
+        car.setTonnageFlag(Optional.ofNullable(commonCarInfoVo.getTonCountDouble())
+                .filter(ton -> ton > 2)
+                .map(ton -> "1")
+                .orElse("0"));
 
+        // 设置过户标志和过户时间
         if (commonCarInfoVo.isTransfer()) {
-            car.setChgOwnerFlag("1"); // 是过户
-            car.setTransferDate(commonCarInfoVo.getTransferDate()); // 过户时间
+            car.setChgOwnerFlag("1");
+            car.setTransferDate(commonCarInfoVo.getTransferDate());
         } else {
-            car.setChgOwnerFlag("0"); // 不是过户
+            car.setChgOwnerFlag("0");
             car.setTransferDate("");
         }
 
+        // 设置车辆使用性质(通过映射表转换,默认8B非营业企业)
         String vehicleUseCode = commonCarInfoVo.getVehicleUseCode();
+        car.setUseNatureCodeCode(TKCommonUtil.VEHICLE_USE_CODE_MAP.getOrDefault(vehicleUseCode, "8B"));
 
-        if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_05.getCode())) {
-            // 05 家庭自用 = 8A 家庭自用
-            car.setUseNatureCodeCode("8A"); // 页面上选择的家庭自用、非营业企业、、、、
-        } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_06.getCode())) {
-            // 06 非营业企业 = 8C 非营业党政机关事业团体
-            car.setUseNatureCodeCode("8C");
-        } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_07.getCode())) {
-            // 07 非营业企业 = 8B 非营业企业
-            car.setUseNatureCodeCode("8B");
-        } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_01.getCode())) {
-            // 01 营业出租租赁 = 9 营业
-            car.setUseNatureCodeCode("9");
-        } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_02.getCode())) {
-            // 02 营业城市公交 = 9 营业
-            car.setUseNatureCodeCode("9");
-        } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_03.getCode())) {
-            // 03 营业公路客运 = 9 营业
-            car.setUseNatureCodeCode("9");
-        } else if (vehicleUseCode.equals(VehicleUseCode.VEHICLE_USE_CODE_04.getCode())) {
-            // 04 营业货运 = 9 营业
-            car.setUseNatureCodeCode("9");
-        } else {
-            // 默认 8B 非营业企业
-            car.setUseNatureCodeCode("8B");
-        }
-
+        // 设置车牌类型并根据渠道调整车辆类型
         TKSimpleService.setLicenseTypeByLicenseNo(car, car.getLicenseNo());
-        String passengerSeat = httpService.carTypePassengerSeat(session.getToken(), session.getTplatformId(), session.getFromid(), session.getComCode());
-        String carType = carType(passengerSeat, car.getCarType(), carOwnerVo.getCarOwnerNature());
-        if (carType != null) {
-            car.setCarType(carType);
-        }
-
+        String passengerSeat = httpService.carTypePassengerSeat(
+                session.getToken(),
+                session.getTplatformId(),
+                session.getFromid(),
+                session.getComCode()
+        );
+        Optional.ofNullable(carType(passengerSeat, car.getCarType(), carOwnerVo.getCarOwnerNature()))
+                .ifPresent(car::setCarType);
+
+        // 预留扩展方法调用(当前传入null,内部直接返回,作为未来扩展占位)
         orderCreateOther(null);
-        car.setCarModel(Arrays.asList(tkCarModel));
-        car.setBrandName(tkCarModel.getModelName()); // todo 与 commonCarInfoVo.getBrandName() 重复
 
-        // 车船税信息
+        // 设置车型列表和品牌名称
+        car.setCarModel(List.of(tkCarModel));
+        car.setBrandName(tkCarModel.getModelName());
+
+        // ========== 5. 车船税信息构建 ==========
+
         TKCarTax carTax = new TKCarTax();
-        carTax.setIsPayTax("1"); // 是否代缴车船税
+        carTax.setIsPayTax("1");
+
+        // ========== 6. 其他信息构建 ==========
 
-        // 其他信息
         SaveBasicInfoParam.OtherInfo otherInfo = new SaveBasicInfoParam.OtherInfo();
         otherInfo.setIsQueryVehicleFlag(false);
-        // 经测试:单交设置:1, 交强+商业设置:"0"
-        otherInfo.setShowEngineNoFlag(commonQuoteVo.getBiRiskInfoVo() == null ? "1" : "0");
         otherInfo.setJyQueryFlag(false);
         otherInfo.setOcrFlag(1);
-        otherInfo.setNewCarFlag("0"); // todo
+        otherInfo.setNewCarFlag("0");
+        otherInfo.setRetrospectId(UUID.randomUUID().toString());
+
+        // 根据是否包含商业险设置特殊标志
+        boolean hasBiRisk = commonQuoteVo.getBiRiskInfoVo() != null;
+        // 经测试:单交设置:1, 交强+商业设置:"0"
+        otherInfo.setShowEngineNoFlag(hasBiRisk ? "0" : "1");
         // 经测试:单交设置:1, 交强+商业设置:""
-        otherInfo.setKindLimitCode(commonQuoteVo.getBiRiskInfoVo() == null ? "1" : "");
-        otherInfo.setRetrospectId(UUID.randomUUID().toString()); // 可回溯ID ,好像不是很需要
+        otherInfo.setKindLimitCode(hasBiRisk ? "" : "1");
 
+        // ========== 7. 请求组装与调用 ==========
 
         SaveBasicInfoParam param = new SaveBasicInfoParam();
         param.setChannelCode("OffWeb");
@@ -746,8 +766,8 @@ public class TKSimpleService {
         param.setFromId(fromId);
         param.setTplatformId(tplatformId);
         param.setTplatLoginKey(tplatLoginKey);
-        param.setPreFilledLastPolicyFlag(""); // 抓包查到没有值
-        param.setIsJgFlag("");  // 抓包查到没有值
+        param.setPreFilledLastPolicyFlag("");
+        param.setIsJgFlag("");
         param.setAuthFailContinueFlag("");
         param.setInsureInfo(insureInfo);
         param.setAreaInfoVo(tkAreaInfoVo);
@@ -756,6 +776,8 @@ public class TKSimpleService {
         param.setCarTaxVo(carTax);
         param.setOtherInfo(otherInfo);
         param.setOrderVersion("1");
+
+        // 调用保司保存接口
         SaveBasicInfoResult saveBasicInfoResult = httpService.saveBasicInfo(session.getToken(), param);
         if (saveBasicInfoResult == null) {
             throw new RuntimeException("HttpService#saveBasicInfo 失败!");
@@ -892,176 +914,237 @@ public class TKSimpleService {
 
     /**
      * 车险投保单报价
+     * <p>
+     * 该方法负责将公司系统的报价参数转换为泰康保司系统的报价参数,
+     * 并调用保司接口进行报价,返回报价结果。
+     * <p>
+     * 主要处理逻辑:
+     * 1. 参数初始化:确保各子对象非空,设置全局固定参数
+     * 2. 险种标志设置:处理交强险、商业险标志
+     * 3. 时间信息同步:同步交强险和商业险的起止日期
+     * 4. 车船税信息同步:映射车船税减免标志
+     * 5. 商业险险种覆盖:根据公司系统选择的险种覆盖保司默认险种
+     * 6. 驾意险同步:匹配公司系统选择的驾意险产品
+     * 7. 非车险产品同步:匹配泰乐途等非车险产品
+     * 8. 调用报价接口:提交参数获取报价结果
      *
-     * @param session
-     * @param mainOrderId
-     * @param preciseInitResult
-     * @param commonQuoteVo
+     * @param session           登录会话信息
+     * @param mainOrderId       主订单ID
+     * @param preciseInitResult 初始化结果
+     * @param commonQuoteVo     公司系统报价参数
+     * @return 报价结果
      */
     public PreciseInitResult orderQuote(TKLoginSession session, String mainOrderId, PreciseInitResult preciseInitResult, QuoteVo commonQuoteVo) {
+        // 将初始化结果转换为报价参数对象
         PreciseInitParam preciseInitParam = Converter.Instance.convertToParam(preciseInitResult);
 
-        // 全局固定参数
+        // 全局固定参数:从登录会话中获取
         String tplatformId = session.getTplatformId();
         String tplatLoginKey = session.getTplatLoginKey();
         String fromId = session.getFromid();
 
-        // 险种选中标志
-        TKFlag flag = preciseInitParam.getFlag();
-        if (flag == null) {
-            flag = new TKFlag();
-            preciseInitParam.setFlag(flag);
-        }
+        // ========== 1. 参数初始化与险种标志设置 ==========
+
+        // 确保险种选中标志对象非空,设置默认标志值
+        TKFlag flag = ensureNonNull(
+                preciseInitParam::getFlag,
+                preciseInitParam::setFlag,
+                TKFlag::new
+        );
         flag.setVipWeekFlag("0");
         flag.setQuoteLinkageFlag("0");
         flag.setInstantInsureBZAllowed("0");
         flag.setPriceDifferFlag("0");
 
-        // 无商业险
+        // 无商业险时设置商业险标志为"0"
         if (CollectionUtils.isEmpty(commonQuoteVo.getKindInfoVo())) {
             flag.setCiFlag("0");
         }
 
-        // 其他参数
-        TKOther other = preciseInitParam.getOther();
-        if (other == null) {
-            other = new TKOther();
-            preciseInitParam.setOther(other);
-        }
+        // 确保其他参数对象非空,设置渠道信息
+        TKOther other = ensureNonNull(
+                preciseInitParam::getOther,
+                preciseInitParam::setOther,
+                TKOther::new
+        );
         other.setFromID(fromId);
         other.setChannelCode("OffWeb");
 
-        // 时间参数
+        // 获取时间参数对象(保司系统已初始化,无需创建)
         TKDateInfo date = preciseInitParam.getDate();
 
-        // 同步交强险
-        TKBzKindVo bzKindVo = preciseInitParam.getBzKindVo();
-        if (bzKindVo == null) {
-            bzKindVo = new TKBzKindVo();
-            preciseInitParam.setBzKindVo(bzKindVo);
-        }
-        bzKindVo.setIsSelect("1"); // 选择交纳交强险
+        // ========== 2. 交强险信息同步 ==========
 
-        // 同步交强险时间信息,从前端参数获取
+        // 确保交强险对象非空,设置为"选择交纳"状态
+        TKBzKindVo bzKindVo = ensureNonNull(
+                preciseInitParam::getBzKindVo,
+                preciseInitParam::setBzKindVo,
+                TKBzKindVo::new
+        );
+        bzKindVo.setIsSelect("1");
+
+        // 从前端参数获取交强险时间信息
         RiskInfoVo commonCiRiskInfoVo = commonQuoteVo.getCiRiskInfoVo();
         if (commonCiRiskInfoVo != null) {
-            // 交强险的时间
             date.setStartDateBZ(commonCiRiskInfoVo.getStartDate());
             date.setEndDateBZ(commonCiRiskInfoVo.getEndDate());
         }
 
-        // 同步车船税
-        TKCarTaxInfo carTaxInfo = preciseInitParam.getCarTaxInfo();
-        if (carTaxInfo == null) {
-            carTaxInfo = new TKCarTaxInfo();
-            preciseInitParam.setCarTaxInfo(carTaxInfo);
-        }
-        // 同步车船税信息,从前端参数获取
-        VehicleAndVesselTaxVo vehicleAndVesselTax = commonQuoteVo.getVehicleAndVesselTaxVo();
-        if (vehicleAndVesselTax != null) {
-            // 车船税标志
-            String taxRelifFlag = vehicleAndVesselTax.getTaxRelifFlag();
-            // 转换为保司的参数
-            if (taxRelifFlag.equals("1")) {
-                carTaxInfo.setTaxFlag("T");
-            } else if (taxRelifFlag.equals("2")) {
-                carTaxInfo.setTaxFlag("E");
-            } else if (taxRelifFlag.equals("3")) {
-                carTaxInfo.setTaxFlag("C");
-            } else if (taxRelifFlag.equals("4")) {
-                carTaxInfo.setTaxFlag("P");
-            }
-        }
-
-        // 同步商业险时间信息,从前端参数获取
-        RiskInfoVo commonBiRiskInfoVo = commonQuoteVo.getBiRiskInfoVo();
-        if (commonBiRiskInfoVo != null) {
-            date.setStartDateCI(commonBiRiskInfoVo.getStartDate());
-            date.setEndDateCI(commonBiRiskInfoVo.getEndDate());
+        // ========== 3. 车船税信息同步 ==========
+
+        // 确保车船税对象非空
+        TKCarTaxInfo carTaxInfo = ensureNonNull(
+                preciseInitParam::getCarTaxInfo,
+                preciseInitParam::setCarTaxInfo,
+                TKCarTaxInfo::new
+        );
+
+        // 从前端参数获取车船税信息,通过映射表转换减免标志
+        Optional.ofNullable(commonQuoteVo.getVehicleAndVesselTaxVo())
+                .map(VehicleAndVesselTaxVo::getTaxRelifFlag)
+                .map(TAX_RELIEF_FLAG_MAP::get)
+                .ifPresent(carTaxInfo::setTaxFlag);
+
+        // ========== 4. 商业险时间信息同步 ==========
+
+        // 从前端参数获取商业险时间信息,若无则保持保司默认值(无需额外处理)
+        Optional.ofNullable(commonQuoteVo.getBiRiskInfoVo())
+                .ifPresent(biRiskInfo -> {
+                    date.setStartDateCI(biRiskInfo.getStartDate());
+                    date.setEndDateCI(biRiskInfo.getEndDate());
+                });
+
+        // ========== 5. 商业险险种覆盖 ==========
+
+        // 使用 Stream 将保司默认商险信息转换为 Map,同时重置为"不投保"状态
+        Map<String, TKCiKindsVo> tkCiKindsVoMap = Optional.ofNullable(preciseInitParam.getCiKindsVo())
+                .orElseGet(Collections::emptyList)
+                .stream()
+                .peek(tkCiKindVo -> {
+                    tkCiKindVo.setBjSelect("0");
+                    tkCiKindVo.setDeductibleAmount(BigDecimal.ZERO);
+                    tkCiKindVo.setIsSelect("0");
+                    tkCiKindVo.setQuantity(0);
+                    tkCiKindVo.setQuoteAmountCN("不投保");
+                })
+                .collect(Collectors.toMap(TKCiKindsVo::getKindCode, Function.identity()));
+
+        // 根据公司系统选择的险种覆盖保司默认险种
+        Optional.ofNullable(commonQuoteVo.getKindInfoVo())
+                .orElseGet(Collections::emptyList)
+                .stream()
+                .filter(kindInfo -> tkCiKindsVoMap.containsKey(kindInfo.getKindCode()))
+                .forEach(commonBiKindInfoVo -> {
+                    TKCiKindsVo tkCiKindVo = tkCiKindsVoMap.get(commonBiKindInfoVo.getKindCode());
+                    tkCiKindVo.setIsSelect("1");
+                    tkCiKindVo.setQuoteAmount(commonBiKindInfoVo.getAmount());
+                    tkCiKindVo.setQuoteAmountCN(commonBiKindInfoVo.getAmount());
+
+                    // 设置数量:优先使用 quantity,其次使用 serviceTimes
+                    String quantity = commonBiKindInfoVo.getQuantity();
+                    String serviceTimes = commonBiKindInfoVo.getServiceTimes();
+                    if (StringUtils.isNotBlank(quantity)) {
+                        tkCiKindVo.setQuantity(Integer.parseInt(quantity));
+                    } else if (StringUtils.isNotBlank(serviceTimes)) {
+                        tkCiKindVo.setQuantity(Integer.parseInt(serviceTimes));
+                    }
+                });
+
+        // ========== 6. 驾意险信息同步 ==========
+
+        // 将驾意险方案列表转换为 Map(按方案代码索引)
+        Map<String, TKCarPlanInfo> carPlanInfoMap = Optional.ofNullable(preciseInitParam.getDriverAccidentVo())
+                .map(TKDriverAccidentVo::getCarPlanInfoList)
+                .orElseGet(Collections::emptyList)
+                .stream()
+                .collect(Collectors.toMap(TKCarPlanInfo::getPlanCode, Function.identity()));
+
+        // 获取保司驾意险对象和公司系统驾意险参数
+        TKDriverAccidentVo driverAccidentVo = preciseInitParam.getDriverAccidentVo();
+        List<AccidentalDrivingVo> accidentalDrivings = commonQuoteVo.getAccidentalDrivings();
+
+        // 根据公司系统驾意险参数进行匹配覆盖
+        // 注意:当 carPlanInfoMap 为空时,跳过处理,保持 driverAccidentVo 原有状态
+        if (CollectionUtils.isNotEmpty(accidentalDrivings) && !carPlanInfoMap.isEmpty()) {
+            accidentalDrivings.stream()
+                    .filter(accidentalDriving -> accidentalDriving.getProductCode() != null)
+                    .forEach(accidentalDriving -> {
+                        TKCarPlanInfo tkCarPlanInfo = carPlanInfoMap.get(accidentalDriving.getProductCode());
+                        if (tkCarPlanInfo != null) {
+                            // 匹配成功:设置为"投保"状态
+                            driverAccidentVo.setIsSelect("1");
+                            driverAccidentVo.setQuantity(accidentalDriving.getQuantity());
+                            driverAccidentVo.setSelectPlanCode(tkCarPlanInfo.getPlanCode());
+                            driverAccidentVo.setQuoteAmountCN(tkCarPlanInfo.getPlanName());
+                            driverAccidentVo.setPremium(new BigDecimal(tkCarPlanInfo.getSumPremium()));
+                        } else {
+                            // 匹配失败:重置为"不投保"状态
+                            resetDriverAccidentVo(driverAccidentVo);
+                        }
+                    });
         } else {
-            date.setStartDateCI(date.getStartDateCI());
-            date.setEndDateCI(date.getEndDateCI());
+            // 公司系统未选择驾意险或驾意险方案列表为空:重置为"不投保"状态
+            resetDriverAccidentVo(driverAccidentVo);
         }
 
-        // 获取保司默认选中的商险信息
-        Map<String, TKCiKindsVo> tkCiKindsVoMap = new HashMap<>();
-        for (TKCiKindsVo tkCiKindVo : preciseInitParam.getCiKindsVo()) {
-            tkCiKindVo.setBjSelect("0");
-            tkCiKindVo.setDeductibleAmount(BigDecimal.ZERO);
-            tkCiKindVo.setIsSelect("0");
-            tkCiKindVo.setQuantity(0);
-            tkCiKindVo.setQuoteAmountCN("不投保");
-            tkCiKindsVoMap.put(tkCiKindVo.getKindCode(), tkCiKindVo);
-        }
+        // ========== 7. 非车险产品同步(泰乐途等) ==========
 
-        // 根据公司系统选择的险种进行覆盖
-        List<KindInfoVo> commonBiKindInfoVos = commonQuoteVo.getKindInfoVo();
-        if (CollectionUtils.isNotEmpty(commonBiKindInfoVos)) {
-            for (KindInfoVo commonBiKindInfoVo : commonBiKindInfoVos) {
-                if (tkCiKindsVoMap.isEmpty() || tkCiKindsVoMap.get(commonBiKindInfoVo.getKindCode()) == null) {
-                    continue;
-                }
-                TKCiKindsVo tkCiKindVo = tkCiKindsVoMap.get(commonBiKindInfoVo.getKindCode());
-                tkCiKindVo.setIsSelect("1");
-                tkCiKindVo.setQuoteAmount(commonBiKindInfoVo.getAmount());
-                tkCiKindVo.setQuoteAmountCN(commonBiKindInfoVo.getAmount());
-
-                String quantity = commonBiKindInfoVo.getQuantity();
-                String serviceTimes = commonBiKindInfoVo.getServiceTimes();
-                if (StringUtils.isNotBlank(quantity)) {
-                    tkCiKindVo.setQuantity(Integer.parseInt(quantity));
-                } else if (StringUtils.isNotBlank(serviceTimes)) {
-                    tkCiKindVo.setQuantity(Integer.parseInt(serviceTimes));
-                }
-            }
-        }
+        // 使用 flatMap 将嵌套的产品列表展平并转换为 Map(按产品代码索引)
+        Map<String, PreciseInitResult.ProductInfoListDTO> productInfoListMap = Optional.ofNullable(preciseInitParam.getNonCarProductList())
+                .orElseGet(Collections::emptyList)
+                .stream()
+                .flatMap(nonCarProduct -> nonCarProduct.getProductInfoList().stream())
+                .collect(Collectors.toMap(
+                        PreciseInitResult.ProductInfoListDTO::getProductCode,
+                        Function.identity()
+                ));
 
-        // 同步驾意险, 系统有选择驾意险进行匹配覆盖(泰康要求必须有驾意险)
-        Map<String, TKCarPlanInfo> carPlanInfoMap = Optional.ofNullable(preciseInitParam.getDriverAccidentVo().getCarPlanInfoList()).orElseGet(Collections::emptyList).stream().collect(Collectors.toMap(TKCarPlanInfo::getPlanCode, Function.identity()));
-        TKDriverAccidentVo driverAccidentVo = preciseInitParam.getDriverAccidentVo();// 泰康系统默认的驾意险信息
-        List<AccidentalDrivingVo> accidentalDrivings = commonQuoteVo.getAccidentalDrivings(); // 公司系统的驾意险参数
-        // 公司系统的驾意险参数为空证明没有选择驾意险
-        if (CollectionUtils.isNotEmpty(accidentalDrivings)) {
-            for (AccidentalDrivingVo accidentalDriving : accidentalDrivings) {
-                if (carPlanInfoMap.isEmpty() || accidentalDriving.getProductCode() == null) {
-                    continue;
-                }
-                TKCarPlanInfo tkCarPlanInfo = carPlanInfoMap.get(accidentalDriving.getProductCode());
-                driverAccidentVo.setIsSelect("1");
-                driverAccidentVo.setQuantity(accidentalDriving.getQuantity());
-                driverAccidentVo.setSelectPlanCode(tkCarPlanInfo.getPlanCode());
-                driverAccidentVo.setQuoteAmountCN(tkCarPlanInfo.getPlanName());
-                driverAccidentVo.setPremium(new BigDecimal(tkCarPlanInfo.getSumPremium()));
-            }
+        // 获取非车险产品列表
+        List<PreciseInitResult.NonCarProductListDTO> nonCarProductList = preciseInitParam.getNonCarProductList();
+
+        // 根据公司系统驾意险参数匹配非车险产品
+        // 注意:当 productInfoListMap 为空或 nonCarProductList 为空时,跳过处理
+        if (CollectionUtils.isNotEmpty(accidentalDrivings) && !productInfoListMap.isEmpty() && CollectionUtils.isNotEmpty(nonCarProductList)) {
+            accidentalDrivings.stream()
+                    .filter(accidentalDriving -> accidentalDriving.getProductCode() != null)
+                    .forEach(accidentalDriving -> {
+                        PreciseInitResult.ProductInfoListDTO productInfoListDTO = productInfoListMap.get(accidentalDriving.getProductCode());
+                        PreciseInitResult.NonCarProductListDTO nonCarProductListDTO = nonCarProductList.get(0);
+
+                        if (productInfoListDTO != null) {
+                            // 匹配成功:设置为"投保"状态
+                            nonCarProductListDTO.setIsSelect("1");
+                            nonCarProductListDTO.setPremium(productInfoListDTO.getPremium());
+                            nonCarProductListDTO.setSelectProductCode(accidentalDriving.getProductCode());
+                            nonCarProductListDTO.setSelectProductName(productInfoListDTO.getProductName());
+                            nonCarProductListDTO.setStartDate(commonCiRiskInfoVo != null ? commonCiRiskInfoVo.getStartDate() : null);
+                            nonCarProductListDTO.setEndDate(commonCiRiskInfoVo != null ? commonCiRiskInfoVo.getEndDate() : null);
+                        } else {
+                            // 匹配失败:重置为"不投保"状态
+                            resetNonCarProduct(nonCarProductListDTO);
+                        }
+                    });
         } else {
-            driverAccidentVo.setIsSelect("0");
-            driverAccidentVo.setQuantity(0);
-            driverAccidentVo.setQuantityCname("不投保");
-            driverAccidentVo.setPremium(BigDecimal.ZERO);
-            driverAccidentVo.setNonCarStartDate(null);
-            driverAccidentVo.setNonCarEndDate(null);
+            // 公司系统未选择驾意险或产品列表为空:所有非车险产品重置为"不投保"状态
+            Optional.ofNullable(nonCarProductList)
+                    .orElseGet(Collections::emptyList)
+                    .forEach(this::resetNonCarProduct);
         }
 
-        // 泰乐途, 默认不投保
-        List<PreciseInitResult.NonCarProductListDTO> nonCarProductList = preciseInitParam.getNonCarProductList();
-        if (CollectionUtils.isNotEmpty(nonCarProductList)) {
-            nonCarProductList.forEach(nonCarProductListDTO -> {
-                nonCarProductListDTO.setIsSelect("0");
-                nonCarProductListDTO.setPremium("0");
-                nonCarProductListDTO.setStartDate(null);
-                nonCarProductListDTO.setEndDate(null);
-            });
-        }
+        // ========== 8. 调用报价接口 ==========
 
-        // 请求接口
+        // 设置请求参数
         preciseInitParam.setChannelCode("OffWeb");
         preciseInitParam.setInitFlag("quote");
         preciseInitParam.setTplatformId(tplatformId);
         preciseInitParam.setTplatLoginKey(tplatLoginKey);
         preciseInitParam.setOrderID(mainOrderId);
         preciseInitParam.setRiskType("0808");
+
+        // 调用保司报价接口
         PreciseInitResult preciseQuoteResult = httpService.preciseInitToQuote(session.getToken(), preciseInitParam);
 
+        // 校验报价结果
         if (preciseQuoteResult == null) {
             throw new RuntimeException("HttpService#preciseInitToQuote (报价)失败!");
         }

Разница между файлами не показана из-за своего большого размера
+ 33 - 29
tenant/insurance/quotation-taikang/src/main/java/com/jzg/quotation/taikang/crawler/service/impl/TKQuotationBaseServiceImpl.java


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

@@ -292,7 +292,7 @@ public class TaiShanCrawlerQuoteProcessServiceImpl implements TaiShanCrawlerQuot
         //String authorization = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1ODI0MjEiLCJhdXRoIjoiUk9MRV9CVVNJTkVTUyIsInBhc3N3b3JkX2V4cGlyZSI6MTUsImV4cCI6MTc4MTY4NzIzMn0.ldWz9TMY4AJ75VE0Bk1mkzAXPC9jJNMIWL82n0kGHrBwASgGvBxO3fe7orHbCIowt0athoRn1zA9m5ZNnO7olg";
 
         //String authorization = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1ODI0MjEiLCJhdXRoIjoiUk9MRV9CVVNJTkVTUyIsInBhc3N3b3JkX2V4cGlyZSI6MTIsImV4cCI6MTc4MTk0MDMyMX0.rsR8yMmSsB5iOstJSToswargqTCFB5He37UydrlK_lKFsHVqtBL-EOAgls4YL4nKWvRtbSxxY6HZRczHFG0iRw";
-        String authorization = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1ODI0MjEiLCJhdXRoIjoiUk9MRV9CVVNJTkVTUyIsInBhc3N3b3JkX2V4cGlyZSI6NjIsImV4cCI6MTc4NDc2NzY0N30.6JLD1Cc4_Vhuh5qSJ-lnEQ5MsNskbn6MzFbgUXRyCwSZzVHCI7-jpU04Mo7kLq2rc8E2LVhWHgmjyYD0rYCIiQ";
+        String authorization = "Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1ODI0MjEiLCJhdXRoIjoiUk9MRV9CVVNJTkVTUyIsInBhc3N3b3JkX2V4cGlyZSI6NTQsImV4cCI6MTc4NTQ4MjUwOX0.AbA_jALziOnj6VuKgbsTArbEM3EhmJN1zso7SF8fmqUSuR4VtSj-GNJuOq3P-xFumSyV-BKFAxCxC08Ryjtzxw";
 
         // 修复后:从缓存获取
         String token = taiShanCrawlerCacheComponent.getAuthorization(username);

+ 2 - 2
tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/service/impl/TaiShanCrawlerRequestImpl.java

@@ -162,8 +162,8 @@ public class TaiShanCrawlerRequestImpl implements TaiShanCrawlerRequest {
                     String startTime = extractDuplicateEndTime(e.getMessage());
                     if (StringUtils.hasLength(startTime)) {
                         String newEndTime = TimeProcessing.minusOneSecondAfterYear(startTime);
-                        String startDate = TimeProcessing.dateTimeToDate(startTime);
-                        String endDate = TimeProcessing.minusDays(TimeProcessing.dateTimeToDate(newEndTime), 1);
+                        String startDate = startTime;
+                        String endDate = TimeProcessing.dateToDateTimeMax(TimeProcessing.minusDays(TimeProcessing.dateTimeToDate(newEndTime), 1));
                         buildDuplicateInsuranceDate(startDate, endDate, quoteVo, taiShanCrawlerQuotationRequest);
                         log.info("泰山报价重复投保日期参数: startTime={}, endTime={}", startTime, newEndTime);
                         // 重新报价

+ 31 - 21
tenant/organization/src/main/java/com/jzg/organization/controller/ReceivableController.java

@@ -25,7 +25,6 @@ import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
 import java.math.BigDecimal;
-import java.math.RoundingMode;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -95,8 +94,11 @@ public class ReceivableController {
         receivableQueryVo.setSystemCode(baseController.getSystemCode());
         HttpResult<Page<InsPlyIncome>> ok = HttpResult.ok(receivableService.getReceivablePage(receivableQueryVo));
         Page<InsPlyIncome> data = ok.getData();
-        List<InsPlyIncomeOrder> receivablePageIds = receivableService.getReceivablePageIds(receivableQueryVo);
-        return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        InsPlyIncomeVO insPlyIncomeVO = receivableService.getReceivablePageIds(receivableQueryVo);
+        insPlyIncomeVO.setInsPlyIncomePage(data);
+        // return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        return HttpResult.ok(insPlyIncomeVO);
+
     }
 
     @PostMapping("getCommissionReceivableTotalAmount")
@@ -173,8 +175,10 @@ public class ReceivableController {
         receivableQueryVo.setSystemCode(baseController.getSystemCode());
         HttpResult<Page<InsPlyIncome>> ok = HttpResult.ok(receivableService.getReceivablePage(receivableQueryVo));
         Page<InsPlyIncome> data = ok.getData();
-        List<InsPlyIncomeOrder> receivablePageIds = receivableService.getReceivablePageIds(receivableQueryVo);
-        return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        InsPlyIncomeVO vo = receivableService.getReceivablePageIds(receivableQueryVo);
+        vo.setInsPlyIncomePage(data);
+        return HttpResult.ok(vo);
+        //return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
     }
 
     @PostMapping("getOtherReceivableTotalAmount")
@@ -205,8 +209,10 @@ public class ReceivableController {
         receivableQueryVo.setSystemCode(baseController.getSystemCode());
         HttpResult<Page<InsPlyIncome>> ok = HttpResult.ok(receivableService.getReceivablePage(receivableQueryVo));
         Page<InsPlyIncome> data = ok.getData();
-        List<InsPlyIncomeOrder> receivablePageIds = receivableService.getReceivablePageIds(receivableQueryVo);
-        return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        InsPlyIncomeVO vo = receivableService.getReceivablePageIds(receivableQueryVo);
+        vo.setInsPlyIncomePage(data);
+        // return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        return HttpResult.ok(vo);
     }
 
     @PostMapping("getJyOtherReceivableList")
@@ -217,8 +223,10 @@ public class ReceivableController {
         receivableQueryVo.setSystemCode(baseController.getSystemCode());
         HttpResult<Page<InsPlyIncome>> ok = HttpResult.ok(receivableService.getReceivablePage(receivableQueryVo));
         Page<InsPlyIncome> data = ok.getData();
-        List<InsPlyIncomeOrder> receivablePageIds = receivableService.getReceivablePageIds(receivableQueryVo);
-        return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        InsPlyIncomeVO vo = receivableService.getReceivablePageIds(receivableQueryVo);
+        vo.setInsPlyIncomePage(data);
+        // return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds));
+        return HttpResult.ok(vo);
     }
 
     @PostMapping("commissionInvoicing")
@@ -441,20 +449,22 @@ public class ReceivableController {
         }
         receivableQueryVo.setSystemCode(baseController.getSystemCode());
         Page<InsPlyIncome> data = receivableService.getPlatFormReceivableList(receivableQueryVo);
-        List<InsPlyIncomeOrder> receivablePageIds = receivableService.getReceivablePageIds(receivableQueryVo);
+        InsPlyIncomeVO vo = receivableService.getReceivablePageIds(receivableQueryVo);
         //满足条件的的总的应收
-        BigDecimal totalReceivablePremium = BigDecimal.ZERO;
-        try{
-            List<String> orderNos = receivablePageIds.stream().map(InsPlyIncomeOrder::getOrderNo).distinct().toList();
-            if(CollUtil.isNotEmpty(orderNos)){
-                totalReceivablePremium = receivableService.queryAllTotalReceivablePremium(orderNos);
-            }
-            totalReceivablePremium = totalReceivablePremium.setScale(5, RoundingMode.HALF_DOWN);
-        }catch (Exception e){
-            log.error("统计总的应收异常",e);
-        }
+//        BigDecimal totalReceivablePremium = BigDecimal.ZERO;
+//        try{
+//            List<String> orderNos = receivablePageIds.stream().map(InsPlyIncomeOrder::getOrderNo).distinct().toList();
+//            if(CollUtil.isNotEmpty(orderNos)){
+//                totalReceivablePremium = receivableService.queryAllTotalReceivablePremium(orderNos);
+//            }
+//            totalReceivablePremium = totalReceivablePremium.setScale(5, RoundingMode.HALF_DOWN);
+//        }catch (Exception e){
+//            log.error("统计总的应收异常",e);
+//        }
         // receivablePageIds.stream().map(InsPlyIncomeOrder::getTotalReceivablePremium).reduce(BigDecimal.ZERO, BigDecimal::add);
-        return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds, Objects.toString(totalReceivablePremium,"汇总金额异常")));
+        //return HttpResult.ok(new InsPlyIncomeVO(data, receivablePageIds, Objects.toString(totalReceivablePremium,"汇总金额异常")));
+        vo.setInsPlyIncomePage(data);
+        return HttpResult.ok(vo);
     }
 
     @PostMapping("/getPlatFormReceivableTotalAmount")

+ 2 - 6
tenant/organization/src/main/java/com/jzg/organization/mapper/ReceivableMapper.java

@@ -269,14 +269,10 @@ public interface ReceivableMapper extends BaseMapper<InsPlyIncome> {
 
     /**
      * 查询
-     * @param startYear
-     * @param endYear
-     * @param systemCode
+     * @param queryFollowIncomeVo
      * @return
      */
-    List<InsPlyIncomeDto> queryFollowIncome(@Param("startYear") Integer startYear,
-                                            @Param("endYear") Integer endYear,
-                                            @Param("systemCode")  String systemCode);
+    List<InsPlyIncomeDto> queryFollowIncome(@Param("query") QueryFollowIncomeVo queryFollowIncomeVo);
 
     /**
      * 查询发票结算的金额

+ 1 - 1
tenant/organization/src/main/java/com/jzg/organization/service/ReceivableService.java

@@ -67,7 +67,7 @@ public interface ReceivableService extends IService<InsPlyIncome> {
      * @author lipf
      * @date 2026/5/23 9:40
      */
-    List<InsPlyIncomeOrder> getReceivablePageIds(ReceivableQueryVo receivableQueryVo);
+    InsPlyIncomeVO getReceivablePageIds(ReceivableQueryVo receivableQueryVo);
 
     List<InsPlyIncome> getReceivableList(ReceivableQueryVo receivableQueryVo);
     /**

+ 53 - 1
tenant/organization/src/main/java/com/jzg/organization/service/impl/PtlSchemeServiceImpl.java

@@ -20,7 +20,6 @@ import com.jzg.commons.entity.agreement.dispatch.vo.PtlAgreementDispatchVo;
 import com.jzg.commons.entity.agreement.dispatch.vo.ValidateDispatchRuleVo;
 import com.jzg.commons.entity.costs.vo.ValidateSchemeRuleVo;
 import com.jzg.commons.entity.po.*;
-import com.jzg.commons.entity.po.jyxSetting;
 import com.jzg.commons.entity.quote.vo.CustomerInfoVo;
 import com.jzg.commons.entity.quote.vo.SalespersonInfoVo;
 import com.jzg.commons.entity.scheme.po.*;
@@ -29,6 +28,7 @@ import com.jzg.commons.entity.vo.CompanyClauseVo;
 import com.jzg.commons.entity.vo.EsmInsCompanyResVo;
 import com.jzg.commons.entity.vo.InsCompanyClauseVo;
 import com.jzg.commons.exception.NoneException;
+import com.jzg.commons.exception.ValidateException;
 import com.jzg.commons.util.AssertionUtils;
 import com.jzg.commons.util.MinioUtils;
 import com.jzg.commons.util.idgen.IdGenerate;
@@ -149,6 +149,8 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
 
         //驾意险信息
         List<PtlSchemeInsuranceDrivingIntention> ptlSchemeInsuranceDrivingIntentionList = ptlSchemeSaveVo.getPtlSchemeInsuranceDrivingIntentionList();
+        //校验驾意险座位数范围不可重复
+        validateDrivingIntentionSeatRange(ptlSchemeInsuranceDrivingIntentionList);
         //清空驾意险信息
         cleanDrivingIntention(ptlSchemeInfoList);
         ptlSchemeInsuranceDrivingIntentionMapper.insertOrUpdate(ptlSchemeInsuranceDrivingIntentionList);
@@ -175,6 +177,46 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
         ptlSchemeInsuranceDrivingIntentionMapper.delete(new LambdaQueryWrapper<PtlSchemeInsuranceDrivingIntention>().in(PtlSchemeInsuranceDrivingIntention::getSchemeInfoId, ids));
     }
 
+    /**
+     * 校验驾意险座位数范围不可重复
+     * 两组座位数范围 [a_min, a_max] 和 [b_min, b_max] 存在重叠即判定为重复
+     * @param drivingIntentionList 驾意险列表
+     */
+    private void validateDrivingIntentionSeatRange(List<PtlSchemeInsuranceDrivingIntention> drivingIntentionList) {
+        if (CollUtil.isEmpty(drivingIntentionList)) {
+            return;
+        }
+        for (int i = 0; i < drivingIntentionList.size(); i++) {
+            PtlSchemeInsuranceDrivingIntention a = drivingIntentionList.get(i);
+            Integer aMin = a.getMinSeatCount();
+            Integer aMax = a.getMaxSeatCount();
+            for (int j = i + 1; j < drivingIntentionList.size(); j++) {
+                PtlSchemeInsuranceDrivingIntention b = drivingIntentionList.get(j);
+                Integer bMin = b.getMinSeatCount();
+                Integer bMax = b.getMaxSeatCount();
+
+                // null 视为无限制:min=null 视为0, max=null 视为∞
+                boolean overlap = isSeatRangeOverlap(aMin, aMax, bMin, bMax);
+                if (overlap) {
+                    throw new ValidateException("驾意险座位数范围存在重叠,请检查!");
+                }
+            }
+        }
+    }
+
+    /**
+     * 判断两个座位数范围是否重叠
+     */
+    private boolean isSeatRangeOverlap(Integer min1, Integer max1, Integer min2, Integer max2) {
+        // 有效下界:null 视为 -∞(即不限制下限)
+        // 有效上界:null 视为 +∞(即不限制上限)
+        // 不重叠条件:max1 < min2 或 max2 < min1
+        // 因此重叠条件:!(max1 < min2) && !(max2 < min1)
+        boolean leftDisjoint = max1 != null && min2 != null && max1 < min2;
+        boolean rightDisjoint = max2 != null && min1 != null && max2 < min1;
+        return !leftDisjoint && !rightDisjoint;
+    }
+
     /**
      * 清空险种数据
      *
@@ -521,6 +563,16 @@ public class PtlSchemeServiceImpl extends ServiceImpl<PtlSchemeMapper, PtlScheme
                         driving.setCoverage(setting.getCoverage() != null ? setting.getCoverage().toString() : null);
                     }
                 });
+                // 根据座位数范围过滤驾意险:只保留座位数在 minSeatCount~maxSeatCount 范围内的
+                drivings.removeIf(driving -> {
+                    if (driving.getMinSeatCount() != null && seats < driving.getMinSeatCount()) {
+                        return true;
+                    }
+                    if (driving.getMaxSeatCount() != null && seats > driving.getMaxSeatCount()) {
+                        return true;
+                    }
+                    return false;
+                });
             });
         }
 

+ 248 - 70
tenant/organization/src/main/java/com/jzg/organization/service/impl/ReceivableServiceImpl.java

@@ -6,7 +6,6 @@ import cn.hutool.core.date.DateTime;
 import cn.hutool.core.lang.tree.Tree;
 import cn.hutool.core.lang.tree.TreeNodeConfig;
 import cn.hutool.core.lang.tree.TreeUtil;
-import cn.hutool.core.util.NumberUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.excel.EasyExcel;
@@ -25,10 +24,7 @@ import com.jzg.commons.entity.finance.dto.*;
 import com.jzg.commons.entity.finance.po.*;
 import com.jzg.commons.entity.finance.vo.*;
 import com.jzg.commons.entity.orders.po.InsOrderAdjust;
-import com.jzg.commons.entity.po.EsmInsCompany;
-import com.jzg.commons.entity.po.InsFeeOrders;
-import com.jzg.commons.entity.po.PtlAgreement;
-import com.jzg.commons.entity.po.SysUploadFiles;
+import com.jzg.commons.entity.po.*;
 import com.jzg.commons.entity.quote.vo.orders.InsOrdersVo;
 import com.jzg.commons.entity.vo.PtlAgreementCostRatioVo;
 import com.jzg.commons.exception.ServiceException;
@@ -288,7 +284,18 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         log.info("获取私有协议手续费应收列表。receivableQueryVo=[{}]", JSONUtil.toJsonStr(receivableQueryVo));
         Page<InsPlyIncome> page = baseMapper.getReceivablePage(receivableQueryVo.getPage(), receivableQueryVo);
         List<InsPlyIncome> records = page.getRecords();
-        records = this.fillOtherInfo(records);
+        records = records.stream().filter(action->{
+            // add by lipf 2026年7月3日09:55:17 只有私有的非车,才需要走下面的逻辑。 将非车保费为0的记录不显示
+            if(StrUtil.isEmpty(receivableQueryVo.getQueryType()) || !"3".equals(receivableQueryVo.getQueryType())){
+                return true;
+            }
+            if(StrUtil.isEmpty(action.getJyPremium())){
+                return false;
+            }
+            BigDecimal JyPremium = new BigDecimal(action.getJyPremium());
+            return BigDecimal.ZERO.compareTo(JyPremium) != 0;
+        }).toList();
+        records = this.fillOtherInfo(records,receivableQueryVo.getInvoiceType());
         page.setRecords(records);
         return page;
     }
@@ -304,19 +311,23 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
      *
      * @param receivableQueryVo 前端传过来的查询条件,包含订单号、保险公司ID、结算状态、时间范围等筛选条件
      * @return List<InsPlyIncomeOrder> 返回应收订单ID和订单号的列表,每个对象包含:
-     *         - id: 应收订单记录ID
-     *         - orderNo: 订单号
+     * - id: 应收订单记录ID
+     * - orderNo: 订单号
      */
     @Override
-    public List<InsPlyIncomeOrder> getReceivablePageIds(ReceivableQueryVo receivableQueryVo) {
+    public InsPlyIncomeVO getReceivablePageIds(ReceivableQueryVo receivableQueryVo) {
         log.info("获取私有协议手续费应收列表的id集合。receivableQueryVo=[{}]", JSONUtil.toJsonStr(receivableQueryVo));
         // 设置page的searhCount 为false,则不会查询总数,会直接查询具体的值
         Page<InsPlyIncome> page1 = new Page<>(1, Integer.MAX_VALUE, false);
         Page<InsPlyIncome> page = baseMapper.getReceivablePage(page1, receivableQueryVo);
         List<InsPlyIncomeOrder> res = page.getRecords().stream()
                 .map(action -> new InsPlyIncomeOrder(action.getId(), action.getOrderNo())).toList();
+        BigDecimal totalAmount = page.getRecords().stream()
+                .map(action -> new BigDecimal(Objects.toString(action.getJyReceivablePremium(), "0"))).reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        InsPlyIncomeVO vo = new InsPlyIncomeVO(null, res,Objects.toString(totalAmount,"0"));
         log.info("获取私有/平台协议手续费应收列表的id集合。receivableQueryVo=[{}]. ids.size=[{}]", JSONUtil.toJsonStr(receivableQueryVo), CollUtil.size(res));
-        return res;
+        return vo;
     }
 
     /**
@@ -647,7 +658,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
             }
         }
 
-        filterIncomes = this.fillOtherInfo(filterIncomes);
+        filterIncomes = this.fillOtherInfo(filterIncomes,incomeDataVO.getInvoiceType());
         return HttpResult.ok(filterIncomes);
     }
 
@@ -711,37 +722,155 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
     /**
      * 私有协议-跟单结算记录-结算报表查询
      * <p>
-     * 该方法用于查询私有协议的跟单结算报表数据,包括以下处理逻辑:
-     * 1. 从数据库查询基础结算报表数据
-     * 2. 调用保险公司客户端获取所有保险公司信息
-     * 3. 如果保险公司数据为空,直接返回原始数据
-     * 4. 匹配并填充保险公司名称
-     * 5. 计算未结算金额(已开票金额 - 已结算金额)
-     * 6. 计算未开票金额(累计应收 - 已开票金额)
+     * 该方法用于查询私有协议下跟单车险的结算报表数据,主要处理逻辑包括:
+     * 1. 查询跟单费用结算报表基础数据
+     * 2. 获取保险公司信息并映射公司名称
+     * 3. 按合作公司ID、年份、月份分组统计结算数据
+     * 4. 计算各项金额指标:总应收、已开票、未开票、已结算、未结算等
+     * 5. 补充收入表中的其他费用应收数据(驾意险相关费用)
      * </p>
      *
-     * @param settlementReportQueryVo 结算报表查询条件对象,包含以下主要查询参数:
-     * - companyId: 保险公司ID  - contactPerson: 对接人  - contactPersonPhone: 对接人手机号  - startTime/endTime: 创建时间范围    - invoiceStartTime/invoiceEndTime: 开票时间范围  - settlementStartTime/settlementEndTime: 结算时间范围     - status: 结算状态   - invoiceTypes: 开票类型列表(1-手续费, 2-跟单费, 3-驾意险手续费, 4-驾意险跟单费)  - agreementType: 协议类型  - invoiceStatus: 开票状态(0-未开票, 1-已开票) - systemCode: 租户代码  - signingStartTime/signingEndTime: 签单时间范围
-     * @return List<FollowCompanyFeeVO> 返回跟随公司费用视图对象列表,每个对象包含:
-     * - companyName: 保险公司名称(根据partnerCompanyId匹配填充) - companyId: 保险公司ID   - contactPerson: 对接人  - contactPersonPhone: 对接人手机号  - partnerCompanyId: 分支保险公司ID  - totalInvoiced: 已开票总金额  - totalUnInvoiced: 未开票总金额(计算得出:累计应收 - 已开票金额)   - totalSettlementAmount: 已结算总金额 - totalUnSettlementAmount: 未结算总金额(计算得出:已开票金额 - 已结算金额)  - totalPaymentDifference: 回款差额  - totalReceivable: 累计应收
+     * @param settlementReportQueryVo 结算报表查询条件对象,包含查询所需的过滤参数(如时间范围、公司ID等)
+     * @return List<FollowCompanyFeeVO> 返回跟单公司费用视图对象列表,每个对象包含:
+     *         - partnerCompanyId: 合作公司ID
+     *         - companyName: 合作公司名称
+     *         - year: 年份
+     *         - month: 月份
+     *         - totalReceivable: 总应收金额
+     *         - totalInvoiced: 已开票总金额
+     *         - totalUnInvoiced: 未开票总金额
+     *         - totalSettlementAmount: 已结算总金额
+     *         - totalUnSettlementAmount: 未结算总金额
      */
     @Override
     public List<FollowCompanyFeeVO> followCompanySuperviseSettlementReport(SettlementReportQueryVo settlementReportQueryVo) {
         log.info("私有协议-跟单结算记录-结算报表: settlementReportQueryVo=[{}]", JSONUtil.toJsonStr(settlementReportQueryVo));
 
+        // 返回值
+        List<FollowCompanyFeeVO> resultList = CollUtil.newArrayList();
+
         List<FollowCompanyFeeVO> followCompanyFeeVos = insFeeFollowOrderMapper.selectFollowCompanySettlementReport(settlementReportQueryVo);
         // 查询所有保险公司,匹配保险公司的id和名称
         HttpResult<Map<String, EsmInsCompany>> mapHttpResult = esmInsCompanyClient.queryAllCompanyMap();
         Map<String, EsmInsCompany> data = mapHttpResult.getData();
         if (CollUtil.isEmpty(data)){
-            return followCompanyFeeVos;
+            return resultList;
         }
-        for (FollowCompanyFeeVO followCompanyFeeVo : followCompanyFeeVos) {
-            followCompanyFeeVo.setCompanyName(null != data.get(followCompanyFeeVo.getPartnerCompanyId()) ? data.get(followCompanyFeeVo.getPartnerCompanyId()).getName() : "");
-            followCompanyFeeVo.setTotalUnSettlementAmount(followCompanyFeeVo.getTotalInvoiced().subtract(followCompanyFeeVo.getTotalSettlementAmount()));
-            followCompanyFeeVo.setTotalUnInvoiced(followCompanyFeeVo.getTotalReceivable().subtract(followCompanyFeeVo.getTotalInvoiced()));
+
+        if (CollUtil.isNotEmpty(followCompanyFeeVos)){
+            // 根据分支公司分组
+            Map<String, List<FollowCompanyFeeVO>> followMap = followCompanyFeeVos.stream().collect(Collectors.groupingBy(follow -> follow.getPartnerCompanyId() + "_" + follow.getYear() + "_" + follow.getMonth()));
+            for (Map.Entry<String, List<FollowCompanyFeeVO>> entry : followMap.entrySet()) {
+                // key = 合作公司ID
+                String key = entry.getKey();
+
+                // 分组参数
+                String[] arr = key.split("_");
+                String partnerCompanyId = arr[0];
+                String year = arr[1];
+                String month = arr[2];
+
+
+                // value = 当前公司下所有VO集合
+                List<FollowCompanyFeeVO> voList = entry.getValue();
+
+                // 返回值
+                FollowCompanyFeeVO followCompanyFeeVO = new FollowCompanyFeeVO();
+                followCompanyFeeVO.setPartnerCompanyId(partnerCompanyId);
+
+                List<String> followIds = voList.stream().map(FollowCompanyFeeVO::getFollowId).toList();
+                List<String> invoiceIds = voList.stream().map(FollowCompanyFeeVO::getInvoiceId).toList();
+                List<String> settlementIds = voList.stream().map(FollowCompanyFeeVO::getSettlementId).toList();
+
+                // 根据所有followCompanyFeeVos的followId统计出已开票
+                LambdaQueryWrapper<InsFeeFollowOrder> insFeeFollowOrderLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                insFeeFollowOrderLambdaQueryWrapper.in(InsFeeFollowOrder::getId, followIds);
+                List<InsFeeFollowOrder> insFeeFollowOrders = insFeeFollowOrderMapper.selectList(insFeeFollowOrderLambdaQueryWrapper);
+                // 总金额
+                InsFeeFollowOrder feeFollowOrder = CollUtil.getFirst(insFeeFollowOrders);
+                // 取应收金额
+                BigDecimal receivablePremium = feeFollowOrder.getReceivablePremium();
+                followCompanyFeeVO.setTotalReceivable(receivablePremium);
+
+                BigDecimal totalInvoiced = insFeeFollowOrders.stream().map(InsFeeFollowOrder::getInvoiced).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
+                // 已开票总金额
+                followCompanyFeeVO.setTotalInvoiced(totalInvoiced);
+
+                // 未结算金额
+                followCompanyFeeVO.setTotalUnInvoiced(receivablePremium.subtract(totalInvoiced));
+
+                // 统计结算金额
+                LambdaQueryWrapper<InsPlyIncomeInvoiceSettlement> insPlyIncomeInvoiceSettlementLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                insPlyIncomeInvoiceSettlementLambdaQueryWrapper.in(InsPlyIncomeInvoiceSettlement::getId, settlementIds);
+                List<InsPlyIncomeInvoiceSettlement> insPlyIncomeInvoiceSettlements = insPlyIncomeInvoiceSettlementMapper.selectList(insPlyIncomeInvoiceSettlementLambdaQueryWrapper);
+                BigDecimal totalSettlementAmount = insPlyIncomeInvoiceSettlements.stream().map(InsPlyIncomeInvoiceSettlement::getActualReceivedAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
+                // 已结算总金额
+                followCompanyFeeVO.setTotalSettlementAmount(totalSettlementAmount);
+
+                // 未结算金额
+                followCompanyFeeVO.setTotalUnSettlementAmount(totalInvoiced.subtract(totalSettlementAmount));
+                followCompanyFeeVO.setCompanyName(null != data.get(partnerCompanyId) ? data.get(partnerCompanyId).getName() : "");
+                followCompanyFeeVO.setYear(year);
+                followCompanyFeeVO.setMonth(month);
+
+                resultList.add(followCompanyFeeVO);
+            }
+        }
+
+        // 根据incomes的partnerCompanyId + signYear + signMonth,过滤resultList的partnerCompanyId + year + month,根据过滤后的数据进行partnerCompanyId + signYear + signMonth的分组,然后将过滤后剩余的数据,将入到resultList中,
+        // 并且设置partnerCompanyId、year、month、totalReceivable为incomes的partnerCompanyId、signYear、signMonth、jq_other_costs_premiums + sy_other_costs_premiums
+        QueryFollowIncomeVo queryFollowIncomeVo = new QueryFollowIncomeVo(null, null, baseController.getSystemCode(), null, settlementReportQueryVo.getCompanyId());
+        List<InsPlyIncomeDto> incomes = baseMapper.queryFollowIncome(queryFollowIncomeVo);
+        if (CollUtil.isNotEmpty(incomes)) {
+            // 1. 将resultList现有数据构建 key: partnerCompanyId_year_month 映射,方便快速匹配
+            Map<String, FollowCompanyFeeVO> existVoMap = resultList.stream()
+                    .collect(Collectors.toMap(
+                            vo -> vo.getPartnerCompanyId() + "_" + vo.getYear() + "_" + vo.getMonth(),
+                            Function.identity(),
+                            (oldVal, newVal) -> oldVal // 重复key保留原有
+                    ));
+
+            // 2. 将incomes按 partnerCompanyId_signYear_signMonth 分组
+            Map<String, List<InsPlyIncomeDto>> incomeGroupMap = incomes.stream()
+                    .collect(Collectors.groupingBy(income -> income.getPartnerCompanyId() + "_" + income.getSignYear() + "_" + income.getSignMonth()));
+
+            // 3. 遍历分组后的收入数据
+            for (Map.Entry<String, List<InsPlyIncomeDto>> entry : incomeGroupMap.entrySet()) {
+                String groupKey = entry.getKey();
+                List<InsPlyIncomeDto> dtoList = entry.getValue();
+                InsPlyIncomeDto firstDto = CollUtil.getFirst(dtoList);
+                String partnerCompanyId = firstDto.getPartnerCompanyId();
+                String signYear = firstDto.getSignYear();
+                String signMonth = firstDto.getSignMonth();
+
+                // 汇总其他费用应收:jq_other_costs_premiums + sy_other_costs_premiums
+                BigDecimal totalOtherReceivable = dtoList.stream()
+                        .map(dto -> new BigDecimal(dto.getJqOtherCostsPremiums()).add(new BigDecimal(dto.getSyOtherCostsPremiums())))
+                        .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
+
+                // 4. 判断当前分组是否已经存在于resultList(原有跟单数据)
+                FollowCompanyFeeVO existVo = existVoMap.get(groupKey);
+                if (existVo == null) {
+                    // 不存在原有数据,新建VO加入结果集
+                    FollowCompanyFeeVO newVo = new FollowCompanyFeeVO();
+                    newVo.setPartnerCompanyId(partnerCompanyId);
+                    newVo.setYear(signYear);
+                    newVo.setMonth(signMonth);
+                    // 设置总应收为其他费用合计
+                    newVo.setTotalReceivable(totalOtherReceivable);
+                    // 其余金额默认0(无开票、结算数据)
+                    newVo.setTotalInvoiced(BigDecimal.ZERO);
+                    newVo.setTotalUnInvoiced(BigDecimal.ZERO);
+                    newVo.setTotalSettlementAmount(BigDecimal.ZERO);
+                    newVo.setTotalUnSettlementAmount(BigDecimal.ZERO);
+                    // 匹配公司名称
+                    newVo.setCompanyName(null != data.get(partnerCompanyId) ? data.get(partnerCompanyId).getName() : "");
+                    resultList.add(newVo);
+                }
+            }
         }
-        return followCompanyFeeVos;
+
+        return resultList;
     }
 
     /**
@@ -781,7 +910,8 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
                 platformCompanyFeeVo.setTotalSettlementAmount(BigDecimal.ZERO);
                 platformCompanyFeeVo.setTotalPaymentDifference(BigDecimal.ZERO);
             }
-            platformCompanyFeeVo.setTotalUnSettlementAmount(BigDecimalUtil.subtract(BigDecimalUtil.subtract(platformCompanyFeeVo.getTotalReceivable(),platformCompanyFeeVo.getTotalSettlementAmount()), platformCompanyFeeVo.getTotalPaymentDifference()));
+            //platformCompanyFeeVo.setTotalUnSettlementAmount(BigDecimalUtil.subtract(BigDecimalUtil.subtract(platformCompanyFeeVo.getTotalReceivable(),platformCompanyFeeVo.getTotalSettlementAmount()), platformCompanyFeeVo.getTotalPaymentDifference()));
+            platformCompanyFeeVo.setTotalUnSettlementAmount(BigDecimalUtil.subtract(platformCompanyFeeVo.getTotalReceivable(),platformCompanyFeeVo.getTotalSettlementAmount()));
         }
         return platformCompanyFeeVos;
     }
@@ -796,17 +926,18 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
      * 3. 批量查询订单与发票的关联关系,构建订单ID到发票ID的映射
      * 4. 查询业务员类型属性映射表
      * 5. 遍历每条订单记录,填充以下字段:
-     *    - settlementStatusName: 结算状态名称(0-未结算,1-已结算)
-     *    - typeAttr: 业务员类型属性代码
-     *    - typeAttrName: 业务员类型属性名称
-     *    - invoiceStatusName: 开票状态名称(有发票ID则为"已开票",否则为"未开票")
-     *    - invoiceId: 关联的发票ID
+     * - settlementStatusName: 结算状态名称(0-未结算,1-已结算)
+     * - typeAttr: 业务员类型属性代码
+     * - typeAttrName: 业务员类型属性名称
+     * - invoiceStatusName: 开票状态名称(有发票ID则为"已开票",否则为"未开票")
+     * - invoiceId: 关联的发票ID
      * </p>
      *
-     * @param records 应收订单列表,需要填充额外信息的订单数据
+     * @param records     应收订单列表,需要填充额外信息的订单数据
+     * @param invoiceType 开票类型 1:手续费 2:跟单费 3:驾意手续费 4:驾意跟单费
      * @return List<InsPlyIncome> 返回填充了必要字段后的完整订单列表
      */
-    List<InsPlyIncome> fillOtherInfo(List<InsPlyIncome> records){
+    List<InsPlyIncome> fillOtherInfo(List<InsPlyIncome> records, String invoiceType){
         if(CollUtil.isEmpty(records)){
             return records;
         }
@@ -816,9 +947,12 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         List<InsPlyIncomeInvoiceLink> links = baseMapper.queryIncomeBindInvoice(invoicingVo);
         Map<String, String> income2InvoceIdMap = links.stream()
                 .filter(action -> StrUtil.isNotEmpty(action.getIncomeId()))
-                .collect(Collectors.toMap(InsPlyIncomeInvoiceLink::getIncomeId, InsPlyIncomeInvoiceLink::getInvoiceId, (oleItem, newItem) -> oleItem));
-
-
+                .filter(action -> {
+                    if(StrUtil.isEmpty(invoiceType)){
+                        return true;
+                    }
+                    return invoiceType.equals(action.getInvoiceType());
+                }).collect(Collectors.toMap(InsPlyIncomeInvoiceLink::getIncomeId, InsPlyIncomeInvoiceLink::getInvoiceId, (oleItem, newItem) -> oleItem));
         Map<String, String> typeAttrMaps = sysUserJzgInfoSalesmanService.queryYwyTypeAttrMap();
         records.forEach(insPlyIncome -> {
             if("0".equals(insPlyIncome.getSettlementStatus())){
@@ -1210,6 +1344,8 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
             insPlyIncomeInvoiceLinkMapper.insert(invoicingLinkList);
             result.setInvoiceId(invoice.getId());
             result.setSumPrice(null != invoicingVo.getReceivableSupervisePremium() ? invoicingVo.getReceivableSupervisePremium().toString() : null);
+
+
             return result;
         } catch (Exception e) {
             log.info("开票异常.invoicingVo=[{}]",JSONUtil.toJsonStr(invoicingVo), e);
@@ -3289,7 +3425,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         // 设置应收
         updateIncome.setJqReceivablePremium(jqInletPremium.toString());
         updateIncome.setSyReceivablePremium(syInletPremium.toString());
-        updateIncome.setJyReceivablePremium(jyPremium.toString());
+        updateIncome.setJyReceivablePremium(jyInletPremium.toString());
 
         // 设置手续费比例
         updateIncome.setJqSuperviseCostsProportion(BigDecimalUtil.toString(ratioVo.getJqCommissionRatio()));
@@ -3588,7 +3724,8 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         String systemCode = baseController.getUserSystemCode();
 
         // 查询该年份下的所有跟进收入数据,并按合作方公司ID进行分组
-        List<InsPlyIncomeDto> incomes = baseMapper.queryFollowIncome(startYear, endYear, systemCode);
+        QueryFollowIncomeVo queryFollowIncomeVo = new QueryFollowIncomeVo(startYear, endYear, systemCode, null, null);
+        List<InsPlyIncomeDto> incomes = baseMapper.queryFollowIncome(queryFollowIncomeVo);
 
         // ====================== 增加动态条件过滤 ======================
         List<InsPlyIncomeDto> filterIncomes = new ArrayList<>();
@@ -3963,7 +4100,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         log.info("获取平台协议列表. receivableQueryVo=[{}]", JSONUtil.toJsonStr(receivableQueryVo));
         Page<InsPlyIncome> page = baseMapper.getReceivablePage(receivableQueryVo.getPage(), receivableQueryVo);
         List<InsPlyIncome> records = page.getRecords();
-        records = this.fillOtherInfo(records);
+        records = this.fillOtherInfo(records,null);
         page.setRecords(records);
         return page;
     }
@@ -4042,6 +4179,10 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
                         .map(InsPlyIncomeInvoiceSettlement::getActualReceivedAmount)
                         .filter(Objects::nonNull)
                         .reduce(BigDecimal.ZERO, BigDecimal::add);
+                BigDecimal settlementPaymentDifference = insPlyIncomeInvoiceSettlements.stream()
+                        .map(InsPlyIncomeInvoiceSettlement::getSettlementPaymentDifference)
+                        .filter(Objects::nonNull)
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                 BigDecimal receivableSupervisePremium = action.getReceivableSupervisePremium()  != null ? action.getReceivableSupervisePremium(): BigDecimal.ZERO;
 //                BigDecimal overinflatedAmount = action.getOverinflatedAmount() != null? action.getOverinflatedAmount(): BigDecimal.ZERO;
                 // 待结算金额 = 开票金额 - 已收金额
@@ -4052,6 +4193,9 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
                 action.setRemainSettlementAmount(remainSettlementAmount);
                 action.setCompanyAllName(allCompanyHierarchyPaths.get(Objects.toString(action.getCompanyId(),"")));
                 action.setSettlementStatus(settlementStatusName);
+                // 回款差额
+                action.setSettlementPaymentDifference(settlementPaymentDifference);
+
                 if ("2".equals(invoiceRecordQueryVo.getInvoiceType())) {
                     LambdaQueryWrapper<InsFeeFollowOrder> insFeeFollowOrderLambdaQueryWrapper = new LambdaQueryWrapper<>();
                     insFeeFollowOrderLambdaQueryWrapper.eq(InsFeeFollowOrder::getYear, action.getYear());
@@ -4369,7 +4513,7 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         receivableQueryVo.setSystemCode(baseController.getSystemCode());
 
         List<InsPlyIncome> receivableList = baseMapper.getReceivableList(receivableQueryVo);
-        receivableList = this.fillOtherInfo(receivableList);
+        receivableList = this.fillOtherInfo(receivableList, null);
 
         // 调用动态导出
         return exportBaseService.upLoadFile(
@@ -4412,18 +4556,60 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
         }else{
             exportSuperviseSettlementDetailExcelDtos = baseMapper.getSuperviseSettlementDetailExcelDtos(page, settlementQueryVo);
         }
-        exportSuperviseSettlementDetailExcelDtos.getRecords().forEach(dto -> {
-            if (dto.getInvoiceParty().equals("1")) {
-                dto.setInvoiceParty("我方开票");
-            } else if (dto.getInvoiceParty().equals("2")) {
-                dto.setInvoiceParty("保司开票");
-            }
-            if (dto.getStatus().equals("1")) {
-                dto.setStatus("已结算");
-            } else if (dto.getStatus().equals("0")) {
-                dto.setStatus("未结算");
+        if (CollUtil.isNotEmpty(exportSuperviseSettlementDetailExcelDtos.getRecords())){
+            Map<String, InsPlyIncomeInvoiceSettlement> settlementMap = new HashMap<>();
+            if (!(settlementQueryVo.getInvoiceTypes().contains("5") && settlementQueryVo.getInvoiceTypes().contains("6"))) {
+                // 根据invoiceId关联查询ins_ply_income_invoice_settlement表
+                LambdaQueryWrapper<InsPlyIncomeInvoiceSettlement> insPlyIncomeInvoiceSettlementLambdaQueryWrapper = new LambdaQueryWrapper<>();
+                insPlyIncomeInvoiceSettlementLambdaQueryWrapper.in(InsPlyIncomeInvoiceSettlement::getInvoiceId, exportSuperviseSettlementDetailExcelDtos.getRecords().stream().map(ExportSuperviseSettlementDetailExcelDto::getInvoiceId).toList());
+                insPlyIncomeInvoiceSettlementLambdaQueryWrapper.eq(InsPlyIncomeInvoiceSettlement::getIsDelete, 0);
+                if (StrUtil.isNotBlank(settlementQueryVo.getSettlementPerson())) {
+                    // 结算人
+                    insPlyIncomeInvoiceSettlementLambdaQueryWrapper.eq(InsPlyIncomeInvoiceSettlement::getCreateBy, settlementQueryVo.getSettlementPerson());
+                }
+                if (StrUtil.isNotBlank(settlementQueryVo.getSettlementTimeStart()) && StrUtil.isNotBlank(settlementQueryVo.getSettlementTimeEnd())) {
+                    // 结算时间
+                    insPlyIncomeInvoiceSettlementLambdaQueryWrapper.between(StrUtil.isNotBlank(settlementQueryVo.getSettlementTimeStart()) && StrUtil.isNotBlank(settlementQueryVo.getSettlementTimeEnd()),
+                            InsPlyIncomeInvoiceSettlement::getCreateTime, settlementQueryVo.getSettlementTimeStart(), settlementQueryVo.getSettlementTimeEnd());
+                }
+                List<InsPlyIncomeInvoiceSettlement> insPlyIncomeInvoiceSettlements = insPlyIncomeInvoiceSettlementMapper.selectList(insPlyIncomeInvoiceSettlementLambdaQueryWrapper);
+                // 如果结算记录不为空,且结算人、结算时间不为空,则根据结算记录的invoiceId过滤列表
+                if (CollUtil.isNotEmpty(insPlyIncomeInvoiceSettlements)) {
+                    if (StrUtil.isNotBlank(settlementQueryVo.getSettlementPerson()) || (StrUtil.isNotBlank(settlementQueryVo.getSettlementTimeStart()) && StrUtil.isNotBlank(settlementQueryVo.getSettlementTimeEnd()))) {
+                        List<String> invoiceList = insPlyIncomeInvoiceSettlements.stream().map(InsPlyIncomeInvoiceSettlement::getInvoiceId).toList();
+                        // filter:lambda 返回 true 的元素留下,false 的剔除
+                        exportSuperviseSettlementDetailExcelDtos.setRecords(exportSuperviseSettlementDetailExcelDtos.getRecords().stream().filter(detail -> invoiceList.contains(detail.getInvoiceId())).toList());
+                    }
+
+                    // 冲突时保留旧值(第一条)
+                    settlementMap = insPlyIncomeInvoiceSettlements.stream()
+                            .collect(Collectors.toMap(
+                                    InsPlyIncomeInvoiceSettlement::getInvoiceId,
+                                    Function.identity(),
+                                    (oldVal, newVal) -> oldVal
+                            ));
+                }
             }
-        });
+
+            final Map<String, InsPlyIncomeInvoiceSettlement> finalSettlementMap = settlementMap;
+            exportSuperviseSettlementDetailExcelDtos.getRecords().forEach(dto -> {
+                if (dto.getInvoiceParty().equals("1")) {
+                    dto.setInvoiceParty("我方开票");
+                } else if (dto.getInvoiceParty().equals("2")) {
+                    dto.setInvoiceParty("保司开票");
+                }
+                if (dto.getStatus().equals("1")) {
+                    dto.setStatus("已结算");
+                } else if (dto.getStatus().equals("0")) {
+                    dto.setStatus("未结算");
+                }
+                InsPlyIncomeInvoiceSettlement insPlyIncomeInvoiceSettlement = finalSettlementMap.get(dto.getInvoiceId());
+                if (null != insPlyIncomeInvoiceSettlement){
+                    dto.setSettlementPerson(insPlyIncomeInvoiceSettlement.getCreateBy());
+                    dto.setSettlementTime(insPlyIncomeInvoiceSettlement.getCreateTime());
+                }
+            });
+        }
         return exportSuperviseSettlementDetailExcelDtos;
     }
 
@@ -4643,22 +4829,14 @@ public class ReceivableServiceImpl extends ServiceImpl<ReceivableMapper, InsPlyI
     public void exportOtherSettlementExcel(Page page, SettlementQueryVo settlementQueryVo) throws IOException {
         verifyUploadPath();
         settlementQueryVo.setSystemCode(baseController.getSystemCode());
-        Page<ExportOtherSettlementExcelDto> exportOtherSettlementExcelDtos = baseMapper.getOtherSettlementExcelDtos(page, settlementQueryVo);
-        exportOtherSettlementExcelDtos.getRecords().forEach(dto -> {
-            if (dto.getInvoiceParty().equals("1")) {
-                dto.setInvoiceParty("我方开票");
-            } else if (dto.getInvoiceParty().equals("2")) {
-                dto.setInvoiceParty("保司开票");
-            }
-            switch (dto.getInvoiceType()) {
-                case "1" -> dto.setInvoiceType("手续费");
-                case "2" -> dto.setInvoiceType("跟单费");
-                case "3" -> dto.setInvoiceType("驾意险手续费");
-                case "4" -> dto.setInvoiceType("驾意险跟单费");
-            }
-        });
-        List<ExportOtherSettlementExcelDto> list = exportOtherSettlementExcelDtos.getRecords();
-        EasyExcelUtils.exportForWeb(ServletUtils.getResponse(), ExportOtherSettlementExcelDto.class, list, "车险跟单费结算记录");
+        log.info("查询平台结算记录:settlementQueryVo=[{}]", JSONUtil.toJsonStr(settlementQueryVo));
+        // 设置空条件
+        setNoneCondition(settlementQueryVo);
+        settlementQueryVo.setSystemCode(baseController.getSystemCode());
+        Page<ExportSuperviseSettlementExcelDto> exportSuperviseSettlementExcelDtos = baseMapper.followSettlementExcelDtos(page, settlementQueryVo);
+        // 补充、转换列表的数据
+        supplementaryListData(exportSuperviseSettlementExcelDtos);
+        EasyExcelUtils.exportForWeb(ServletUtils.getResponse(), ExportSuperviseSettlementExcelDto.class, exportSuperviseSettlementExcelDtos.getRecords(), "车险跟单费结算记录");
     }
 
     /**

+ 13 - 6
tenant/organization/src/main/resources/mapper/InsFeeFollowOrderMapper.xml

@@ -48,9 +48,11 @@
     <select id="selectFollowCompanySettlementReport" resultType="com.jzg.commons.entity.finance.vo.FollowCompanyFeeVO">
         SELECT
         ffo.partner_company_id AS partnerCompanyId,
-        SUM(COALESCE(ffo.invoiced, 0.00)) AS totalInvoiced,
-        SUM(COALESCE(sett.actual_received_amount, 0.00)) AS totalSettlementAmount,
-        MAX(COALESCE(ffo.reconciliation_amount, 0.00)) AS totalReceivable
+        ffo.id AS followId,
+        ffo.year,
+        ffo.month,
+        inv.id AS invoiceId,
+        sett.id AS settlementId
         FROM ins_fee_follow_order ffo
         -- 跟单订单关联发票中间表
         INNER JOIN ins_ply_follow_invoice_link link
@@ -64,8 +66,14 @@
         <where>
             ffo.is_delete = 0
             -- 保险公司companyId筛选
-            <if test="query.companyId != null and query.companyId != ''">
-                AND ffo.company_id = #{query.companyId}
+            <if test="query.companyIdList != null and query.companyIdList.size() > 0" >
+                AND ffo.partner_company_id in
+                <foreach collection="query.companyIdList" item="item" open="(" separator="," close=")">
+                    #{item}
+                </foreach>
+            </if>
+            <if test="query.companyId != null and query.companyId != ''" >
+                AND ffo.partner_company_id = #{query.companyId}
             </if>
             -- 跟单订单创建时间范围
             <if test="query.startTime != null and query.startTime != '' and query.endTime != null and query.endTime != ''">
@@ -84,7 +92,6 @@
                 AND sett.create_time BETWEEN #{query.settlementStartTime} AND #{query.settlementEndTime}
             </if>
         </where>
-        GROUP BY ffo.partner_company_id
     </select>
 
 

+ 3 - 1
tenant/organization/src/main/resources/mapper/PtlSchemeInsuranceDrivingIntentionMapper.xml

@@ -9,7 +9,9 @@
         psidi.selected_status,
         psidi.quantity,
         psidi.scheme_info_id,
-        psidi.driving_intention_id
+        psidi.driving_intention_id,
+        psidi.min_seat_count,
+        psidi.max_seat_count
         FROM
         ptl_scheme_insurance_driving_intention psidi
         LEFT JOIN jyx_config jc ON psidi.driving_intention_id = jc.id AND jc.is_delete = 0

+ 5 - 1
tenant/organization/src/main/resources/mapper/PtlSchemeMapper.xml

@@ -46,6 +46,8 @@
                 <result column="premium" property="premium"/>
                 <result column="quantity" property="quantity"/>
                 <result column="selected_status" property="selectedStatus"/>
+                <result column="min_seat_count" property="minSeatCount"/>
+                <result column="max_seat_count" property="maxSeatCount"/>
             </collection>
         </collection>
     </resultMap>
@@ -160,7 +162,9 @@
             jc.parent_id,
             jc1.product_code as parent_product_code,
             psidi.quantity,
-            psidi.selected_status
+            psidi.selected_status,
+            psidi.min_seat_count,
+            psidi.max_seat_count
         FROM
             ptl_scheme ps
                 left JOIN ptl_scheme_info psi ON ps.id = psi.scheme_id AND psi.is_delete = 0

+ 33 - 43
tenant/organization/src/main/resources/mapper/ReceivableMapper.xml

@@ -239,6 +239,7 @@
         ipi.jq_other_costs_premiums,
         ipi.sy_other_costs_premiums,
         ipi.jy_other_costs_premiums,
+        COALESCE(ipi.jy_supervise_costs_premiums, 0) + COALESCE(ipi.jy_other_costs_premiums,0) as jyTotalPremiums,
         io.product_name,
         iocui.name AS insuredPerson,
         ipi.jq_premium,
@@ -248,6 +249,7 @@
         ipi.jq_receivable_premium,
         ipi.sy_receivable_premium,
         ipi.jy_receivable_premium,
+
         ipi.jq_supervise_costs_proportion,
         ipi.sy_supervise_costs_proportion,
         ipi.jy_supervise_costs_proportion,
@@ -336,8 +338,8 @@
         </if>
         <if test="receivableQueryVo.queryType!= null and (receivableQueryVo.queryType == 3 or receivableQueryVo.queryType == 4)">
             AND ipi.jy_premium != 0
-            AND ipi.jy_supervise_settlement = 0
-            AND ipi.jy_other_settlement = 0
+<!--            AND ipi.jy_supervise_settlement = 0-->
+<!--            AND ipi.jy_other_settlement = 0-->
         </if>
         <if test="receivableQueryVo.licenseNo != null and receivableQueryVo.licenseNo != ''">
             AND ipi.license_no LIKE CONCAT('%', #{receivableQueryVo.licenseNo}, '%')
@@ -729,8 +731,8 @@
         GROUP_CONCAT(DISTINCT ipii.invoice_party SEPARATOR ',') AS invoice_party,
         GROUP_CONCAT(DISTINCT ipii.tax_point SEPARATOR ',') AS tax_point,
         GROUP_CONCAT(DISTINCT ipiis.settlement_payment_reason SEPARATOR ',') AS settlement_payment_reason,
-        MAX(ipiis.settlement_payment_difference) AS settlement_payment_difference,
-        MAX(ipiis.actual_received_amount) AS actual_received_amount,
+        MAX(COALESCE(ipiis.settlement_payment_difference, 0)) AS settlement_payment_difference,
+        MAX(COALESCE(ipiis.actual_received_amount, 0)) AS actual_received_amount,
         MAX(ipiis.receive_payment_date) AS receive_payment_date,
         MAX(ipiis.create_by) AS create_by,
         MAX(ipiis.create_time) AS create_time
@@ -876,10 +878,9 @@
 
     <select id="getSuperviseSettlementDetailExcelDtos" resultType="com.jzg.commons.entity.finance.dto.ExportSuperviseSettlementDetailExcelDto">
         select ipi.contact_person, ipi.contact_person_phone,
-        ipiis.id as settlementId,
-        ipiis.invoice_id as invoiceId,
         io.company_name as companyName,
         io.signing_time as signingTime,
+        ipii.id as invoiceId,
         ipii.status as status,
         ipi.license_no as licenseNo,
         io.product_name as productName,
@@ -896,29 +897,14 @@
         ipi.jy_premium as jyPremium,
         ipi.jy_supervise_costs_proportion as jySuperviseCostsProportion,
         ipi.jy_policy_no as jyPolicyNo,
-        ipi.tax_premium as taxPremium,
-        ipii.create_by as invoicePerson,
-        ipii.create_time as invoiceTime,
-        ipiis.create_by as settlementPerson,
-        ipiis.create_time as settlementTime
-        FROM
-        ins_ply_income_invoice ipii
-        LEFT JOIN ins_ply_income_invoice_settlement ipiis ON ipiis.invoice_id = ipii.id AND ipiis.is_delete = 0
-        LEFT JOIN ins_ply_income_invoice_link ipil ON ipil.invoice_id = ipiis.invoice_id AND ipil.is_delete = 0
-        LEFT JOIN ins_ply_income ipi ON ipi.id = ipil.income_id AND ipi.is_delete = 0
-        LEFT JOIN ins_orders io ON ipi.order_no = io.id AND io.is_delete = 0
+        ipi.tax_premium as taxPremium
+        FROM ins_ply_income_invoice ipii
+        LEFT JOIN ins_ply_income_invoice_link ipil ON ipii.id = ipil.invoice_id AND ipil.is_delete = 0
+        LEFT JOIN ins_ply_income ipi ON ipi.id = ipil.income_id  AND ipi.is_delete = 0
+        LEFT JOIN ins_orders io  ON ipi.order_no = io.id  AND io.is_delete = 0
         <where>
             ipii.is_delete = 0
             AND ipii.system_code = #{settlementQueryVo.systemCode}
-            <if test="settlementQueryVo.settlementId != null and settlementQueryVo.settlementId != ''">
-                AND ipiis.id = #{settlementQueryVo.settlementId}
-            </if>
-            <if test="settlementQueryVo.settlementIds != null and settlementQueryVo.settlementIds != ''">
-                AND ipiis.id IN
-                <foreach collection="settlementQueryVo.settlementIds" item="settlementId" open="(" separator="," close=")">
-                    #{settlementId}
-                </foreach>
-            </if>
             <if test="settlementQueryVo.invoiceTypes != null and settlementQueryVo.invoiceTypes != ''">
                 AND ipii.invoice_type IN
                 <foreach collection="settlementQueryVo.invoiceTypes" item="invoiceType" open="(" separator="," close=")">
@@ -926,7 +912,7 @@
                 </foreach>
             </if>
             <if test="settlementQueryVo.invoiceId != null and settlementQueryVo.invoiceId != ''">
-                AND ipiis.invoice_id = #{settlementQueryVo.invoiceId}
+                AND ipii.id = #{settlementQueryVo.invoiceId}
             </if>
             <if test="settlementQueryVo.companyId != null and settlementQueryVo.companyId != ''">
                 AND ipi.company_id = #{settlementQueryVo.companyId}
@@ -934,12 +920,6 @@
             <if test="settlementQueryVo.invoiceParty != null and settlementQueryVo.invoiceParty != ''">
                 AND ipii.invoice_party = #{settlementQueryVo.invoiceParty}
             </if>
-            <if test="settlementQueryVo.settlementPerson != null and settlementQueryVo.settlementPerson != ''">
-                AND ipiis.create_by = #{settlementQueryVo.settlementPerson}
-            </if>
-            <if test="settlementQueryVo.settlementTimeStart != null and settlementQueryVo.settlementTimeStart != ''">
-                AND ipiis.create_time between #{settlementQueryVo.signingTimeStart} and #{settlementQueryVo.signingTimeEnd}
-            </if>
             <if test="settlementQueryVo.agreementType != null and settlementQueryVo.agreementType != ''">
                 AND ipi.agreement_type = #{settlementQueryVo.agreementType}
             </if>
@@ -1498,11 +1478,7 @@
         LEFT JOIN ins_ply_income_invoice_settlement ipiis ON ipii.id = ipiis.invoice_id AND ipiis.is_delete = 0
         LEFT JOIN esm_ins_company eic on eic.id = ipii.company_id AND eic.is_delete = 0
         <where>
-            ipii.is_delete = 0
-            AND ipii.system_code = #{invoiceRecordQueryVo.systemCode}
-            <if test="invoiceRecordQueryVo.invoiceType != null and invoiceRecordQueryVo.invoiceType != ''">
-                AND ipii.invoice_type = #{invoiceRecordQueryVo.invoiceType}
-            </if>
+            ipii.is_delete = 0 AND ipii.system_code = #{invoiceRecordQueryVo.systemCode}
             <if test="invoiceRecordQueryVo.accountsReceivable != null and invoiceRecordQueryVo.accountsReceivable != ''">
                 AND ipii.invoice_risk_type = #{invoiceRecordQueryVo.accountsReceivable}
             </if>
@@ -1512,6 +1488,9 @@
             <if test="invoiceRecordQueryVo.companyId != null and invoiceRecordQueryVo.companyId != ''">
                 AND ipii.company_id = #{invoiceRecordQueryVo.companyId}
             </if>
+            <if test="invoiceRecordQueryVo.invoiceType != null and invoiceRecordQueryVo.invoiceType != ''">
+                AND ipii.invoice_type = #{invoiceRecordQueryVo.invoiceType}
+            </if>
             <if test="invoiceRecordQueryVo.invoiceParty != null and invoiceRecordQueryVo.invoiceParty != ''">
                 AND ipii.invoice_party = #{invoiceRecordQueryVo.invoiceParty}
             </if>
@@ -2053,6 +2032,7 @@
         a.*,
         date(a.signing_time ) as sign_date,
         year(a.signing_time) as sign_year,
+        month(a.signing_time) as sign_month,
         b.partner_company_id,
         (SELECT eic.name FROM esm_ins_company eic WHERE b.partner_company_id = eic.id AND eic.is_delete = 0) AS partnerCompanyName,
         (SELECT eic.name FROM esm_ins_company eic WHERE a.company_id = eic.id AND eic.is_delete = 0) AS companyName
@@ -2061,11 +2041,21 @@
         <where>
             a.is_delete=0
             AND a.agreement_type = 2
-            <if test="startYear != null and endYear != null">
-                AND YEAR(a.signing_time) BETWEEN #{startYear} AND #{endYear}
+            AND a.signing_time is not null
+            <if test="query.startYear != null and query.endYear != null">
+                AND YEAR(a.signing_time) BETWEEN #{query.startYear} AND #{query.endYear}
             </if>
-            <if test="systemCode != null">
-                AND a.system_code = #{systemCode}
+            <if test="query.systemCode != null">
+                AND a.system_code = #{query.systemCode}
+            </if>
+            <if test="query.companyId != null and query.companyId != ''">
+                AND a.company_id = #{query.companyId}
+            </if>
+            <if test="query.companyIdList != null and query.companyIdList.size() > 0" >
+                AND a.company_id in
+                <foreach collection="query.companyIdList" item="item" open="(" separator="," close=")">
+                    #{item}
+                </foreach>
             </if>
         </where>
     </select>
@@ -2100,7 +2090,7 @@
         LEFT JOIN ins_ply_income_invoice_settlement sett ON ipii.id = sett.invoice_id AND sett.is_delete = 0
         left join ins_orders io on ipi.order_no = io.id
         <where>
-            ipi.is_delete = 0
+            ipi.is_delete = 0  and ipi.agreement_type = 1
             <if test="query.companyId != null and query.companyId != ''">
                 AND ipi.company_id = #{query.companyId}
             </if>

Некоторые файлы не были показаны из-за большого количества измененных файлов