Jelajahi Sumber

Merge branch 'master' into wrq_dev

wangrq 3 bulan lalu
induk
melakukan
d7d1ddfded
23 mengubah file dengan 642 tambahan dan 89 penghapusan
  1. 3 2
      commons/src/main/java/com/jzg/commons/entity/po/SysAmountAuditing.java
  2. 3 0
      commons/src/main/java/com/jzg/commons/entity/po/SysRelationPerson.java
  3. 8 0
      commons/src/main/java/com/jzg/commons/entity/vo/AmountAuditVo.java
  4. 2 0
      commons/src/main/java/com/jzg/commons/entity/vo/InsOrdersOtherInfoVo.java
  5. 3 0
      commons/src/main/java/com/jzg/commons/entity/vo/SysUserWalletVo.java
  6. 4 2
      commons/src/main/java/com/jzg/commons/util/PDFUtil.java
  7. 191 0
      commons/src/main/java/com/jzg/commons/util/UserContextUtil.java
  8. 202 0
      gateway/src/main/java/com/jzg/filter/AuthGlobalFilter.java
  9. 14 12
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerCarQueryRequest.java
  10. 108 41
      tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/service/Impl/HuaTaiCrawlerRequestImpl.java
  11. 1 1
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/FactorMatch.java
  12. 12 8
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/aspect/QuoteVerificationAspect.java
  13. 1 2
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/controller/InsFeeAuditController.java
  14. 3 1
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/InsFeeAuditService.java
  15. 32 13
      tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsFeeAuditServiceImpl.java
  16. 2 0
      tenant/insurance/quotation-summary/src/main/resources/mapper/InsOrdersMapper.xml
  17. 6 0
      tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/build/TaiShanCrawlerQuoteRequestVoBuild.java
  18. 6 0
      tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/build/ZmCrawlerQuoteResultVoBuild.java
  19. 19 5
      tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/service/Impl/ZhongMeiCrawlerRequestImpl.java
  20. 5 1
      tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserAccountServiceImpl.java
  21. 5 1
      tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserJzgAppServiceImpl.java
  22. 10 0
      tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserServiceImpl.java
  23. 2 0
      tenant/organization/src/main/resources/mapper/SysAmountAuditingMapper.xml

+ 3 - 2
commons/src/main/java/com/jzg/commons/entity/po/SysAmountAuditing.java

@@ -9,6 +9,7 @@ import com.fasterxml.jackson.annotation.JsonFormat;
 import com.jzg.commons.core.base.BaseModel;
 import com.jzg.commons.entity.dto.ApplyWithdrawParam;
 import com.jzg.commons.entity.dto.SysUserJzgInfoBankCardDto;
+import com.jzg.commons.util.BigDecimalUtil;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -114,8 +115,8 @@ public class SysAmountAuditing extends BaseModel {
     ){
         // 提现金额
         this.setAmount(applyWithdrawParam.getWithdrawAmount());
-        // 当前账户金额 modify by lipf 2026年6月3日16:55:06 账户中剩余的金额。 该金额 是没有扣除当前提现金额的金额
-        this.setBalanceAmount(sysUserAccount.getSumAmountUnpaid());
+        // 当前账户金额 modify by lipf 2026年6月3日16:55:06 账户中剩余的金额。 该金额 是扣除当前提现金额的金额
+        this.setBalanceAmount(BigDecimalUtil.subtract(sysUserAccount.getSumAmountUnpaid(),applyWithdrawParam.getWithdrawAmount()));
         // 提现用户
         this.setUserId(userId);
         // 银行卡卡号

+ 3 - 0
commons/src/main/java/com/jzg/commons/entity/po/SysRelationPerson.java

@@ -39,4 +39,7 @@ public class SysRelationPerson extends BaseModel {
     @Schema(description = "推荐类型(0:合伙人  1:机构负责人 )")
     private Integer type;
 
+    @Schema(description = "租户代码")
+    private String systemCode;
+
 }

+ 8 - 0
commons/src/main/java/com/jzg/commons/entity/vo/AmountAuditVo.java

@@ -27,6 +27,14 @@ public class AmountAuditVo  extends BaseModel {
     @Schema(description = "sys_user_jzg_info的主键")
     private String userJzgInfoId;
 
+    @ExcelProperty("用户信息userId")
+    @Schema(description = "sys_user表的主键")
+    private String userId;
+
+    @ExcelProperty("租户代码")
+    @Schema(description = "租户代码")
+    private String systemCode;
+
     @ExcelProperty("业务员姓名")
     @Schema(description = "业务员姓名")
     private String name;

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

@@ -1,6 +1,8 @@
 package com.jzg.commons.entity.vo;
 
 import com.alibaba.fastjson.JSONArray;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;

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

@@ -24,6 +24,9 @@ public class SysUserWalletVo {
     @Schema(description = "钱包余额")
     private BigDecimal walletBalance;
 
+    @Schema(description = "钱包余额(扣除冻结)")
+    private BigDecimal walletBalanceRemoveWithdraw;
+
     @Schema(description = "提现中金额")
     private BigDecimal withdrawingBalance;
 

+ 4 - 2
commons/src/main/java/com/jzg/commons/util/PDFUtil.java

@@ -177,7 +177,8 @@ public class PDFUtil {
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
-       return text;
+        log.info("解析pdf。pdfFilePath=[{}].解析得到的内容是:[{}]",pdfFilePath,text);
+        return text;
     }
 
     public static String getPdfContentStr(String pdfText, String beginStr,String endStr,String dateType) {
@@ -437,6 +438,7 @@ public class PDFUtil {
         return null;
     }
     private static LocalDateTime getStringTranferDate(String payDate) {
+        String payDateBefore = payDate;
         if(StringUtils.isNotBlank(payDate)){
             payDate=payDate.replaceAll(" ", "");
             payDate=payDate.replaceAll("\\n+", "");
@@ -444,7 +446,7 @@ public class PDFUtil {
             payDate=payDate.replaceAll("\\r+", "");
             payDate = payDate.replaceAll("\\s+", "");
             payDate = payDate.replaceAll("[\u4e00-\u9fa5]", "");
-            System.out.println(payDate.length());
+            log.info("getStringTranferDate. 处理前。payDateBefore=[{}] payDate=[{}]. len=[{}]",payDateBefore, payDate, payDate.length());
             if(payDate.length() == 18){
                 payDate = payDate.substring(0,10)+" "+ payDate.substring(10,18);
             }else if(payDate.length() == 15){

+ 191 - 0
commons/src/main/java/com/jzg/commons/util/UserContextUtil.java

@@ -0,0 +1,191 @@
+package com.jzg.commons.util;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 用户上下文工具类 —— 从网关透传的请求头中读取用户信息
+ * <p>
+ * 网关层 {@code AuthGlobalFilter} 在 Token 校验通过后,
+ * 会将用户信息注入到以下请求头中,下游微服务可直接读取,无需重复查询 Redis:
+ * <ul>
+ *   <li>{@code X-User-Id} —— 用户ID</li>
+ *   <li>{@code X-Username} —— 用户名/手机号</li>
+ *   <li>{@code X-User-Name} —— 姓名</li>
+ *   <li>{@code X-System-Code} —— 登录系统编码</li>
+ *   <li>{@code X-Dept-Id} —— 部门ID</li>
+ *   <li>{@code X-Work-Number} —— 工号</li>
+ *   <li>{@code X-Province} —— 省份</li>
+ *   <li>{@code X-Auths} —— 权限列表(JSON数组)</li>
+ *   <li>{@code X-Data-Scope} —— 数据权限(JSON对象)</li>
+ *   <li>{@code X-Head-Sculpture} —— 头像URL</li>
+ * </ul>
+ * </p>
+ *
+ * @author lipf
+ * @date 2026/6/16
+ */
+public class UserContextUtil {
+
+    private UserContextUtil() {
+        // 工具类,禁止实例化
+    }
+
+    /**
+     * 获取当前 HTTP 请求(可能为 null,非 Web 上下文时)
+     */
+    private static HttpServletRequest getRequest() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        if (attributes == null) {
+            return null;
+        }
+        return attributes.getRequest();
+    }
+
+    /**
+     * 从请求头获取用户ID
+     *
+     * @return 用户ID,未获取到时返回空字符串
+     */
+    public static String getUserId() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-User-Id");
+    }
+
+    /**
+     * 从请求头获取用户名
+     *
+     * @return 用户名,未获取到时返回空字符串
+     */
+    public static String getUsername() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-Username");
+    }
+
+    /**
+     * 从请求头获取姓名
+     *
+     * @return 姓名,未获取到时返回空字符串
+     */
+    public static String getUserName() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-User-Name");
+    }
+
+    /**
+     * 从请求头获取系统编码
+     *
+     * @return 系统编码,未获取到时返回空字符串
+     */
+    public static String getSystemCode() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-System-Code");
+    }
+
+    /**
+     * 从请求头获取部门ID
+     *
+     * @return 部门ID,未获取到时返回空字符串
+     */
+    public static String getDeptId() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-Dept-Id");
+    }
+
+    /**
+     * 从请求头获取工号
+     *
+     * @return 工号,未获取到时返回空字符串
+     */
+    public static String getWorkNumber() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-Work-Number");
+    }
+
+    /**
+     * 从请求头获取省份
+     *
+     * @return 省份,未获取到时返回空字符串
+     */
+    public static String getProvince() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-Province");
+    }
+
+    /**
+     * 从请求头获取头像URL
+     *
+     * @return 头像URL,未获取到时返回空字符串
+     */
+    public static String getHeadSculpture() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "";
+        return headerOrEmpty(request, "X-Head-Sculpture");
+    }
+
+    /**
+     * 从请求头获取用户权限列表
+     *
+     * @return 权限标识列表,未获取到时返回空列表
+     */
+    public static List<String> getPerms() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return new ArrayList<>();
+
+        String authsJson = headerOrEmpty(request, "X-Auths");
+        if (authsJson.isEmpty()) return new ArrayList<>();
+
+        List<String> perms = new ArrayList<>();
+        try {
+            JSONArray auths = JSONArray.parseArray(authsJson);
+            for (int i = 0; i < auths.size(); i++) {
+                JSONObject auth = auths.getJSONObject(i);
+                String perm = auth.getString("perms");
+                if (perm != null) {
+                    perms.add(perm);
+                }
+            }
+        } catch (Exception e) {
+            // 解析失败,返回空列表
+        }
+        return perms;
+    }
+
+    /**
+     * 从请求头获取数据权限
+     *
+     * @return 数据权限 JSON 字符串,未获取到时返回 "{}"
+     */
+    public static String getDataScope() {
+        HttpServletRequest request = getRequest();
+        if (request == null) return "{}";
+        return headerOrEmpty(request, "X-Data-Scope");
+    }
+
+    /**
+     * 判断用户信息是否来自网关透传(即请求头中是否存在 X-User-Id)
+     *
+     * @return true 表示本次请求已经过网关鉴权
+     */
+    public static boolean isFromGateway() {
+        return !getUserId().isEmpty();
+    }
+
+    private static String headerOrEmpty(HttpServletRequest request, String name) {
+        String value = request.getHeader(name);
+        return value != null ? value : "";
+    }
+}

