Browse Source

feat:添加入驻营业执照相关信息存入

penghao 1 week ago
parent
commit
a6ab8c9809

+ 30 - 4
alien-entity/src/main/java/shop/alien/entity/store/StoreInfo.java

@@ -221,12 +221,12 @@ public class StoreInfo {
     @TableField("food_licence_reason")
     private String foodLicenceReason;
 
-    @ApiModelProperty(value = "经营许可证到期时间")
+    @ApiModelProperty(value = "食品经营许可证到期时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("food_licence_expiration_time")
     private Date  foodLicenceExpirationTime;
 
-    @ApiModelProperty(value = "变更经营许可证提交时间")
+    @ApiModelProperty(value = "变更食品经营许可证提交时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("update_food_licence_time")
     private Date  updateFoodLicenceTime;
@@ -244,8 +244,6 @@ public class StoreInfo {
     @TableField("business_classify_name")
     private String businessClassifyName;
 
-
-
     @ApiModelProperty(value = "是否提供餐食")
     @TableField("meals_flag")
     private Integer  mealsFlag;
@@ -267,4 +265,32 @@ public class StoreInfo {
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     @TableField("update_entertainment_licence_time")
     private Date updateEntertainmentLicenceTime;
+
+    @ApiModelProperty(value = "续签合同到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("renew_contract_expiration_time")
+    private Date renewContractExpirationTime;
+
+    @ApiModelProperty(value = "营业执照状态 字典 foodLicenceStatus")
+    @TableField("business_license_status")
+    private Integer businessLicenseStatus;
+
+    @ApiModelProperty(value = "营业执照失败原因")
+    @TableField("business_license_reason")
+    private String businessLicenseReason;
+
+    @ApiModelProperty(value = "营业执照到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("business_license_expiration_time")
+    private Date businessLicenseExpirationTime;
+
+    @ApiModelProperty(value = "变更营业执照提交时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("update_business_license_time")
+    private Date updateBusinessLicenseTime;
+
+    @ApiModelProperty(value = "审核时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @TableField("review_date")
+    private Date reviewDate;
 }

+ 12 - 3
alien-entity/src/main/java/shop/alien/entity/store/dto/StoreInfoDto.java

@@ -213,8 +213,17 @@ public class StoreInfoDto {
     @DateTimeFormat(pattern = "yyyy-MM-dd")
     private Date entertainmentLicenceExpirationTime;
 
-//    @ApiModelProperty(value = "分类id(词典表 键为 business_classify)(多个ID用逗号拼接)")
-//    @JsonDeserialize(using = StringToListDeserializer.class)
-//    private List<String> businessClassify;
+    @ApiModelProperty(value = "营业执照图片URL")
+    private String businessLicenseUrl;
 
+    @ApiModelProperty(value = "营业执照状态 字典 foodLicenceStatus")
+    private Integer businessLicenseStatus;
+
+    @ApiModelProperty(value = "营业执照失败原因")
+    private String businessLicenseReason;
+
+    @ApiModelProperty(value = "营业执照到期时间")
+    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd")
+    private Date businessLicenseExpirationTime;
 }

+ 132 - 24
alien-store/src/main/java/shop/alien/store/service/impl/StoreInfoServiceImpl.java

@@ -13,7 +13,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.BeanUtils;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.data.geo.Point;
 import org.springframework.http.*;
@@ -37,7 +36,6 @@ import shop.alien.entity.storePlatform.StoreOperationalActivity;
 import shop.alien.mapper.*;
 import shop.alien.mapper.storePlantform.StoreLicenseHistoryMapper;
 import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
-import shop.alien.mapper.storePlantform.StoreOperationalActivityMapper;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.config.GaoDeMapUtil;
 import shop.alien.store.config.WebSocketProcess;
@@ -46,7 +44,6 @@ import shop.alien.store.util.CommonConstant;
 import shop.alien.store.util.FileUploadUtil;
 import shop.alien.store.util.GroupConstant;
 import shop.alien.store.util.ai.AiAuthTokenUtil;
-import shop.alien.store.util.ali.AliApi;
 import shop.alien.util.ali.AliOSSUtil;
 import shop.alien.util.common.DistanceUtil;
 import shop.alien.util.common.constant.CouponStatusEnum;
@@ -914,10 +911,13 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 storeInfo.setFoodLicenceStatus(2);
                 storeInfo.setUpdateFoodLicenceTime(new Date());
             }
-        } else if (storeInfoDto.getFoodLicenceExpirationTime() != null) {
-            // 没有食品经营许可证URL,但有传入到期时间时直接使用
-            storeInfo.setFoodLicenceExpirationTime(storeInfoDto.getFoodLicenceExpirationTime());
-            log.info("无食品经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getFoodLicenceExpirationTime());
+        } else {
+            // 没有食品经营许可证URL,初始化状态为"未提交"(字典值0)
+            storeInfo.setFoodLicenceStatus(0);
+            if (storeInfoDto.getFoodLicenceExpirationTime() != null) {
+                storeInfo.setFoodLicenceExpirationTime(storeInfoDto.getFoodLicenceExpirationTime());
+                log.info("无食品经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getFoodLicenceExpirationTime());
+            }
         }
 
         // 处理娱乐经营许可证OCR数据(复用营业执照OCR类型,数据库中ocr_type存的是BUSINESS_LICENSE)
@@ -965,13 +965,78 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 storeInfo.setEntertainmentLicenceStatus(2);
                 storeInfo.setUpdateEntertainmentLicenceTime(new Date());
             }
-        } else if (storeInfoDto.getEntertainmentLicenceExpirationTime() != null) {
-            // 没有娱乐经营许可证URL,但有传入到期时间时直接使用
-            storeInfo.setEntertainmentLicenceExpirationTime(storeInfoDto.getEntertainmentLicenceExpirationTime());
-            log.info("无娱乐经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getEntertainmentLicenceExpirationTime());
+        } else {
+            // 没有娱乐经营许可证URL,初始化状态为"未提交"(字典值0)
+            storeInfo.setEntertainmentLicenceStatus(0);
+            if (storeInfoDto.getEntertainmentLicenceExpirationTime() != null) {
+                storeInfo.setEntertainmentLicenceExpirationTime(storeInfoDto.getEntertainmentLicenceExpirationTime());
+                log.info("无娱乐经营许可证URL,使用DTO中的到期时间:{}", storeInfoDto.getEntertainmentLicenceExpirationTime());
+            }
+        }
+
+        // 处理营业执照OCR数据(复用营业执照OCR类型,数据库中ocr_type存的是BUSINESS_LICENSE)
+        // 营业执照使用 businessLicenseAddress 列表中的第一个URL查询OCR
+        String businessLicenseOcrUrl = null;
+        if (!CollectionUtils.isEmpty(storeInfoDto.getBusinessLicenseAddress())) {
+            businessLicenseOcrUrl = storeInfoDto.getBusinessLicenseAddress().get(0);
+        } else if (StringUtils.isNotEmpty(storeInfoDto.getBusinessLicenseUrl())) {
+            businessLicenseOcrUrl = storeInfoDto.getBusinessLicenseUrl();
+        }
+
+        if (StringUtils.isNotEmpty(businessLicenseOcrUrl)) {
+            // 查询营业执照OCR识别记录
+            OcrImageUpload businessLicenseOcr = ocrImageUploadMapper.selectOne(
+                    new LambdaQueryWrapper<OcrImageUpload>()
+                            .eq(OcrImageUpload::getImageUrl, businessLicenseOcrUrl)
+                            .eq(OcrImageUpload::getOcrType, OcrTypeEnum.BUSINESS_LICENSE.getCode())
+                            .orderByDesc(OcrImageUpload::getCreateTime)
+                            .last("limit 1")
+            );
+            if (businessLicenseOcr != null && StringUtils.isNotEmpty(businessLicenseOcr.getOcrResult())) {
+                try {
+                    com.alibaba.fastjson2.JSONObject ocrResult = com.alibaba.fastjson2.JSONObject.parseObject(businessLicenseOcr.getOcrResult());
+                    // 营业执照OCR字段:validToDate(优先,格式"20241217")、validPeriod(格式"2020年09月04日至2022年09月03日")
+                    Date expirationTime = parseBusinessLicenseExpirationDate(ocrResult);
+                    if (expirationTime != null) {
+                        storeInfo.setBusinessLicenseExpirationTime(expirationTime);
+                    } else if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                        // OCR解析结果为空时,使用DTO中传入的值
+                        storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                        log.info("使用DTO中的营业执照到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+                    }
+                    // 设置营业执照状态为"待审核"(字典值2)
+                    storeInfo.setBusinessLicenseStatus(2);
+                    storeInfo.setUpdateBusinessLicenseTime(new Date());
+                    log.info("营业执照OCR数据解析成功,到期时间:{}", storeInfo.getBusinessLicenseExpirationTime());
+                } catch (Exception e) {
+                    log.error("解析营业执照OCR数据失败", e);
+                    // 解析失败时使用DTO中传入的值
+                    if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                        storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                        log.info("OCR解析失败,使用DTO中的营业执照到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+                    }
+                    storeInfo.setBusinessLicenseStatus(2);
+                    storeInfo.setUpdateBusinessLicenseTime(new Date());
+                }
+            } else {
+                // 没有OCR记录时,使用DTO中传入的值
+                if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                    storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                    log.info("无OCR记录,使用DTO中的营业执照到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+                }
+                storeInfo.setBusinessLicenseStatus(2);
+                storeInfo.setUpdateBusinessLicenseTime(new Date());
+            }
+        } else {
+            // 没有营业执照URL,初始化状态为"未提交"(字典值0)
+            storeInfo.setBusinessLicenseStatus(0);
+            if (storeInfoDto.getBusinessLicenseExpirationTime() != null) {
+                storeInfo.setBusinessLicenseExpirationTime(storeInfoDto.getBusinessLicenseExpirationTime());
+                log.info("无营业执照URL,使用DTO中的到期时间:{}", storeInfoDto.getBusinessLicenseExpirationTime());
+            }
         }
 
-        // 计算并设置到期时间为三个过期时间的最小值
+        // 计算并设置到期时间为个过期时间的最小值
         List<Date> expirationTimeList = new ArrayList<>();
         // 收集所有非空的过期时间
         if (storeInfoDto.getExpirationTime() != null) {
@@ -983,11 +1048,14 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         if (storeInfo.getEntertainmentLicenceExpirationTime() != null) {
             expirationTimeList.add(storeInfo.getEntertainmentLicenceExpirationTime());
         }
+        if (storeInfo.getBusinessLicenseExpirationTime() != null) {
+            expirationTimeList.add(storeInfo.getBusinessLicenseExpirationTime());
+        }
         // 取最小值设置为门店到期时间
         if (!expirationTimeList.isEmpty()) {
             Date minExpirationTime = Collections.min(expirationTimeList);
             storeInfo.setExpirationTime(minExpirationTime);
-            log.info("设置门店到期时间为个过期时间的最小值:{}", minExpirationTime);
+            log.info("设置门店到期时间为个过期时间的最小值:{}", minExpirationTime);
         }
 
         storeInfoMapper.insert(storeInfo);
@@ -1336,8 +1404,12 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
 
     @Override
     public List<StoreDictionaryVo> getBusinessSectionTypes(String parentId) {
-        StoreDictionary businessSection = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getTypeName, "business_section").eq(StoreDictionary::getDictId, parentId));
-        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>().eq(StoreDictionary::getParentId, businessSection.getId()));
+        StoreDictionary businessSection = storeDictionaryMapper.selectOne(new LambdaQueryWrapper<StoreDictionary>()
+                .eq(StoreDictionary::getTypeName, "business_section")
+                .eq(StoreDictionary::getDictId, parentId)
+                .isNull(StoreDictionary::getParentId));
+        List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(new LambdaQueryWrapper<StoreDictionary>()
+                .eq(StoreDictionary::getParentId, businessSection.getId()));
         List<StoreDictionaryVo> voList = new ArrayList<>();
         for (StoreDictionary storeDictionary : storeDictionaries) {
             StoreDictionaryVo vo = new StoreDictionaryVo();
@@ -1950,7 +2022,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             List<String> storeIds = Arrays.asList(storeId.split(","));
             if (StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime)) {
                 List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().in(StoreBusinessInfo::getStoreId, storeIds));
-                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, java.time.format.SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
+                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
                 List<StoreBusinessInfo> list = storeBusinessInfos.stream().filter(item -> {
                     // 商家开门时间
                     LocalTime timeStart = LocalTime.parse(item.getEndTime(), formatter);
@@ -2030,7 +2102,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             List<String> storeIds = Arrays.asList(storeId.split(","));
             if (StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime)) {
                 List<StoreBusinessInfo> storeBusinessInfos = storeBusinessInfoMapper.selectList(new LambdaQueryWrapper<StoreBusinessInfo>().in(StoreBusinessInfo::getStoreId, storeIds));
-                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, java.time.format.SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
+                DateTimeFormatter formatter = new DateTimeFormatterBuilder().appendValue(ChronoField.HOUR_OF_DAY, 1, 2, SignStyle.NOT_NEGATIVE).appendLiteral(':').appendValue(ChronoField.MINUTE_OF_HOUR, 2).toFormatter();
                 List<StoreBusinessInfo> list = storeBusinessInfos.stream().filter(item -> {
                     // 商家开门时间
                     LocalTime timeStart = LocalTime.parse(item.getEndTime(), formatter);
@@ -2973,17 +3045,17 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                 .eq(StoreOperationalActivity::getDeleteFlag, 0)
                 .eq(StoreOperationalActivity::getStatus, 5);
         List<StoreOperationalActivity> activities = storeOperationalActivityMapper.selectList(activityWrapper);
-        
+
         // 如果没有活动,返回空列表
         if (CollectionUtils.isEmpty(activities)) {
             return new ArrayList<>();
         }
-        
+
         // 获取活动ID列表
         List<Integer> activityIds = activities.stream()
                 .map(StoreOperationalActivity::getId)
                 .collect(Collectors.toList());
-        
+
         // 查询与活动关联的图片
         LambdaQueryWrapper<StoreImg> queryWrapper = new LambdaQueryWrapper<StoreImg>()
                 .eq(StoreImg::getStoreId, Integer.parseInt(storeId))
@@ -3110,9 +3182,9 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             // KTV=3、洗浴汗蒸=4、按摩足浴=5,酒吧的数值未知(由调用方传入)
             queryWrapper.eq("a.business_section", businessType);
         } else {
-            // 如果没有指定businessType,则查询所有种类型的店铺
+            // 如果没有指定businessType,则查询所有种类型的店铺
             // 需要查询字典表获取所有四种类型的dictId
-            List<String> storeTypeNames = Arrays.asList("丽人美发", "运动健身");
+            List<String> storeTypeNames = Arrays.asList("酒吧", "KTV", "洗浴汗蒸", "按摩足疗");
             List<StoreDictionary> storeDictionaries = storeDictionaryMapper.selectList(
                     new LambdaQueryWrapper<StoreDictionary>()
                             .eq(StoreDictionary::getTypeName, "business_section")
@@ -3218,7 +3290,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
             LambdaQueryWrapper<LifeUserOrder> orderWrapper = new LambdaQueryWrapper<>();
             orderWrapper.in(LifeUserOrder::getStoreId, storeIds)
                     .and(w -> w.and(w1 -> w1.eq(LifeUserOrder::getStatus, 7)
-                                    .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
+                            .ge(LifeUserOrder::getFinishTime, sevenDaysAgoStr))
                             .or(w2 -> w2.eq(LifeUserOrder::getStatus, 1)
                                     .ge(LifeUserOrder::getPayTime, sevenDaysAgoStr)))
                     .eq(LifeUserOrder::getDeleteFlag, 0);
@@ -3349,7 +3421,7 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
                     store.setDistance(storeDistance);
                     // 使用反射或扩展字段存储finalScore,这里我们使用一个临时字段
                     // 由于StoreInfoVo没有finalScore字段,我们使用distance字段临时存储,排序后再恢复
-                    return new Object[]{store, finalScore};
+                    return new Object[] { store, finalScore };
                 })
                 .filter(item -> {
                     // 距离优先模式:过滤掉超出范围的
@@ -4649,6 +4721,42 @@ public class StoreInfoServiceImpl extends ServiceImpl<StoreInfoMapper, StoreInfo
         return null;
     }
 
+
+    /**
+     * 解析营业执照OCR结果中的到期时间(复用营业执照OCR类型)
+     * 营业执照OCR返回字段:validToDate(格式"20241217"或空)、validPeriod(可能为空)
+     * 注意:营业执照的validToDate可能为空,表示长期有效
+     *
+     * @param ocrResult OCR识别结果JSON对象
+     * @return 到期日期,如果解析失败或为长期有效则返回null
+     */
+    private Date parseBusinessLicenseExpirationDate(com.alibaba.fastjson2.JSONObject ocrResult) {
+        if (ocrResult == null) {
+            return null;
+        }
+
+        // 优先使用validToDate字段(格式为yyyyMMdd,如"20241217",可能为空表示长期有效)
+        String validToDate = ocrResult.getString("validToDate");
+        if (StringUtils.isNotEmpty(validToDate)) {
+            Date date = parseDateString(validToDate);
+            if (date != null) {
+                return date;
+            }
+        }
+
+        // 其次使用validPeriod字段(格式为"2020年09月04日至2022年09月03日",可能为空)
+        String validPeriod = ocrResult.getString("validPeriod");
+        if (StringUtils.isNotEmpty(validPeriod)) {
+            Date date = parseValidPeriodEndDate(validPeriod);
+            if (date != null) {
+                return date;
+            }
+        }
+
+        log.info("营业执照OCR结果中未找到有效的到期时间,validToDate={},validPeriod={},可能为长期有效", validToDate, validPeriod);
+        return null;
+    }
+
     /**
      * 解析日期字符串
      * 支持的格式:yyyyMMdd、yyyy-MM-dd、yyyy/MM/dd、yyyy年MM月dd日