Prechádzať zdrojové kódy

添加一个楼宇-租赁表预计算类

ximinghao 4 mesiacov pred
rodič
commit
95d2d5f979

+ 392 - 0
src/main/java/com/skyversation/xjcy/counter/DataPreCounter.java

@@ -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);
+        }
+    }
+}