浏览代码

补充产业数智平台需要的各种信息

ximinghao 10 月之前
父节点
当前提交
1c03714f10

+ 8 - 0
src/main/java/com/skyversation/xjcy/bean/DateInterval.java

@@ -0,0 +1,8 @@
+package com.skyversation.xjcy.bean;
+
+import java.time.LocalDate;
+
+public interface DateInterval {
+    LocalDate getStartDate();
+    LocalDate getEndDate();
+}

+ 31 - 2
src/main/java/com/skyversation/xjcy/bean/Enterprise.java

@@ -1,16 +1,45 @@
 package com.skyversation.xjcy.bean;
 
-import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
 import lombok.Data;
 
+import java.time.LocalDate;
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Stream;
+
 @Data
-public class Enterprise implements FromJSON {
+public class Enterprise implements FromJSON ,DateInterval {
 
     private String cUnifiedSocialCreditCode;
     private String cEnterpriseName;
     private String regType;
+    private String householdUnitId;
+    private String cSsxqCode;
     private String isGsqy;
     private String operationStatus;
     private String industry;
+    private String companyLevel;
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cCertificateDate;
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cMoveOutDate;
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate revocationTime;
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cancellationTime;
+    @Override
+    public LocalDate getStartDate() {
+        return Optional.ofNullable(cCertificateDate).orElse(LocalDate.MIN);
+    }
 
+    @Override
+    public LocalDate getEndDate() {
+        return Stream.of(cMoveOutDate, revocationTime, cancellationTime)
+                .filter(Objects::nonNull)
+                .min(Comparator.naturalOrder())
+                .orElse(LocalDate.MAX);
+    }
 }

+ 16 - 1
src/main/java/com/skyversation/xjcy/bean/IndustrialPark.java

@@ -1,19 +1,34 @@
 package com.skyversation.xjcy.bean;
 
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
 import lombok.Data;
 import java.math.BigDecimal;
+import java.time.LocalDate;
 
 @Data
-public class IndustrialPark implements FromJSON {
+public class IndustrialPark implements FromJSON , DateInterval{
 
     private String cParkCode;
     private String cParentCode;
     private String cResourceName;
     private String cResourceType;
     private String cZlqydm;
+    private String cLandUse;
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate createTime;
     private BigDecimal cFloorArea;
     private BigDecimal cBuildingArea;
     private BigDecimal cVacantArea;
 
+    @Override
+    public LocalDate getStartDate() {
+        return createTime;
+    }
+
+    @Override
+    public LocalDate getEndDate() {
+        return LocalDate.MAX;
+    }
 }

+ 43 - 0
src/main/java/com/skyversation/xjcy/bean/LeaseDetail.java

@@ -0,0 +1,43 @@
+package com.skyversation.xjcy.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.skyversation.xjcy.config.LocalDateFromTimestampDeserializer;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Data
+public class LeaseDetail implements FromJSON , DateInterval{
+    /** 房间 code,关联楼宇表,资源类型=房间 */
+    private String cRoomCode;
+
+    /** 企业 code,关联企业表 */
+    private String cEnterpriseCode;
+
+    /** 租赁开始日期,格式 yyyy-MM-dd */
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cStartDate;
+
+    /** 租赁结束日期,格式 yyyy-MM-dd */
+    @JSONField(deserializeUsing = LocalDateFromTimestampDeserializer.class)
+    private LocalDate cEndDate;
+
+    /** 租赁用途枚举:1=生产,2=办公,3=仓储,4=其他 */
+    private Integer cLeasePurpose;
+
+    /** 是否为该房间最新租约:1=否,2=是 */
+    private Integer cIsLatestLease;
+
+    /** 租赁状态枚举:1=使用中,2=将到期,3=已到期,4=已预约,5=合同终止 */
+    private Integer cLeaseStatus;
+
+    @Override
+    public LocalDate getStartDate() {
+        return cStartDate;
+    }
+
+    @Override
+    public LocalDate getEndDate() {
+        return cEndDate;
+    }
+}

+ 33 - 0
src/main/java/com/skyversation/xjcy/config/LocalDateFromTimestampDeserializer.java

@@ -0,0 +1,33 @@
+package com.skyversation.xjcy.config;
+
+import com.alibaba.fastjson.parser.DefaultJSONParser;
+import com.alibaba.fastjson.parser.ParserConfig;
+import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.lang.reflect.Type;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+
+@Component
+public class LocalDateFromTimestampDeserializer implements ObjectDeserializer {
+    @Override
+    public LocalDate deserialze(DefaultJSONParser parser, Type type, Object fieldName) {
+        Number timestamp = parser.parseObject(Number.class);
+        if (timestamp == null) return null;
+        return Instant.ofEpochMilli(timestamp.longValue())
+                      .atZone(ZoneId.systemDefault())
+                      .toLocalDate();
+    }
+
+    @Override
+    public int getFastMatchToken() {
+        return 0;
+    }
+    @PostConstruct
+    public void init() {
+        ParserConfig.getGlobalInstance().putDeserializer(LocalDate.class, new LocalDateFromTimestampDeserializer());
+    }
+}

+ 24 - 0
src/main/java/com/skyversation/xjcy/enums/EnterpriseLevel.java

@@ -0,0 +1,24 @@
+package com.skyversation.xjcy.enums;
+
+import java.util.Optional;
+
+public enum EnterpriseLevel {
+    A,B,C;
+    /**
+     * 把 JSON 中的值 1/2/3 转成枚举。
+     * @throws IllegalArgumentException 如果不是 1、2、3
+     */
+    public static Optional<EnterpriseLevel> ofJsonValue(String jsonValue) {
+        if (jsonValue==null)return Optional.empty();
+        switch (jsonValue) {
+            case "1":
+                return Optional.of(A);
+            case "2":
+                return Optional.of(B);
+            case "3":
+                return Optional.of(C);
+            default:
+                return Optional.empty();
+        }
+    }
+}

+ 3 - 1
src/main/java/com/skyversation/xjcy/query/DMSQuery.java

@@ -11,7 +11,9 @@ public enum DMSQuery {
     INDUSTRIAL_PARK("1580", IndustrialPark.class),
     ENTERPRISE      ("1593", Enterprise.class),
     ECONOMIC_ALL_INDEED("1594", EnterpriseEconomic.class),
-    ORDER           ("1587", Order.class);
+    ORDER           ("1587", Order.class),
+    LAST_LEASE_DETAIL("1574", LeaseDetail.class),
+    LAST_YEAR_LEASE_DETAIL("1574", LeaseDetail.class);
 
     private final String columnId;
     private final Class<? extends FromJSON> resultType;

+ 31 - 7
src/main/java/com/skyversation/xjcy/service/DMSService.java

@@ -3,6 +3,7 @@ package com.skyversation.xjcy.service;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.ParserConfig;
 import com.skyversation.xjcy.bean.FromJSON;
 import com.skyversation.xjcy.query.DMSQuery;
 import com.skyversation.xjcy.util.HttpUtil;
@@ -14,6 +15,8 @@ import org.springframework.util.MultiValueMap;
 import org.springframework.util.StringUtils;
 import org.springframework.web.client.RestClientException;
 
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -28,9 +31,6 @@ public class DMSService {
     @Value("${app.oauth.path}")
     private String oauthPath;
 
-//    private String cacheToken="";
-
-
     private static class DMSRequest {
         DMSRequestType type;
         Map<String,String> params = new HashMap<>();
@@ -122,7 +122,7 @@ public class DMSService {
     private void addWhere(DMSRequest request,String field,String type , String start,String end){
         JSONObject where = new JSONObject();
         where.put("field",field);
-        where.put("type",type);
+        where.put("searchType",type);
         JSONObject contents = new JSONObject();
         contents.put("start",start);
         contents.put("end",end);
@@ -178,23 +178,47 @@ public class DMSService {
     public List<JSONObject> simpleQuery(String token,String columId){
         return queryDmsList(new DMSRequest(),token,columId);
     }
-    public List<JSONObject> lastYearQuery(String token, String columId,String timeField){
+    public List<JSONObject> indeedEconomicQuery(String token, String columId){
         DMSRequest request = new DMSRequest();
         Set<String> monthInDeed = new HashSet<>();
         monthInDeed.addAll(SpecialisationStringCollector.YearMonthByLast12Month());
         monthInDeed.addAll(SpecialisationStringCollector.YearMonthByYtd());
         monthInDeed.addAll(SpecialisationStringCollector.YearMonthByYtd(1));
-        addWhere(request,timeField,"5", monthInDeed );
+        addWhere(request,"c_year_month","5", monthInDeed);
         return queryDmsList(request,token,columId);
     }
+    public List<JSONObject> indeedLeaseQuery(String token ,String columId){
+        DMSRequest request = new DMSRequest();
+        LocalDate now = LocalDate.now();
+        LocalDate indeedStartTime = now.minusMonths(13).withDayOfMonth(1); // 2024-06-01
+        LocalDate indeedEndTime   = now.minusMonths(1)                     // 上个月
+                .withDayOfMonth(
+                        now.minusMonths(1)
+                                .lengthOfMonth());
 
+        LocalDate timeOfFarBefore = LocalDate.of(1970,1,1);
+        LocalDate timeOfFarAfter = indeedEndTime.plusYears(100);
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+        addWhere(request,"c_start_date","3",timeOfFarBefore.format(formatter),indeedEndTime.format(formatter));
+        addWhere(request,"c_end_date","3",indeedStartTime.format(formatter),timeOfFarAfter.format(formatter));
+        return  queryDmsList(request,token,columId);
+    }
+    public List<JSONObject> lastLeaseQuery(String token ,String columId){
+        DMSRequest request = new DMSRequest();
+        addWhere(request,"c_is_latest_lease","1","2");
+        return queryDmsList(request,token,columId);
+    }
     public <T extends FromJSON> List<T> query(String token,DMSQuery query,Class<T> clazz){
         if(query.getResultType()!=clazz){
             throw new RuntimeException("类型与枚举提供的类型不符,请确认枚举中的类型");
         }
         List<JSONObject> dmsResult;
         if (query == DMSQuery.ECONOMIC_ALL_INDEED){
-            dmsResult = lastYearQuery(token,query.getColumnId(),"c_year_month");
+            dmsResult = indeedEconomicQuery(token,query.getColumnId());
+        }else if (query == DMSQuery.LAST_LEASE_DETAIL){
+            dmsResult = lastLeaseQuery(token,query.getColumnId());
+        }else if (query == DMSQuery.LAST_YEAR_LEASE_DETAIL){
+            dmsResult = indeedLeaseQuery(token,query.getColumnId());
         }else {
             dmsResult = simpleQuery(token, query.getColumnId());
         }

+ 204 - 12
src/main/java/com/skyversation/xjcy/service/DataCountService.java

@@ -2,12 +2,11 @@ package com.skyversation.xjcy.service;
 
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
-import com.skyversation.xjcy.bean.Enterprise;
-import com.skyversation.xjcy.bean.EnterpriseEconomic;
-import com.skyversation.xjcy.bean.IndustrialPark;
-import com.skyversation.xjcy.bean.Order;
+import com.skyversation.xjcy.bean.*;
+import com.skyversation.xjcy.enums.EnterpriseLevel;
 import com.skyversation.xjcy.query.DMSQuery;
 import com.skyversation.xjcy.util.SpecialisationStringCollector;
+import com.skyversation.xjcy.util.TimeUtil;
 import lombok.NoArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.util.LinkedMultiValueMap;
@@ -16,17 +15,18 @@ import org.springframework.util.MultiValueMap;
 import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 @Service
 public class DataCountService {
     @Resource
     DMSService dmsService;
 
-    private boolean counted = false;
     private JSONObject cacheTownData;
     private Map<String, JSONObject> cacheParkData;
     private Map<String, JSONObject> cacheBuildData;
@@ -35,9 +35,9 @@ public class DataCountService {
     private Map<String, JSONObject> tempCacheParkData;
     private Map<String, JSONObject> tempCacheBuildData;
 
-    Map<String,EnterpriseEconomicCount> economicCountBy12Month = new HashMap<>();
-    Map<String,EnterpriseEconomicCount> economicCountByYtdThisYear = new HashMap<>();
-    Map<String,EnterpriseEconomicCount> economicCountByYtdLastYear = new HashMap<>();
+    Map<String, EnterpriseEconomicCount> economicCountBy12Month = new HashMap<>();
+    Map<String, EnterpriseEconomicCount> economicCountByYtdThisYear = new HashMap<>();
+    Map<String, EnterpriseEconomicCount> economicCountByYtdLastYear = new HashMap<>();
 
     Map<String, Enterprise> aliveEnterpriseMap = new HashMap<>();
 
@@ -77,11 +77,13 @@ public class DataCountService {
             running.set(false);
         }
     }
+
     @NoArgsConstructor
-    private static class EnterpriseEconomicCount{
+    private static class EnterpriseEconomicCount {
         BigDecimal tax = BigDecimal.ZERO;
         BigDecimal product = BigDecimal.ZERO;
         BigDecimal revenue = BigDecimal.ZERO;
+
         public EnterpriseEconomicCount(List<EnterpriseEconomic> enterpriseEconomicList) {
             for (EnterpriseEconomic enterpriseEconomic : enterpriseEconomicList) {
                 tax = tax.add(Optional.ofNullable(enterpriseEconomic.getCApprovedOutputValue()).orElse(BigDecimal.ZERO));
@@ -102,12 +104,13 @@ public class DataCountService {
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
-        counted = true;
         //统一查询所有需要的数据避免处理时间太长token过期,数据量大不了所以不做流式
         List<IndustrialPark> allIndustrialPark = dmsService.query(token, DMSQuery.INDUSTRIAL_PARK, IndustrialPark.class);
         List<Enterprise> allEnterprise = dmsService.query(token, DMSQuery.ENTERPRISE, Enterprise.class);
         List<EnterpriseEconomic> inDateEnterpriseEconomic = dmsService.query(token, DMSQuery.ECONOMIC_ALL_INDEED, EnterpriseEconomic.class);
         List<Order> allOrder = dmsService.query(token, DMSQuery.ORDER, Order.class);
+        List<LeaseDetail> lastLeaseDetail = dmsService.query(token, DMSQuery.LAST_LEASE_DETAIL, LeaseDetail.class);
+        List<LeaseDetail> lastYearLease = dmsService.query(token, DMSQuery.LAST_YEAR_LEASE_DETAIL, LeaseDetail.class);
 
         //处理一下拿到的数据,建一建索引
         Map<String, List<EnterpriseEconomic>> enterpriseEconomicMap = new HashMap<>();
@@ -133,17 +136,85 @@ public class DataCountService {
         //企呼我应
         countOrder(allOrder);
 
+        //租赁信息相关统计
+        countLease(lastLeaseDetail);
+
+        //历史企业总数统计
+        //历史数据,所以不论企业现状如何
+        Map<String, List<Enterprise>> enterpriseMap = TimeUtil.split(allEnterprise);
+        Map<String, Integer> enterpriseByMonth = enterpriseMap.entrySet()
+                .stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size()));
+
+        //历史异地企业统计
+        //同上
+        Map<String, List<Enterprise>> enterpriseInTownMap = TimeUtil.split(allEnterprise
+                .stream()
+                .filter(e -> Objects.equals(e.getCSsxqCode(), "#YIDIQIYE"))
+                .collect(Collectors.toList()));
+        Map<String, Integer> enterpriseInTownByMonth = enterpriseInTownMap.entrySet()
+                .stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size()));
+
+        //历史房间总数统计
+        Map<String, List<IndustrialPark>> industrialParkMap = TimeUtil.split(allIndustrialPark
+                .stream()
+                .filter(e->Objects.equals(e.getCResourceType(),"1"))
+                .collect(Collectors.toList())
+        );
+        Map<String,Integer> roomByMonth = industrialParkMap.entrySet().stream().collect(
+                Collectors.toMap(Map.Entry::getKey, e -> e.getValue().size())
+        );
+
+        //历史已使用房间总数统计
+        Map<String, List<LeaseDetail>> leaseMap = TimeUtil.split(lastYearLease);
+        Map<String, Long> leaseByMonth = leaseMap.entrySet()
+                .stream()
+                .collect(
+                        Collectors.toMap(
+                                Map.Entry::getKey,
+                                e -> {
+                                    Set<String> parkCodes = industrialParkMap.getOrDefault(e.getKey(), new ArrayList<>())
+                                            .stream()
+                                            .map(IndustrialPark::getCParkCode)
+                                            .collect(Collectors.toSet());
+                                    return e.getValue()
+                                            .stream()
+                                            .map(LeaseDetail::getCRoomCode)
+                                            .filter(parkCodes::contains)
+                                            .distinct()
+                                            .count();
+                                }
+                        )
+                );
+
+
+
+
+        tempCacheTownData.put("enterpriseByMonth", enterpriseByMonth);
+        tempCacheTownData.put("enterpriseInTownByMonth", enterpriseInTownByMonth);
+        tempCacheTownData.put("leaseByMonth", leaseByMonth);
+        tempCacheTownData.put("roomByMonth", roomByMonth);
+
         cacheTownData = tempCacheTownData;
         cacheParkData = tempCacheParkData;
         cacheBuildData = tempCacheBuildData;
     }
 
+
     private void countIndustrialPark(List<IndustrialPark> allIndustrialPark) {
         //简单统计并按父级编码建映射
         simpleCountPark(allIndustrialPark);
+        Map<EnterpriseLevel, Map<String, Integer>> enterPriceLevelCountByPark = new HashMap<>();
+        for (EnterpriseLevel value : EnterpriseLevel.values()) {
+            enterPriceLevelCountByPark.put(value, new HashMap<>());
+        }
         //按楼宇父子关系统计
         for (IndustrialPark industrialPark : allPark) {
             String parkCode = industrialPark.getCParkCode();
+
+            for (EnterpriseLevel value : EnterpriseLevel.values()) {
+                enterPriceLevelCountByPark.get(value).put(industrialPark.getCResourceName(), 0);
+            }
+
             List<IndustrialPark> allBuild;
             List<IndustrialPark> allRoom = new ArrayList<>();
 
@@ -237,17 +308,26 @@ public class DataCountService {
             for (String enterpriseCode : inParkEnterpriseCode) {
                 if (aliveEnterpriseMap.containsKey(enterpriseCode)) {
                     Enterprise enterprise = aliveEnterpriseMap.get(enterpriseCode);
+
+                    EnterpriseLevel.ofJsonValue(enterprise.getCompanyLevel()).ifPresent(level -> {
+                        Map<String, Integer> map = enterPriceLevelCountByPark.get(level);
+                        map.put(enterpriseCode, map.get(enterprise.getCEnterpriseName()) + 1);
+                    });
+
                     EnterpriseEconomicCount ytdThisYear = economicCountByYtdThisYear.get(enterpriseCode);
                     EnterpriseEconomicCount ytdLastYear = economicCountByYtdLastYear.get(enterpriseCode);
                     allTaxByYtdThisYear = allTaxByYtdThisYear.add(ytdThisYear.tax);
                     allTaxByYtdLastYear = allTaxByYtdLastYear.add(ytdLastYear.tax);
+
                     if ("2".equals(enterprise.getIsGsqy())) {
                         allGsProductByYtdThisYear = allGsProductByYtdThisYear.add(ytdThisYear.product);
                         allGsProductByYtdLastYear = allGsProductByYtdLastYear.add(ytdLastYear.product);
                         enterPriceGsCount++;
                     }
+
                     enterPriceCount++;
                     aliveEnterprise.add(enterpriseCode);
+
                     String industry = enterprise.getIndustry();
                     if (industry != null) {
                         if (industryCount.containsKey(industry)) {
@@ -290,6 +370,22 @@ public class DataCountService {
             parkDataCount.put("topRevenue", topRevenue);
 
             tempCacheParkData.put(parkCode, parkDataCount);
+
+            for (EnterpriseLevel level : EnterpriseLevel.values()) {
+                Map<String, Integer> map = enterPriceLevelCountByPark.get(level);
+                Map<String, Integer> sortedDesc = map.entrySet()
+                        .stream()
+                        .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
+                        .limit(10)
+                        .collect(Collectors.toMap(
+                                Map.Entry::getKey,
+                                Map.Entry::getValue,
+                                (e1, e2) -> e1,
+                                LinkedHashMap::new
+                        ));
+                enterPriceLevelCountByPark.put(level, sortedDesc);
+            }
+            tempCacheTownData.put("enterpriseLevelCountByPark", tempCacheParkData);
         }
 
 
@@ -300,6 +396,8 @@ public class DataCountService {
         BigDecimal totalFloorArea = BigDecimal.ZERO;
         BigDecimal totalBuildArea = BigDecimal.ZERO;
         BigDecimal totalVacantArea = BigDecimal.ZERO;
+
+        Map<String, Integer> parkTypeCount = new HashMap<>();
         for (IndustrialPark industrialPark : allIndustrialPark) {
             try {
                 int type = Integer.parseInt(industrialPark.getCResourceType());
@@ -322,6 +420,11 @@ public class DataCountService {
                     case 4: {
                         parkCount++;
                         allPark.add(industrialPark);
+                        if (parkTypeCount.containsKey(industrialPark.getCLandUse())) {
+                            parkTypeCount.put(industrialPark.getCLandUse(), parkCount + 1);
+                        } else {
+                            parkTypeCount.put(industrialPark.getCLandUse(), 1);
+                        }
                         try {
                             totalFloorArea = totalFloorArea.add(industrialPark.getCFloorArea());
                         } catch (NumberFormatException ignore) {
@@ -345,6 +448,7 @@ public class DataCountService {
         }
 
         tempCacheTownData.put("parkCount", parkCount);
+        tempCacheTownData.put("parkType", parkTypeCount);
         tempCacheTownData.put("areaBuild", totalBuildArea);
         tempCacheTownData.put("areaFloor", totalFloorArea);
         tempCacheTownData.put("areaVacant", totalVacantArea);
@@ -404,7 +508,7 @@ public class DataCountService {
                                     economicByYtdLastYear.add(e);
                                 }
                             });
-                    economicCountBy12Month.put(enterPriceCode,new EnterpriseEconomicCount(economicBy12Month));
+                    economicCountBy12Month.put(enterPriceCode, new EnterpriseEconomicCount(economicBy12Month));
                     economicCountByYtdThisYear.put(enterPriceCode, new EnterpriseEconomicCount(economicByYtdThisYear));
                     economicCountByYtdLastYear.put(enterPriceCode, new EnterpriseEconomicCount(economicByYtdLastYear));
                 }
@@ -419,7 +523,7 @@ public class DataCountService {
         tempCacheTownData.put("enterPriseIndustry", industryCount);
     }
 
-    public void countOrder(List<Order> allOrder) {
+    private void countOrder(List<Order> allOrder) {
         int orderComplete = 0;
         int orderCount = 0;
         for (Order order : allOrder) {
@@ -443,7 +547,95 @@ public class DataCountService {
         tempCacheTownData.put("orderCount", orderCount);
     }
 
+    private void countLease(List<LeaseDetail> allLease) {
+        List<LeaseDetail> timeoutIn1Month = new ArrayList<>();
+        List<LeaseDetail> timeoutIn3Month = new ArrayList<>();
+
+        LocalDate now = LocalDate.now();
+
+        Map<String, LeaseDetail> leaseMap = new HashMap<>();
+        for (LeaseDetail lease : allLease) {
+            if (lease.getCEndDate() != null) {
+                if (lease.getCEndDate().isAfter(now)) {
+                    if (lease.getCEndDate().isBefore(now.plusMonths(1))) {
+                        timeoutIn1Month.add(lease);
+                    } else if (lease.getCEndDate().isBefore(now.plusMonths(3))) {
+                        timeoutIn3Month.add(lease);
+                    }
+                }
+            }
+            leaseMap.put(lease.getCRoomCode(), lease);
+        }
+
+        int vacantRoomCount = 0;
+        BigDecimal vacantAreaCount = BigDecimal.ZERO;
+        int expiringSoonRoomCount = 0;
+        BigDecimal expiringSoonAreaCount = BigDecimal.ZERO;
+        int utilizedRoomCount = 0;
+        BigDecimal utilizedAreaCount = BigDecimal.ZERO;
+        int reservedRoomCount = 0;
+        BigDecimal reservedAreaCount = BigDecimal.ZERO;
+        int terminatedRoomCount = 0;
+        BigDecimal terminatedAreaCount = BigDecimal.ZERO;
+        for (List<IndustrialPark> roomList : roomMap.values()) {
+            for (IndustrialPark room : roomList) {
+                LeaseDetail lease = leaseMap.get(room.getCParkCode());
+                if (lease == null) {
+                    vacantRoomCount++;
+                    vacantAreaCount = vacantAreaCount.add(Optional.ofNullable(room.getCBuildingArea()).orElse(BigDecimal.ZERO));
+                    continue;
+                }
+                if (lease.getCEndDate().isAfter(now) && lease.getCStartDate().isBefore(now)) {
+                    utilizedRoomCount++;
+                    utilizedAreaCount = utilizedAreaCount.add(Optional.ofNullable(room.getCBuildingArea()).orElse(BigDecimal.ZERO));
+                    continue;
+                }
+                if (lease.getCStartDate().isAfter(now) && lease.getCEndDate().isAfter(now)) {
+                    reservedRoomCount++;
+                    reservedAreaCount = reservedAreaCount.add(Optional.ofNullable(room.getCBuildingArea()).orElse(BigDecimal.ZERO));
+                    continue;
+                }
+                if (lease.getCEndDate().isBefore(now) && lease.getCEndDate().plusMonths(1).isAfter(now)) {
+                    terminatedRoomCount++;
+                    terminatedAreaCount = terminatedAreaCount.add(Optional.ofNullable(room.getCBuildingArea()).orElse(BigDecimal.ZERO));
+                    continue;
+                }
+                if (lease.getCEndDate().isAfter(now) && lease.getCEndDate().isBefore(now.plusMonths(3))) {
+                    expiringSoonRoomCount++;
+                    expiringSoonAreaCount = expiringSoonAreaCount.add(Optional.ofNullable(room.getCBuildingArea()).orElse(BigDecimal.ZERO));
+                    continue;
+                }
+                vacantRoomCount++;
+                vacantAreaCount = vacantAreaCount.add(Optional.ofNullable(room.getCBuildingArea()).orElse(BigDecimal.ZERO));
+            }
+        }
+
+        timeoutIn1Month.sort(Comparator.comparing(LeaseDetail::getCEndDate));
+        timeoutIn3Month.sort(Comparator.comparing(LeaseDetail::getCEndDate));
+
+        List<LeaseDetail> sortedLeaseTimeout = Stream.concat(timeoutIn1Month.stream(), timeoutIn3Month.stream())
+                .sorted(Comparator.comparing(LeaseDetail::getCEndDate))
+                .limit(25)
+                .collect(Collectors.toList());
 
+        tempCacheTownData.put("leaseTimeoutIn1Month", timeoutIn1Month.size());
+        tempCacheTownData.put("leaseTimeoutIn3Month", timeoutIn3Month.size());
+        tempCacheTownData.put("leaseTimeout", sortedLeaseTimeout);
+        tempCacheTownData.put("leaseVacantRoomCount", vacantRoomCount);
+        tempCacheTownData.put("leaseVacantAreaCount", vacantAreaCount);
+
+        tempCacheTownData.put("leaseExpiringSoonRoomCount", expiringSoonRoomCount);
+        tempCacheTownData.put("leaseExpiringSoonAreaCount", expiringSoonAreaCount);
+
+        tempCacheTownData.put("leaseUtilizedRoomCount", utilizedRoomCount);
+        tempCacheTownData.put("leaseUtilizedAreaCount", utilizedAreaCount);
+
+        tempCacheTownData.put("leaseReservedRoomCount", reservedRoomCount);
+        tempCacheTownData.put("leaseReservedAreaCount", reservedAreaCount);
+
+        tempCacheTownData.put("leaseTerminatedRoomCount", terminatedRoomCount);
+        tempCacheTownData.put("leaseTerminatedAreaCount", terminatedAreaCount);
+    }
 
     public JSONObject getTownData() {
         tryCount();

+ 44 - 0
src/main/java/com/skyversation/xjcy/util/TimeUtil.java

@@ -0,0 +1,44 @@
+package com.skyversation.xjcy.util;
+
+import com.skyversation.xjcy.bean.DateInterval;
+
+import java.time.LocalDate;
+import java.time.YearMonth;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+
+public class TimeUtil {
+    private static final DateTimeFormatter MM_FMT = DateTimeFormatter.ofPattern("yyyy-MM");
+
+    private TimeUtil() {}
+
+    public static <T extends DateInterval> Map<String, List<T>> split(List<T> src) {
+        LocalDate today      = LocalDate.now();
+        YearMonth firstMonth = YearMonth.from(today.minusMonths(12));
+        YearMonth lastMonth  = YearMonth.from(today.minusMonths(1));
+
+        // 1. 创建 12 个桶,key 为 "yyyy-MM"
+        Map<String, List<T>> map = new LinkedHashMap<>();
+        for (YearMonth ym = firstMonth; !ym.isAfter(lastMonth); ym = ym.plusMonths(1)) {
+            map.put(ym.format(MM_FMT), new ArrayList<>());
+        }
+
+        // 2. 分桶
+        for (T item : src) {
+            if (item == null) continue;
+            LocalDate s = item.getStartDate();
+            LocalDate e = item.getEndDate();
+            if (s == null || e == null) continue;
+
+            for (Map.Entry<String, List<T>> entry : map.entrySet()) {
+                YearMonth ym = YearMonth.parse(entry.getKey(), MM_FMT);
+                LocalDate monthStart = ym.atDay(1);
+                LocalDate monthEnd   = ym.atEndOfMonth();
+                if (!e.isBefore(monthStart) && !s.isAfter(monthEnd)) {
+                    entry.getValue().add(item);
+                }
+            }
+        }
+        return map;
+    }
+}