Browse Source

Merge branch 'master' of http://121.41.40.202:3000/zhuwei/bladex

huangtf 1 year ago
parent
commit
489aeae736
23 changed files with 1283 additions and 241 deletions
  1. 12 8
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/utils/NumberUtil.java
  2. 1 2
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ElementBlock.java
  3. 100 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/EvaSummary.java
  4. 52 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/FB01.java
  5. 31 112
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/FB02.java
  6. 16 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Item01.java
  7. 24 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Item02.java
  8. 4 4
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ItemBlock.java
  9. 3 3
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemContract.java
  10. 3 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemProject.java
  11. 4 0
      blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/feign/IUserClient.java
  12. 6 2
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java
  13. 5 5
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TaskServiceImpl.java
  14. 3 2
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java
  15. 0 3
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaDataBlockService.java
  16. 18 42
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  17. 3 7
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java
  18. 20 3
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MidPayItemController.java
  19. 212 0
      blade-service/blade-user/src/main/java/org/springblade/system/user/bean/NodeVO.java
  20. 107 0
      blade-service/blade-user/src/main/java/org/springblade/system/user/controller/WbsTreeController.java
  21. 5 0
      blade-service/blade-user/src/main/java/org/springblade/system/user/feign/UserClient.java
  22. 5 0
      blade-service/blade-user/src/main/java/org/springblade/system/user/service/IUserService.java
  23. 649 48
      blade-service/blade-user/src/main/java/org/springblade/system/user/service/impl/UserServiceImpl.java

+ 12 - 8
blade-service-api/blade-business-api/src/main/java/org/springblade/business/utils/NumberUtil.java