+ 202 - 0
gateway/src/main/java/com/jzg/filter/AuthGlobalFilter.java

@@ -0,0 +1,202 @@
+package com.jzg.filter;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.redisson.api.RedissonClient;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 全局鉴权过滤器 —— 网关层统一认证入口
+ * <p>
+ * 所有外部请求经过网关时,先在此过滤器校验 Token 的有效性。
+ * 白名单内的路径直接放行,其他路径必须携带有效的 Authorization 头。
+ * Token 校验通过后,将用户信息通过 X-* 请求头透传给下游微服务,
+ * </p>
+ *
+ * @author wh
+ * @date 2026/6/16
+ */
+@Component
+@Order(Ordered.HIGHEST_PRECEDENCE + 10)
+@Slf4j
+public class AuthGlobalFilter implements GlobalFilter {
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    /**
+     * 无需鉴权的白名单路径(前缀匹配)
+     */
+    private static final List<String> WHITELIST = new ArrayList<>();
+
+    static {
+        // ==================== Swagger / API 文档 ====================
+        WHITELIST.add("/swagger-ui/");
+        WHITELIST.add("/v3/api-docs");
+        WHITELIST.add("/swagger-resources/");
+        WHITELIST.add("/webjars/");
+        WHITELIST.add("/favicon.ico");
+
+        // ==================== 登录 & 注册(网页端) ====================
+        WHITELIST.add("/auth/login");
+        WHITELIST.add("/auth/captchaById");
+        WHITELIST.add("/auth/register");
+        WHITELIST.add("/auth/sendSmsCode");
+        WHITELIST.add("/auth/forgotPassword");
+        WHITELIST.add("/auth/publicKeyExchange");
+
+        // ==================== APP 登录 ====================
+        WHITELIST.add("/appAuth/smsCodeLogin");
+        WHITELIST.add("/appAuth/appLogin");
+        WHITELIST.add("/appAuth/partnerAppLogin");
+
+        // ==================== 短信 ====================
+        WHITELIST.add("/send/sendSmsCode");
+        WHITELIST.add("/sendSmsCode");
+        WHITELIST.add("/appLogin");
+        WHITELIST.add("/smsCodeLogin");
+
+        // ==================== 其他公开接口 ====================
+        WHITELIST.add("/system/sysListAccountLinking");
+        WHITELIST.add("/supplementOrder/getOrderDetail");
+        WHITELIST.add("/dict/get");
+
+
+        // ==================== 本地生活调用的接口 ====================
+        WHITELIST.add("/signIn/updateSignCompleteRuleIn");
+
+    }
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        String path = exchange.getRequest().getURI().getPath();
+
+        // 1. 白名单路径直接放行
+        if (isWhitelisted(path)) {
+            return chain.filter(exchange);
+        }
+
+        // 2. 获取 Authorization 头(兼容大小写)
+        HttpHeaders headers = exchange.getRequest().getHeaders();
+        String authorization = headers.getFirst("Authorization");
+        if (authorization == null || authorization.isEmpty()) {
+            authorization = headers.getFirst("authorization");
+        }
+
+        // 3. Token 不存在 → 401
+        if (authorization == null || authorization.isEmpty()) {
+            log.warn("【网关鉴权】请求缺少 Authorization 头。path=[{}]", path);
+            return unauthorized(exchange, "未提供认证令牌");
+        }
+
+        // 4. 校验 Token 在 Redis 中是否存在
+        String userJsonStr;
+        try {
+            Object bucketValue = redissonClient.getBucket(authorization).get();
+            if (bucketValue == null) {
+                log.warn("【网关鉴权】Token 无效或已过期。path=[{}]", path);
+                return unauthorized(exchange, "认证令牌无效或已过期");
+            }
+            userJsonStr = bucketValue.toString();
+        } catch (Exception e) {
+            log.error("【网关鉴权】Redis 查询异常。path=[{}], error=[{}]", path, e.getMessage(), e);
+            return unauthorized(exchange, "认证服务异常");
+        }
+
+        // 5. 解析用户信息并透传到下游微服务
+        try {
+            JSONObject userInfo = JSONObject.parseObject(userJsonStr);
+
+            ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
+                    .header("X-User-Id", safeGet(userInfo, "userId"))
+                    .header("X-Username", safeGet(userInfo, "username"))
+                    .header("X-User-Name", safeGet(userInfo, "name"))
+                    .header("X-System-Code", safeGet(userInfo, "system"))
+                    .header("X-Dept-Id", safeGet(userInfo, "deptId"))
+                    .header("X-Work-Number", safeGet(userInfo, "workNumber"))
+                    .header("X-Province", safeGet(userInfo, "province"))
+                    .header("X-Auths", safeGetJsonArray(userInfo, "auths"))
+                    .header("X-Data-Scope", safeGetJsonObject(userInfo, "dataScope"))
+                    .header("X-Head-Sculpture", safeGet(userInfo, "headSculpture"))
+                    .build();
+
+            log.debug("【网关鉴权】认证通过。path=[{}], user=[{}], system=[{}]",
+                    path, safeGet(userInfo, "username"), safeGet(userInfo, "system"));
+
+            return chain.filter(exchange.mutate().request(mutatedRequest).build());
+
+        } catch (Exception e) {
+            log.error("【网关鉴权】用户信息解析异常。path=[{}], error=[{}]", path, e.getMessage(), e);
+            return unauthorized(exchange, "用户信息解析失败");
+        }
+    }
+
+    /**
+     * 判断路径是否在白名单中(前缀匹配)
+     */
+    private boolean isWhitelisted(String path) {
+        for (String pattern : WHITELIST) {
+            if (path.startsWith(pattern)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 安全获取字符串字段(null → "")
+     */
+    private String safeGet(JSONObject json, String key) {
+        String value = json.getString(key);
+        return value != null ? value : "";
+    }
+
+    /**
+     * 安全获取 JSONArray 字段(转为 JSON 字符串,null → "[]")
+     */
+    private String safeGetJsonArray(JSONObject json, String key) {
+        JSONArray array = json.getJSONArray(key);
+        return array != null ? array.toJSONString() : "[]";
+    }
+
+    /**
+     * 安全获取 JSONObject 字段(转为 JSON 字符串,null → "{}")
+     */
+    private String safeGetJsonObject(JSONObject json, String key) {
+        JSONObject obj = json.getJSONObject(key);
+        return obj != null ? obj.toJSONString() : "{}";
+    }
+
+    /**
+     * 返回 401 未授权响应(HTTP 200 + JSON body,与原有风格保持一致)
+     */
+    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
+        exchange.getResponse().setStatusCode(HttpStatus.OK);
+        exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
+
+        JSONObject body = new JSONObject();
+        body.put("code", 401);
+        body.put("msg", message);
+
+        byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
+        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
+        return exchange.getResponse().writeWith(Mono.just(buffer));
+    }
+}

+ 14 - 12
tenant/insurance/quotation-huatai/src/main/java/com/jzg/quotation/huatai/crawler/entity/Request/HtCrawlerCarQueryRequest.java

@@ -24,7 +24,7 @@ public class HtCrawlerCarQueryRequest implements Serializable {
 
     public HtCrawlerCarQueryRequest(CarInfoVo carInfoVo, String modelcodexh) {
         DataDTO dataDTO = new DataDTO();
-        dataDTO.setVehicleModel(carInfoVo.getBrandName());
+        dataDTO.setVehicleModel("");//carInfoVo.getBrandName()
         // 车辆类型自动判断
         if ("04".equals(carInfoVo.getVehicleUseCode())) {
             dataDTO.setMotortypecode("H0");
@@ -63,8 +63,9 @@ public class HtCrawlerCarQueryRequest implements Serializable {
         dataDTO.setLicenseplateno(carInfoVo.getLicenseNo());
         dataDTO.setEngineno(carInfoVo.getEngineNo());
         dataDTO.setVehicleregisterdate(carInfoVo.getRegisterDate());
-        dataDTO.setFueltype("");
-        dataDTO.setPurerange("");
+//        dataDTO.setFueltype("");
+//        dataDTO.setPurerange("");
+        dataDTO.setModelflag("1");
         dataDTO.setModelcodexh(modelcodexh);
         String data = JSON.toJSONString(dataDTO);
         String key = HuaTaiCryptUtil.generateKey(16);
@@ -127,29 +128,30 @@ public class HtCrawlerCarQueryRequest implements Serializable {
         private String engineno;
         @JsonProperty("vin")
         private String vin;
-        @JsonProperty("fueltype")
-        private String fueltype;
-        @JsonProperty("purerange")
-        private String purerange;
+//        @JsonProperty("fueltype")
+//        private String fueltype;
+//        @JsonProperty("purerange")
+//        private String purerange;
         @JsonProperty("vehicleregisterdate")
         private String vehicleregisterdate;
         @JsonProperty("pageno")
         private String pageno = "1";
         @JsonProperty("rowsperpage")
-        private String rowsperpage = "10";
+        private String rowsperpage = "50";
         @JsonProperty("reformRisk")
         private String reformRisk;
         @JsonProperty("modelcodexh")
         private String modelcodexh;
         @JsonProperty("checkno")
-        private String checkno;
+        private String checkno="";
         @JsonProperty("modelflag")
         private String modelflag;
         @JsonProperty("checkcode")
-        private String checkcode;
+        private String checkcode="";
         @JsonProperty("gdjgvehiclecheckflag")
-        private String gdjgvehiclecheckflag;
+        private String gdjgvehiclecheckflag = "";
         @JsonProperty("jLlxVehicleList")
-        private List<String> jLlxVehicleList;
+        //private List<String> jLlxVehicleList;
+        private String jLlxVehicleList = "{}";
     }
 }

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

@@ -147,7 +147,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
             ctx.init(null, tm, new SecureRandom());
             c.setSSLSocketFactory(ctx.getSocketFactory());
             c.setHostnameVerifier((h, s) -> true);
-        } catch (Exception e) { throw new RuntimeException(e); }
+        } catch (Exception e) { throw new SystemException(e.getMessage()); }
     }
 
     // ====================== 登录 ======================
@@ -177,7 +177,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
     public Map<String, Object> xbQuery(String username, String cookie, CarInfoVo car) throws Exception {
         log.info("开始续保查询");
         boolean special = SPECIAL_USERS.contains(username) || SPECIAL_ACCOUNT_XN.equals(username);
-
+        CarInfoVo carCopy = JSONObject.parseObject(JSONObject.toJSONString(car), CarInfoVo.class);
         HttpHeaders headers = special ? buildSpecialHeaders(cookie, true) : buildHtmlHeaders(cookie);
         if (special) {
             get(ORIGIN + "/htcsp/ShakeHandsAction.do?cmd=menuControl&ip=172.16.100.27&host=9000&menuCode=car_001&messageClickType=&mesFlag=", headers, null);
@@ -188,7 +188,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         String quotationNo = extractQuotationNo(body, special);
         log.info("quotationNo: {}", quotationNo);
 
-        Map<String, Object> dataMap = buildXbQueryMap(car, quotationNo);
+        Map<String, Object> dataMap = buildXbQueryMap(carCopy, quotationNo);
         Map<String, Object> encrypted = encrypt(dataMap);
         log.info("xbQuery encrypted: {}", encrypted);
 
@@ -303,6 +303,8 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
     private record StepResult(HtCrawlerQuoteResponse.DataDTO data, String personalInsuredJSON) { }
 
+    private record DuplQuoteResult(QuoteVo q, Boolean dupl) { }
+
     private void saveOrder(QuoteVo q, CoreResult c) {
         HtCrawlerOrder o = new HtCrawlerOrder();
         o.setOrdersNo(q.getOrder().getOrdersNo());
@@ -317,7 +319,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                                    CarInfoVo car, String modelcodexh, String cookie, String quotationNo) throws Exception {
         HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
                 HtCrawlerConfigureParameters.class, attr.getConfigInfo());
-        params.setFullComCode(mmap.get("comCode").toString());
+        params.setFullComCode(params.getComCode());
         //参数格式保持一致
         car.setPurchasePrice(car.getPurchasePrice().replace(".00", ""));
         //车辆查询
@@ -325,6 +327,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                 new HtCrawlerCarQueryRequest(car, modelcodexh), req.getHeaders1(), car);
         //过滤车型
         HtCrawlerCarQueryResponse.DataDTO.DataListDTO carDTO = CarModelsFilterUtils.to(dataDTO.getDatalist(), car);
+        log.info("过滤后的车型carDTO: {}", JSONObject.toJSONString(carDTO));
         //保存车辆信息
         executeSaveCarQuery(carDTO, dataDTO, car, cookie);
         //车型查询
@@ -423,27 +426,82 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
     private StepResult executeQuoteSteps(QuoteVo q, String quotationNo, LoginDTO dto,
                                          String cookie, PreResult pre, Map<String, Object> gsInfo) {
-        HttpHeaders h = buildQuoteStep1Headers(cookie);
-        HtCrawlerSaveOneStepRequest r = new HtCrawlerSaveOneStepRequest(q, pre.carDTO(), pre.params(), quotationNo, pre.dataDTO(), dto, gsInfo);
-        String body = req.saveOneStept(r, h).getBody().toString();
-        String mealid = extractMealId(body);
-        //机动车损失保险
-        String limitAmount = extractLimitAmount(body);
-        log.info("mealid: {}", mealid);
-        log.info("limitAmount: {}", limitAmount);
-        //组装驾意险参数,没有就空字符串
-        String personalInsuredJSON = buildPersonalInsuredJSON(q);
-
-        h = buildQuoteStep2Headers(cookie);
-        //组装报价参数
-        HtCrawlerQuoteRequest qr = new HtCrawlerQuoteRequest(q, mealid, limitAmount, "", personalInsuredJSON);
-        //报价第一次
-        String decryptData = req.quote(qr, h);
-        //处理重复投保返回终保日期的
-        q = handleQuoteErrors(decryptData, q);
-        //处理可能的录入的校验码有误
-        HtCrawlerQuoteResponse.DataDTO data = resolveQuoteResponse(q, decryptData, mealid, limitAmount, h, personalInsuredJSON);
-        return new StepResult(data, personalInsuredJSON);
+        // 初始化重试次数和最大重试阈值
+        int modelCodeRetryCount = 0;
+        final int MAX_MODEL_CODE_RETRY = 2; // 限制最大重试次数,避免死循环
+        String currentModelCodexh = pre.carDTO().getModelcodexh();
+        PreResult currentPre = pre;
+        while (modelCodeRetryCount < MAX_MODEL_CODE_RETRY) {
+            String decryptData = "";
+            try {
+                HttpHeaders h = buildQuoteStep1Headers(cookie);
+                HtCrawlerSaveOneStepRequest r = new HtCrawlerSaveOneStepRequest(q, currentPre.carDTO(),
+                        currentPre.params(), quotationNo, currentPre.dataDTO(), dto, gsInfo);
+                String body = req.saveOneStept(r, h).getBody().toString();
+                String mealid = extractMealId(body);
+                //机动车损失保险
+                String limitAmount = extractLimitAmount(body);
+                log.info("mealid: {}", mealid);
+                log.info("limitAmount: {}", limitAmount);
+                //组装驾意险参数,没有就空字符串
+                String personalInsuredJSON = buildPersonalInsuredJSON(q);
+
+                h = buildQuoteStep2Headers(cookie);
+                //组装报价参数
+                HtCrawlerQuoteRequest qr = new HtCrawlerQuoteRequest(q, mealid, limitAmount, "", personalInsuredJSON);
+                //报价第一次
+                decryptData = req.quote(qr, h);
+                //处理重复投保返回终保日期的
+                DuplQuoteResult duplQuoteResult = handleQuoteErrors(decryptData, q);
+                if (duplQuoteResult.dupl()) {
+                    qr = new HtCrawlerQuoteRequest(duplQuoteResult.q(), mealid, limitAmount, "", personalInsuredJSON);
+                    decryptData = req.quote(qr, h);
+                }
+
+                // 处理可能的录入的校验码有误
+                HtCrawlerQuoteResponse.DataDTO data = null;
+                try {
+                    data = resolveQuoteResponse(duplQuoteResult, decryptData, mealid,
+                            limitAmount, h, personalInsuredJSON);
+                } catch (Exception e) {
+                    decryptData = e.getMessage();
+                    // 检测modelcodexh不在平台车型范围内的异常
+                    if (decryptData.contains("errrorFlag") && decryptData.contains("carChange")) {
+                        modelCodeRetryCount++;
+                        log.warn("检测到modelcodexh[{}]不在平台车型范围内,开始第{}次重试获取新编码",
+                                currentModelCodexh, modelCodeRetryCount);
+
+                        // 提取新的modelcodexh
+                        Map<String, Object> map = MAPPER.readValue(decryptData, Map.class);
+                        List<Map<String, String>> list = (List)map.get("carModelList");
+                        String newModelCodexh = list.get(0).get("vehiclecode");
+
+                        currentModelCodexh = newModelCodexh;
+
+                        // 重新执行报价预处理流程
+                        AttributionInformationVo attr = q.getOrder().getAttributionInformationVo();
+                        currentPre = prepareQuote(attr, gsInfo, q.getCarInfoVo(),
+                                currentModelCodexh, cookie, quotationNo);
+                        log.info("已重新执行prepareQuote,新modelcodexh:{}", currentModelCodexh);
+
+                        // 跳过当前循环,重新执行报价流程
+                        continue;
+                    } else {
+                        throw new SystemException(decryptData);
+                    }
+                }
+                return new StepResult(data, personalInsuredJSON);
+
+            } catch (Exception e) {
+                modelCodeRetryCount++;
+                if (modelCodeRetryCount >= MAX_MODEL_CODE_RETRY) {
+                    throw new SystemException(decryptData);
+                }
+                // 重试前短暂休眠,避免高频请求
+                sleep(SHORT_DELAY_MS);
+            }
+        }
+        throw new SystemException("报价失败");
     }
 
     private String extractMealId(String body) {
@@ -518,13 +576,11 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
     }
 
     // ====================== 报价错误处理 ======================
-    private QuoteVo handleQuoteErrors(String decryptData, QuoteVo q) {
-        boolean hasError = decryptData.contains("erroeInfo") || decryptData.contains("errrorFlag");
-        if (!hasError) return q;
-        if (decryptData.contains("发生重复投保") && decryptData.contains("终保日期") && !decryptData.contains("重复投保的其它公司")) {
-            return handleDuplicateInsurance(decryptData, q);
+    private DuplQuoteResult handleQuoteErrors(String decryptData, QuoteVo q) {
+        if (decryptData.contains("发生重复投保") && decryptData.contains("终保日期") ) {//&& !decryptData.contains("重复投保的其它公司")
+            return new DuplQuoteResult(handleDuplicateInsurance(decryptData, q), true);
         }
-        return q;
+        return new DuplQuoteResult(q, false);
     }
 
     private QuoteVo handleDuplicateInsurance(String decryptData, QuoteVo q) {
@@ -546,9 +602,9 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         return q;
     }
 
-    private HtCrawlerQuoteResponse.DataDTO resolveQuoteResponse(QuoteVo q, String decryptData, String mealid, String limitAmount,
+    private HtCrawlerQuoteResponse.DataDTO resolveQuoteResponse(DuplQuoteResult duplQuoteResult, String decryptData, String mealid, String limitAmount,
                                                                 HttpHeaders h, String personalInsuredJSON) {
-        if (!decryptData.contains("录入的校验码有误") && decryptData.contains("erroeInfo")) {
+        if (!decryptData.contains("录入的校验码有误") && decryptData.contains("erroeInfo") && !duplQuoteResult.dupl()) {
             log.error("报价失败: {}", decryptData);
             throw new SystemException(ERR_PREFIX + convertResponseMsg(decryptData));
         }
@@ -561,19 +617,24 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
         for (int i = 0; i < MAX_RETRIES; i++) {
             String basePartJSON = buildBasePartJSON(list);
-            String response = req.quote(new HtCrawlerQuoteRequest(q, mealid, limitAmount, basePartJSON, personalInsuredJSON), h);
+            String response = req.quote(new HtCrawlerQuoteRequest(duplQuoteResult.q(), mealid, limitAmount, basePartJSON, personalInsuredJSON), h);
             log.info("resolveQuoteResponse attempt {}/{}: {}", i + 1, MAX_RETRIES, response);
 
-            HtCrawlerQuoteResponse.DataDTO result = JSONObject.parseObject(response, HtCrawlerQuoteResponse.DataDTO.class);
+            HtCrawlerQuoteResponse.DataDTO result = null;
+            try {
+                result = JSONObject.parseObject(response, HtCrawlerQuoteResponse.DataDTO.class);
+            } catch (Exception e) {
+                throw new RuntimeException(response);
+            }
             if (result != null && result.getApplicationdto() != null) {
                 return result;
             }
 
             // autocheckcode 可能不准确,重新获取 quote 刷新验证码
             if (response.contains("录入的校验码有误") && i < MAX_RETRIES - 1) {
-                log.warn("autocheckcode 校验失败,重新获取 quote 刷新验证码 (attempt {}/{})", i + 1, MAX_RETRIES);
+                log.warn("验证码 校验失败,重新获取 刷新验证码 (尝试 {}/{})", i + 1, MAX_RETRIES);
                 // 必须传空 basePart,接口才会返回新的 basePart 列表(含新 autocheckcode)
-                String fresh = req.quote(new HtCrawlerQuoteRequest(q, mealid, limitAmount, "", personalInsuredJSON), h);
+                String fresh = req.quote(new HtCrawlerQuoteRequest(duplQuoteResult.q(), mealid, limitAmount, "", personalInsuredJSON), h);
                 result = JSONObject.parseObject(fresh, HtCrawlerQuoteResponse.DataDTO.class);
                 if (result != null && result.getApplicationdto() != null) {
                     return result;
@@ -630,7 +691,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
     // ====================== 核保 ======================
     @Override
-    public UnderwritingResultsVo underwriting(UnderwritingVo vo) throws JsonProcessingException {
+    public UnderwritingResultsVo underwriting(UnderwritingVo vo) {
         AttributionInformationVo attr = vo.getOrder().getAttributionInformationVo();
         HtCrawlerConfigureParameters params = ConfigureParametersConversion.conversionEntity(
                 HtCrawlerConfigureParameters.class, attr.getConfigInfo());
@@ -642,7 +703,13 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
 
         HtCrawlerSubmitRequest submit = new HtCrawlerSubmitRequest(vo, params, order.getCiCategory(), order.getBiCategory());
         String res = req.submit2(submit, req.getHeaders());
-        Map<String, Object> map = MAPPER.readValue(res, Map.class);
+        log.info("提交核保结果: {}", res);
+        Map<String, Object> map = new HashMap<>();
+        try {
+            map = MAPPER.readValue(res, Map.class);
+        } catch (JsonProcessingException e) {
+            throw new SystemException("核保失败,未知异常");
+        }
 
         if (isUnderwritingFailed(order.getCiCategory(), map, res)) {
             throw new SystemException("核保失败-------" + convertResponseMsg(map.get("MSG").toString()));
@@ -781,7 +848,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
                     attr.getUsername(), params.getComCode(), convertCookieToMap(req.getCookie()),
                     quotationRelaNo + "_", token, updata);
             cache.cacheYingXiangUploaded(quotationRelaNo);
-        } catch (Exception e) { throw new RuntimeException(e); }
+        } catch (Exception e) { throw new SystemException(e.getMessage()); }
     }
 
     private Map<String, Object> fetchUploadInitParam(String token) {
@@ -1087,7 +1154,7 @@ public class HuaTaiCrawlerRequestImpl implements HuaTaiCrawlerRequest {
         for (int i = 0; i < 16; i++) kb.append(alphabet.charAt(r.nextInt(alphabet.length())));
         String key = kb.toString();
         return Map.of("sourceCode", 8, "accessToken", CryptoUtil.rsaEncrypt(key),
-                "data", CryptoUtil.aesEncrypt(MAPPER.writeValueAsString(x), key), "insuredCode", "");
+                "data", Objects.requireNonNull(CryptoUtil.aesEncrypt(MAPPER.writeValueAsString(x), key)), "insuredCode", "");
     }
 
     public Map<String, Object> decrypt(Map<String, Object> x) throws Exception {

+ 1 - 1
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/aop/FactorMatch.java

@@ -183,7 +183,7 @@ public class FactorMatch {
                 log.info("承保规则校验-传入值为null: attrCode={}, useFlag=false", ptlAgreementProductCostsAttrLink.getAttrCode());
             }
 
-            if (useFlag) {
+            if (!useFlag) {
                 log.info("承保规则校验-条件命中! attrCode={}, 传入值={}, 规则min={}, max={}, operator={}",
                         ptlAgreementProductCostsAttrLink.getAttrCode(), ruleAttrMappingVo.getValue(),
                         ptlAgreementProductCostsAttrLink.getMin(), ptlAgreementProductCostsAttrLink.getMax(),

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

@@ -84,7 +84,7 @@ public class QuoteVerificationAspect {
             // 获取订单险种信息
             List<InsOrdersKind> kindList = insOrdersKindService.list(new LambdaQueryWrapper<InsOrdersKind>().eq(InsOrdersKind::getOrderNo, insOrders.getId()));
             // 获取订单驾意险信息
-            InsOrdersAccidentalDriving insOrdersAccidentalDriving = insOrdersAccidentalDrivingService.getById(insOrders.getId());
+            InsOrdersAccidentalDriving insOrdersAccidentalDriving = insOrdersAccidentalDrivingService.getByOrderId(insOrders.getId());
             // 获取订单其他信息
             InsOrdersOther insOrdersOther = insOrdersOtherService.getById(insOrders.getId());
             // 获取协议信息
@@ -360,20 +360,24 @@ public class QuoteVerificationAspect {
 
         // 计算应收金额(保费 * 税率因子 * (入口比例 + 加投比例 + 跟单比例))
         BigDecimal taxablePremium = premium.multiply(taxFactor);
-        BigDecimal receivable = taxablePremium.multiply(inletPercent)
-                .add(taxablePremium.multiply(addThrowPercent))
-//                .add(taxablePremium.multiply(followRatio))
-                .setScale(5, RoundingMode.HALF_EVEN);
 
         // 计算应付金额(保费 * 出口比例,保留2位小数)
         BigDecimal payable = premium.multiply(exportPercent.setScale(5, RoundingMode.DOWN));
 
         // 计算各类费用并设置到订单对象
-        setReceivable.accept(insFeeOrders, receivable);
         setPayable.accept(insFeeOrders, payable);
-        setCommission.accept(insFeeOrders, taxablePremium.multiply(commissionRatio).setScale(5, RoundingMode.HALF_EVEN));
-        setFollow.accept(insFeeOrders, taxablePremium.multiply(followRatio).setScale(5, RoundingMode.HALF_EVEN));
+        BigDecimal commission = taxablePremium.multiply(commissionRatio).setScale(5, RoundingMode.HALF_EVEN);
+        BigDecimal follow = taxablePremium.multiply(followRatio).setScale(5, RoundingMode.HALF_EVEN);
+        setCommission.accept(insFeeOrders, commission);
+        setFollow.accept(insFeeOrders, commission);
         setAdditional.accept(insFeeOrders, taxablePremium.multiply(addThrowPercent).setScale(5, RoundingMode.HALF_EVEN));
+
+        BigDecimal receivable = taxablePremium.add(commission)
+                .add(follow)
+//                .add(taxablePremium.multiply(addThrowPercent))
+//                .add(taxablePremium.multiply(followRatio))
+                .setScale(5, RoundingMode.HALF_EVEN);
+        setReceivable.accept(insFeeOrders, receivable);
     }
 
     /**

+ 1 - 2
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/controller/InsFeeAuditController.java

@@ -119,8 +119,7 @@ public class InsFeeAuditController {
     @Operation(summary = "更新签单时间")
     public HttpResult<String> updateSigningTime(@RequestParam("orderNo") String orderNo){
         String userName = baseController.getUserName();
-        insFeeAuditService.updateSigningTime(orderNo, userName);
-        return HttpResult.ok();
+        return insFeeAuditService.updateSigningTime(orderNo, userName);
     }
 
     /**

+ 3 - 1
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/InsFeeAuditService.java

@@ -2,6 +2,7 @@ package com.jzg.quotation.summary.service;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
+import com.jzg.commons.core.page.HttpResult;
 import com.jzg.commons.entity.dto.CostCalculateRespVO;
 import com.jzg.commons.entity.dto.FeeAuditParam;
 import com.jzg.commons.entity.dto.VerifyOrderParam;
@@ -70,8 +71,9 @@ public interface InsFeeAuditService extends IService<InsFeeAudit> {
      *
      * @param orderNo  订单号(jzg系统内部的车险订单号)
      * @param userName 操作用户
+     * @return HttpResult<String>
      */
-    void updateSigningTime(String orderNo, String userName);
+    HttpResult<String> updateSigningTime(String orderNo, String userName);
 
     /**
      * 审核订单

+ 32 - 13
tenant/insurance/quotation-summary/src/main/java/com/jzg/quotation/summary/service/impl/InsFeeAuditServiceImpl.java

@@ -17,10 +17,7 @@ import com.jzg.commons.entity.dto.VerifyOrderParam;
 import com.jzg.commons.entity.orders.po.*;
 import com.jzg.commons.entity.orders.vo.AuditCostInfoVo;
 import com.jzg.commons.entity.orders.vo.FeeAuditVo;
-import com.jzg.commons.entity.po.InsFeeOrders;
-import com.jzg.commons.entity.po.SysUser;
-import com.jzg.commons.entity.po.SysUserAccount;
-import com.jzg.commons.entity.po.SysUserJzgInfo;
+import com.jzg.commons.entity.po.*;
 import com.jzg.commons.entity.quote.vo.aggregated.QuoteEchoVo;
 import com.jzg.commons.entity.rights.po.InsOrderRights;
 import com.jzg.commons.entity.rights.po.InsOrderRightsPackage;
@@ -333,14 +330,14 @@ public class InsFeeAuditServiceImpl extends ServiceImpl<InsFeeAuditMapper, InsFe
      *
      * @param orderNo  订单号(jzg系统内部的车险订单号)
      * @param userName 操作用户
+     * @return HttpResult<String>
      */
     @Override
-    public void updateSigningTime(String orderNo, String userName) {
+    public HttpResult<String> updateSigningTime(String orderNo, String userName) {
         log.info("用户[{}]更新订单[{}]的签单时间",userName,orderNo);
-        InsOrders orders = insOrdersService.getById(orderNo);
+        InsOrders order = insOrdersService.getById(orderNo);
         // 查询电子保单路径
         InsOrdersPdfUrl pdfUrl = ordersPdfUrlService.getById(orderNo);
-        log.info("订单[{}]没有生成电子表单",orderNo);
         AssertionUtils.isFail(Objects.isNull(pdfUrl), "未生成电子保单!");
         String url = null;
         int type = 0;
@@ -355,11 +352,23 @@ public class InsFeeAuditServiceImpl extends ServiceImpl<InsFeeAuditMapper, InsFe
 
         // 转为本地路径
         String localUrl = PDFUtil.trafferHttpToLocalPath(url);
+        log.info("url[{}]转换为本地路径为[{}]",url,localUrl);
+
+        String companyId = order.getCompanyId();
+        HttpResult<EsmInsCompany> companyHttpResult = esmInsCompanyClient.findById(companyId);
+        EsmInsCompany insCompany = companyHttpResult.getData();
+        if(insCompany == null){
+            return HttpResult.error("订单关联的保险公司不存在");
+        }
         //通过保险公司配置读取截取的前缀和后缀
         LambdaQueryWrapper<InsOrderReadPdf> qws = new LambdaQueryWrapper<>();
-        qws.eq(InsOrderReadPdf::getCompanyId, orders.getCompanyId());
+        qws.eq(InsOrderReadPdf::getCompanyId, insCompany.getCompanyCode());
         qws.eq(InsOrderReadPdf::getType, type);
         List<InsOrderReadPdf> list = ordersReadPdfService.list(qws);
+        if(CollUtil.isEmpty(list)){
+            log.error("保险公司发票读取规则未配置. companyId=[{}]",order.getCompanyId());
+            return HttpResult.error("保险公司发票读取规则未配置");
+        }
 
         HashMap<String, LocalDateTime> map = new HashMap<>();
         String pdfText = PDFUtil.getPdfText(localUrl);
@@ -370,6 +379,7 @@ public class InsFeeAuditServiceImpl extends ServiceImpl<InsFeeAuditMapper, InsFe
                     map.put(insOrderReadPdf.getOrderMsg(), pdfContentDate);
                 }
             }
+            log.info("ins_order_read_pdf配置转换得到的map是[{}]", JSONUtil.toJsonStr(map));
         }
 
         //刪除临时文件
@@ -378,13 +388,22 @@ public class InsFeeAuditServiceImpl extends ServiceImpl<InsFeeAuditMapper, InsFe
             file.delete();
         }
         if (map.get("payDate") != null) {
-            orders.setPayTime(map.get("payDate"));
+            order.setPayTime(map.get("payDate"));
         }
         if (map.get("signingTime") != null) {
-            orders.setSigningTime(map.get("signingTime"));
-        }
-        //更新时间表
-        insOrdersService.updateById(orders);
+            order.setSigningTime(map.get("signingTime"));
+        }
+        //更新时间表 modify by lipf, 只更新指定的字段 2026年6月15日16:41:32
+        // insOrdersService.updateById(order);
+        LambdaUpdateWrapper<InsOrders> updateWrapper = new LambdaUpdateWrapper<>();
+        updateWrapper.eq(InsOrders::getId,order.getId());
+        updateWrapper.set(InsOrders::getPayTime,order.getPayTime());
+        updateWrapper.set(InsOrders::getSigningTime,order.getSigningTime());
+        updateWrapper.set(InsOrders::getUpdateTime, LocalDateTime.now());
+        updateWrapper.set(InsOrders::getUpdateBy, baseController.getUserName());
+
+        insOrdersService.update(null, updateWrapper);
+        return HttpResult.ok("更新成功");
     }
 
     /**

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

@@ -120,6 +120,7 @@
         <id property="orderNo" column="order_no" jdbcType="VARCHAR"/>
         <result property="jqScore" column="jq_score" jdbcType="VARCHAR"/>
         <result property="score" column="score" jdbcType="VARCHAR"/>
+        <result property="scoreJson" column="score_json" jdbcType="VARCHAR"     typeHandler="com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler"/>
         <result property="syScore" column="sy_score" jdbcType="VARCHAR"/>
         <result property="lossRation" column="loss_ration" jdbcType="VARCHAR"/>
         <result property="jqLossRation" column="jq_loss_ration" jdbcType="VARCHAR"/>
@@ -723,6 +724,7 @@
     <select id="selectOrdersOtherByOrderNo" resultMap="InsOrdersOtherMap">
         SELECT ot.order_no,
         ot.score,
+        ot.score_json,
         ot.jq_score,
         ot.sy_score,
         ot.loss_ration,

+ 6 - 0
tenant/insurance/quotation-taishan/src/main/java/com/jzg/quotation/taishan/crawler/build/TaiShanCrawlerQuoteRequestVoBuild.java

@@ -254,6 +254,12 @@ public class TaiShanCrawlerQuoteRequestVoBuild {
                 continue;
             }
 
+            // 跳过保额为空的险种,避免数值解析时空指针异常
+            if (infoVo.getAmount() == null) {
+                log.info("泰山报价-跳过保额为空的险种: platformKindCode={}, kindCode={}", kindCode, infoVo.getKindCode());
+                continue;
+            }
+
             TaiShanCrawlerQuotationRequest.BusinessInsurance.ItemKind itemKind;
 
             // 获取对应险种的模板默认值(与Python insurance_Map对应)

+ 6 - 0
tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/build/ZmCrawlerQuoteResultVoBuild.java

@@ -10,6 +10,7 @@ import com.jzg.commons.entity.quote.vo.aggregated.QuoteResultsVo;
 import com.jzg.commons.entity.quote.vo.QuoteVo;
 import com.jzg.quotation.zhongmei.crawler.entity.Response.ZmCrawlerQuotedPriceResponse;
 
+import java.math.BigDecimal;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
@@ -121,6 +122,11 @@ public final class ZmCrawlerQuoteResultVoBuild {
             });
             quoteResultsVo.getCosts().setNiPremium(String.valueOf(niPremium));
         }
+        BigDecimal jqPremium = quoteResultsVo.getCosts().getCiPremium() == null || quoteResultsVo.getCosts().getCiPremium().isEmpty() ? new BigDecimal("0.00") : new BigDecimal(quoteResultsVo.getCosts().getCiPremium());
+        BigDecimal syPremium = quoteResultsVo.getCosts().getBiPremium() == null || quoteResultsVo.getCosts().getBiPremium().isEmpty() ? new BigDecimal("0.00") : new BigDecimal(quoteResultsVo.getCosts().getBiPremium());
+        BigDecimal niPremium = quoteResultsVo.getCosts().getNiPremium() == null || quoteResultsVo.getCosts().getNiPremium().isEmpty() ? new BigDecimal("0.00") : new BigDecimal(quoteResultsVo.getCosts().getNiPremium());
+        BigDecimal vVTax = quoteResultsVo.getCosts().getVvTax() == null || quoteResultsVo.getCosts().getVvTax().isEmpty() ? new BigDecimal("0.00") : new BigDecimal(quoteResultsVo.getCosts().getVvTax());
+        quoteResultsVo.getCosts().setSumPremium(String.valueOf(jqPremium.add(syPremium).add(niPremium).add(vVTax)));
     }
 
     public static QuoteResultsVo.ScoreVo buildScore(ZmCrawlerQuotedPriceResponse quotedPriceResponse) {

+ 19 - 5
tenant/insurance/quotation-zhongmei/src/main/java/com/jzg/quotation/zhongmei/crawler/service/Impl/ZhongMeiCrawlerRequestImpl.java

@@ -300,17 +300,31 @@ public class ZhongMeiCrawlerRequestImpl implements ZhongMeiCrawlerRequest {
     }
 
     private static final Pattern DUPLICATE_START_TIME_PATTERN = Pattern.compile("起保日期:(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2})");
+    private static final Pattern DUPLICATE_END_TIME_PATTERN_SY = Pattern.compile("终保日期:(\\d{4}-\\d{2}-\\d{2})");
     private String extractDuplicateStartTime(String answer) {
-        Matcher matcher = DUPLICATE_START_TIME_PATTERN.matcher(answer);
-        if (matcher.find()) {
+        // 优先匹配终保日期
+        Matcher matcherEndTime = DUPLICATE_END_TIME_PATTERN_SY.matcher(answer);
+        if (matcherEndTime.find()) {
             // 取出匹配到的时间
-            String timeStr = matcher.group(1);
+            String timeStr = matcherEndTime.group(1) + " 00:00:00";
             DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
 
             LocalDateTime dateTime;
             LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
-            LocalDateTime afterAdd = localDateTime.plusYears(1);
-            return afterAdd.format(formatter);
+            return localDateTime.format(formatter);
+        } else {
+            // 匹配不到终保日期去匹配起保日期
+            Matcher matcher = DUPLICATE_START_TIME_PATTERN.matcher(answer);
+            if (matcher.find()) {
+                // 取出匹配到的时间
+                String timeStr = matcher.group(1);
+                DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+                LocalDateTime dateTime;
+                LocalDateTime localDateTime = LocalDateTime.parse(timeStr, formatter);
+                LocalDateTime afterAdd = localDateTime.plusYears(1);
+                return afterAdd.format(formatter);
+            }
         }
         return null;
     }

+ 5 - 1
tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserAccountServiceImpl.java

@@ -13,6 +13,7 @@ import com.jzg.commons.entity.vo.IncomeAndExpenseVo;
 import com.jzg.commons.entity.vo.SysUserAccountVo;
 import com.jzg.commons.entity.vo.SysUserWalletVo;
 import com.jzg.commons.entity.vo.UserAccountStatisVo;
+import com.jzg.commons.util.BigDecimalUtil;
 import com.jzg.organization.client.QuoteClient;
 import com.jzg.organization.mapper.SysUserAccountMapper;
 import com.jzg.organization.service.*;
@@ -128,12 +129,15 @@ public class SysUserAccountServiceImpl extends ServiceImpl<SysUserAccountMapper,
         sysUserWalletVo.setWalletBalance(sysUserAccount.getSumAmountUnpaid());
         List<SysAmountAuditing> sysAmountAuditingList = sysAmountAuditingService
                 .list(new LambdaQueryWrapper<SysAmountAuditing>()
-                        .eq(SysAmountAuditing::getUserId, sysUser.getId()).eq(SysAmountAuditing::getAuditingStatus, 0));
+                        .eq(SysAmountAuditing::getUserId, sysUser.getId())
+                        .eq(SysAmountAuditing::getSystemCode,sysUserJzgInfo.getSystemCode())
+                        .eq(SysAmountAuditing::getAuditingStatus, 0));
         BigDecimal withdrawingBalance = BigDecimal.ZERO;
         for (SysAmountAuditing sysAmountAuditing : sysAmountAuditingList) {
             withdrawingBalance = withdrawingBalance.add(sysAmountAuditing.getAmount());
         }
         sysUserWalletVo.setWithdrawingBalance(withdrawingBalance);
+        sysUserWalletVo.setWalletBalanceRemoveWithdraw(BigDecimalUtil.subtract(sysUserAccount.getSumAmountUnpaid(),withdrawingBalance));
         sysUserWalletVo.setIntegral(sysUserAccount.getIntegral());
         InsFeeOrdersParam param = new InsFeeOrdersParam();
         param.setUserId(sysUserJzgInfo.getUserId());

+ 5 - 1
tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserJzgAppServiceImpl.java

@@ -96,6 +96,9 @@ public class SysUserJzgAppServiceImpl extends ServiceImpl<SysUserJzgAppMapper, S
     @Override
     public UserInfoVo userInfoGet(String userName) {
         String systemCode = baseController.getSystemCode();
+        if (StrUtil.isBlank(userName) || StrUtil.isBlank(systemCode)) {
+            throw new SystemException("用户名、租户编码不能为空");
+        }
         log.info("获取用户信息:systemCode=[{}],userName=[{}]", systemCode, userName);
         UserInfoVo infoVo = sysUserJzgAppMapper.userInfoGet(userName,systemCode);
         infoVo.setHeadSculpture(MinioUtils.getPresignedObjectUrl("jzg", infoVo.getHeadSculpture()));
@@ -298,7 +301,6 @@ public class SysUserJzgAppServiceImpl extends ServiceImpl<SysUserJzgAppMapper, S
                 }
                 sysUserId = user.getId();
             }
-            // add by lipf 2026年6月9日15:16:39 向 sys_user_jzg_info_salesman 表中插入一条记录 
             // add by lipf 2026年6月9日15:16:39 向 sys_user_jzg_info_salesman 表中插入一条记录
             Map<String, String> stringStringMap = sysUserJzgInfoSalesmanService.queryAllTypeAttrMap();
             String typeAttrs = stringStringMap.getOrDefault(yqrInfo.getId(), "");
@@ -595,6 +597,7 @@ public class SysUserJzgAppServiceImpl extends ServiceImpl<SysUserJzgAppMapper, S
      * @param appUser
      */
     private void addRelationPerson(SysUserJzgApp sourceAppUser, SysUserJzgApp appUser) {
+        log.info("添加推荐人关系:sourceAppUser=[{}], appUser=[{}]", JSONUtil.toJsonStr(sourceAppUser), JSONUtil.toJsonStr(appUser));
         // 查询推荐人关系
         LambdaQueryWrapper<SysRelationPerson> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(SysRelationPerson::getSuggestId, sourceAppUser.getId())
@@ -605,6 +608,7 @@ public class SysUserJzgAppServiceImpl extends ServiceImpl<SysUserJzgAppMapper, S
         relationPerson.setSuggestId(appUser.getId());
         relationPerson.setSource(0);
         relationPerson.setType(0);
+        relationPerson.setSystemCode(sourceAppUser.getSystemCode());
         if (Objects.isNull(person)) {
             relationPerson.setSuggestLevel(1);
         } else {

+ 10 - 0
tenant/organization/src/main/java/com/jzg/organization/service/impl/SysUserServiceImpl.java

@@ -1,5 +1,6 @@
 package com.jzg.organization.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.StrUtil;
@@ -79,6 +80,9 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
     @Resource
     private SysUserJzgInfoMapper sysUserJzgInfoMapper;
 
+    @Resource
+    private SysUserJzgAppMapper sysUserJzgAppMapper;
+
     @Autowired
     private SysAreaService sysAreaService;
 
@@ -564,6 +568,12 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
         sysUserJzgInfoSalesman1.setTypeAttr(sysUserVo.getTypeAttr());  // 业务员类型
         sysUserJzgInfoSalesmanList.add(sysUserJzgInfoSalesman1);
 
+        // app内也要新增一条数据
+        SysUserJzgApp sysUserJzgApp = BeanUtil.copyProperties(sysUserJzgInfo, SysUserJzgApp.class);
+        sysUserJzgApp.setId(IdGenerate.nextId());
+        sysUserJzgApp.setSystemCode(baseController.getAppSystemCode());
+
+        sysUserJzgAppMapper.insert(sysUserJzgApp);
         sysUserJzgInfoSalesmanService.saveBatch(sysUserJzgInfoSalesmanList);
     }
 

+ 2 - 0
tenant/organization/src/main/resources/mapper/SysAmountAuditingMapper.xml

@@ -162,6 +162,8 @@
         saa.bank_card_num AS bankCardNum,
         saa.bank_name AS bankName,
         saa.bank_num AS bankNum,
+        saa.user_id,
+        saa.system_code,
         saa.auditing_time
         FROM sys_amount_auditing saa
         LEFT JOIN sys_user_jzg_info suji on saa.user_id = suji.user_id AND suji.is_delete = 0