|
|
@@ -0,0 +1,392 @@
|
|
|
+package com.skyversation.xjcy.counter;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import lombok.AllArgsConstructor;
|
|
|
+import lombok.Getter;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.time.Instant;
|
|
|
+import java.time.LocalDate;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.time.ZoneId;
|
|
|
+import java.util.*;
|
|
|
+import java.util.function.Supplier;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
+
|
|
|
+public class DataPreCounter {
|
|
|
+ /**
|
|
|
+ * 注意输入的数据有副作用
|
|
|
+ * @param allPark 所有参与的park数据,为避免数据不完整,请提供整个产业园的数据
|
|
|
+ * @param allLease 所有参与的租赁,提供所有涉及的房间的所有数据即可,如果数据出现房间级的不完整,就会导致结果有误
|
|
|
+ */
|
|
|
+ public DataPreCounter(List<JSONObject> allPark, List<JSONObject> allLease) {
|
|
|
+ this.parkNeedUpload = new HashSet<>();
|
|
|
+ this.leaseNeedUpload = new HashSet<>();
|
|
|
+ this.allPark = allPark;
|
|
|
+ this.allLease = allLease;
|
|
|
+ allLy = new ArrayList<>();
|
|
|
+ allLd = new ArrayList<>();
|
|
|
+ allLc = new ArrayList<>();
|
|
|
+ allRoom = new ArrayList<>();
|
|
|
+ allPark.stream().filter(Objects::nonNull).forEach(park -> {
|
|
|
+ switch (park.getString(String.valueOf(ParkKey.c_resource_type))) {
|
|
|
+ case "1":
|
|
|
+ allRoom.add(park);
|
|
|
+ break;
|
|
|
+ case "2":
|
|
|
+ allLc.add(park);
|
|
|
+ break;
|
|
|
+ case "3":
|
|
|
+ allLd.add(park);
|
|
|
+ break;
|
|
|
+ case "4":
|
|
|
+ allLy.add(park);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ //doNothing
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum LeaseKey {
|
|
|
+ c_room_code, c_enterprise_code, c_start_date, c_end_date, c_lease_purpose, c_is_latest_lease, c_lease_status, update_time, c_enterprise_name
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum ParkKey {
|
|
|
+ c_park_code, c_zlqydm, c_zlqymc, c_room_status, c_room_sale_type, c_enterprise_count, c_floor_count, c_building_area, c_self_used_area, c_sellable_area, c_sold_area, c_rentable_area, c_rented_area, c_vacant_area, c_resource_type, c_parent_code
|
|
|
+ }
|
|
|
+
|
|
|
+ @AllArgsConstructor
|
|
|
+ private static class parkDataCount {
|
|
|
+ final BigDecimal buildArea;
|
|
|
+ final BigDecimal selfUseArea;
|
|
|
+ final BigDecimal sellableArea;
|
|
|
+ final BigDecimal soldArea;
|
|
|
+ final BigDecimal rentableArea;
|
|
|
+ final BigDecimal rentedArea;
|
|
|
+ final BigDecimal vacantArea;
|
|
|
+
|
|
|
+ parkDataCount(JSONObject v) {
|
|
|
+ buildArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_building_area)));
|
|
|
+ selfUseArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_self_used_area)));
|
|
|
+ sellableArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_sellable_area)));
|
|
|
+ soldArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_sold_area)));
|
|
|
+ rentableArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_rentable_area)));
|
|
|
+ rentedArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_rented_area)));
|
|
|
+ vacantArea = nullToZero(v.getBigDecimal(String.valueOf(ParkKey.c_vacant_area)));
|
|
|
+ }
|
|
|
+
|
|
|
+ parkDataCount() {
|
|
|
+ buildArea = BigDecimal.ZERO;
|
|
|
+ selfUseArea = BigDecimal.ZERO;
|
|
|
+ sellableArea = BigDecimal.ZERO;
|
|
|
+ soldArea = BigDecimal.ZERO;
|
|
|
+ rentableArea = BigDecimal.ZERO;
|
|
|
+ rentedArea = BigDecimal.ZERO;
|
|
|
+ vacantArea = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ parkDataCount add(parkDataCount countData) {
|
|
|
+ return new parkDataCount(
|
|
|
+ this.buildArea.add(countData.buildArea),
|
|
|
+ this.selfUseArea.add(countData.selfUseArea),
|
|
|
+ this.sellableArea.add(countData.sellableArea),
|
|
|
+ this.soldArea.add(countData.soldArea),
|
|
|
+ this.rentableArea.add(countData.rentableArea),
|
|
|
+ this.rentedArea.add(countData.rentedArea),
|
|
|
+ this.vacantArea.add(countData.vacantArea)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Getter
|
|
|
+ private final Set<JSONObject> parkNeedUpload;
|
|
|
+ @Getter
|
|
|
+ private final Set<JSONObject> leaseNeedUpload;
|
|
|
+ private final List<JSONObject> allPark;
|
|
|
+ private final List<JSONObject> allLy;
|
|
|
+ private final List<JSONObject> allLd;
|
|
|
+ private final List<JSONObject> allLc;
|
|
|
+ private final List<JSONObject> allRoom;
|
|
|
+ private final List<JSONObject> allLease;
|
|
|
+
|
|
|
+ private final LocalDateTime now = LocalDateTime.now();
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 运行预统计,有副作用,注意只能运行提供了租赁数据的房间,你可以从parkNeedUpload和leaseNeedUpload中获取所有更新过的数据
|
|
|
+ * @param roomRange 统计的范围
|
|
|
+ */
|
|
|
+ public void run(Collection<JSONObject> roomRange){
|
|
|
+ if (roomRange==null) {
|
|
|
+ roomRange = allRoom;
|
|
|
+ }
|
|
|
+ //更新并检查所有需更新房间
|
|
|
+ List<JSONObject> updatedRooms = roomRange.stream()
|
|
|
+ .filter(this::countRoom).collect(Collectors.toList());
|
|
|
+ //按层级统计面积
|
|
|
+ //楼层
|
|
|
+ Set<JSONObject> fatherLcs = findFathers(updatedRooms,allLc);
|
|
|
+ fatherLcs.forEach(this::countParkByChild);
|
|
|
+ //楼栋
|
|
|
+ Set<JSONObject> fatherLds = findFathers(fatherLcs,allLd);
|
|
|
+ fatherLds.forEach(this::countParkByChild);
|
|
|
+ //楼宇/产业园
|
|
|
+ Set<JSONObject> fatherLys = findFathers(fatherLds,allLy);
|
|
|
+ fatherLys.forEach(this::countParkByChild);
|
|
|
+
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 运行预统计,有副作用,注意只能运行提供了租赁数据的房间,你可以从parkNeedUpload和leaseNeedUpload中获取所有更新过的数据
|
|
|
+ */
|
|
|
+ public void run(){
|
|
|
+ run(allRoom);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Set<JSONObject> findFathers(Collection<JSONObject> children,List<JSONObject> fatherRange) {
|
|
|
+ Set<String> fatherCode= children.stream()
|
|
|
+ .map(v->v.getString(String.valueOf(ParkKey.c_parent_code)))
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ return fatherRange.stream()
|
|
|
+ .filter(v->fatherCode.contains(v.getString(String.valueOf(ParkKey.c_park_code))))
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * 预计算单房间与相关租赁,会修改输入值,会自动统计变动对象
|
|
|
+ *
|
|
|
+ * @return 如果该房间数据改变。则返回true
|
|
|
+ */
|
|
|
+ private boolean countRoom(JSONObject room) {
|
|
|
+ Supplier<Stream<JSONObject>> involveLease = () -> allLease.stream()
|
|
|
+ .filter(v -> room.getString(String.valueOf(ParkKey.c_park_code)).equals(v.getString(String.valueOf(LeaseKey.c_room_code))));
|
|
|
+
|
|
|
+ JSONObject newWorkingLease = getWorkingLease(involveLease);
|
|
|
+
|
|
|
+ //取出所有last指示数为2的lease
|
|
|
+ List<JSONObject> oldWorkingLeases = involveLease.get()
|
|
|
+ .filter(v -> "2".equals(v.getString(String.valueOf(LeaseKey.c_is_latest_lease))))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ //评估lease last指示的状态
|
|
|
+ boolean shouldLeaseUpdateFlag = shouldLeaseUpdate(oldWorkingLeases, newWorkingLease);
|
|
|
+
|
|
|
+ if (shouldLeaseUpdateFlag) {
|
|
|
+
|
|
|
+ //更新lease last指示数的状态
|
|
|
+ updateLeaseLast(newWorkingLease, oldWorkingLeases);
|
|
|
+
|
|
|
+ //更新房间状态
|
|
|
+ updateRoomLease(room, newWorkingLease);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateRoomLease(JSONObject room, JSONObject lease) {
|
|
|
+ if (lease == null) {
|
|
|
+ //绑定企业信息
|
|
|
+ updateStringValue(room,
|
|
|
+ String.valueOf(ParkKey.c_zlqymc),
|
|
|
+ null,
|
|
|
+ parkNeedUpload);
|
|
|
+ updateStringValue(room,
|
|
|
+ String.valueOf(ParkKey.c_zlqydm),
|
|
|
+ null,
|
|
|
+ parkNeedUpload);
|
|
|
+ //更新房间状态
|
|
|
+ updateStringValue(room, String.valueOf(ParkKey.c_room_status), "1", parkNeedUpload);
|
|
|
+ //更新面积数据
|
|
|
+ String roomSaleType = room.getString(String.valueOf(ParkKey.c_room_sale_type));
|
|
|
+ boolean isForSale = "3".equals(roomSaleType);
|
|
|
+ boolean isForRent = "1".equals(roomSaleType);
|
|
|
+ Double BuildArea = room.getDouble(String.valueOf(ParkKey.c_building_area));
|
|
|
+
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_self_used_area), 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_sellable_area), isForSale ? BuildArea : 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_sold_area), 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_rentable_area), isForRent ? BuildArea : 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_rented_area), 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_vacant_area), isForRent || isForSale ? BuildArea : 0d, parkNeedUpload);
|
|
|
+ } else {
|
|
|
+ //绑定企业信息
|
|
|
+ updateStringValue(room,
|
|
|
+ String.valueOf(ParkKey.c_zlqydm),
|
|
|
+ lease.getString(String.valueOf(LeaseKey.c_enterprise_code)),
|
|
|
+ parkNeedUpload);
|
|
|
+ updateStringValue(room,
|
|
|
+ String.valueOf(ParkKey.c_zlqymc),
|
|
|
+ lease.getString(String.valueOf(LeaseKey.c_enterprise_name)),
|
|
|
+ parkNeedUpload);
|
|
|
+ //更新房间状态
|
|
|
+ String roomSaleType = room.getString(String.valueOf(ParkKey.c_room_sale_type));
|
|
|
+ boolean isForSale = "3".equals(roomSaleType);
|
|
|
+ boolean isForRent = "1".equals(roomSaleType);
|
|
|
+ boolean isForSelfUsed = "2".equals(roomSaleType);
|
|
|
+
|
|
|
+ String state;
|
|
|
+ if (isForSelfUsed) {
|
|
|
+ state = "2"; // 已自用
|
|
|
+ } else if (isForRent) {
|
|
|
+ state = "3"; // 已出租
|
|
|
+ } else if (isForSale) {
|
|
|
+ state = "4"; // 已出售
|
|
|
+ } else {
|
|
|
+ state = null; // 如果房间缺失用途数据,则默许缺失蔓延到状态数据
|
|
|
+ }
|
|
|
+ updateStringValue(room, String.valueOf(ParkKey.c_room_status), state, parkNeedUpload);
|
|
|
+
|
|
|
+ //更新面积数据
|
|
|
+
|
|
|
+ Double BuildArea = room.getDouble(String.valueOf(ParkKey.c_building_area));
|
|
|
+
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_self_used_area), isForSelfUsed ? BuildArea : 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_sellable_area), 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_sold_area), isForSale ? BuildArea : 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_rentable_area), 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_rented_area), isForRent ? BuildArea : 0d, parkNeedUpload);
|
|
|
+ updateDoubleValue(room, String.valueOf(ParkKey.c_vacant_area), 0d, parkNeedUpload);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateLeaseLast(JSONObject newWorkingLease, List<JSONObject> oldWorkingLeases) {
|
|
|
+ oldWorkingLeases.forEach(v -> updateStringValue(v, String.valueOf(LeaseKey.c_is_latest_lease),
|
|
|
+ "1", leaseNeedUpload));
|
|
|
+ updateStringValue(newWorkingLease, String.valueOf(LeaseKey.c_is_latest_lease), "2", leaseNeedUpload);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean shouldLeaseUpdate(List<JSONObject> allLastWorkingLease, JSONObject workingLease) {
|
|
|
+ if (allLastWorkingLease.size() > 1) {
|
|
|
+ //指示数异常,强制更新
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ JSONObject lastWorkingLease = null;
|
|
|
+ if (!allLastWorkingLease.isEmpty()) {
|
|
|
+ lastWorkingLease = allLastWorkingLease.get(0);
|
|
|
+ }
|
|
|
+ //新旧执行中lease不相同
|
|
|
+ return workingLease != lastWorkingLease;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private JSONObject getWorkingLease(Supplier<Stream<JSONObject>> involveLease) {
|
|
|
+ JSONObject workingLease = involveLease.get()
|
|
|
+ .sorted(Comparator.comparingLong((JSONObject v) -> v.getLong(String.valueOf(LeaseKey.update_time))).reversed())
|
|
|
+ .filter(v -> {
|
|
|
+ LocalDate startTime = getLeaseStartTime(v);
|
|
|
+ LocalDate endTime = getLeaseEndTime(v);
|
|
|
+ if (startTime == null || endTime == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return !startTime.isAfter(now.toLocalDate()) && !endTime.isBefore(now.toLocalDate());
|
|
|
+ })
|
|
|
+ .findFirst().orElse(null);
|
|
|
+ if (workingLease == null) {
|
|
|
+ workingLease = involveLease.get()
|
|
|
+ .filter(v -> {
|
|
|
+ LocalDate startTime = getLeaseStartTime(v);
|
|
|
+ if (startTime == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return startTime.isAfter(now.toLocalDate());
|
|
|
+ }).min(Comparator.comparingLong(v -> v.getLong(String.valueOf(LeaseKey.c_start_date)))).orElse(null);
|
|
|
+ }
|
|
|
+ if (workingLease == null) {
|
|
|
+ workingLease = involveLease.get()
|
|
|
+ .filter(v -> {
|
|
|
+ LocalDate endTime = getLeaseEndTime(v);
|
|
|
+ if (endTime == null) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return endTime.isBefore(now.toLocalDate());
|
|
|
+ })
|
|
|
+ .max(Comparator.comparingLong(v -> v.getLong(String.valueOf(LeaseKey.c_end_date)))).orElse(null);
|
|
|
+ }
|
|
|
+ return workingLease;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据父子关系统计面积等等数据,使用时从下到上,一层层处理
|
|
|
+ *
|
|
|
+ * @param father 统计和更新的父类
|
|
|
+ */
|
|
|
+ private void countParkByChild(JSONObject father) {
|
|
|
+ String fatherParkCode = father.getString(String.valueOf(ParkKey.c_park_code));
|
|
|
+ if (fatherParkCode == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String parkType = father.getString(String.valueOf(ParkKey.c_resource_type));
|
|
|
+ List<JSONObject> childParkRange;
|
|
|
+ switch (parkType) {
|
|
|
+ case "2":
|
|
|
+ childParkRange = allRoom;
|
|
|
+ break;
|
|
|
+ case "3":
|
|
|
+ childParkRange = allLc;
|
|
|
+ break;
|
|
|
+ case "4":
|
|
|
+ childParkRange = allLd;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<JSONObject> childPark = childParkRange.stream()
|
|
|
+ .filter(v -> fatherParkCode.equals(v.getString(String.valueOf(ParkKey.c_parent_code))))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+
|
|
|
+ parkDataCount countResult = childPark.stream()
|
|
|
+ .map(parkDataCount::new)
|
|
|
+ .reduce(new parkDataCount(), parkDataCount::add, parkDataCount::add);
|
|
|
+
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_building_area), countResult.buildArea.doubleValue(), parkNeedUpload);
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_sellable_area), countResult.sellableArea.doubleValue(), parkNeedUpload);
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_sold_area), countResult.soldArea.doubleValue(), parkNeedUpload);
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_rentable_area), countResult.rentableArea.doubleValue(), parkNeedUpload);
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_rented_area), countResult.rentedArea.doubleValue(), parkNeedUpload);
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_self_used_area), countResult.selfUseArea.doubleValue(), parkNeedUpload);
|
|
|
+ updateDoubleValue(father, String.valueOf(ParkKey.c_vacant_area), countResult.vacantArea.doubleValue(), parkNeedUpload);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static BigDecimal nullToZero(BigDecimal v) {
|
|
|
+ return v == null ? BigDecimal.ZERO : v;
|
|
|
+ }
|
|
|
+
|
|
|
+ //simple tool
|
|
|
+ private LocalDate getKeyAndPackToTime(JSONObject obj, String key) {
|
|
|
+ Long time = obj.getLong(key);
|
|
|
+ if (time == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return LocalDateTime.ofInstant(
|
|
|
+ Instant.ofEpochMilli(time),
|
|
|
+ ZoneId.systemDefault()
|
|
|
+ ).toLocalDate();
|
|
|
+ }
|
|
|
+
|
|
|
+ private LocalDate getLeaseStartTime(JSONObject lease) {
|
|
|
+ return getKeyAndPackToTime(lease, String.valueOf(LeaseKey.c_start_date));
|
|
|
+ }
|
|
|
+
|
|
|
+ private LocalDate getLeaseEndTime(JSONObject lease) {
|
|
|
+ return getKeyAndPackToTime(lease, String.valueOf(LeaseKey.c_end_date));
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateStringValue(JSONObject obj, String key, String value, Set<JSONObject> uploadSet) {
|
|
|
+ String oldValue = obj.getString(key);
|
|
|
+ if (!oldValue.equals(value)) {
|
|
|
+ obj.put(key, value);
|
|
|
+ uploadSet.add(obj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void updateDoubleValue(JSONObject obj, String key, Double value, Set<JSONObject> uploadSet) {
|
|
|
+ Double oldValue = obj.getDouble(key);
|
|
|
+ if (!oldValue.equals(value)) {
|
|
|
+ obj.put(key, value);
|
|
|
+ uploadSet.add(obj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|