@@ -46,7 +46,7 @@ public class NumberUtil {
     public static Short parseShortObj(String pValue) {
         if (pValue == null) return null;
         try {
-            return new Short(Short.parseShort(pValue));
+            return Short.parseShort(pValue);
         } catch (Exception ex) {
             log.error(ex);
             return null;
@@ -67,7 +67,7 @@ public class NumberUtil {
     public static Integer parseInteger(String pValue) {
         if (pValue == null) return null;
         try {
-            return new Integer(Integer.parseInt(pValue));
+            return Integer.parseInt(pValue);
         } catch (Exception ex) {
             log.error(ex);
             return null;
@@ -88,7 +88,7 @@ public class NumberUtil {
     public static Long parseLongObj(String pValue) {
         if (pValue == null) return null;
         try {
-            return new Long(Long.parseLong(pValue));
+            return Long.parseLong(pValue);
         } catch (Exception ex) {
             log.error(ex);
             return null;
@@ -236,7 +236,7 @@ public class NumberUtil {
     // 四舍五入.
     public static Double round(Double d, int precision) {
         if (d != null) {
-            return new Double(round(d.doubleValue(), precision));
+            return round(d.doubleValue(), precision);
         } else {
             return null;
         }
@@ -244,7 +244,7 @@ public class NumberUtil {
 
     public static int intValue(Integer integer) {
         if (integer != null) {
-            return integer.intValue();
+            return integer;
         } else {
             return 0;
         }
@@ -252,7 +252,7 @@ public class NumberUtil {
 
     public static double doubleValue(Double d) {
         if (d != null) {
-            return d.doubleValue();
+            return d;
         } else {
             return 0;
         }
@@ -444,9 +444,9 @@ public class NumberUtil {
         String strRMB = "" + rmb;
         DecimalFormat nf = new DecimalFormat("#.#");
         nf.setMaximumFractionDigits(2);
-        strRMB = nf.format(rmb).toString();
+        strRMB = nf.format(rmb);
         strRMB = numberToZH(strRMB, true);
-        if (strRMB.indexOf("点") >= 0) {
+        if (strRMB.contains("点")) {
             strRMB = strRMB + "零";
             strRMB = strRMB.replaceAll("点", "圆");
             String s1 = strRMB.substring(0, strRMB.indexOf("圆") + 1);
@@ -458,4 +458,8 @@ public class NumberUtil {
         return "人民币(大写):" + strRMB;
     }
 
+    public static void main(String[] args) {
+        System.out.println(numberToRMB(1254.32));
+    }
+
 }

+ 1 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/ElementBlock.java → blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ElementBlock.java

@@ -1,7 +1,6 @@
-package org.springblade.manager.formula;
+package org.springblade.manager.vo;
 
 import com.alibaba.fastjson.annotation.JSONField;
-import com.fasterxml.jackson.annotation.JsonIgnore;
 import lombok.Data;
 
 import java.util.List;

+ 100 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/EvaSummary.java

@@ -0,0 +1,100 @@
+package org.springblade.manager.vo;
+
+import lombok.Data;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.manager.dto.Coords;
+import org.springblade.manager.dto.ElementData;
+import org.springblade.manager.dto.FormData;
+import org.springblade.manager.entity.WbsTreeContract;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * @author yangyj
+ * @Date 2023/12/5 15:56
+ * @description TODO
+ */
+@Data
+public abstract class EvaSummary<T> {
+    protected  Integer  rowSize=20;
+    protected  final List<FormData> fds = new ArrayList<>();
+    protected  final List<T> datas = new ArrayList<>();
+    protected  Map<String, BiFunction<List<T>,Integer,List<Object>>> fm = new HashMap<>();
+    protected  final List<String> sqlList = new ArrayList<>();
+
+    public abstract Integer getPageNum();
+
+    public boolean initFdCoords(Map<String,String> tableKeysCoordsMap){
+        boolean f=true;
+        for(FormData fd:this.fds){
+            String coords =tableKeysCoordsMap.get(fd.getKey());
+            if(Func.isNotBlank(coords)){
+                fd.flushCoords(coords);
+            }else{
+                /*只要有一个元素的定位信息找不就算失败*/
+                f=false;
+            }
+        }
+        if(f) {
+            this.rowSize = this.fds.get(0).getCoordsList().size();
+        }
+        return f;
+    }
+
+    public  boolean flush( List<WbsTreeContract> wtcList,List<T> datas){
+        if(wtcList.size()>0) {
+            Map<Integer, List<T>> pages = IntStream.range(0, datas.size()).boxed()
+                    .collect(Collectors.groupingBy(
+                            i -> i / rowSize,
+                            LinkedHashMap::new,
+                            Collectors.mapping(datas::get, Collectors.toList())
+                    ));
+
+            for (Map.Entry<Integer, List<T>> entry : pages.entrySet()) {
+                int k = entry.getKey();
+                if (wtcList.size() <= k) {
+                    break;
+                }
+                List<T> v = entry.getValue();
+                WbsTreeContract w = wtcList.get(k);
+                StringBuilder  sb =new StringBuilder();
+                this.fds.forEach(fd -> {
+                    sb.append(putEd(k,v,fd,w)).append(StringPool.COMMA);
+                });
+                String template="update "+w.getInitTableName()+" set "+sb.deleteCharAt(sb.length()-1)+" where id ="+w.getPKeyId();
+                sqlList.add(template);
+            }
+
+            return true;
+        }
+        return false;
+    }
+
+    /**把数据放到对应元素*/
+    private String putEd(int pn, List<T> items, FormData fd, WbsTreeContract w){
+        BiFunction<List<T>,Integer,List<Object>> fc = this.fm.get(fd.getCode());
+        if(fc!=null) {
+            List<Object> data = fc.apply(items,pn);
+            List<ElementData> list = new ArrayList<>();
+            for (int i = 0; i < data.size(); i++) {
+                Coords c = fd.getCoordsList().get(i);
+                list.add(new ElementData(pn, 0, data.get(i), c.getX(), c.getY()));
+            }
+            fd.getValues().addAll(list);
+            return fd.getKey()+StringPool.EQUALS+StringPool.SINGLE_QUOTE+recovery(list)+StringPool.SINGLE_QUOTE;
+        }
+        return StringPool.EMPTY;
+    }
+
+    public String recovery(List<ElementData> dataList) {
+        if (Func.isNotEmpty(dataList)) {
+            return dataList.stream().filter(e -> !e.isEmpty()).map(e -> e.stringValue() + "_^_" + e.getY() + "_" + e.getX()).collect(Collectors.joining("☆"));
+        }
+        return StringPool.EMPTY;
+    }
+
+}

+ 52 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/FB01.java

@@ -0,0 +1,52 @@
+package org.springblade.manager.vo;
+
+import org.springblade.manager.dto.FormData;
+
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * @author yangyj
+ * @Date 2023/12/5 17:16
+ * @description 分部工程质量检验评定表1
+ */
+public class FB01 extends EvaSummary<Item01>{
+
+    private FormData number;
+    private FormData name;
+    private FormData level;
+    private FormData remark;
+    @Override
+    public Integer getPageNum() {
+        if(this.name!=null){
+            return  (this.getDatas().size()/this.name.getCoordsList().size())+1;
+        }
+        return null;
+    }
+    public FB01(List<FormData> processFds) {
+        for(FormData fd:processFds){
+            if(fd.getEName().contains("分项工程编号")){
+                this.number=fd;
+                this.fm.put(fd.getCode(),(List<Item01> l,Integer pn)->l.stream().map(Item01::getNumber).collect(Collectors.toList()));
+            }else if(fd.getEName().contains("分项工程名称")){
+                this.name=fd;
+                this.fm.put(fd.getCode(),(List<Item01> l,Integer pn)->l.stream().map(Item01::getName).collect(Collectors.toList()));
+            }else if(fd.getEName().contains("质量等级")){
+                this.level=fd;
+                this.fm.put(fd.getCode(),(List<Item01> l,Integer pn)->l.stream().map(Item01::getLevel).collect(Collectors.toList()));
+            }else if(fd.getEName().trim().equals("备注")){
+                this.remark=fd;
+                this.fm.put(fd.getCode(),(List<Item01> l,Integer pn)->l.stream().map(Item01::getRemark).collect(Collectors.toList()));
+            }
+            if(this.number!=null&&this.name!=null&&this.level!=null&&this.remark!=null){
+                /*匹配完成*/
+                this.fds.add(this.number);
+                this.fds.add(this.name);
+                this.fds.add(this.level);
+                this.fds.add(this.remark);
+                break;
+            }
+        }
+    }
+}

+ 31 - 112
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/FB02.java

@@ -1,15 +1,11 @@
 package org.springblade.manager.vo;
 
+import com.alibaba.fastjson.JSON;
 import lombok.Data;
-import org.springblade.core.tool.utils.Func;
-import org.springblade.core.tool.utils.StringPool;
-import org.springblade.manager.dto.Coords;
-import org.springblade.manager.dto.ElementData;
+import lombok.EqualsAndHashCode;
 import org.springblade.manager.dto.FormData;
-import org.springblade.manager.entity.WbsTreeContract;
-
+import org.springblade.manager.entity.FormulaDataBlock;
 import java.util.*;
-import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -18,47 +14,15 @@ import java.util.stream.IntStream;
  * @Date 2023/11/1 17:24
  * @description 评定表FB02
  */
+@EqualsAndHashCode(callSuper = true)
 @Data
-public class FB02 {
+public class FB02 extends EvaSummary<Item02>{
     private FormData subItem;
     private FormData sn;
     private FormData name;
     private FormData passRate;
     private FormData weight;
-    private Integer  rowSize=20;
-    private List<FormData> fds = new ArrayList<>();
-    private List<Item> datas = new ArrayList<>();
-    private Map<String, BiFunction<List<Item>,Integer,List<Object>>> fm = new HashMap<>();
-    private List<String> sqlList = new ArrayList<>();
-
-
-    @Data
-    public static class Item{
-        private String subItem;
-        private String name;
-        private Double passRate=100d;
-        private Integer weight=1;
-        public Item(String subItem, String name, Double passRate, Integer weight) {
-            this.subItem = subItem;
-            this.name = name;
-            this.passRate = passRate;
-            this.weight = weight;
-        }
-        public Item() {
-        }
-
-        public String getName() {
-            return name;
-        }
-
-        public void setName(String name) {
-            if(name!=null&&name.contains("△")){
-                this.weight=2;
-            }
-            this.name = name;
-        }
-    }
-
+  @Override
     public Integer getPageNum(){
         if(this.name!=null){
           return  (this.getDatas().size()/this.name.getCoordsList().size())+1;
@@ -66,23 +30,24 @@ public class FB02 {
         return null;
     }
 
+
     public FB02(List<FormData> processFds) {
          for(FormData fd:processFds){
               if(fd.getEName().contains("分项工程名称")){
                   this.subItem=fd;
-                  this.fm.put(fd.getCode(),(List<Item> l,Integer pn)->l.stream().map(Item::getSubItem).collect(Collectors.toList()));
+                  this.fm.put(fd.getCode(),(List<Item02> l,Integer pn)->l.stream().map(Item02::getSubItem).collect(Collectors.toList()));
               }else if(fd.getEName().contains("序号")){
                   this.sn=fd;
-                  this.fm.put(fd.getCode(),(List<Item> l,Integer pn)->IntStream.range(0, l.size()).boxed().map(i->i+pn*l.size()).collect(Collectors.toList()));
+                  this.fm.put(fd.getCode(),(List<Item02> l,Integer pn)->IntStream.range(0, l.size()).boxed().map(i->i+pn*l.size()).collect(Collectors.toList()));
               }else if(fd.getEName().contains("实测项目")){
                   this.name=fd;
-                  this.fm.put(fd.getCode(),(List<Item> l,Integer pn)->l.stream().map(Item::getName).collect(Collectors.toList()));
+                  this.fm.put(fd.getCode(),(List<Item02> l,Integer pn)->l.stream().map(Item02::getName).collect(Collectors.toList()));
               }else if(fd.getEName().contains("实测合格率")){
                   this.passRate=fd;
-                  this.fm.put(fd.getCode(),(List<Item> l,Integer pn)->l.stream().map(Item::getPassRate).collect(Collectors.toList()));
+                  this.fm.put(fd.getCode(),(List<Item02> l,Integer pn)->l.stream().map(Item02::getPassRate).collect(Collectors.toList()));
               }else if(fd.getEName().trim().equals("权值")){
                   this.weight=fd;
-                  this.fm.put(fd.getCode(),(List<Item> l,Integer pn)->l.stream().map(Item::getWeight).collect(Collectors.toList()));
+                  this.fm.put(fd.getCode(),(List<Item02> l,Integer pn)->l.stream().map(Item02::getWeight).collect(Collectors.toList()));
               }
               if(this.subItem!=null&&this.name!=null&&this.passRate!=null&&this.weight!=null&&this.sn!=null){
                   /*匹配完成*/
@@ -96,73 +61,27 @@ public class FB02 {
          }
     }
 
-    public void initFdCoords(Map<String,String> tableKeysCoordsMap){
-        boolean f=false;
-        for(FormData fd:this.fds){
-            String coords =tableKeysCoordsMap.get(fd.getKey());
-            if(Func.isNotBlank(coords)){
-                fd.flushCoords(coords);
-                f=true;
-            }
-        }
-        if(f) {
-            this.rowSize = this.fds.get(0).getCoordsList().size();
-        }
-    }
-
-    public boolean flush( List<WbsTreeContract> wtcList){
-        if(wtcList.size()>0) {
-            Map<Integer, List<Item>> pages = IntStream.range(0, datas.size()).boxed()
-                    .collect(Collectors.groupingBy(
-                            i -> i / rowSize,
-                            LinkedHashMap::new,
-                            Collectors.mapping(datas::get, Collectors.toList())
-                    ));
-
-            for (Map.Entry<Integer, List<Item>> entry : pages.entrySet()) {
-                int k = entry.getKey();
-                if (wtcList.size() <= k) {
-                    break;
+    public void source(List<FormulaDataBlock> formulaDataBlocks, Map<String,String> swNameMap ){
+        formulaDataBlocks.forEach(fdb -> {
+            List<ElementBlock> elementBlockList = JSON.parseArray(fdb.getVal(), ElementBlock.class);
+            elementBlockList.forEach(eb -> {
+                String name = eb.getEName();
+                Item02 xm = new Item02();
+                xm.setName(name);
+                xm.setSubItem(swNameMap.get(fdb.getSwId().toString()));
+                List<ItemBlock> ib = eb.getList();
+                int total = ib.stream().mapToInt(ItemBlock::getSubTotal).sum();
+                int pass = ib.stream().mapToInt(ItemBlock::getSubPass).sum();
+                if (total > 0) {
+                    double rate = (double) pass / (double) total;
+                    if (rate >= 0 && rate <= 100) {
+                        xm.setPassRate(rate);
+                    }
                 }
-                List<Item> v = entry.getValue();
-                WbsTreeContract w = wtcList.get(k);
-                StringBuilder  sb =new StringBuilder();
-                this.fds.forEach(fd -> {
-                    sb.append(putEd(k,v,fd,w)).append(StringPool.COMMA);
-                });
-                String template="update "+w.getInitTableName()+" set "+sb.deleteCharAt(sb.length()-1)+" where id ="+w.getPKeyId();
-                sqlList.add(template);
-            }
-
-            return true;
-        }
-       return false;
+                this.getDatas().add(xm);
+            });
+        });
     }
 
-   /**把数据放到对应元素*/
-    private String putEd(int pn,List<Item> items, FormData fd,WbsTreeContract w){
-        BiFunction<List<Item>,Integer,List<Object>> fc = this.fm.get(fd.getCode());
-        if(fc!=null) {
-            List<Object> data = fc.apply(items,pn);
-            List<ElementData> list = new ArrayList<>();
-            for (int i = 0; i < data.size(); i++) {
-                Coords c = fd.getCoordsList().get(i);
-                list.add(new ElementData(pn, 0, data.get(i), c.getX(), c.getY()));
-            }
-            fd.getValues().addAll(list);
-            return fd.getKey()+StringPool.EQUALS+StringPool.SINGLE_QUOTE+recovery(list)+StringPool.SINGLE_QUOTE;
-        }
-        return StringPool.EMPTY;
-    }
-
-    public String recovery(List<ElementData> dataList) {
-        if (Func.isNotEmpty(dataList)) {
-            return dataList.stream().filter(e -> !e.isEmpty()).map(e -> e.stringValue() + "_^_" + e.getY() + "_" + e.getX()).collect(Collectors.joining("☆"));
-        }
-        return StringPool.EMPTY;
-    }
-
-
-
 
 }

+ 16 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Item01.java

@@ -0,0 +1,16 @@
+package org.springblade.manager.vo;
+
+import lombok.Data;
+
+/**
+ * @author yangyj
+ * @Date 2023/12/5 17:10
+ * @description 分部工程质量评定表小项
+ */
+@Data
+public class Item01 {
+    private String number;
+    private String name ;
+    private String level;
+    private String remark;
+}

+ 24 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Item02.java

@@ -0,0 +1,24 @@
+package org.springblade.manager.vo;
+
+import lombok.Data;
+
+/**
+ * @author yangyj
+ * @Date 2023/12/5 16:19
+ * @description TODO
+ */
+@Data
+public class Item02 {
+    private String subItem;
+    private String name;
+    private Double passRate=100d;
+    private Integer weight=1;
+
+    public void setName(String name) {
+        if(name!=null&&name.contains("△")){
+            this.weight=2;
+        }
+        this.name = name;
+    }
+
+}

+ 4 - 4
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/ItemBlock.java → blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/ItemBlock.java

@@ -1,8 +1,8 @@
-package org.springblade.manager.formula;
+package org.springblade.manager.vo;
 
 import com.alibaba.fastjson.annotation.JSONField;
-import com.mixsmart.utils.StringUtils;
 import lombok.Data;
+import org.springblade.core.tool.utils.Func;
 
 import java.util.Arrays;
 import java.util.List;
@@ -51,13 +51,13 @@ public class ItemBlock {
     private String dev;
 
     public void setD(String d) {
-        if (StringUtils.isNotEmpty(d)) {
+        if (Func.isNotEmpty(d)) {
             this.data = Arrays.stream(d.split("/")).map(e -> Arrays.stream(e.split(",")).map(Double::parseDouble).collect(Collectors.toList())).collect(Collectors.toList());
         }
     }
 
     public void setS(String s) {
-        if (StringUtils.isNotEmpty(s)) {
+        if (Func.isNotEmpty(s)) {
             this.designs = Arrays.stream(s.split("/")).map(Double::parseDouble).collect(Collectors.toList());
         }
     }

+ 3 - 3
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemContract.java

@@ -60,10 +60,10 @@ public class MeterMidPayItemContract extends BaseEntity {
     @ApiModelProperty(value = "排序")
     private Integer sort;
 
-    @ApiModelProperty(value = "数据源类型 1=原始引用、2=新增")
-    private Integer dataSourceType;
-
     @ApiModelProperty(value = "备注")
     private String remarks;
 
+    @ApiModelProperty(value = "数据源id(项目级数据id)")
+    private Long dataSourceId;
+
 }

+ 3 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemProject.java

@@ -63,4 +63,7 @@ public class MeterMidPayItemProject extends BaseEntity {
     @ApiModelProperty(value = "备注")
     private String remarks;
 
+    @ApiModelProperty(value = "数据源id(系统级数据id)")
+    private Long dataSourceId;
+
 }

+ 4 - 0
blade-service-api/blade-user-api/src/main/java/org/springblade/system/user/feign/IUserClient.java

@@ -177,4 +177,8 @@ public interface IUserClient {
     @PostMapping(SAVE_USER_DTO)
     R<Boolean> saveUserDTO(@RequestBody UserDTO user);
 
+    /*删除合同段本地缓存*/
+    @GetMapping(API_PREFIX + "/deleteContractLocalCache")
+    void deleteContractLocalCache(@RequestParam String contractId);
+
 }

+ 6 - 2
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java

@@ -954,7 +954,9 @@ public class InformationQueryServiceImpl extends BaseServiceImpl<InformationQuer
                     }
 
                     //删除合同段本地缓存
-                    wbsTreeContractClient.deleteContractLocalCache(String.valueOf(sgId));
+                    //wbsTreeContractClient.deleteContractLocalCache(String.valueOf(sgId));
+                    /*剥离到user服务中*/
+                    userClient.deleteContractLocalCache(String.valueOf(sgId));
                 }
             }
 
@@ -979,7 +981,9 @@ public class InformationQueryServiceImpl extends BaseServiceImpl<InformationQuer
             }
 
             //删除合同段本地缓存
-            wbsTreeContractClient.deleteContractLocalCache(contractId);
+            //wbsTreeContractClient.deleteContractLocalCache(contractId);
+            /*剥离到user服务中*/
+            userClient.deleteContractLocalCache(contractId);
         }
     }
 

+ 5 - 5
blade-service/blade-business/src/main/java/org/springblade/business/service/impl/TaskServiceImpl.java

@@ -724,7 +724,7 @@ public class TaskServiceImpl extends BaseServiceImpl<TaskMapper, Task> implement
                     //  }
 
                     //return前先删除合同段树redis缓存
-                    informationQueryService.delAsyncWbsTree(masterTask.getContractId());
+                    /*informationQueryService.delAsyncWbsTree(masterTask.getContractId());*/
 
                     //返回电签成功的pdf路径,给试验用
                     return finalPdfUrl;
@@ -732,8 +732,8 @@ public class TaskServiceImpl extends BaseServiceImpl<TaskMapper, Task> implement
                     //只更新PDF路径
                     this.updateBusinessDataByFormDataId(masterTask, 1, eVisaStatus.contains("@@@@") ? eVisaStatus.split("@@@@")[1] : null, taskApprovalVO.getUserId());
 
-                    //return前先删除合同段树redis缓存
-                    informationQueryService.delAsyncWbsTree(masterTask.getContractId());
+                    /*//return前先删除合同段树redis缓存
+                    informationQueryService.delAsyncWbsTree(masterTask.getContractId());*/
 
                     return eVisaStatus.contains("@@@@") ? eVisaStatus.split("@@@@")[1] : null;
                 }
@@ -819,8 +819,8 @@ public class TaskServiceImpl extends BaseServiceImpl<TaskMapper, Task> implement
             this.abolishMessage(masterTask, currentLink, comment, taskApprovalVO.getNickName());
         }
 
-        //return前先删除合同段树redis缓存
-        informationQueryService.delAsyncWbsTree(masterTask.getContractId());
+        /*//return前先删除合同段树redis缓存
+        informationQueryService.delAsyncWbsTree(masterTask.getContractId());*/
 
         return "";
     }

+ 3 - 2
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/WbsTreeContractController.java

@@ -193,7 +193,8 @@ public class WbsTreeContractController extends BladeController {
      * @author liuyc
      * @date 2023年7月17日10:28:49
      */
-    @GetMapping("/lazyQueryContractWbsTree")
+    @Deprecated /*(已剥离到user服务)*/
+    /*@GetMapping("/lazyQueryContractWbsTree")
     @ApiOperationSupport(order = 9)
     @ApiOperation(value = "客户端懒加载获取合同段树(统计颜色、填报数量)")
     @ApiImplicitParams(value = {
@@ -203,7 +204,7 @@ public class WbsTreeContractController extends BladeController {
             @ApiImplicitParam(name = "contractIdRelation", value = "合同段关联id(监理、业主合同关联施工合同id)"),
             @ApiImplicitParam(name = "classifyType", value = "合同段区分,施工合同段=1,监理合同段=2"),
             @ApiImplicitParam(name = "tableOwner", value = "所属方节点权限,施工=1,监理=2,区分节点的数量、颜色")
-    })
+    })*/
     public R<List<WbsTreeContractLazyVO>> lazyQueryContractWbsTree(@RequestParam String primaryKeyId, @RequestParam String parentId, @RequestParam String contractId, @RequestParam String contractIdRelation, @RequestParam String classifyType, @RequestParam String tableOwner) {
         //这里是对应的监理合同段下,加载树时primaryKeyId=parentId;与前端对接时没沟通好入参,就不单独处理了,直接重新映射赋值一下
         if (StringUtils.isNotEmpty(primaryKeyId)) {

+ 0 - 3
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaDataBlockService.java

@@ -2,9 +2,6 @@ package org.springblade.manager.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.springblade.manager.entity.FormulaDataBlock;
-import org.springblade.manager.formula.ElementBlock;
-
-import java.util.List;
 
 /**
  * @author yangyj

+ 18 - 42
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java

@@ -17,7 +17,6 @@ import org.jsoup.nodes.Document;
 import org.springblade.common.utils.BaseUtils;
 import org.springblade.common.utils.CommonUtil;
 import org.springblade.common.utils.SnowFlakeUtil;
-import org.springblade.core.mp.base.BaseService;
 import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.*;
@@ -32,15 +31,11 @@ import org.springblade.manager.formula.impl.SubTable;
 import org.springblade.manager.formula.impl.TableElementConverter;
 import org.springblade.manager.mapper.FormulaMapper;
 import org.springblade.manager.service.*;
-import org.springblade.manager.vo.AppWbsTreeContractVO;
-import org.springblade.manager.vo.CurrentNode;
-import org.springblade.manager.vo.FB02;
-import org.springblade.manager.vo.WtcEva;
+import org.springblade.manager.vo.*;
 import org.springframework.context.annotation.Scope;
 import org.springframework.context.annotation.ScopedProxyMode;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
-import org.springframework.util.LinkedCaseInsensitiveMap;
 import org.springframework.web.context.WebApplicationContext;
 import javax.validation.constraints.NotNull;
 import java.util.*;
@@ -688,9 +683,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
             }
         }
         this.tec.getLog().put(FormulaLog.PARAM,result.entrySet().stream().map(p->logMap.get(p.getKey())+":"+p.getValue()).collect(Collectors.joining(";")));
-        /*元素动态绑定*/
         /*绑定节点参数公式*/
-      /*  total= new ArrayList<>(total.stream().collect(Collectors.toMap(WbsParam::getK, w -> w, (v1, v2) -> v2)).values());*/
         this.bindParamFormula(total);
         return result;
     }
@@ -2206,8 +2199,6 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     }
 
 
-
-
     public List<String> getCodeByEl(String el){
         List<String> l = new ArrayList<>();
         if(Func.isNotBlank(el)){
@@ -2219,6 +2210,7 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         }
         return l;
     }
+
     /**把计算结果放入固定常量集,创建key来引用*/
     public String putDataWithKey(Object data){
         String key ="HA"+HashUtil.identityHashCode(data);
@@ -2240,7 +2232,6 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         return target;
     }
 
-
     @Override
     public Map<String,Object> getElementInfoByCodes(String codes){
         if(StringUtils.isNotEmpty(codes)){
@@ -2436,39 +2427,24 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
                 LinkedList<String> treeCode = new LinkedList<>(FormulaUtils.treeCodeSplit(wtcEva.getTreeCode()));
                 List<FormulaDataBlock> formulaDataBlocks = this.getSqlList("select a.* from m_formula_data_block a join (select parent_id from m_wbs_tree_contract where tree_code like '" + treeCode.getLast() + "%' and contract_id =" + wtcEva.getContractId() + " and major_data_type=2 and is_deleted=0 ORDER BY tree_code)b on a.sw_id=b.parent_id ", FormulaDataBlock.class);
                 if (formulaDataBlocks!=null&&formulaDataBlocks.size() > 0) {
-                    /*获取分部工程名称*/
-/*                    WbsTreeContract node = this.wbsTreeContractService.getOne(Wrappers.<WbsTreeContract>lambdaQuery().eq(WbsTreeContract::getId, wtcEva.getParentId()).eq(WbsTreeContract::getContractId,wtcEva.getContractId()));
-                    WbsTreePrivate wtp = this.wtpId(node.getPKeyId());
-                    WbsTreePrivate publicWtp = this.getOriginWtp(wtp.getPKeyId());
-                    List<WbsParam> list= this.getNodeWps(publicWtp.getId(),wtp.getPKeyId());*/
-
-                    formulaDataBlocks.forEach(fdb -> {
-                        List<ElementBlock> elementBlockList = JSON.parseArray(fdb.getVal(), ElementBlock.class);
-                        elementBlockList.forEach(eb -> {
-                            String name = eb.getEName();
-                            FB02.Item xm = new FB02.Item();
-                            xm.setName(name);
-                            List<ItemBlock> ib = eb.getList();
-                            int total = ib.stream().mapToInt(ItemBlock::getSubTotal).sum();
-                            int pass = ib.stream().mapToInt(ItemBlock::getSubPass).sum();
-                            if (total > 0) {
-                                double rate = (double) pass / (double) total;
-                                if (rate >= 0 && rate <= 100) {
-                                    xm.setPassRate(rate);
-                                }
-                            }
-                            fb02.getDatas().add(xm);
-                        });
-                    });
+                    /*就是分项工程节点的父节点名称*/
+                    String swIds = formulaDataBlocks.stream().map(FormulaDataBlock::getSwId).map(Objects::toString).collect(Collectors.joining(","));
+                    List<Map<String,Object>> listMaps= this.jdbcTemplate.queryForList("select id ,node_name nodeName,full_name fullName  from m_wbs_tree_contract where is_deleted =0 and contract_id="+wtcEva.getContractId()+" and id in("+swIds+")");
+                    Map<String,String> swNameMap = new HashMap<>();
+                    if(listMaps.size()>0){
+                       swNameMap.putAll(listMaps.stream().collect(Collectors.toMap(m->m.get("id").toString(),m->m.get("fullName").toString(),(v1,v2)->v2)));
+                    }
+                     fb02.source(formulaDataBlocks,swNameMap);
                     if (fb02.getDatas().size() > 0) {
                         Map<String, String> tableKeysCoordsMap = FormulaUtils.getElementCell(wtcEva.getHtmlUrl());
-                        fb02.initFdCoords(tableKeysCoordsMap);
-                        /*根据数据长度,增减页数*/
-                        List<WbsTreeContract> wtcList = this.adjustPageSize(fb02.getPageNum(), wtcEva, processFds);
-                        if (fb02.flush(wtcList)) {
-                            fb02.getSqlList().forEach(System.out::println);
-                            //this.jdbcTemplate.execute(fb02.getSql());
-                            return R.success("成功");
+                        if(fb02.initFdCoords(tableKeysCoordsMap)) {
+                            /*根据数据长度,增减页数*/
+                            List<WbsTreeContract> wtcList = this.adjustPageSize(fb02.getPageNum(), wtcEva, processFds);
+                            if (fb02.flush(wtcList,fb02.getDatas())) {
+                                fb02.getSqlList().forEach(System.out::println);
+                                //this.jdbcTemplate.execute(fb02.getSql());
+                                return R.success("成功");
+                            }
                         }
                     }
                 }

+ 3 - 7
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/WbsTreeContractServiceImpl.java

@@ -68,10 +68,10 @@ import java.util.stream.Stream;
 @AllArgsConstructor
 public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractMapper, WbsTreeContract> implements IWbsTreeContractService {
 
-    static {
-        /*parallelStream并行流粒度*/
+    /*static {
+        *//*parallelStream并行流粒度*//*
         System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "4");
-    }
+    }*/
 
     private static final Logger logger = LoggerFactory.getLogger(WbsTreeContractServiceImpl.class);
     private final ConstructionLedgerFeignClient constructionLedgerFeign;
@@ -2792,10 +2792,6 @@ public class WbsTreeContractServiceImpl extends BaseServiceImpl<WbsTreeContractM
                 iterator_2.remove();
             }
         }
-
-        /*更新缓存,主动清理后,重新加载进本地缓存中*/
-        this.getNodeAll(contractId);
-
     }
 
 }

+ 20 - 3
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MidPayItemController.java

@@ -16,6 +16,7 @@ import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.BeanUtil;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.ObjectUtil;
 import org.springblade.meter.entity.*;
 import org.springblade.meter.service.MidPayItemContractService;
 import org.springblade.meter.service.MidPayItemProjectService;
@@ -132,6 +133,7 @@ public class MidPayItemController extends BladeController {
                 if (projectPay != null) {
                     projectPay.setId(SnowFlakeUtil.getId());
                     projectPay.setProjectId(projectId);
+                    projectPay.setDataSourceId(systemPay.getId());
                     meterMidPayItemProjects.add(projectPay);
                 }
             }
@@ -162,7 +164,14 @@ public class MidPayItemController extends BladeController {
     @ApiOperationSupport(order = 9)
     @ApiOperation(value = "项目中期支付项删除", notes = "传入id")
     public R<Object> projectRemove(@RequestParam String id) {
-        return R.data(payItemProjectService.removeById(id));
+        MeterMidPayItemProject obj = payItemProjectService.getById(id);
+        if (obj != null && ObjectUtil.isNotEmpty(obj.getDataSourceId())) {
+            /*修改回未被引用状态*/
+            if (payItemSystemService.update(Wrappers.<MeterMidPayItemSystem>lambdaUpdate().set(MeterMidPayItemSystem::getIsReferenced, 0).eq(MeterMidPayItemSystem::getId, obj.getDataSourceId()))) {
+                return R.data(payItemProjectService.removeById(id));
+            }
+        }
+        return R.fail("操作失败");
     }
 
     @PostMapping("/project/page")
@@ -222,6 +231,7 @@ public class MidPayItemController extends BladeController {
                     contractPay.setId(SnowFlakeUtil.getId());
                     contractPay.setProjectId(projectId);
                     contractPay.setContractId(contractId);
+                    contractPay.setDataSourceId(projectPay.getId());
                     meterMidPayItemContracts.add(contractPay);
                 }
             }
@@ -252,7 +262,14 @@ public class MidPayItemController extends BladeController {
     @ApiOperationSupport(order = 15)
     @ApiOperation(value = "合同段中期支付项删除", notes = "传入id")
     public R<Object> contractRemove(@RequestParam String id) {
-        return R.data(payItemContractService.removeById(id));
+        MeterMidPayItemContract obj = payItemContractService.getById(id);
+        if (obj != null && ObjectUtil.isNotEmpty(obj.getDataSourceId())) {
+            /*修改回未被引用状态*/
+            if (payItemProjectService.update(Wrappers.<MeterMidPayItemProject>lambdaUpdate().set(MeterMidPayItemProject::getIsReferenced, 0).eq(MeterMidPayItemProject::getId, obj.getDataSourceId()))) {
+                return R.data(payItemContractService.removeById(id));
+            }
+        }
+        return R.fail("操作失败");
     }
 
     @PostMapping("/contract/page")
@@ -276,7 +293,7 @@ public class MidPayItemController extends BladeController {
     @PostMapping("/bind/submit")
     @ApiOperationSupport(order = 18)
     @ApiOperation(value = "中期支付项添加汇总项", notes = "传入id、汇总项列表id逗号拼接成bindIds")
-    public R<Object> bind(@RequestParam String id, @RequestParam String bindIds) {
+    public R<Object> bindSubmit(@RequestParam String id, @RequestParam String bindIds) {
         if (StringUtils.isNotEmpty(id)) {
             if (StringUtils.isEmpty(bindIds)) {
                 /*删除*/

+ 212 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/bean/NodeVO.java

@@ -0,0 +1,212 @@
+package org.springblade.system.user.bean;
+
+import cn.hutool.core.util.ObjectUtil;
+import lombok.Data;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.stream.Collectors;
+
+/**
+ * @author liuyc
+ * @date 2023年9月27日16:46:43
+ * @description 计算节点颜色状态
+ */
+@Data
+public class NodeVO {
+
+    private Long pKeyId;
+    private Long id;
+    private Long parentId;
+    private List<NodeVO> children;
+    private Integer status;
+
+    /**
+     * DFS
+     *
+     * @param nodeList
+     * @param nodeParentGroupToIdMap
+     */
+    public static void calculateStatusToDFS(List<NodeVO> nodeList, Map<Long, NodeVO> nodeParentGroupToIdMap) {
+        NodeVO rootNode = findRootNode(nodeList);
+        if (rootNode != null) {
+            calculateNodeStatusToDFS(rootNode, nodeParentGroupToIdMap);
+        }
+    }
+
+    /**
+     * BFS
+     *
+     * @param nodeList
+     * @param nodeParentGroupToIdMap
+     */
+    public static void calculateStatusToBFS(List<NodeVO> nodeList, Map<Long, NodeVO> nodeParentGroupToIdMap) {
+        NodeVO rootNode = findRootNode(nodeList);
+        if (rootNode != null) {
+            calculateNodeStatusToBFS(rootNode);
+            Queue<NodeVO> queue = new LinkedList<>(rootNode.getChildren());
+            while (!queue.isEmpty()) {
+                NodeVO node = queue.poll();
+                if (node != null) {
+                    calculateNodeStatusToBFS(node);
+
+                    //将子节点逐个加入队列
+                    if (node.getChildren() != null) {
+                        queue.addAll(node.getChildren());
+                    }
+
+                    if (node.getParentId() != null) {
+                        NodeVO nodeVO = nodeParentGroupToIdMap.get(node.getParentId());
+                        if (nodeVO != null) {
+                            List<NodeVO> children = nodeVO.getChildren();
+                            if (children != null) {
+                                queue.addAll(children);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 获取根节点
+     *
+     * @param nodeList
+     * @return
+     */
+    private static NodeVO findRootNode(List<NodeVO> nodeList) {
+        for (NodeVO node : nodeList) {
+            if (node.getParentId().equals(0L)) {
+                return node;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * DFS 判断节点状态
+     *
+     * @param node
+     * @param nodeParentGroupToIdMap
+     */
+    private static void calculateNodeStatusToDFS(NodeVO node, Map<Long, NodeVO> nodeParentGroupToIdMap) {
+        //最底层节点直接返回
+        if (ObjectUtil.isEmpty(node.getChildren())) {
+            return;
+        }
+
+        //递归处理子节点
+        for (NodeVO child : node.getChildren()) {
+            calculateNodeStatusToDFS(child, nodeParentGroupToIdMap);
+        }
+
+        //判断子级
+        List<Integer> childStatusList = node.getChildren().stream().map(NodeVO::getStatus).collect(Collectors.toList());
+
+        //如果子节点都是相同的状态,则父节点状态也为该状态
+        if (childStatusList.stream().distinct().count() == 1) {
+            node.setStatus(childStatusList.get(0));
+            //更新父节点的状态
+            if (node.getParentId() != null) {
+                NodeVO parentNode = nodeParentGroupToIdMap.get(node.getParentId());
+                if (parentNode != null) {
+                    calculateNodeStatusToDFS(parentNode, nodeParentGroupToIdMap);
+                }
+            }
+            return;
+        }
+
+        //判断是否存在同时只存在1、3的情况
+        if (childStatusList.contains(1) && childStatusList.contains(3) && !childStatusList.contains(2)) {
+            node.setStatus(2);
+            return;
+        }
+
+        //判断是否存在同时只存在3、4的情况
+        if (childStatusList.contains(3) && childStatusList.contains(4) && !childStatusList.contains(1) && !childStatusList.contains(2)) {
+            node.setStatus(3);
+            return;
+        }
+
+        //判断是否存在只有1但不全是1的情况
+        if (childStatusList.contains(1) && !childStatusList.contains(2) && !childStatusList.contains(3) && !childStatusList.contains(4)) {
+            node.setStatus(2);
+            return;
+        }
+
+        //判断是否存在只有2但不全是2的情况
+        if (childStatusList.contains(2) && !childStatusList.contains(1) && !childStatusList.contains(3) && !childStatusList.contains(4)) {
+            node.setStatus(2);
+            return;
+        }
+
+        //判断是否存在只有3但不全是3的情况
+        if (childStatusList.contains(3) && !childStatusList.contains(1) && !childStatusList.contains(2) && !childStatusList.contains(4)) {
+            node.setStatus(3);
+            return;
+        }
+
+        //其他情况,父节点状态默认为2
+        node.setStatus(2);
+    }
+
+    /**
+     * BFS
+     *
+     * @param node
+     */
+    private static void calculateNodeStatusToBFS(NodeVO node) {
+        //最底层节点直接返回
+        if (ObjectUtil.isEmpty(node.getChildren())) {
+            return;
+        }
+
+        //判断子级
+        List<Integer> childStatusList = node.getChildren().stream().map(NodeVO::getStatus).collect(Collectors.toList());
+
+        //如果子节点都是相同的状态,则父节点状态也为该状态
+        if (childStatusList.stream().distinct().count() == 1) {
+            node.setStatus(childStatusList.get(0));
+            return;
+        }
+
+        //判断是否存在同时只存在1、3的情况
+        if (childStatusList.contains(1) && childStatusList.contains(3) && !childStatusList.contains(2)) {
+            node.setStatus(2);
+            return;
+        }
+
+        //判断是否存在同时只存在3、4的情况
+        if (childStatusList.contains(3) && childStatusList.contains(4) && !childStatusList.contains(1) && !childStatusList.contains(2)) {
+            node.setStatus(3);
+            return;
+        }
+
+        //判断是否存在只有1但不全是1的情况
+        if (childStatusList.contains(1) && !childStatusList.contains(2) && !childStatusList.contains(3) && !childStatusList.contains(4)) {
+            node.setStatus(2);
+            return;
+        }
+
+        //判断是否存在只有2但不全是2的情况
+        if (childStatusList.contains(2) && !childStatusList.contains(1) && !childStatusList.contains(3) && !childStatusList.contains(4)) {
+            node.setStatus(2);
+            return;
+        }
+
+        //判断是否存在只有3但不全是3的情况
+        if (childStatusList.contains(3) && !childStatusList.contains(1) && !childStatusList.contains(2) && !childStatusList.contains(4)) {
+            node.setStatus(3);
+            return;
+        }
+
+        //其他情况,父节点状态默认为2
+        node.setStatus(2);
+    }
+
+
+}

+ 107 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/controller/WbsTreeController.java

@@ -0,0 +1,107 @@
+package org.springblade.system.user.controller;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiImplicitParam;
+import io.swagger.annotations.ApiImplicitParams;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang.StringUtils;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.manager.vo.WbsTreeContractLazyVO;
+import org.springblade.system.user.service.IUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping(value = "/wbs")
+@AllArgsConstructor
+@Api(value = "WBS树接口", tags = "WBS树接口")
+public class WbsTreeController extends BladeController {
+
+    @Autowired
+    StringRedisTemplate redisTemplate;
+    private final IUserService iUserService;
+
+    /**
+     * 客户端懒加载获取合同段树(统计颜色、填报数量)
+     *
+     * @author liuyc
+     * @date 2023年12月6日09:52:54(接口剥离,从manager剥离到user,解决manager服务与mysql不在同一服务器中,网络延迟问题)
+     */
+    @GetMapping("/lazyQueryContractWbsTree")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "客户端懒加载获取合同段树(统计颜色、填报数量)")
+    @ApiImplicitParams(value = {
+            @ApiImplicitParam(name = "primaryKeyId"),
+            @ApiImplicitParam(name = "parentId", value = "父节点id,为空则查询第一级节点"),
+            @ApiImplicitParam(name = "contractId", value = "合同段id"),
+            @ApiImplicitParam(name = "contractIdRelation", value = "合同段关联id(监理、业主合同关联施工合同id)"),
+            @ApiImplicitParam(name = "classifyType", value = "合同段区分,施工合同段=1,监理合同段=2"),
+            @ApiImplicitParam(name = "tableOwner", value = "所属方节点权限,施工=1,监理=2,区分节点的数量、颜色")
+    })
+    public R<List<WbsTreeContractLazyVO>> lazyQueryContractWbsTree(@RequestParam String primaryKeyId, @RequestParam String parentId, @RequestParam String contractId, @RequestParam String contractIdRelation, @RequestParam String classifyType, @RequestParam String tableOwner) {
+        //这里是对应的监理合同段下,加载树时primaryKeyId=parentId;与前端对接时没沟通好入参,就不单独处理了,直接重新映射赋值一下
+        if (StringUtils.isNotEmpty(primaryKeyId)) {
+            parentId = primaryKeyId;
+        }
+
+        //结果集
+        List<WbsTreeContractLazyVO> vos;
+
+        //构造Redis缓存Key
+        String dataInfoId = "";
+        if (("1").equals(classifyType)) {
+            dataInfoId = contractId + "_" + parentId + "_" + classifyType + "_" + tableOwner;
+        } else if (("2").equals(classifyType)) {
+            //监理合同段下,classifyType=1,直接查询对应的施工树缓存
+            dataInfoId = contractIdRelation + "_" + parentId + "_" + "1" + "_" + tableOwner;
+        }
+
+        //获取Redis缓存信息
+        Object data = null;
+        if (ObjectUtil.isNotEmpty(dataInfoId)) {
+            if (("2").equals(classifyType) && ObjectUtil.isNotEmpty(contractIdRelation)) {
+                //监理根据contractIdRelation关联合同段id来判断获取缓存
+                data = redisTemplate.opsForValue().get("blade-manager::contract:wbstree:" + dataInfoId);
+            } else if (("1").equals(classifyType)) {
+                //施工直接获取缓存
+                data = redisTemplate.opsForValue().get("blade-manager::contract:wbstree:" + dataInfoId);
+            }
+        }
+
+        if (data != null) {
+            //返回缓存
+            vos = JSON.parseArray(data.toString(), WbsTreeContractLazyVO.class);
+
+        } else {
+            //响应结果集
+            vos = iUserService.lazyQueryContractWbsTree(parentId, contractId, contractIdRelation, tableOwner);
+
+            //存储缓存
+            if (vos != null && ObjectUtil.isNotEmpty(dataInfoId)) {
+                //监理根据contractIdRelation关联合同段id来判断存储缓存
+                if (("2").equals(classifyType) && ObjectUtil.isNotEmpty(contractIdRelation)) {
+                    JSONArray array = JSONArray.parseArray(JSON.toJSONString(vos));
+                    redisTemplate.opsForValue().set("blade-manager::contract:wbstree:" + dataInfoId, JSON.toJSON(array).toString());
+                } else if (("1").equals(classifyType)) {
+                    //施工直接存储缓存
+                    JSONArray array = JSONArray.parseArray(JSON.toJSONString(vos));
+                    redisTemplate.opsForValue().set("blade-manager::contract:wbstree:" + dataInfoId, JSON.toJSON(array).toString());
+                }
+            }
+        }
+        return R.data(vos);
+    }
+
+}

+ 5 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/feign/UserClient.java

@@ -140,5 +140,10 @@ public class UserClient implements IUserClient {
         return R.data(true);
     }
 
+    @Override
+    public void deleteContractLocalCache(String contractId) {
+        service.deleteContractLocalCache(contractId);
+    }
+
 
 }

+ 5 - 0
blade-service/blade-user/src/main/java/org/springblade/system/user/service/IUserService.java

@@ -23,6 +23,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.springblade.core.mp.base.BaseService;
 import org.springblade.core.mp.support.Query;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
+import org.springblade.manager.vo.WbsTreeContractLazyVO;
 import org.springblade.system.user.dto.UserDTO;
 import org.springblade.system.user.entity.User;
 import org.springblade.system.user.entity.UserInfo;
@@ -241,4 +242,8 @@ public interface IUserService extends BaseService<User> {
 
     List<User> selectUserAll();
 
+    List<WbsTreeContractLazyVO> lazyQueryContractWbsTree(String parentId, String contractId, String contractIdRelation, String tableOwner);
+
+    void deleteContractLocalCache(String contractId);
+
 }

+ 649 - 48
blade-service/blade-user/src/main/java/org/springblade/system/user/service/impl/UserServiceImpl.java

@@ -1,25 +1,7 @@
-/*
- *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions are met:
- *
- *  Redistributions of source code must retain the above copyright notice,
- *  this list of conditions and the following disclaimer.
- *  Redistributions in binary form must reproduce the above copyright
- *  notice, this list of conditions and the following disclaimer in the
- *  documentation and/or other materials provided with the distribution.
- *  Neither the name of the dreamlu.net developer nor the names of its
- *  contributors may be used to endorse or promote products derived from
- *  this software without specific prior written permission.
- *  Author: Chill 庄骞 (smallchill@163.com)
- */
 package org.springblade.system.user.service.impl;
 
-
-import cn.hutool.crypto.SecureUtil;
-import cn.hutool.crypto.digest.MD5;
-import com.alibaba.nacos.common.utils.MD5Utils;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
 import com.alibaba.nacos.common.utils.StringUtils;
 import com.baomidou.mybatisplus.core.conditions.Wrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -32,7 +14,6 @@ import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.mp.base.BaseServiceImpl;
 import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
-import org.springblade.core.secure.BladeUser;
 import org.springblade.core.secure.utils.AuthUtil;
 import org.springblade.core.tenant.BladeTenantProperties;
 import org.springblade.core.tool.api.R;
@@ -41,15 +22,17 @@ import org.springblade.core.tool.jackson.JsonUtil;
 import org.springblade.core.tool.support.Kv;
 import org.springblade.core.tool.utils.*;
 import org.springblade.manager.dto.SaveUserInfoByProjectDTO;
+import org.springblade.manager.entity.ContractInfo;
 import org.springblade.manager.feign.ContractClient;
-import org.springblade.manager.feign.SaveUserInfoByProjectClient;
+import org.springblade.manager.vo.WbsTreeContractLazyQueryInfoVO;
+import org.springblade.manager.vo.WbsTreeContractLazyVO;
 import org.springblade.system.cache.DictCache;
-import org.springblade.system.cache.ParamCache;
 import org.springblade.system.cache.SysCache;
 import org.springblade.system.entity.Dept;
 import org.springblade.system.entity.Tenant;
 import org.springblade.system.enums.DictEnum;
 import org.springblade.system.feign.ISysClient;
+import org.springblade.system.user.bean.NodeVO;
 import org.springblade.system.user.cache.UserCache;
 import org.springblade.system.user.dto.UserDTO;
 import org.springblade.system.user.entity.*;
@@ -59,37 +42,47 @@ import org.springblade.system.user.mapper.UserMapper;
 import org.springblade.system.user.service.IUserDeptService;
 import org.springblade.system.user.service.IUserOauthService;
 import org.springblade.system.user.service.IUserService;
-import org.springblade.system.user.vo.UserContractInfoVO;
 import org.springblade.system.user.vo.UserVO;
 import org.springblade.system.user.wrapper.UserWrapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
-import sun.security.rsa.RSASignature;
 
 import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
-import static org.springblade.common.constant.CommonConstant.DEFAULT_PARAM_PASSWORD;
-
-/**
- * 服务实现类
- *
- * @author Chill
- */
 @Service
 @AllArgsConstructor
 public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implements IUserService {
-    private static final String GUEST_NAME = "guest";
 
+    static {
+        /*parallelStream并行流粒度*/
+        System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism", "6");
+    }
+
+    /*存储当前合同段contractId对应的合同段树*/
+    private final Map<String, List<WbsTreeContractLazyVO>> localCacheNodes = new ConcurrentHashMap<>();
+    /*存储当前合同段contractId_tableOwner对应的资料查询信息*/
+    private final Map<String, List<WbsTreeContractLazyQueryInfoVO>> localCacheQueryInfos = new ConcurrentHashMap<>();
+    /*存储当前合同段contractId_tableOwner对应的节点数量统计缓存信息*/
+    private final Map<String, List<WbsTreeContractLazyVO>> localCacheParentCountNodes = new ConcurrentHashMap<>();
+
+    private static final String GUEST_NAME = "guest";
     private final IUserDeptService userDeptService;
     private final IUserOauthService userOauthService;
     private final ISysClient sysClient;
     private final BladeTenantProperties tenantProperties;
     private final ContractClient contractClient;
-    private final SaveUserInfoByProjectClient saveUserInfoByProjectClient;
     private final JdbcTemplate jdbcTemplate;
+    @Autowired
+    StringRedisTemplate redisTemplate;
 
 
     @Override
@@ -121,13 +114,13 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
         }
 
         // 内控修改  当为部门负责人不为空时 执行
-        if (StringUtil.isNotBlank(user.getIsLeader())){
+        if (StringUtil.isNotBlank(user.getIsLeader())) {
             // 由于一个部门只有一个负责人  需要修改 之前的部门负责人
-            String [] leader = Func.toStrArray(user.getIsLeader());
-            for (String deptId : leader){
-                String reData = deptId+",";
-                String eqSql = "update blade_user set is_leader = null where  is_leader='"+deptId+"' ";
-                String fiSql = "update blade_user set is_leader = REPLACE(is_leader,"+reData+",'') where  FIND_IN_SET('"+deptId+"',is_leader) ";
+            String[] leader = Func.toStrArray(user.getIsLeader());
+            for (String deptId : leader) {
+                String reData = deptId + ",";
+                String eqSql = "update blade_user set is_leader = null where  is_leader='" + deptId + "' ";
+                String fiSql = "update blade_user set is_leader = REPLACE(is_leader," + reData + ",'') where  FIND_IN_SET('" + deptId + "',is_leader) ";
                 jdbcTemplate.execute(eqSql);
                 jdbcTemplate.execute(fiSql);
             }
@@ -233,13 +226,13 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
             throw new ServiceException(StringUtil.format("当前用户 [{}] 已存在!", user.getAccount()));
         }
         // 内控修改  当为部门负责人不为空时 执行
-        if (StringUtil.isNotBlank(user.getIsLeader())){
+        if (StringUtil.isNotBlank(user.getIsLeader())) {
             // 由于一个部门只有一个负责人  需要修改 之前的部门负责人
-            String [] leader = Func.toStrArray(user.getIsLeader());
-            for (String deptId : leader){
-                String reData = deptId+",";
-                String eqSql = "update blade_user set is_leader = REPLACE(is_leader,'"+deptId+"','') where  FIND_IN_SET('"+deptId+"',is_leader) ";
-                String fiSql = "update blade_user set is_leader = REPLACE(is_leader,'"+reData+"','') where  FIND_IN_SET('"+deptId+"',is_leader) ";
+            String[] leader = Func.toStrArray(user.getIsLeader());
+            for (String deptId : leader) {
+                String reData = deptId + ",";
+                String eqSql = "update blade_user set is_leader = REPLACE(is_leader,'" + deptId + "','') where  FIND_IN_SET('" + deptId + "',is_leader) ";
+                String fiSql = "update blade_user set is_leader = REPLACE(is_leader,'" + reData + "','') where  FIND_IN_SET('" + deptId + "',is_leader) ";
                 jdbcTemplate.execute(fiSql);
                 jdbcTemplate.execute(eqSql);
             }
@@ -269,9 +262,9 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
     public boolean updateUserInfo(User user) {
 
         // 当为内控是 可以修改密码
-        if( StringUtils.isNotEmpty(user.getUserType()) && user.getUserType().equals("5") && StringUtil.isNotBlank(user.getPassword())){
+        if (StringUtils.isNotEmpty(user.getUserType()) && user.getUserType().equals("5") && StringUtil.isNotBlank(user.getPassword())) {
             user.setPassword(DigestUtil.encrypt(user.getPassword()));
-        }else{
+        } else {
             user.setPassword(null);
         }
         return updateById(user);
@@ -690,4 +683,612 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, User> implement
     public List<User> selectUserAll() {
         return baseMapper.selectList(Wrappers.<User>query().lambda());
     }
+
+    @Override
+    public List<WbsTreeContractLazyVO> lazyQueryContractWbsTree(String id, String contractId, String contractIdRelation, String tableOwner) {
+        if (org.apache.commons.lang.StringUtils.isEmpty(tableOwner)) {
+            throw new ServiceException("tableOwner is not null");
+        }
+        if (cn.hutool.core.util.ObjectUtil.isNotEmpty(contractId)) {
+            ContractInfo contractInfo = jdbcTemplate.query("SELECT contract_name,contract_type FROM m_contract_info WHERE id = " + contractId, new BeanPropertyRowMapper<>(ContractInfo.class)).stream().findAny().orElse(null);
+            if (contractInfo != null) {
+                /*施工合同段*/
+                if (new Integer(1).equals(contractInfo.getContractType())) {
+                    //获取当前层懒加载节点
+                    String sql = "select p_key_id,contract_id,(SELECT id FROM u_contract_tree_drawings where process_id = p_key_id and is_deleted = 0 limit 1) AS drawingsId,id,parent_id,node_type,type,wbs_type,is_concrete,major_data_type,partition_code,old_id,contract_id_relation,is_concealed_works_node,CASE (SELECT count(1) FROM u_tree_contract_first AS tcf WHERE tcf.is_deleted = 0 AND tcf.wbs_node_id = a.p_key_id) WHEN 0 THEN 'false' ELSE 'true' END AS isFirst,IFNULL(if(length(trim(full_name))>0,full_name,node_name),node_name) AS title,(SELECT CASE WHEN count(1) > 0 THEN 1 ELSE 0 END FROM m_wbs_tree_contract b WHERE b.parent_id = a.id AND b.type = 1 and b.status = 1 AND b.contract_id = " + contractId + " AND b.is_deleted = 0 ) AS hasChildren from m_wbs_tree_contract a where a.node_type != 111 and a.type = 1 and a.status = 1 and a.is_deleted = 0 and parent_id = " + (org.apache.commons.lang.StringUtils.isNotEmpty(id) ? id : 0) + " and contract_id = " + contractId + " ORDER BY a.sort,title,a.create_time";
+                    List<WbsTreeContractLazyVO> lazyNodes = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(WbsTreeContractLazyVO.class));
+                    if (lazyNodes.size() > 0) {
+
+                        /*根节点直接返回*/
+                        if (lazyNodes.stream().anyMatch(f -> f.getParentId().equals(0L))) {
+                            for (WbsTreeContractLazyVO lazyNode : lazyNodes) {
+                                lazyNode.setSubmitCounts(0);
+                                lazyNode.setColorStatus(2);
+                                lazyNode.setType(lazyNode.getNodeType());
+                                lazyNode.setNotExsitChild(!lazyNode.getHasChildren().equals(1));
+                                lazyNode.setPrimaryKeyId(lazyNode.getPKeyId());
+                                lazyNode.setTitle(contractInfo.getContractName());
+                            }
+                            return lazyNodes;
+                        }
+
+                        //获取本地缓存节点信息
+                        List<WbsTreeContractLazyVO> nodesAll = this.getNodeAll(contractId);
+                        List<WbsTreeContractLazyVO> distinctNodesAll = nodesAll.stream()
+                                .collect(Collectors.collectingAndThen(
+                                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(WbsTreeContractLazyVO::getPKeyId))),
+                                        ArrayList::new
+                                ));
+
+                        //所有最底层节点
+                        List<WbsTreeContractLazyVO> distinctLowestNodesAll = distinctNodesAll.stream().filter(f -> f.getHasChildren().equals(0)).collect(Collectors.collectingAndThen(
+                                Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(WbsTreeContractLazyVO::getPKeyId))),
+                                ArrayList::new
+                        ));
+
+                        //获取本地缓存资料信息
+                        List<WbsTreeContractLazyQueryInfoVO> queryInfoList = this.getQueryInfoList(contractId, tableOwner);
+
+                        Map<Long, Integer> queryInfoMaps = queryInfoList.stream().filter(f -> cn.hutool.core.util.ObjectUtil.isNotEmpty(f.getWbsId()))
+                                .collect(Collectors.toMap(WbsTreeContractLazyQueryInfoVO::getWbsId, WbsTreeContractLazyQueryInfoVO::getStatus, (existingValue, newValue) -> existingValue));
+                        List<Long> pKeyIdList = new ArrayList<>(queryInfoMaps.keySet());
+
+                        /*处理数量*/
+                        //填报过的所有最底层节点
+                        List<WbsTreeContractLazyVO> lowestNodesTB = distinctLowestNodesAll.parallelStream().filter(f -> pKeyIdList.contains(f.getPKeyId())).collect(Collectors.toList());
+                        List<Long> lowestNodeParentIdsTB = lowestNodesTB.parallelStream().map(WbsTreeContractLazyVO::getParentId).collect(Collectors.toList());
+                        //获取本地缓存节点数量统计
+                        List<WbsTreeContractLazyVO> resultParentNodesTB = this.getCachedParentCountNodes(contractId, lowestNodeParentIdsTB, nodesAll, tableOwner);
+
+                        /*List<WbsTreeContractLazyVO> resultParentNodesTB = new ArrayList<>();
+                        long startTime1 = System.currentTimeMillis();
+                        this.recursiveGetParentNodes(resultParentNodesTB, lowestNodeParentIdsTB, nodesAll);
+                        long endTime1 = System.currentTimeMillis();
+                        long executionTime1 = endTime1 - startTime1;
+                        System.out.println("合同段 " + contractId + " wbs节点树 数量计算 执行时间:" + executionTime1 + " ms");*/
+
+                        //最底层节点颜色构造后Map
+                        Map<Long, WbsTreeContractLazyVO> lowestNodesMap = lowestNodesTB.stream()
+                                .peek(vo -> {
+                                    Integer colorStatus = queryInfoMaps.get(vo.getPKeyId());
+                                    if (colorStatus != null) {
+                                        //任务状态0=未上报=颜色2蓝色、任务状态1=待审批=颜色3橙色、任务状态2=已审批=颜色4绿色、
+                                        ///*任务状态3=已废除=颜色1黑色*/(2023年10月16日13:59:00 已废除改为=颜色2蓝色)
+                                        vo.setColorStatus(colorStatus == 0 ? 2 : (colorStatus == 1 ? 3 : (colorStatus == 2 ? 4 : 2)));
+                                    } else {
+                                        //未填报的=颜色1黑色
+                                        vo.setColorStatus(1);
+                                    }
+                                }).collect(Collectors.toMap(WbsTreeContractLazyVO::getPKeyId, Function.identity()));
+
+                        /*处理颜色*/
+                        long startTime = System.currentTimeMillis();
+                        //先将WbsTreeContractLazyVO转为NodeVO
+                        List<NodeVO> nodeVOList = distinctNodesAll.stream().map(this::convertToNodeVO).collect(Collectors.toList());
+                        //转为Map<Long, NodeVO>
+                        Map<Long, NodeVO> nodeVOMap = nodeVOList.stream().collect(Collectors.toMap(NodeVO::getId, vo -> vo, (existing, replacement) -> existing));
+                        //把distinctNodesAll把所有节点转为树形结构,再转为List<NodeVO>对象
+                        List<NodeVO> treeNodeVOList = this.buildNodeTreeByStream(distinctNodesAll, lowestNodesMap);
+                        //处理节点颜色
+                        NodeVO.calculateStatusToDFS(treeNodeVOList, nodeVOMap);
+                        //把树形结构转为普通List集合
+                        List<NodeVO> nodeVOS = this.flattenTree(treeNodeVOList);
+                        //获取所有节点颜色Map
+                        Map<Long, Integer> nodeColorStatusMap = nodeVOS.stream().collect(Collectors.toMap(NodeVO::getPKeyId, NodeVO::getStatus, (existing, replacement) -> existing));
+                        long endTime = System.currentTimeMillis();
+                        long executionTime = endTime - startTime;
+                        System.out.println("合同段 " + contractId + " 处理颜色 执行时间:" + executionTime + " ms");
+
+                        /*处理最终结果集*/
+                        if (lazyNodes.size() > 0) {
+                            //处理填报数量
+                            Map<Long, Integer> countMap = new HashMap<>();
+                            for (WbsTreeContractLazyVO node : resultParentNodesTB) {
+                                Long key = node.getPKeyId();
+                                if (countMap.containsKey(key)) {
+                                    countMap.put(key, countMap.get(key) + 1);
+                                } else {
+                                    countMap.put(key, 1);
+                                }
+                            }
+
+                            //返回最终结果集
+                            for (WbsTreeContractLazyVO lazyNodeVO : lazyNodes) {
+                                lazyNodeVO.setType(lazyNodeVO.getNodeType()); //前端显示需要一样的,所以修改成一样的
+                                lazyNodeVO.setNotExsitChild(!lazyNodeVO.getHasChildren().equals(1));
+                                lazyNodeVO.setPrimaryKeyId(lazyNodeVO.getPKeyId());
+                                if (lazyNodeVO.getParentId() == 0L) {
+                                    if (cn.hutool.core.util.ObjectUtil.isNotEmpty(contractInfo.getContractName())) {
+                                        lazyNodeVO.setTitle(contractInfo.getContractName());
+                                    }
+                                }
+
+                                //设置数量
+                                lazyNodeVO.setSubmitCounts(cn.hutool.core.util.ObjectUtil.isNotEmpty(countMap.get(lazyNodeVO.getPKeyId())) ? countMap.get(lazyNodeVO.getPKeyId()) : (cn.hutool.core.util.ObjectUtil.isNotEmpty(queryInfoMaps.get(lazyNodeVO.getPKeyId())) ? 1 : 0));
+
+                                //设置颜色
+                                if (lazyNodeVO.getSubmitCounts().equals(0)) {
+                                    lazyNodeVO.setColorStatus(1);
+                                    continue;
+                                }
+
+                                Integer parentColorStatus = nodeColorStatusMap.get(lazyNodeVO.getPKeyId());
+                                if (parentColorStatus != null) {
+                                    lazyNodeVO.setColorStatus(parentColorStatus);
+                                } else {
+                                    WbsTreeContractLazyVO lowestNode = lowestNodesMap.get(lazyNodeVO.getPKeyId());
+                                    if (lowestNode != null) {
+                                        lazyNodeVO.setColorStatus(lowestNode.getColorStatus());
+                                    }
+                                }
+                            }
+                        }
+                        return lazyNodes;
+                    }
+
+                    /*监理、业主合同段*/
+                } else if (new Integer("2").equals(contractInfo.getContractType()) || new Integer("3").equals(contractInfo.getContractType())) {
+                    List<WbsTreeContractLazyVO> lazyNodesAll = new ArrayList<>();
+                    List<String> contractIds = new ArrayList<>();
+                    if (cn.hutool.core.util.ObjectUtil.isNotEmpty(contractIdRelation) && cn.hutool.core.util.ObjectUtil.isNotEmpty(id)) {
+                        //非根节点时选择加载施工合同段的树
+                        contractIds.add(contractIdRelation);
+                    } else {
+                        //根节点时默认加载所有施工合同段的树
+                        contractIds = this.contractClient.getProcessContractByJLContractId(contractId);
+                    }
+                    if (cn.hutool.core.util.ObjectUtil.isEmpty(contractIds) || contractIds.size() <= 0) {
+                        return null;
+                    }
+                    for (String sgContractId : contractIds) {
+                        ContractInfo sgContractInfo = jdbcTemplate.query("select contract_name from m_contract_info where id = " + sgContractId, new BeanPropertyRowMapper<>(ContractInfo.class)).stream().findAny().orElse(null);
+                        if (sgContractInfo != null) {
+                            List<WbsTreeContractLazyVO> lazyNodes = jdbcTemplate.query("select p_key_id,contract_id,(SELECT id FROM u_contract_tree_drawings where process_id = p_key_id and is_deleted = 0 limit 1) AS drawingsId,id,parent_id,node_type,type,wbs_type,major_data_type,partition_code,old_id,contract_id_relation,is_concealed_works_node,CASE (SELECT count(1) FROM u_tree_contract_first AS tcf WHERE tcf.is_deleted = 0 AND tcf.wbs_node_id = a.p_key_id) WHEN 0 THEN 'false' ELSE 'true' END AS isFirst,IFNULL(if(length(trim(full_name))>0,full_name,node_name),node_name) AS title,(SELECT CASE WHEN count(1) > 0 THEN 1 ELSE 0 END FROM m_wbs_tree_contract b WHERE b.parent_id = a.id AND  b.type = 1 and b.status = 1 AND b.contract_id = " + sgContractId + " AND b.is_deleted = 0 ) AS hasChildren from m_wbs_tree_contract a where a.node_type != 111 and a.type = 1 and a.status = 1 and a.is_deleted = 0 and parent_id = " + (org.apache.commons.lang.StringUtils.isNotEmpty(id) ? id : 0) + " and contract_id = " + sgContractId + " ORDER BY a.sort,title,a.create_time", new BeanPropertyRowMapper<>(WbsTreeContractLazyVO.class));
+                            if (lazyNodes.size() > 0) {
+
+                                if (lazyNodes.stream().anyMatch(f -> f.getParentId().equals(0L))) {
+                                    for (WbsTreeContractLazyVO lazyNode : lazyNodes) {
+                                        lazyNode.setSubmitCounts(0);
+                                        lazyNode.setColorStatus(2);
+                                        lazyNode.setType(lazyNode.getNodeType());
+                                        lazyNode.setNotExsitChild(!lazyNode.getHasChildren().equals(1));
+                                        lazyNode.setPrimaryKeyId(lazyNode.getPKeyId());
+                                        lazyNode.setContractIdRelation(sgContractId);
+                                        lazyNode.setTitle(sgContractInfo.getContractName());
+                                    }
+                                    return lazyNodes;
+                                }
+
+                                List<WbsTreeContractLazyVO> nodesAll = this.getNodeAll(sgContractId);
+                                List<WbsTreeContractLazyVO> distinctNodesAll = nodesAll.stream()
+                                        .collect(Collectors.collectingAndThen(
+                                                Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(WbsTreeContractLazyVO::getPKeyId))),
+                                                ArrayList::new
+                                        ));
+                                List<WbsTreeContractLazyVO> distinctLowestNodesAll = distinctNodesAll.stream().filter(f -> f.getHasChildren().equals(0)).collect(Collectors.collectingAndThen(
+                                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(WbsTreeContractLazyVO::getPKeyId))),
+                                        ArrayList::new
+                                ));
+
+                                List<WbsTreeContractLazyQueryInfoVO> queryInfoList = this.getQueryInfoList(sgContractId, tableOwner);
+
+                                Map<Long, Integer> queryInfoMaps = queryInfoList.stream().filter(f -> cn.hutool.core.util.ObjectUtil.isNotEmpty(f.getWbsId()))
+                                        .collect(Collectors.toMap(WbsTreeContractLazyQueryInfoVO::getWbsId, WbsTreeContractLazyQueryInfoVO::getStatus, (existingValue, newValue) -> existingValue));
+                                List<Long> pKeyIdList = new ArrayList<>(queryInfoMaps.keySet());
+
+                                List<WbsTreeContractLazyVO> lowestNodesTB = distinctLowestNodesAll.parallelStream().filter(f -> pKeyIdList.contains(f.getPKeyId())).collect(Collectors.toList());
+                                List<Long> lowestNodeParentIdsTB = lowestNodesTB.parallelStream().map(WbsTreeContractLazyVO::getParentId).collect(Collectors.toList());
+
+                                List<WbsTreeContractLazyVO> resultParentNodesTB = this.getCachedParentCountNodes(sgContractId, lowestNodeParentIdsTB, nodesAll, tableOwner);
+
+                                Map<Long, WbsTreeContractLazyVO> lowestNodesMap = lowestNodesTB.stream()
+                                        .peek(vo -> {
+                                            Integer colorStatus = queryInfoMaps.get(vo.getPKeyId());
+                                            if (colorStatus != null) {
+                                                vo.setColorStatus(colorStatus == 0 ? 2 : (colorStatus == 1 ? 3 : (colorStatus == 2 ? 4 : 2)));
+                                            } else {
+                                                vo.setColorStatus(1);
+                                            }
+                                        }).collect(Collectors.toMap(WbsTreeContractLazyVO::getPKeyId, Function.identity()));
+
+                                List<NodeVO> nodeVOList = distinctNodesAll.stream().map(this::convertToNodeVO).collect(Collectors.toList());
+                                Map<Long, NodeVO> nodeVOMap = nodeVOList.stream().collect(Collectors.toMap(NodeVO::getId, vo -> vo, (existing, replacement) -> existing));
+                                List<NodeVO> treeNodeVOList = this.buildNodeTreeByStream(distinctNodesAll, lowestNodesMap);
+                                NodeVO.calculateStatusToDFS(treeNodeVOList, nodeVOMap);
+                                List<NodeVO> nodeVOS = this.flattenTree(treeNodeVOList);
+                                Map<Long, Integer> nodeColorStatusMap = nodeVOS.stream().collect(Collectors.toMap(NodeVO::getPKeyId, NodeVO::getStatus, (existing, replacement) -> existing));
+
+                                if (lazyNodes.size() > 0) {
+                                    Map<Long, Integer> countMap = new HashMap<>();
+                                    for (WbsTreeContractLazyVO node : resultParentNodesTB) {
+                                        Long key = node.getPKeyId();
+                                        if (countMap.containsKey(key)) {
+                                            countMap.put(key, countMap.get(key) + 1);
+                                        } else {
+                                            countMap.put(key, 1);
+                                        }
+                                    }
+
+                                    for (WbsTreeContractLazyVO lazyNodeVO : lazyNodes) {
+                                        lazyNodeVO.setType(lazyNodeVO.getNodeType());
+                                        lazyNodeVO.setNotExsitChild(!lazyNodeVO.getHasChildren().equals(1));
+                                        lazyNodeVO.setPrimaryKeyId(lazyNodeVO.getPKeyId());
+                                        lazyNodeVO.setContractIdRelation(sgContractId);
+                                        if (lazyNodeVO.getParentId() == 0L) {
+                                            if (cn.hutool.core.util.ObjectUtil.isNotEmpty(sgContractInfo.getContractName())) {
+                                                lazyNodeVO.setTitle(sgContractInfo.getContractName());
+                                            }
+                                        }
+
+                                        lazyNodeVO.setSubmitCounts(cn.hutool.core.util.ObjectUtil.isNotEmpty(countMap.get(lazyNodeVO.getPKeyId())) ? countMap.get(lazyNodeVO.getPKeyId()) : (cn.hutool.core.util.ObjectUtil.isNotEmpty(queryInfoMaps.get(lazyNodeVO.getPKeyId())) ? 1 : 0));
+                                        if (lazyNodeVO.getSubmitCounts().equals(0)) {
+                                            lazyNodeVO.setColorStatus(1);
+                                            continue;
+                                        }
+
+                                        Integer parentColorStatus = nodeColorStatusMap.get(lazyNodeVO.getPKeyId());
+                                        if (parentColorStatus != null) {
+                                            lazyNodeVO.setColorStatus(parentColorStatus);
+                                        } else {
+                                            WbsTreeContractLazyVO lowestNode = lowestNodesMap.get(lazyNodeVO.getPKeyId());
+                                            if (lowestNode != null) {
+                                                lazyNodeVO.setColorStatus(lowestNode.getColorStatus());
+                                            }
+                                        }
+                                    }
+                                }
+                                lazyNodesAll.addAll(lazyNodes);
+                            }
+                        }
+                    }
+                    return lazyNodesAll;
+                }
+            }
+        }
+        return null;
+    }
+
+    /*非批量电签时,清理缓存,如资料填报保存、任务上报等*/
+    @Override
+    public void deleteContractLocalCache(String contractId) {
+        /*删除节点缓存*/
+        localCacheNodes.remove(contractId);
+
+        /*删除资料缓存*/
+        Iterator<Map.Entry<String, List<WbsTreeContractLazyQueryInfoVO>>> iterator_1 = localCacheQueryInfos.entrySet().iterator();
+        while (iterator_1.hasNext()) {
+            Map.Entry<String, List<WbsTreeContractLazyQueryInfoVO>> entry = iterator_1.next();
+            String cacheKey = entry.getKey();
+            if (cacheKey.startsWith(contractId + "_")) {
+                iterator_1.remove();
+            }
+        }
+
+        /*删除节点计算统计缓存*/
+        Iterator<Map.Entry<String, List<WbsTreeContractLazyVO>>> iterator_2 = localCacheParentCountNodes.entrySet().iterator();
+        while (iterator_2.hasNext()) {
+            Map.Entry<String, List<WbsTreeContractLazyVO>> entry = iterator_2.next();
+            String cacheKey = entry.getKey();
+            if (cacheKey.startsWith(contractId + "_")) {
+                iterator_2.remove();
+            }
+        }
+    }
+
+    /*定时清理缓存(本地+Redis)信息,不在批量电签时删除,批量电签是多线程,会一直删除,导致缓存基本不生效*/
+    @Scheduled(fixedRate = 120000) //每2分钟执行一次
+    public void clearContractLocalCacheAndRedisCache() {
+        //获取锁
+        ReentrantLock lock = new ReentrantLock();
+        lock.lock();
+        try {
+            //清除本地缓存
+            localCacheNodes.clear();
+            localCacheQueryInfos.clear();
+            localCacheParentCountNodes.clear();
+
+            //清除RedisWbs树节点缓存
+            Set<String> keysByNodes = redisTemplate.keys("blade-manager::contract:wbstree:*");
+            if (keysByNodes != null) {
+                redisTemplate.delete(keysByNodes);
+            }
+        } finally {
+            //释放锁
+            lock.unlock();
+            System.out.println("定时执行任务:{clearContractLocalCacheAndRedisCache} 完成 ... ");
+        }
+    }
+
+    /**
+     * 获取当前合同段所有节点缓存
+     *
+     * @param contractId
+     * @return
+     */
+    public List<WbsTreeContractLazyVO> getNodeAll(String contractId) {
+        //获取本地缓存
+        List<WbsTreeContractLazyVO> nodesAll = localCacheNodes.get(contractId);
+
+        if (nodesAll == null || nodesAll.isEmpty()) {
+            //从Redis获取数据
+            Object data = redisTemplate.opsForValue().get("blade-manager::contract:wbstree:" + contractId);
+            if (data != null) {
+                nodesAll = JSON.parseArray(data.toString(), WbsTreeContractLazyVO.class);
+                //更新本地缓存
+                localCacheNodes.put(contractId, nodesAll);
+            } else {
+                //返回数据库数据
+                long startTime = System.currentTimeMillis();
+
+                /*分页查询,每次5000条*/
+                int pageSize = 5000;
+                int pageNumber = 1;
+                int offset;
+
+                nodesAll = new ArrayList<>();
+                List<WbsTreeContractLazyVO> nodesAllPage;
+                do {
+                    offset = (pageNumber - 1) * pageSize;
+                    nodesAllPage = jdbcTemplate.query(
+                            "SELECT p_key_id, id, parent_id FROM m_wbs_tree_contract " +
+                                    "WHERE type = 1 " +
+                                    "AND status = 1 " +
+                                    "AND is_deleted = 0 " +
+                                    "AND contract_id = ? " +
+                                    "LIMIT ? OFFSET ?",
+                            new Object[]{contractId, pageSize, offset},
+                            new BeanPropertyRowMapper<>(WbsTreeContractLazyVO.class)
+                    );
+                    nodesAll.addAll(nodesAllPage);
+                    pageNumber++;
+                } while (nodesAllPage.size() == pageSize);
+
+                long endTime = System.currentTimeMillis();
+                long executionTime = endTime - startTime;
+                System.out.println("合同段 " + contractId + " 查询所有wbs节点树 执行时间:" + executionTime + " ms");
+
+                if (nodesAll.size() > 0) {
+                    //判断是否有子级,赋值
+                    Map<Long, List<WbsTreeContractLazyVO>> groupedByParentId = nodesAll.stream().collect(Collectors.groupingBy(WbsTreeContractLazyVO::getParentId));
+                    for (WbsTreeContractLazyVO vo : nodesAll) {
+                        if (vo.getParentId() == 0) {
+                            vo.setHasChildren(1);
+                        }
+                        List<WbsTreeContractLazyVO> childNodes = groupedByParentId.getOrDefault(vo.getId(), null);
+                        if (childNodes != null && childNodes.size() > 0) {
+                            vo.setHasChildren(1);
+                        } else {
+                            vo.setHasChildren(0);
+                        }
+                    }
+
+                    //存储到Redis中
+                    JSONArray array = JSONArray.parseArray(JSON.toJSONString(nodesAll));
+                    redisTemplate.opsForValue().set("blade-manager::contract:wbstree:" + contractId, JSON.toJSON(array).toString());
+
+                    //更新本地缓存
+                    localCacheNodes.put(contractId, nodesAll);
+                }
+            }
+        }
+        return nodesAll;
+    }
+
+    /**
+     * 获取节点数量统计缓存
+     *
+     * @param contractId
+     * @param lowestNodeParentIdsTB
+     * @param nodesAll
+     * @return
+     */
+    public List<WbsTreeContractLazyVO> getCachedParentCountNodes(String contractId, List<Long> lowestNodeParentIdsTB, List<WbsTreeContractLazyVO> nodesAll, String tableOwner) {
+        //从本地缓存获取数据
+        String cacheKey = contractId + "_" + tableOwner;
+        List<WbsTreeContractLazyVO> resultParentNodesTB = localCacheParentCountNodes.get(cacheKey);
+
+        if (resultParentNodesTB == null || resultParentNodesTB.isEmpty()) {
+
+            //从Redis获取数据
+            Object data = redisTemplate.opsForValue().get("blade-manager::contract:wbstree:byParentCountNodes:" + cacheKey);
+            if (data != null) {
+                resultParentNodesTB = JSON.parseArray(data.toString(), WbsTreeContractLazyVO.class);
+                //更新本地缓存
+                localCacheParentCountNodes.put(cacheKey, resultParentNodesTB);
+
+            } else {
+                /*重新计算,进行递归获取父节点计数统计*/
+                resultParentNodesTB = new ArrayList<>();
+                long startTime = System.currentTimeMillis();
+
+                this.recursiveGetParentNodes(resultParentNodesTB, lowestNodeParentIdsTB, nodesAll);
+
+                long endTime = System.currentTimeMillis();
+                long executionTime = endTime - startTime;
+                System.out.println("合同段 " + contractId + " wbs节点树 数量计算 执行时间:" + executionTime + " ms");
+
+                if (resultParentNodesTB.size() > 0) {
+                    //存储到Redis中
+                    JSONArray array = JSONArray.parseArray(JSON.toJSONString(resultParentNodesTB));
+                    redisTemplate.opsForValue().set("blade-manager::contract:wbstree:byParentCountNodes:" + cacheKey, JSON.toJSON(array).toString());
+
+                    //更新本地缓存
+                    localCacheParentCountNodes.put(cacheKey, resultParentNodesTB);
+                }
+            }
+        }
+
+        return resultParentNodesTB;
+    }
+
+    /**
+     * 构造从最底层节点获取他的所有父级,并统计出现次数(父级Id=子级parentId)
+     *
+     * @param result              结果集
+     * @param lowestNodeParentIds 最底层节点ParentIds
+     * @param nodesAll            所有节点
+     */
+    public void recursiveGetParentNodes(List<WbsTreeContractLazyVO> result, List<Long> lowestNodeParentIds, List<WbsTreeContractLazyVO> nodesAll) {
+        if (lowestNodeParentIds.isEmpty()) {
+            return;
+        }
+
+        Map<Long, Long> parentIdGroup = lowestNodeParentIds.stream()
+                .collect(Collectors.groupingByConcurrent(Function.identity(), Collectors.counting()));
+
+        List<WbsTreeContractLazyVO> collectedNodes = parentIdGroup.entrySet().parallelStream()
+                .flatMap(entry -> {
+                    List<WbsTreeContractLazyVO> nodes = nodesAll.stream()
+                            .filter(f -> entry.getKey().equals(f.getId()))
+                            .collect(Collectors.toList());
+
+                    if (entry.getValue() > 1L) {
+                        nodes = nodes.stream().limit(1)
+                                .flatMap(node -> Collections.nCopies(entry.getValue().intValue(), node).stream())
+                                .collect(Collectors.toList());
+                    }
+
+                    return nodes.stream();
+                })
+                .collect(Collectors.toList());
+
+        List<Long> collect = collectedNodes.stream()
+                .map(WbsTreeContractLazyVO::getParentId)
+                .collect(Collectors.toList());
+
+        if (!collect.isEmpty()) {
+            result.addAll(collectedNodes);
+            this.recursiveGetParentNodes(result, collect, nodesAll);
+        }
+    }
+
+    /**
+     * 获取当前合同段所有填报资料缓存信息
+     *
+     * @param contractId
+     * @param tableOwner
+     * @return
+     */
+    public List<WbsTreeContractLazyQueryInfoVO> getQueryInfoList(String contractId, String tableOwner) {
+        //从本地缓存获取
+        String cacheKey = contractId + "_" + tableOwner;
+        List<WbsTreeContractLazyQueryInfoVO> cachedQueryInfoList = localCacheQueryInfos.get(cacheKey);
+
+        if (cachedQueryInfoList != null && !cachedQueryInfoList.isEmpty()) {
+            return cachedQueryInfoList;
+        }
+
+        //从Redis获取数据
+        Object dataInformationQuery = redisTemplate.opsForValue().get("blade-manager::contract:wbstree:byInformationQuery:" + cacheKey);
+
+        List<WbsTreeContractLazyQueryInfoVO> queryInfoList = new ArrayList<>();
+        if (dataInformationQuery != null) {
+            queryInfoList = JSON.parseArray(dataInformationQuery.toString(), WbsTreeContractLazyQueryInfoVO.class);
+            //更新本地缓存
+            localCacheQueryInfos.put(cacheKey, queryInfoList);
+
+        } else {
+            //返回数据库数据
+            long startTime = System.currentTimeMillis();
+
+            /*分页查询,每次5000条*/
+            int pageSize = 5000;
+            int pageNumber = 1;
+            int offset;
+
+            List<WbsTreeContractLazyQueryInfoVO> queryInfoListPage;
+
+            do {
+                offset = (pageNumber - 1) * pageSize;
+                queryInfoListPage = jdbcTemplate.query(
+                        "SELECT wbs_id, status FROM u_information_query " +
+                                "WHERE type = 1 " +
+                                "AND contract_id = ? " +
+                                "AND classify = ? " +
+                                "LIMIT ? OFFSET ?",
+                        new Object[]{contractId, tableOwner, pageSize, offset},
+                        new BeanPropertyRowMapper<>(WbsTreeContractLazyQueryInfoVO.class));
+                queryInfoList.addAll(queryInfoListPage);
+                pageNumber++;
+            } while (queryInfoListPage.size() == pageSize);
+
+            long endTime = System.currentTimeMillis();
+            long executionTime = endTime - startTime;
+            System.out.println("合同段 " + contractId + " 查询所有资料信息 执行时间:" + executionTime + " ms");
+
+            if (queryInfoList.size() > 0) {
+                JSONArray array = JSONArray.parseArray(JSON.toJSONString(queryInfoList));
+
+                //存储到Redis中
+                redisTemplate.opsForValue().set("blade-manager::contract:wbstree:byInformationQuery:" + cacheKey, JSON.toJSON(array).toString());
+
+                //更新本地缓存
+                localCacheQueryInfos.put(cacheKey, queryInfoList);
+            }
+        }
+
+        return queryInfoList;
+    }
+
+    //转换VO
+    public NodeVO convertToNodeVO(WbsTreeContractLazyVO wbsTreeContractLazyVO) {
+        NodeVO nodeVO = new NodeVO();
+        nodeVO.setId(wbsTreeContractLazyVO.getId());
+        nodeVO.setParentId(wbsTreeContractLazyVO.getParentId());
+        nodeVO.setPKeyId(wbsTreeContractLazyVO.getPKeyId());
+        nodeVO.setStatus(cn.hutool.core.util.ObjectUtil.isNotEmpty(wbsTreeContractLazyVO.getColorStatus()) ? wbsTreeContractLazyVO.getColorStatus() : 1);
+        return nodeVO;
+    }
+
+    /**
+     * 构造树形结构数据 (解决节点颜色问题)
+     *
+     * @param distinctNodesAll 去重后所有节点数据
+     * @return
+     */
+    public List<NodeVO> buildNodeTreeByStream(List<WbsTreeContractLazyVO> distinctNodesAll,
+                                              Map<Long, WbsTreeContractLazyVO> lowestNodesMap) {
+        List<WbsTreeContractLazyVO> list = distinctNodesAll.stream().filter(f -> f.getParentId().equals(0L)).collect(Collectors.toList());
+        Map<Long, List<WbsTreeContractLazyVO>> map = distinctNodesAll.stream().collect(Collectors.groupingBy(WbsTreeContractLazyVO::getParentId));
+        return recursionFnNodeTree(list, map, lowestNodesMap);
+    }
+
+    public List<NodeVO> recursionFnNodeTree(List<WbsTreeContractLazyVO> list, Map<Long, List<WbsTreeContractLazyVO>> map,
+                                            Map<Long, WbsTreeContractLazyVO> lowestNodesMap) {
+        List<NodeVO> result = new ArrayList<>();
+        for (WbsTreeContractLazyVO vo : list) {
+            if (vo.getHasChildren().equals(0)) {
+                WbsTreeContractLazyVO lowestNodeVO = lowestNodesMap.getOrDefault(vo.getPKeyId(), null);
+                if (lowestNodeVO != null && cn.hutool.core.util.ObjectUtil.isNotEmpty(lowestNodeVO.getColorStatus())) {
+                    //最底层颜色初始化
+                    vo.setColorStatus(lowestNodeVO.getColorStatus());
+                }
+            } else {
+                //非最底层节点,颜色默认=1黑色
+                vo.setColorStatus(1);
+            }
+            //转换为NodeVO
+            NodeVO nodeVO = convertToNodeVO(vo);
+            List<WbsTreeContractLazyVO> childrenList = map.get(vo.getId());
+            if (childrenList != null && !childrenList.isEmpty()) {
+                nodeVO.setChildren(recursionFnNodeTree(childrenList, map, lowestNodesMap));
+            }
+            result.add(nodeVO);
+        }
+        return result;
+    }
+
+    //把树形结构转为普通List
+    public List<NodeVO> flattenTree(List<NodeVO> tree) {
+        List<NodeVO> result = new ArrayList<>();
+        for (NodeVO node : tree) {
+            result.add(node);
+            if (node.getChildren() != null && !node.getChildren().isEmpty()) {
+                result.addAll(flattenTree(node.getChildren()));
+                node.setChildren(null);
+            }
+        }
+        return result;
+    }
+
 }