Pārlūkot izejas kodu

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

huangtf 1 gadu atpakaļ
vecāks
revīzija
d7d97d5cd3
74 mainītis faili ar 3305 papildinājumiem un 257 dzēšanām
  1. 27 0
      blade-common/src/main/java/org/springblade/common/utils/BaseUtils.java
  2. 4 1
      blade-ops/blade-swagger/src/main/resources/application-dev.yml
  3. 5 1
      blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/TaskClient.java
  4. 6 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ProjectInfo.java
  5. 16 0
      blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/vo/Redo.java
  6. 18 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractDTO.java
  7. 30 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractSaveBatchDTO.java
  8. 21 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractSaveDTO.java
  9. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/test.java
  10. 1 1
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/ChangeTokenInventory.java
  11. 6 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/ContractInventoryForm.java
  12. 66 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterContractInfo.java
  13. 63 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemContract.java
  14. 60 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemProject.java
  15. 19 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemRelation.java
  16. 57 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemSystem.java
  17. 92 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeContract.java
  18. 57 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeProject.java
  19. 45 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeSystem.java
  20. 18 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeTemplateInfo.java
  21. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/test.java
  22. 12 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/feign/MeterTreeSystemClient.java
  23. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/feign/test.java
  24. 6 1
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/ContractInventoryFormVO.java
  25. 8 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/FormTreeVO.java
  26. 40 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/InventoryFormDetailVO.java
  27. 18 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/MeterTreeContractVO.java
  28. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/test.java
  29. 6 0
      blade-service/blade-business/src/main/java/org/springblade/business/feignClient/TaskClientImpl.java
  30. 7 2
      blade-service/blade-business/src/main/java/org/springblade/business/service/impl/InformationQueryServiceImpl.java
  31. 9 0
      blade-service/blade-manager/pom.xml
  32. 141 31
      blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java
  33. 364 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/FormulaExecutor.java
  34. 5 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/FormulaHandleChain.java
  35. 0 8
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/ITable.java
  36. 32 17
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/ITurnPointCalculator.java
  37. 16 6
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/LevelInfo.java
  38. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/NodeTable.java
  39. 5 1
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/TurnPoint.java
  40. 3 5
      blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/TableElementConverter.java
  41. 4 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaService.java
  42. 3 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/IWbsParamService.java
  43. 1 0
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java
  44. 14 9
      blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java
  45. 5 0
      blade-service/blade-meter/pom.xml
  46. 36 27
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/ContractInventoryFormController.java
  47. 26 43
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/ContractMaterialController.java
  48. 677 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MeterTreeController.java
  49. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/test.java
  50. 11 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/feign/MeterTreeSystemClientImpl.java
  51. 10 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/ContractInventoryFormMapper.java
  52. 25 2
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/ContractInventoryFormMapper.xml
  53. 13 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeContractMapper.java
  54. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeContractMapper.xml
  55. 13 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeProjectMapper.java
  56. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeProjectMapper.xml
  57. 13 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeSystemMapper.java
  58. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeSystemMapper.xml
  59. 8 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeTemplateInfoMapper.java
  60. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeTemplateInfoMapper.xml
  61. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/test.java
  62. 14 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/IContractInventoryFormService.java
  63. 20 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeContractService.java
  64. 12 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeProjectService.java
  65. 10 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeSystemService.java
  66. 8 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeTemplateInfoService.java
  67. 296 66
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/ContractInventoryFormServiceImpl.java
  68. 432 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeContractServiceImpl.java
  69. 321 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeProjectServiceImpl.java
  70. 17 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeSystemServiceImpl.java
  71. 12 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeTemplateInfoServiceImpl.java
  72. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/test.java
  73. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/test.java
  74. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/utils/test.java

+ 27 - 0
blade-common/src/main/java/org/springblade/common/utils/BaseUtils.java

@@ -299,7 +299,34 @@ public class BaseUtils {
         return result;
     }
 
+    /*是否包含链*/
+     public static boolean notInChain(List<String> cp,String s){
+        if(cp!=null&& isNotEmpty(s)){
+            if(cp.size()==0){
+                /*空结合*/
+                return true;
+            }else{
+                /*没有一个已知路径包含*/
+                return cp.stream().noneMatch(c->c.contains(s));
+            }
 
+        }
+        return false;
+     }
+
+    public static boolean inChain(List<String> cp,String s){
+        if(cp!=null&& isNotEmpty(s)){
+            if(cp.size()==0){
+                /*空结合*/
+                return true;
+            }else{
+                /*没有一个已知路径包含*/
+                return cp.stream().anyMatch(s::contains);
+            }
+
+        }
+        return false;
+    }
 
 
 

+ 4 - 1
blade-ops/blade-swagger/src/main/resources/application-dev.yml

@@ -27,4 +27,7 @@ knife4j:
 #        location: /blade-control/v2/api-docs
       - name: 征拆接口
         uri: 127.0.0.1:8090
-        location: /blade-land/v2/api-docs
+        location: /blade-land/v2/api-docs
+      - name: 计量接口
+        uri: 127.0.0.1:8090
+        location: /blade-meter/v2/api-docs

+ 5 - 1
blade-service-api/blade-business-api/src/main/java/org/springblade/business/feign/TaskClient.java

@@ -36,7 +36,7 @@ public interface TaskClient {
     String SAVE_TASK_PARALLEL = API_PREFIX + "/save_task_parallel";
     String QUERY_USER_TASK = API_PREFIX + "/query-user-task";
     String DELETE_USER_TASK = API_PREFIX + "/delete-user-task";
-
+    String BATCH_RE_SIGN = API_PREFIX + "/re-sign";
     /**
      * 保存任务
      */
@@ -116,4 +116,8 @@ public interface TaskClient {
     @PostMapping(ABOLISH_TASK)
     R<Boolean> abolishTask(@RequestBody Task task);
 
+    /**重签*/
+    @PostMapping(BATCH_RE_SIGN)
+    R<Boolean> reSigningEVisa(@RequestParam String taskIds, @RequestParam String contractId,@RequestParam String projectId,@RequestParam String header);
+
 }

+ 6 - 0
blade-service-api/blade-manager-api/src/main/java/org/springblade/manager/entity/ProjectInfo.java

@@ -173,4 +173,10 @@ public class ProjectInfo extends BaseEntity {
     @ApiModelProperty(value = "审批类型(电签方式) 1=垂直审批(顺序审批) 2=平行审批")
     private Integer approvalType;
 
+    /**
+     * 计量单元模板id
+     */
+    @ApiModelProperty(value = "系统计量单元模板id")
+    private Long meterTemplateId;
+
 }

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

@@ -0,0 +1,16 @@
+package org.springblade.manager.vo;
+
+import lombok.Data;
+
+/**
+ * @author yangyj
+ * @Date 2023/11/28 14:58
+ * @description 重新保存或重签
+ */
+@Data
+public class Redo {
+    public String pkeyId;
+    public Integer status;
+    public String treeCode;
+    public String userId;
+}

+ 18 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractDTO.java

@@ -0,0 +1,18 @@
+package org.springblade.meter.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.meter.entity.MeterTreeContract;
+
+import java.util.List;
+
+/**
+ * 合同段计量单元树详情DTO
+ */
+@Data
+public class MeterTreeContractDTO extends MeterTreeContract {
+
+    @ApiModelProperty(value = "分解清单列表详情")
+    private List<Object> decompositionList; //TODO 此处Obj替换为分解清单Bean对象
+
+}

+ 30 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractSaveBatchDTO.java

@@ -0,0 +1,30 @@
+package org.springblade.meter.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+@Data
+public class MeterTreeContractSaveBatchDTO implements Serializable {
+
+    @ApiModelProperty(value = "入参集合")
+    private List<MeterTreeContractSaveDTO> dataList;
+
+    @ApiModelProperty(value = "请求类型 1=节点新增 2=增补单元新增")
+    private Integer requestType;
+
+    @ApiModelProperty(value = "选择新增的合同段节点id")
+    private Long contractNodeId;
+
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+
+    @ApiModelProperty(value = "合同段id")
+    private Long contractId;
+
+    @ApiModelProperty(value = "计量模板id")
+    private Long templateId;
+
+}

+ 21 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractSaveDTO.java

@@ -0,0 +1,21 @@
+package org.springblade.meter.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.meter.entity.MeterTreeContract;
+
+import java.util.List;
+
+/**
+ * 合同段计量单元树详情DTO
+ */
+@Data
+public class MeterTreeContractSaveDTO extends MeterTreeContract {
+
+    @ApiModelProperty(value = "左边选择的节点id")
+    private Long leftNodeId;
+
+    @ApiModelProperty(value = "是否划分子节点 0=否 1=是")
+    private Integer isAddChildNode;
+
+}

+ 0 - 4
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.dto;
-
-public class test {
-}

+ 1 - 1
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/ChangeTokenInventory.java

@@ -57,7 +57,7 @@ public class ChangeTokenInventory extends BaseEntity {
      * 合同工程清单id
      */
     @ApiModelProperty(value = "合同工程清单id")
-    private Long inventoryFormId;
+    private Long contractFormId;
     /**
      * 变更前数量
      */

+ 6 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/ContractInventoryForm.java

@@ -191,6 +191,12 @@ public class ContractInventoryForm extends BaseEntity {
     @ApiModelProperty(value = "节点层级")
     @ExcelIgnore
     private Integer nodeTier;
+    /**
+     * 生成变更时划分数量,零号变更时存入
+     */
+    @ApiModelProperty(value = "生成变更时划分数量,零号变更时存入")
+    @ExcelIgnore
+    private Integer buildChangeTotal;
 
 
 

+ 66 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterContractInfo.java

@@ -0,0 +1,66 @@
+package org.springblade.meter.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 计量合同段信息详情表
+ */
+@Data
+public class MeterContractInfo implements Serializable {
+
+    @ApiModelProperty(value = "合同段id")
+    private Long contractId;
+
+    @ApiModelProperty(value = "动员-总额")
+    private BigDecimal dyTotalAmount;
+
+    @ApiModelProperty(value = "动员-起扣点")
+    private BigDecimal dyStartDeductPoint;
+
+    @ApiModelProperty(value = "动员-全额扣回点")
+    private BigDecimal dyFullDeductPoint;
+
+    @ApiModelProperty(value = "动员-扣回比例")
+    private BigDecimal dyDeductRatio;
+
+    @ApiModelProperty(value = "材料-预付款限额")
+    private BigDecimal clAdvancePaymentQuota;
+
+    @ApiModelProperty(value = "材料-隔几期扣回")
+    private Integer clIntervalDeduct;
+
+    @ApiModelProperty(value = "材料-从几期起扣")
+    private Integer clFromIntervalDeduct;
+
+    @ApiModelProperty(value = "材料-预付款比例")
+    private BigDecimal clPrepaymentRatio;
+
+    @ApiModelProperty(value = "材料-扣回比例")
+    private BigDecimal clDeductRatio;
+
+    @ApiModelProperty(value = "保留金-比例")
+    private BigDecimal blReserveFundsRatio;
+
+    @ApiModelProperty(value = "保留金-起扣点")
+    private BigDecimal blStartDeductPoint;
+
+    @ApiModelProperty(value = "保留金-累计扣回限额")
+    private BigDecimal blTotalDeductQuota;
+
+    @ApiModelProperty(value = "农民-保证金起扣点")
+    private BigDecimal nmSdStartDeductPoint;
+
+    @ApiModelProperty(value = "农民-保证金比例")
+    private BigDecimal nmSdRatio;
+
+    @ApiModelProperty(value = "农民-保证金扣回限额")
+    private BigDecimal nmTotalDeductQuota;
+
+    @ApiModelProperty(value = "农民-预扣农民工资保证金")
+    private BigDecimal nmPayDepositWithheld;
+
+}

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

@@ -0,0 +1,63 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 合同段中期支付项信息表
+ */
+@Data
+@TableName(value = "s_meter_mid_pay_item_contract")
+public class MeterMidPayItemContract extends BaseEntity {
+
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+
+    @ApiModelProperty(value = "合同段id")
+    private Long contractId;
+
+    @ApiModelProperty(value = "支付项编号")
+    private String payNumber;
+
+    @ApiModelProperty(value = "支付项名称")
+    private String payName;
+
+    @ApiModelProperty(value = "支付项类型")
+    private Integer payType;
+
+    @ApiModelProperty(value = "合同计算公式ids")
+    private String contractFormulaIds;
+
+    @ApiModelProperty(value = "更改计算公式ids")
+    private String updateFormulaIds;
+
+    @ApiModelProperty(value = "当前期计算公式ids")
+    private String currentFormulaIds;
+
+    @ApiModelProperty(value = "是否为扣款项")
+    private Integer isDeduct;
+
+    @ApiModelProperty(value = "是否加粗")
+    private Integer isBoldText;
+
+    @ApiModelProperty(value = "是否手动输入")
+    private Integer isManualInput;
+
+    @ApiModelProperty(value = "是否显示百分比")
+    private Integer isShowPercent;
+
+    @ApiModelProperty(value = "是否合计项")
+    private Integer isTotalTerms;
+
+    @ApiModelProperty(value = "支付适用类型")
+    private Integer payApplicableType;
+
+    @ApiModelProperty(value = "租户id")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+}

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

@@ -0,0 +1,60 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 项目中期支付项信息表
+ */
+@Data
+@TableName(value = "s_meter_mid_pay_item_project")
+public class MeterMidPayItemProject extends BaseEntity {
+
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+
+    @ApiModelProperty(value = "支付项编号")
+    private String payNumber;
+
+    @ApiModelProperty(value = "支付项名称")
+    private String payName;
+
+    @ApiModelProperty(value = "支付项类型")
+    private Integer payType;
+
+    @ApiModelProperty(value = "合同计算公式ids")
+    private String contractFormulaIds;
+
+    @ApiModelProperty(value = "更改计算公式ids")
+    private String updateFormulaIds;
+
+    @ApiModelProperty(value = "当前期计算公式ids")
+    private String currentFormulaIds;
+
+    @ApiModelProperty(value = "是否为扣款项")
+    private Integer isDeduct;
+
+    @ApiModelProperty(value = "是否加粗")
+    private Integer isBoldText;
+
+    @ApiModelProperty(value = "是否手动输入")
+    private Integer isManualInput;
+
+    @ApiModelProperty(value = "是否显示百分比")
+    private Integer isShowPercent;
+
+    @ApiModelProperty(value = "是否合计项")
+    private Integer isTotalTerms;
+
+    @ApiModelProperty(value = "支付适用类型")
+    private Integer payApplicableType;
+
+    @ApiModelProperty(value = "租户id")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+}

+ 19 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemRelation.java

@@ -0,0 +1,19 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 中期支付项关系表-汇总项(系统、项目、合同段)
+ */
+@Data
+@TableName(value = "s_meter_mid_pay_item_relation")
+public class MeterMidPayItemRelation implements Serializable {
+
+    private Long id;
+    private Long midPayId;
+    private Long midPayIdRelation;
+
+}

+ 57 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemSystem.java

@@ -0,0 +1,57 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 系统中期支付项信息表
+ */
+@Data
+@TableName(value = "s_meter_mid_pay_item_system")
+public class MeterMidPayItemSystem extends BaseEntity {
+
+    @ApiModelProperty(value = "支付项编号")
+    private String payNumber;
+
+    @ApiModelProperty(value = "支付项名称")
+    private String payName;
+
+    @ApiModelProperty(value = "支付项类型")
+    private Integer payType;
+
+    @ApiModelProperty(value = "合同计算公式ids")
+    private String contractFormulaIds;
+
+    @ApiModelProperty(value = "更改计算公式ids")
+    private String updateFormulaIds;
+
+    @ApiModelProperty(value = "当前期计算公式ids")
+    private String currentFormulaIds;
+
+    @ApiModelProperty(value = "是否为扣款项")
+    private Integer isDeduct;
+
+    @ApiModelProperty(value = "是否加粗")
+    private Integer isBoldText;
+
+    @ApiModelProperty(value = "是否手动输入")
+    private Integer isManualInput;
+
+    @ApiModelProperty(value = "是否显示百分比")
+    private Integer isShowPercent;
+
+    @ApiModelProperty(value = "是否合计项")
+    private Integer isTotalTerms;
+
+    @ApiModelProperty(value = "支付适用类型")
+    private Integer payApplicableType;
+
+    @ApiModelProperty(value = "租户id")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+}

+ 92 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeContract.java

@@ -0,0 +1,92 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+import java.math.BigDecimal;
+
+/**
+ * 合同段计量单元树详情表
+ */
+@Data
+@TableName(value = "s_meter_tree_contract")
+public class MeterTreeContract extends BaseEntity {
+
+    @ApiModelProperty(value = "模板id")
+    private Long templateId;
+
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+
+    @ApiModelProperty(value = "合同段id")
+    private Long contractId;
+
+    @ApiModelProperty(value = "节点名")
+    private String nodeName;
+
+    @ApiModelProperty(value = "节点编码")
+    private String nodeCode;
+
+    @ApiModelProperty(value = "节点类型")
+    private Integer nodeType;
+
+    @ApiModelProperty(value = "工程类型")
+    private Integer engineeringType;
+
+    @ApiModelProperty(value = "数据源类型 1=原始引用、2=新增、3=导入、4=复制")
+    private Integer dataSourceType;
+
+    @ApiModelProperty(value = "源节点id(项目树节点id)")
+    private Long sourceNodeId;
+
+    @ApiModelProperty(value = "编辑状态 0=未编辑,1=已编辑")
+    private Integer updateStatus;
+
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "祖级id")
+    private String ancestor;
+
+    @ApiModelProperty(value = "备注")
+    private String remarks;
+
+    @ApiModelProperty(value = "租户id")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+    @ApiModelProperty(value = "显示类型")
+    private Integer showType;
+
+    @ApiModelProperty(value = "起始桩号")
+    private String startStake;
+
+    @ApiModelProperty(value = "结束桩号")
+    private String endStake;
+
+    @ApiModelProperty(value = "施工图金额")
+    private BigDecimal buildPictureMoney;
+
+    @ApiModelProperty(value = "合同图号")
+    private String contractPicture;
+
+    @ApiModelProperty(value = "变更合同图号")
+    private String changePicture;
+
+    @ApiModelProperty(value = "变更后金额")
+    private BigDecimal changeMoney;
+
+    @ApiModelProperty(value = "是否增补 0=否 1=是")
+    private Integer isSupplement;
+
+    @ApiModelProperty(value = "是否分解清单 0=否 1=是")
+    private Integer isResolveForm;
+
+    @ApiModelProperty(value = "是否锁定节点 0=否 1=是")
+    private Integer isLock;
+
+}

+ 57 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeProject.java

@@ -0,0 +1,57 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 项目级计量单元树详情表
+ */
+@Data
+@TableName(value = "s_meter_tree_project")
+public class MeterTreeProject extends BaseEntity {
+
+    @ApiModelProperty(value = "模板id")
+    private Long templateId;
+
+    @ApiModelProperty(value = "项目id")
+    private Long projectId;
+
+    @ApiModelProperty(value = "节点名")
+    private String nodeName;
+
+    @ApiModelProperty(value = "节点编码")
+    private String nodeCode;
+
+    @ApiModelProperty(value = "节点类型")
+    private Integer nodeType;
+
+    @ApiModelProperty(value = "工程类型")
+    private Integer engineeringType;
+
+    @ApiModelProperty(value = "数据源类型 1=原始引用、2=新增、3=导入、4=复制")
+    private Integer dataSourceType;
+
+    @ApiModelProperty(value = "源节点id(项目树节点id)")
+    private Long sourceNodeId;
+
+    @ApiModelProperty(value = "编辑状态 0=非编辑,1=是编辑")
+    private Integer updateStatus;
+
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "祖级id")
+    private String ancestor;
+
+    @ApiModelProperty(value = "备注")
+    private String remarks;
+
+    @ApiModelProperty(value = "租户id")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+}

+ 45 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeSystem.java

@@ -0,0 +1,45 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+/**
+ * 系统级计量单元树详情表
+ */
+@Data
+@TableName(value = "s_meter_tree_system")
+public class MeterTreeSystem extends BaseEntity {
+
+    @ApiModelProperty(value = "模板id")
+    private Long templateId;
+
+    @ApiModelProperty(value = "节点名")
+    private String nodeName;
+
+    @ApiModelProperty(value = "节点编码")
+    private String nodeCode;
+
+    @ApiModelProperty(value = "节点类型")
+    private Integer nodeType;
+
+    @ApiModelProperty(value = "工程类型")
+    private Integer engineeringType;
+
+    @ApiModelProperty(value = "父级id")
+    private Long parentId;
+
+    @ApiModelProperty(value = "祖级id")
+    private String ancestor;
+
+    @ApiModelProperty(value = "备注")
+    private String remarks;
+
+    @ApiModelProperty(value = "租户id")
+    private Long tenantId;
+
+    @ApiModelProperty(value = "排序")
+    private Integer sort;
+
+}

+ 18 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeTemplateInfo.java

@@ -0,0 +1,18 @@
+package org.springblade.meter.entity;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.core.mp.base.BaseEntity;
+
+@Data
+@TableName(value = "s_meter_tree_template_info")
+public class MeterTreeTemplateInfo extends BaseEntity {
+
+    @ApiModelProperty(value = "名称")
+    private String name;
+
+    @ApiModelProperty(value = "备注")
+    private String remarks;
+
+}

+ 0 - 4
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.entity;
-
-public class test {
-}

+ 12 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/feign/MeterTreeSystemClient.java

@@ -0,0 +1,12 @@
+package org.springblade.meter.feign;
+
+
+import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.web.bind.annotation.RequestParam;
+
+import static org.springblade.core.launch.constant.AppConstant.APPLICATION_NAME_PREFIX;
+
+@FeignClient(value = APPLICATION_NAME_PREFIX + "meter")
+public interface MeterTreeSystemClient {
+
+}

+ 0 - 4
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/feign/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.feign;
-
-public class test {
-}

+ 6 - 1
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/ContractInventoryFormVO.java

@@ -16,12 +16,14 @@
  */
 package org.springblade.meter.vo;
 
+import com.alibaba.excel.annotation.ExcelIgnore;
+import io.swagger.annotations.ApiModelProperty;
 import org.springblade.meter.entity.ContractInventoryForm;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
 /**
- * 合同工程清单表视图实体类
+ * 合同工程清单VO
  *
  * @author BladeX
  * @since 2023-11-29
@@ -31,4 +33,7 @@ import lombok.EqualsAndHashCode;
 public class ContractInventoryFormVO extends ContractInventoryForm {
 	private static final long serialVersionUID = 1L;
 
+	@ApiModelProperty(value = "是否增补节点")
+	private String isSupplementName;
+
 }

+ 8 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/FormTreeVO.java

@@ -1,5 +1,7 @@
 package org.springblade.meter.vo;
 
+import com.alibaba.excel.annotation.ExcelIgnore;
+import com.alibaba.excel.annotation.ExcelProperty;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
@@ -57,5 +59,11 @@ public class FormTreeVO implements INode<FormTreeVO> {
     @ApiModelProperty(value = "清单名称")
     private String formName;
 
+    @ApiModelProperty(value = "是否锁定0否1是")
+    private Integer isLock;
+
+    @ApiModelProperty(value = "章编号")
+    private String chapterNumber;
+
 
 }

+ 40 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/InventoryFormDetailVO.java

@@ -0,0 +1,40 @@
+/*
+ *      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.meter.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springblade.meter.entity.ContractInventoryForm;
+
+import java.util.List;
+
+/**
+ * 合同工程清单详情VO,包含下级清单集合
+ *
+ * @author BladeX
+ * @since 2023-11-29
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class InventoryFormDetailVO extends ContractInventoryForm {
+	private static final long serialVersionUID = 1L;
+
+	@ApiModelProperty(value = "下级节点集合")
+	private List<ContractInventoryFormVO> list;
+
+}

+ 18 - 0
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/MeterTreeContractVO.java

@@ -0,0 +1,18 @@
+package org.springblade.meter.vo;
+
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springblade.meter.entity.MeterTreeContract;
+
+import java.util.List;
+
+/**
+ * 合同段计量单元树详情VO
+ */
+@Data
+public class MeterTreeContractVO extends MeterTreeContract {
+
+    @ApiModelProperty(value = "分解清单列表详情")
+    private List<Object> decompositionList; //TODO 此处Obj替换为分解清单Bean对象
+
+}

+ 0 - 4
blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.vo;
-
-public class test {
-}

+ 6 - 0
blade-service/blade-business/src/main/java/org/springblade/business/feignClient/TaskClientImpl.java

@@ -151,4 +151,10 @@ public class TaskClientImpl implements TaskClient {
     public R<Boolean> abolishTask(Task task) {
         return R.data(this.taskService.abolishTask(task));
     }
+
+    @Override
+    public R<Boolean> reSigningEVisa(String taskIds, String contractId, String projectId, String header) {
+        this.taskService.reSigningEVisa(taskIds,contractId,projectId,header);
+        return null;
+    }
 }

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

@@ -371,8 +371,13 @@ public class InformationQueryServiceImpl extends BaseServiceImpl<InformationQuer
             userNmae = userR.getData().getName();
         }else{
             BladeUser user = AuthUtil.getUser();
-            userId = user.getUserId();
-            userNmae = user.getNickName();
+            if(user!=null) {
+                userId = user.getUserId();
+                userNmae = user.getNickName();
+            }else{
+                /*非客户端调用,request里不一定有用户信息,暂停后续操作*/
+                return null;
+            }
         }
 
         if (StringUtils.isNotEmpty(isFirst) && "true".equals(isFirst)) {

+ 9 - 0
blade-service/blade-manager/pom.xml

@@ -172,6 +172,15 @@
             <artifactId>commons-net</artifactId>
             <version>3.7</version>
         </dependency>
+
+        <!--计量-->
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-meter-api</artifactId>
+            <version>2.9.1.RELEASE</version>
+            <scope>compile</scope>
+        </dependency>
+
     </dependencies>
     <build>
         <plugins>

+ 141 - 31
blade-service/blade-manager/src/main/java/org/springblade/manager/controller/ExcelTabController.java

@@ -1,5 +1,6 @@
 package org.springblade.manager.controller;
 
+import cn.hutool.log.StaticLog;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
@@ -30,8 +31,10 @@ import org.springblade.business.entity.ContractLog;
 import org.springblade.business.entity.InformationQuery;
 import org.springblade.business.feign.ContractLogClient;
 import org.springblade.business.feign.InformationQueryClient;
+import org.springblade.business.feign.TaskClient;
 import org.springblade.business.vo.SaveContractLogVO;
 import org.springblade.common.constant.CommonConstant;
+import org.springblade.common.utils.BaseUtils;
 import org.springblade.common.utils.CommonUtil;
 import org.springblade.common.utils.MathUtil;
 import org.springblade.common.utils.SnowFlakeUtil;
@@ -52,7 +55,6 @@ import org.springblade.manager.enums.ExecuteType;
 import org.springblade.manager.mapper.ExcelTabMapper;
 import org.springblade.manager.mapper.WbsTreePrivateMapper;
 import org.springblade.manager.service.*;
-import org.springblade.manager.service.impl.WbsTreeContractServiceImpl;
 import org.springblade.manager.utils.ExcelInfoUtils;
 import org.springblade.manager.utils.FileUtils;
 import org.springblade.manager.utils.RegularExpressionUtil;
@@ -82,6 +84,7 @@ import java.net.URLEncoder;
 import java.text.SimpleDateFormat;
 import java.util.List;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
@@ -147,6 +150,9 @@ public class ExcelTabController extends BladeController {
 
     private final IWbsParamService wbsParamService;
 
+    private final TaskClient taskClient;
+
+
     @Autowired
     StringRedisTemplate RedisTemplate;
 
@@ -2216,34 +2222,139 @@ public class ExcelTabController extends BladeController {
     }
 
 
-    @PostMapping("/data-batch-refresh")
+    public static final  String CACHE_DEL="delete from m_cache where remark is null";
+    @GetMapping("/data-batch-refresh")
     @ApiOperationSupport(order = 100)
     @ApiOperation(value = "批量重新保存", notes = "批量重新保存")
-    public R saveBussDataBatchRefresh(Long pkeyId){
-        List<Map<String ,Object>> listMaps= this.jdbcTemplate.queryForList("");
-        if(listMaps.size()>0){
-            listMaps.forEach(node->{
-                try {
-                    this.start(node.get("pkeyId").toString());
-                }catch (Exception e){
-                    e.printStackTrace();
-                }
+    @ApiImplicitParams(value = {
+            @ApiImplicitParam(name = "pkeyId", value = "共同祖先节点pkeyId", required = true),
+            @ApiImplicitParam(name = "reset", value = "是否重置任务,是:1 否:非1"),
+            @ApiImplicitParam(name = "mode", value = "执行模式,0未上报,1已上报,2全部", required = true)
+    })
+    public R saveBussDataBatchRefresh(Long pkeyId,Integer mode,Integer reset){
+        /*进度条(未上报30工序执行一批)(已上报15工序执行一批)*/
+        WbsTreeContract parent =this.wbsTreeContractService.getOne(Wrappers.<WbsTreeContract>lambdaQuery().eq(WbsTreeContract::getPKeyId,pkeyId));
+        if(reset!=null&&reset==1){
+            /*重置任务*/
+            this.jdbcTemplate.execute(CACHE_DEL);
+        }
+        this.batchRedo(parent.getProjectId(),parent.getContractId(),parent.getTreeCode(),"1", mode);
+       return   R.success("完成");
+    }
 
-            });
+    public void batchInsert(List<String> sqlInit){
+        try {
+            List<List<String>> sqlListSeg = BaseUtils.splitList(sqlInit, 200);
+            sqlListSeg.parallelStream().map(l -> String.join(";", l)).forEach(this.jdbcTemplate::execute);
+        }catch (Exception e){
+            e.printStackTrace();
         }
-      return   R.success("无数据");
     }
 
 
-    public R start(String nodeId) throws Exception {
-        JSONArray dataArray = new JSONArray();
-//        String nodeId = tableInfo1.getString("nodeId");
-        String contractId ="1630017379264610305";
-        String projectId = "1630011899725201410";
-        String classify = "1";
 
-        /*全加载,或者可以优化成依赖加载*/
+    public void batchRedo(String projectId,String contractId,String treeCode,String classify ,Integer mode){
+        String XXt="002004";
+        List<String> list= this.jdbcTemplate.queryForList("select tree_code treeCode from m_wbs_tree_contract where  contract_id=1630017379264610305 and is_deleted=0 and (id in(1541601335840210946,1541601355272421378,1703032661549219842,1541708754314547201,1575309161938681858) or old_id in(1541601335840210946,1541601355272421378,1703032661549219842,1541708754314547201,1575309161938681858)) and tree_code is not null ORDER BY tree_code",String.class);
+       if(list.size()<1){
+           return;
+       }
+       Set<String> treeCodeSet = new HashSet<>(list);
+       List<String>treeCodeList = new ArrayList<>();
+        treeCodeSet.forEach(t->{
+            if(BaseUtils.notInChain(treeCodeList,t)){
+                treeCodeList.add(t);
+            }
+        });
+        /*删除测试分支*/
+        treeCodeList.removeIf(s->s.startsWith(XXt));
+        if(Func.isNotEmpty(contractId)&&Func.isNotBlank(treeCode)) {
+            /*List<Map<String, Object>> listMaps = this.jdbcTemplate.queryForList("select a.p_key_id pkeyId,a.update_user user,b.status from m_wbs_tree_contract a join u_information_query b on a.p_key_id = b.wbs_id where a.contract_id=" + contractId + " and a.is_deleted=0 and b.is_deleted=0 and b.classify=1 and a.node_type=6 and a.tree_code like '" + treeCode + "%' and b.is_deleted=0 ORDER BY a.sort");*/
+            List<Redo> redoList = this.jdbcTemplate.query("select  a.p_key_id pkeyId,a.update_user userId,b.status,tree_code treeCode from m_wbs_tree_contract a join u_information_query b on a.p_key_id = b.wbs_id where a.contract_id=" + contractId + " and a.is_deleted=0 and b.is_deleted=0 and b.classify=1 and a.node_type=6 and a.tree_code like '" + treeCode + "%' and b.is_deleted=0 ORDER BY a.tree_code", new BeanPropertyRowMapper<>(Redo.class));
+            redoList.removeIf(x->!BaseUtils.inChain(treeCodeList,x.getTreeCode()));
+            List<Map<String,Object>> excludeListMap = this.jdbcTemplate.queryForList("select id  from m_cache where remark is null");
+            if(excludeListMap.size()>0){
+               List<String>  excludeList=  excludeListMap.stream().map(m->m.get("id").toString()).collect(Collectors.toList());
+               /*排除已经执行的*/
+                redoList.removeIf(x->excludeList.contains(x.getPkeyId()));
+            }
+            /*st03 未上报 st12 已上报*/
+            boolean st03=false,st12=false;
+            if(mode==2){
+                st03=true;
+                st12=true;
+            }else if(mode==0){
+                st03=true;
+            }else if(mode==1){
+                st12=true;
+            }
+            if (redoList.size() > 0) {
+                /*未上报*/
+                List<String> status0 = redoList.stream().filter(m -> m.getStatus().equals(0)||m.getStatus().equals(3)).map(Redo::getPkeyId).collect(Collectors.toList());
+                /*已上报*/
+                List<String> status12 = redoList.stream().filter(m -> m.getStatus().equals(1)||m.getStatus().equals(2)).map(Redo::getPkeyId).collect(Collectors.toList());
+                if(status0.size()>0&&st03){
+                   List<List<String>> status0Seg=  BaseUtils.splitList(status0,30);
+                   List<String> sqlIntA= new ArrayList<>();
+                   String start ="未上报数据共计"+status0.size()+"条,开始任务";
+                    StaticLog.info(start);
+                   AtomicInteger sum= new AtomicInteger();
+                   sqlIntA.add("insert into m_cache (id,remark,create_time) VALUES ("+SnowFlakeUtil.getId()+",'"+start+"',NOW())");
+                   status0Seg.forEach(l->{
+                       try {
+                           sqlIntA.addAll(l.stream().map(id->"insert into m_cache (id) VALUES ("+id+")").collect(Collectors.toList()));
+                           this.synPDFInfo(contractId,String.join(",",l),"1",projectId);
+                           sum.addAndGet(l.size());
+                           double d=100*(double) sum.get()/(double)status0.size();
+                           String str ="未上报数据共计"+status0.size()+"条,已经执行"+sum.get()+"条("+d+"%)";
+                           sqlIntA.add("insert into m_cache (id,remark,create_time) VALUES ("+SnowFlakeUtil.getId()+",'"+str+"',NOW())");
+                           this.batchInsert(sqlIntA);
+                          sqlIntA.clear();
+                       }catch (Exception e){
+                           e.printStackTrace();
+                       }
+                   });
 
+                }
+                if(status12.size()>0&&st12){
+                    try {
+                        String nodeIds = status12.stream().distinct().collect(Collectors.joining(","));
+                        List<Map<String,Object>> taskList= this.jdbcTemplate.queryForList("select c.id taskId,b.wbs_id nodeId  from  u_information_query b   join u_task c on b.id=c.form_data_id where  b.is_deleted=0 and b.classify=1 and b.status in(1,2)and b.wbs_id in("+nodeIds+") ORDER BY c.update_time ");
+                        List<String> taskIds= taskList.stream().collect(Collectors.toMap(m->m.get("nodeId"),m->m,(m1,m2)->m2)).values().stream().map(m->m.get("taskId").toString()).collect(Collectors.toList());
+                        if(taskIds.size()>0) {
+                            /*处理已经上报部分 */
+                            List<List<String>> taskIdsSeg=  BaseUtils.splitList(taskIds,15);
+                            List<String> sqlIntA= new ArrayList<>();
+                            String start ="已经上报数据共计"+taskIds.size()+"条,开始任务";
+                            StaticLog.info(start);
+                            AtomicInteger sum= new AtomicInteger();
+                            sqlIntA.add("insert into m_cache (id,remark,create_time) VALUES ("+SnowFlakeUtil.getId()+",'"+start+"',NOW())");
+                            taskIdsSeg.forEach(l->{
+                                try {
+                                    sqlIntA.addAll(l.stream().map(id->"insert into m_cache (id) VALUES ("+id+")").collect(Collectors.toList()));
+                                    this.taskClient.reSigningEVisa(String.join(",",l),contractId,projectId,AuthUtil.getHeader());
+                                    sum.addAndGet(l.size());
+                                    double d=100*(double) sum.get()/(double)taskIds.size();
+                                    String str ="未上报数据共计"+taskIdsSeg.size()+"条,已经执行"+sum.get()+"条("+d+"%)";
+                                    sqlIntA.add("insert into m_cache (id,remark,create_time) VALUES ("+SnowFlakeUtil.getId()+",'"+str+"',NOW())");
+                                    this.batchInsert(sqlIntA);
+                                    sqlIntA.clear();
+                                }catch (Exception e){
+                                    e.printStackTrace();
+                                }
+                            });
+                        }
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+                this.jdbcTemplate.execute("insert into m_cache (id,remark,create_time) VALUES ("+SnowFlakeUtil.getId()+",'完成',NOW())");
+            }
+        }
+    }
+   @Deprecated
+    public void redo(String nodeId,String projectId,String contractId, String classify) throws Exception {
+        JSONArray dataArray = new JSONArray();
         List<AppWbsTreeContractVO> tableAll  = wbsTreeContractService.searchNodeAllTable(nodeId, "1", contractId, projectId,null);
         List<Long> tableAllIds = tableAll.stream().map(AppWbsTreeContractVO::getPKeyId).collect(Collectors.toList());
         for (Long pk : tableAllIds) {
@@ -2254,7 +2365,6 @@ public class ExcelTabController extends BladeController {
             }
         }
         List<TableInfo> tableInfoList = this.excelTabService.getTableInfoList(dataArray);
-        /*默认额外加载的默认不更新,除非有元素数据变动*/
         tableInfoList.removeIf(e -> e.getPkeyId() == null);
         tableInfoList.forEach(e -> {
             e.setToBeUpdated(false);
@@ -2270,20 +2380,19 @@ public class ExcelTabController extends BladeController {
         //保存数据到数据库
         R<Object> result = this.excelTabService.saveOrUpdateInfo(tableInfoList);
         if (!result.isSuccess()) {
-            return R.fail(result.getMsg());
+            R.fail(result.getMsg());
+            return;
         }
 
 
         List<String> errorPKeyIds = new ArrayList<>();
         //单个pdf加载
-        if (tableInfoList != null) {
-            for (TableInfo tableInfo : tableInfoList) {
-                R bussPdfInfo = excelTabService.getBussPdfInfo(Long.parseLong(tableInfo.getPkeyId()));
+        for (TableInfo tableInfo : tableInfoList) {
+            R bussPdfInfo = excelTabService.getBussPdfInfo(Long.parseLong(tableInfo.getPkeyId()));
 
-                if (ObjectUtil.isEmpty(bussPdfInfo) || bussPdfInfo.getCode() != 200) {
-                    //如果返回的单张pdfUrl为空,那么表示发生异常,返回异常信息
-                    errorPKeyIds.add(tableInfo.getPkeyId());
-                }
+            if (ObjectUtil.isEmpty(bussPdfInfo) || bussPdfInfo.getCode() != 200) {
+                //如果返回的单张pdfUrl为空,那么表示发生异常,返回异常信息
+                errorPKeyIds.add(tableInfo.getPkeyId());
             }
         }
 
@@ -2297,7 +2406,8 @@ public class ExcelTabController extends BladeController {
             }
             if (errorTabs.size() > 0) {
                 List<String> names = errorTabs.stream().map(WbsTreeContract::getNodeName).collect(Collectors.toList());
-                return R.fail("以下的表在生成pdf文件时发生了异常【" + StringUtils.join(names, "、") + "】");
+                R.fail("以下的表在生成pdf文件时发生了异常【" + StringUtils.join(names, "、") + "】");
+                return;
             }
         }
 
@@ -2306,7 +2416,6 @@ public class ExcelTabController extends BladeController {
 
         //更新缓存
         informationQueryClient.delAsyncWbsTree(contractId);
-        return R.success("OK");
     }
     /***
      * 覆盖上传-  (修改为上传导入模板)
@@ -4008,6 +4117,7 @@ public class ExcelTabController extends BladeController {
     }
 
 
+
     @PostMapping("/save_nodeId")
     @ApiOperationSupport(order = 72)
     @ApiOperation(value = "pdf", notes = "pdf")

+ 364 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/FormulaExecutor.java

@@ -0,0 +1,364 @@
+package org.springblade.manager.formula;
+
+import cn.hutool.log.StaticLog;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.mixsmart.utils.CustomFunction;
+import com.mixsmart.utils.FormulaUtils;
+import com.mixsmart.utils.StringUtils;
+import lombok.Data;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.core.tool.utils.StringPool;
+import org.springblade.manager.dto.*;
+import org.springblade.manager.entity.Formula;
+import org.springblade.manager.entity.WbsTreeContract;
+import org.springblade.manager.enums.ExecuteType;
+import org.springblade.manager.formula.impl.TableElementConverter;
+import org.springblade.manager.service.*;
+import org.springblade.manager.service.impl.FormulaServiceImpl;
+import org.springblade.manager.vo.AppWbsTreeContractVO;
+import org.springblade.manager.vo.CurrentNode;
+
+import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * @author yangyj
+ * @Date 2023/11/30 16:18
+ * @description 公式执行器
+ */
+@Data
+public class FormulaExecutor {
+    private final FormulaServiceImpl service;
+    private final TableElementConverter tec;
+    public final static String WP="WP";
+    public final static String CHAIN="trees";
+    public final static String FC="FC.";
+    public final static String TABLE_LIST="TBL";
+    public final static String CHECK_ITEMS="CKI";
+    public static final Pattern AP=Pattern.compile("(E|WP)\\[([^]']+)]");
+    public static final String IF_REG= "(?<=T\\(com.mixsmart.utils.CustomFunction\\).ifelse\\()[^,]+(?=,)";
+    public static final String FC_REG="T\\(com.mixsmart.utils.CustomFunction\\)\\.";
+    public static final String ELE_CODE_REG= "(?<=E\\[)[^]]+(?=])";
+    public static final Pattern P = Pattern.compile(ELE_CODE_REG);
+    public final static String CTI="ContractInfo";
+
+    public FormulaExecutor(FormulaServiceImpl service, TableElementConverter tec) {
+        this.service = service;
+        this.tec = tec;
+    }
+
+
+    public void execute(){
+        List<FormulaHandleChain> chainList = new LinkedList<>();
+           chainList.add(new Init());
+           chainList.add(new Sort());
+           chainList.add(new Pre());
+           chainList.add(new Special());
+           chainList.add(new Calculate());
+           chainList.add(new Format());
+           chainList.forEach(FormulaHandleChain::handle);
+    }
+
+    public class Init implements  FormulaHandleChain{
+        @Override
+        public void handle() {
+            /*基础数据*/
+            baseData();
+            if(ExecuteType.INSPECTION.equals(tec.getExecuteType())) {
+                /*依赖加载*/
+                checkingMissingList();
+                /*公式参数*/
+                //option();
+                /*评定表*/
+                //assessmentForm();
+            }
+        }
+
+        /**补充缺失的元素依赖*/
+        public void checkingMissingList(){
+            List<String> missingList = new ArrayList<>();
+            if(tec.isNew){
+                tec.formDataList.forEach(fd->{
+                    if (fd.executable()) {
+                        service.relyParse(fd.getFormula());
+                        Formula f = fd.getFormula();
+                        List<String> relyList = f.getRelyList();
+                        if (Func.isNotEmpty(relyList)) {
+                            relyList.forEach(r -> {
+                                if (!tec.formDataMap.containsKey(r)) {
+                                    missingList.add(r);
+                                }
+                            });
+                        }
+                    }
+                });
+                missingFill2(missingList);
+            }else {
+                tec.formDataList.forEach(fd -> {
+                    if (fd.executable()) {
+                        service.relyParse(fd.getFormula());
+                        Formula f = fd.getFormula();
+                        List<String> relyList = f.getRelyList();
+                        if (Func.isNotEmpty(relyList)) {
+                            relyList.forEach(r -> {
+                                if (tec.formDataMap.values().stream().map(FormData::getCode).noneMatch(k -> StringUtils.isEquals(k, r))) {
+                                    missingList.add(r);
+                                }
+                            });
+                        }
+                    }
+                });
+                missingFill(missingList);
+            }
+            if(Func.isNotEmpty(missingList)){
+                StringBuilder sb = new StringBuilder();
+                missingList.stream().collect(Collectors.groupingBy(s->s.split(StringPool.COLON)[0])).forEach((k, v)->{
+                    sb.append(k).append("(").append(v.stream().map(c->c.split(StringPool.COLON)[1]).collect(Collectors.joining(","))).append(")");
+                });
+                tec.getLog().put(FormulaLog.CROSS,sb.toString());
+            }
+        }
+        public void missingFill(List<String> missingList){
+            try {
+                /*数据池里面没有任何元素匹配和当前依赖匹配*/
+                if (Func.isNotEmpty(missingList)) {
+                    StaticLog.info("需要挂载的元素{}", String.join(";", missingList));
+                    Map<String, Object> elementInfoMap = service.getElementInfoByCodes(String.join(",", missingList));
+                    /*1从当前节点其它表格中查找匹配的元素*/
+                    List<String> removeList = new ArrayList<>();
+                    if (Func.isNotEmpty(missingList)) {
+                        /*2从当前节点的兄弟节点中查找匹配的元素*/
+                        CurrentNode currentNode = tec.getCurrentNode();
+                        /*List<Map<String,Object>> tableNamePkIdsMaps= this.jdbcTemplate.queryForList("select c.init_table_name tableName,c.p_key_id pkId,c.html_url url from (select b.id from m_wbs_tree_contract a join m_wbs_tree_contract b on (a.contract_id=b.contract_id and b.ancestors like CONCAT(a.ancestors,'%')) where a.p_key_id="+currentNode.getPkId()+" and b.is_deleted=0 and b.node_type=6 ORDER BY b.sort) k join m_wbs_tree_contract c on c.parent_id = k.id where  c.contract_id="+tec.getContractId()+" and c.is_deleted=0 ");*/
+                        WbsTreeContract parent = service.wbsTreeContractService().getOne(Wrappers.<WbsTreeContract>lambdaQuery().eq(WbsTreeContract::getPKeyId, currentNode.getParentPkeyId()));
+                        if (parent == null) {
+                            return;
+                        }
+                        List<Map<String, Object>> tableNamePkIdsMaps = tec.jdbcTemplate.queryForList(
+                                "select c.init_table_name tableName,c.p_key_id pkId,c.html_url url from" +
+                                        " (select id from m_wbs_tree_contract where contract_id=" + currentNode.getContractId() + " and is_deleted=0 and node_type=6 and tree_code like '" + parent.getTreeCode() + "%' ORDER BY sort) k" +
+                                        " join m_wbs_tree_contract c on c.parent_id = k.id " +
+                                        "where  c.contract_id=" + currentNode.getContractId() + " and c.is_deleted=0 "
+                        );
+                        if (Func.isNotEmpty(tableNamePkIdsMaps)) {
+                            // removeList.clear();
+                            missingList.forEach(miss -> {
+                                @SuppressWarnings("unckecked")
+                                Map<String, Object> elementInfo = (Map<String, Object>) elementInfoMap.get(miss);
+                                String tn = miss.substring(0, miss.indexOf(StringPool.COLON));
+                                String key = miss.substring(miss.indexOf(StringPool.COLON) + 1);
+                                String targetIds = tableNamePkIdsMaps.stream().filter(m -> StringUtils.isEquals(m.get("tableName"), tn)).map(m -> m.get("pkId")).map(StringUtils::handleNull).collect(Collectors.joining(","));
+                                if (Func.isNotEmpty(targetIds)) {
+                                    if (!tec.getCoordinateMap().containsKey(tn)) {
+                                        tableNamePkIdsMaps.stream().filter(m -> StringUtils.isEquals(m.get("tableName"), tn)).findAny().ifPresent(m -> {
+                                            tec.getCoordinateMap().put(tn, FormulaUtils.getElementCell(StringUtils.handleNull(m.get("url"))));
+                                        });
+                                    }
+                                    List<Map<String, Object>> tableDatas = tec.jdbcTemplate.queryForList("select * from " + tn + " where p_key_id in (" + targetIds + ")");
+                                    String tmp = elementInfo == null ? "" : StringUtils.handleNull(elementInfo.get("ename"));
+                                    fill(tableDatas, removeList, tn, key, tmp);
+                                }
+                            });
+                        }
+                    }
+                    if (Func.isNotEmpty(removeList)) {
+                        /*移除已经找到的元素数据*/
+                        missingList.removeIf(removeList::contains);
+                    }
+                }
+            }catch (Exception e){
+                e.printStackTrace();
+            }
+        }
+        public void fill(List<Map<String,Object>> tableDatas,List<String> removeList,String tn,String key,String name){
+            if(Func.isNotEmpty(tableDatas)){
+                Map<String,Object> map = new HashMap<>();
+                tableDatas.forEach(m->{
+                    for(Map.Entry<String,Object> entry:m.entrySet()){
+                        if(entry.getValue()!=null){
+                            map.merge(entry.getKey(), entry.getValue(), (v1, v2) -> v1 + ";;" + v2);
+                        }
+                    }
+                });
+                tec.tableDataMaps.put(tn,map);
+                String values= StringUtils.handleNull(map.get(key));
+                String r= tn+StringPool.COLON+key;
+                FormData tmp=createFormDataFast(name,r,values);
+                if(tmp!=null){
+                    tec.formDataMap.put(r,tmp);
+                }
+            }
+        }
+        public FormData createFormDataFast(String name,String code,String values){
+            if(StringUtils.isNotEmpty(code,name)){
+                String[] arr=code.split(":");
+                String coords = tec.getCoordinateMap().get(arr[0]).get(arr[1]);
+                if(StringUtils.isNotEmpty(coords)) {
+                    /*定位信息存在才合法*/
+                    List<Coords> coordsList = Stream.of(coords).flatMap(s -> Arrays.stream(s.split(";"))).map(s -> {
+                        String[] xy = s.split("_");
+                        return new Coords(xy[1], xy[0]);
+                    }).collect(Collectors.toList());
+                    List<ElementData> eds = new ArrayList<>();
+                    if (StringUtils.isNotEmpty(values)) {
+                        String[] pages = values.split(";;");
+                        for (int index = 0; index < pages.length; index++) {
+                            String pg = pages[index];
+                            if (Func.isNotBlank(pg)) {
+                                String[] val = pg.split("☆");
+                                Map<String, Object> tmpMap = new LinkedHashMap<>();
+                                for (String s : val) {
+                                    String[] t = s.split("_\\^_");
+                                    String[] c = t[1].split("_");
+                                    tmpMap.put(StringUtils.join(code, 0, index, Func.toInt(c[1]), Func.toInt(c[0]), StringPool.AT), t[0]);
+                                }
+                                for (Coords c : coordsList) {
+                                    Object data = null;
+                                    String key = StringUtils.join(code, 0, index, c.getX(), c.getY(), StringPool.AT);
+                                    if (tmpMap.containsKey(key)) {
+                                        data = tmpMap.get(key);
+                                    }
+                                    eds.add(new ElementData(index, 0, data, c.getX(), c.getY()));
+                                }
+                            }
+                        }
+
+                    } else {
+                        eds = coordsList.stream().map(c -> new ElementData(0, 0, null, c.getX(), c.getY())).collect(Collectors.toList());
+                    }
+                    FormData one = new FormData(code, eds, null, coords);
+                    one.setEName(name);
+                    /*备份原始数据,用于更新比较*/
+                    one.init();
+                    return one;
+                }
+                tec.getLog().put(FormulaLog.POSITION,code+"("+name+")");
+            }
+            return null;
+        }
+        public void missingFill2(List<String> missingList){
+            /*数据池里面没有任何元素匹配和当前依赖匹配*/
+            if(Func.isNotEmpty(missingList)){
+                StaticLog.info("需要挂载的元素{}", String.join(";", missingList));
+                Map<String,FormData> fdsMap = service.createFormDataByCode(String.join(",",missingList));
+                /*1从当前节点其它表格中查找匹配的元素*/
+                List<String> removeList=new ArrayList<>();
+                /*2从当前节点的兄弟节点中查找匹配的元素*/
+                CurrentNode currentNode = tec.getCurrentNode();
+                String sql= "select c.init_table_name tableName,c.p_key_id pkeyId,c.html_url url from (select b.id from m_wbs_tree_contract a join m_wbs_tree_contract b on (a.contract_id=b.contract_id and b.ancestors like CONCAT(a.ancestors,'%')) where a.p_key_id="+currentNode.getPkId()+" and b.is_deleted=0 and b.node_type=6 ORDER BY b.sort) k join m_wbs_tree_contract c on c.parent_id = k.id where  c.contract_id="+tec.getContractId()+" and c.is_deleted=0 ";
+                List<TableFormMapper> tableNamePkIdsList = service.getSqlList(sql,TableFormMapper.class);
+                if(Func.isNotEmpty(tableNamePkIdsList)) {
+                    List<Long> pkeyIds=tec.getTableAll().stream().map(NodeTable::getPKeyId).collect(Collectors.toList());
+                    Map<String,List<Long>>tableNamePkIdsMaps=tableNamePkIdsList.stream().filter(m->!pkeyIds.contains(m.getPkeyId())).collect(Collectors.groupingBy(TableFormMapper::getTableName,Collectors.mapping(TableFormMapper::getPkeyId,Collectors.toList())));
+                    Map<String,String> tableNameUrlMaps=tableNamePkIdsList.stream().collect(Collectors.toMap(TableFormMapper::getTableName,TableFormMapper::getUrl,(p,n)->p));
+                    List<FormData> fds=new ArrayList<>(fdsMap.values());
+                    Map<String,List<FormData>> group = fds.stream().collect(Collectors.groupingBy(FormData::getTableName,Collectors.toList()));
+                    group.forEach((k,v)->{
+                        String ids =tableNamePkIdsMaps.get(k).stream().map(Objects::toString).collect(Collectors.joining(","));
+                        String sqlTableDataInfo="select id,p_key_id pageId,tab_key tabKey ,key_val value from table_data_info where  p_key_id in("+ids+")";
+                        List<CellDataVo> list= service.getSqlList(sqlTableDataInfo,CellDataVo.class);
+                        if(list!=null){
+                            Set<String> set= v.stream().map(FormData::getTableName).collect(Collectors.toSet());
+                            Map<String, List<CellDataVo>>  targetGroup=list.stream().filter(e->set.contains(e.getKey())).collect(Collectors.groupingBy(CellDataVo::getKey,Collectors.toList()));
+                            for(FormData fd:v){
+                                fd.setCellDataVoList(targetGroup.get(fd.getKey()));
+                                if(fd.getCellDataVoList()!=null) {
+                                    List<Long> pageIds = tableNamePkIdsMaps.get(fd.getTableName());
+                                    String coords =tec.getCoordinateMap().computeIfAbsent(fd.getTableName(), x -> FormulaUtils.getElementCell(tableNameUrlMaps.get(x))).get(fd.getKey());
+                                    if(Func.isNotBlank(coords)){
+                                        fd.flushCoords(coords);
+                                    }else{
+                                        tec.getLog().put(FormulaLog.POSITION,fd.getCode()+"("+fd.getEName()+")");
+                                        continue;
+                                    }
+                                    List<Coords> coordsList = fd.getCoordsList();
+                                    List<ElementData> eds = fd.getValues();
+                                    Map<String, Object> map = fd.getCellDataVoList().stream().collect(Collectors.toMap(e -> StringUtils.join(pageIds.indexOf(e.getPageId()), e.getX(), e.getY(), StringPool.AT), CellDataVo::getValue));
+                                    for (int i = 0; i < pageIds.size(); i++) {
+                                        for (Coords c : coordsList) {
+                                            String key = StringUtils.join(i, c.getX(), c.getY(), StringPool.AT);
+                                            eds.add(new ElementData(i, 0, map.get(key), c.getX(), c.getY()));
+                                        }
+                                    }
+                                    /*数据初始化加载*/
+                                    fd.setInitReady(true);
+                                    /*备份原始数据,用于更新比较*/
+                                    fd.init();
+                                }
+                            }
+                        }
+                    });
+                }
+                if(Func.isNotEmpty(removeList)){
+                    /*移除已经找到的元素数据*/
+                    missingList.removeIf(removeList::contains);
+                }
+            }
+        }
+        public void keyWord(Map<String,Object> constantMap){
+            CustomFunction.KEYWORD_SET.forEach(e->{
+                constantMap.put(e,e);
+            });
+        }
+        public void baseData(){
+            /*List<FormData> list =this.tec.getFds();*/
+            CurrentNode one=tec.getCurrentNode();
+            /*tec.formDataList=list;*/
+            /*预设关键字*/
+            keyWord(tec.constantMap);
+            /*wbs节点链*/
+            if(ExecuteType.INSPECTION.equals(tec.getExecuteType())) {
+                List<WbsTreeContract> nodes = service.wpService().tracing(one.getPkId());
+                tec.constantMap.put(CHAIN,nodes.stream().map(e-> StringUtils.isNotEmpty(e.getFullName())?e.getFullName():e.getNodeName()).collect(Collectors.toList()));
+                /*节点参数*/
+                tec.constantMap.put(WP,service.getWpMap(one));
+                /*监表质量附件,过滤掉隐藏表格*/
+                tec.constantMap.put("tableNames",tec.getTableAll().stream().filter(e->StringUtils.isEquals(e.getIsBussShow(),1)&&StringUtils.isNotEquals(e.getTableType(),4)).map(NodeTable::getNodeName).collect(Collectors.toList()));
+            }
+        }
+    }
+
+    public class Sort implements  FormulaHandleChain{
+        @Override
+        public void handle() {
+            tec.getFormDataList().sort(Comparator.comparingInt(FormData::getStep));
+        }
+    }
+
+    public class Pre implements  FormulaHandleChain{
+        @Override
+        public void handle() {
+            for(FormData fd:tec.getFormDataList()){
+                System.out.println(fd.getEName());
+            }
+        }
+    }
+    public class Special implements  FormulaHandleChain{
+        @Override
+        public void handle() {
+            for(FormData fd:tec.getFormDataList()){
+                System.out.println(fd.getEName());
+            }
+        }
+    }
+    public class Calculate implements  FormulaHandleChain{
+        @Override
+        public void handle() {
+             for(FormData fd:tec.getFormDataList()){
+                 System.out.println(fd.getEName());
+             }
+        }
+    }
+    public class Format implements  FormulaHandleChain{
+        @Override
+        public void handle() {
+            for(FormData fd:tec.getFormDataList()){
+                System.out.println(fd.getEName());
+            }
+        }
+    }
+
+
+}

+ 5 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/FormulaHandleChain.java

@@ -0,0 +1,5 @@
+package org.springblade.manager.formula;
+
+public interface FormulaHandleChain {
+    void handle();
+}

+ 0 - 8
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/ITable.java

@@ -1,8 +0,0 @@
-package org.springblade.manager.formula;
-
-/**
- * @author yangyj
- */
-public interface ITable {
-
-}

+ 32 - 17
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/ITurnPointCalculator.java

@@ -11,6 +11,7 @@ import java.math.BigDecimal;
 import java.util.*;
 import java.util.concurrent.ThreadLocalRandom;
 
+import static com.mixsmart.utils.CustomFunction.min;
 import static com.mixsmart.utils.CustomFunction.rangeList;
 import static java.math.BigDecimal.ROUND_HALF_UP;
 import static org.springblade.manager.formula.TurnPoint.*;
@@ -19,7 +20,7 @@ import static org.springblade.manager.formula.TurnPoint.*;
  * @author yangyj
  */
 public interface ITurnPointCalculator {
-    Long VERSION=202311130910L;
+    Long VERSION=202311301530L;
      String ZD_REG="(?i)zd\\d+";
      /*核心计算*/
     static List<TurnPoint> create(List<Map<String, Object>> data, LinkedHashMap<String, String> configMap,LevelInfo levelInfo) {
@@ -84,23 +85,43 @@ public interface ITurnPointCalculator {
     static boolean persist(List<TurnPoint> sourceList){
         /*在补充计算之前对副本进行完整性校验(只校验不改数据),如果成功则不会执行转点计算*/
         if(Func.isNotEmpty(sourceList)){
-              LevelInfo tmp = new LevelInfo();
-              LevelInfo info = sourceList.get(0).getLevelInfo();
-              BeanUtils.copyProperties(info,tmp);
-              List<TurnPoint> list =BaseUtils.copyList(sourceList);
-            assert list != null;
-            list.forEach(tp->tp.setLevelInfo(tmp));
+            List<TurnPoint> list =BaseUtils.copyList(sourceList);
+            if(list==null){
+                return false;
+            }
+            LevelInfo info =list.get(0).getLevelInfo();
             boolean pass=false;
             for(TurnPoint tp:list){
                   if(CE.equals(tp.getType())||CLOSE.equals(tp.getType())){
-                     pass=tp.isCompletedCe()&&tp.isRealScMatch();
+                     pass=tp.isCompletedCe()&&tp.isRealScMatch()&&tp.isRealDxMatch();
+                     if(pass&&info.isDistance()&&CE.equals(tp.getType())){
+                         Double mileage= BaseUtils.milestone(tp.getName());
+                         if(mileage!=null) {
+                             double d=mileage-info.getMilestone();
+                             double dx =Math.abs(d)-info.getHeightTurnLength();
+                             if(dx>200.0000001){
+                                 return false;
+                             }else{
+                                 if(dx<0){
+                                     /*等效距离已经超过需要距离转点,则把仪器里程设置为当前测点*/
+                                     info.setMilestone(mileage);
+                                 }else{
+                                     double move = Math.ceil(d/200)*200;
+                                     info.setMilestone(info.getMilestone()+move);
+                                 }
+                             }
+                         }
+                     }
                   }else if(ZD.equals(tp.getType())){
                       pass=tp.isCompletedZd()&&tp.isWithinRangeValueZd(tp.getQ())&&tp.isWithinRangeValueZd(tp.getH())&&tp.isRealZdMatch();
                       if(pass) {
-                          tmp.setSightHeight(tp.getBmd0L());
+                          info.setSightHeight(tp.getBmd0L());
+                          /*等效距离*/
+                          info.setHeightTurnLength(info.getHeightTurnLength()+200);
                       }
                   }else if(BMD.equals(tp.getType())){
-                       pass= tp.isCompletedBm()&&tp.isRealBmMatch();
+                      pass= tp.isCompletedBm()&&tp.isRealBmMatch();
+                      info.checkDistance(tp.getName());
                   }
                   if(!pass){
                       return false;
@@ -220,13 +241,7 @@ public interface ITurnPointCalculator {
                     } else if (tp.getType().equals(TurnPoint.BMD)) {
                         /*水准点处理*/
                         tpHandlerBmd(tp);
-                         Double  milestone= info.getBmMap().get(tp.getName().trim());
-                         if(milestone!=null) {
-                             /*如果水准点包含里程信息就会启动距离计算转点 200米一个点,配合高差按多的那个算*/
-                             info.setDistance(true);
-                             /*设置初始里程*/
-                             info.setMilestone(milestone);
-                         }
+                        info.checkDistance(tp.getName());
                     }
                     /*把数据放到输出缓存*/
                     putCache(tp);

+ 16 - 6
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/LevelInfo.java

@@ -209,8 +209,16 @@ public class LevelInfo implements  Cloneable, Serializable {
         this.milestone = milestone;
     }
 
-
-
+    /*检查BM点是否包含里程信息,如果包含则开启距离转点*/
+    public void checkDistance(String BmName){
+        Double  milestone= this.getBmMap().get(BmName.trim());
+        if(milestone!=null) {
+            /*如果水准点包含里程信息就会启动距离计算转点 200米一个点,配合高差按多的那个算*/
+           setDistance(true);
+            /*设置初始里程*/
+           setMilestone(milestone);
+        }
+    }
     /*重置仪器里程,返回需要转点的数量*/
     public int resetMilestone(double x){
         /*只要包含里程信息就必须重置等效间距*/
@@ -233,11 +241,13 @@ public class LevelInfo implements  Cloneable, Serializable {
             }
             /*重置等效高程转点*/
             this.heightTurnLength =0D;
-        }else{
-            /*this.levelInfo.setMilestone(x-(ThreadLocalRandom.current().nextInt(21)+5)*symbol);*/
-            /*this.levelInfo.setMilestone(x);*/
-            this.milestone=this.milestone+((int)(d/200))*200;
         }
+   /*     else{
+            *//*this.levelInfo.setMilestone(x-(ThreadLocalRandom.current().nextInt(21)+5)*symbol);*//*
+            *//*this.levelInfo.setMilestone(x);*//*
+            this.milestone=this.milestone+((int)(d/200))*200;
+        }*/
+        /*先抵消高程等效间距的影响再计算实际距离转点*/
         if(Math.abs(d)>200.00001){
             /*间距超过200则返回需要距离转点的数量*/
             int n= (int)Math.floor(Math.abs(d)/200);

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/NodeTable.java

@@ -19,4 +19,5 @@ public class NodeTable {
     private String ancestors;
     private Integer sort;
     private String htmlUrl;
+    private Integer isBussShow;
 }

+ 5 - 1
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/TurnPoint.java

@@ -252,6 +252,10 @@ public class TurnPoint implements Serializable {
         /*由视线高和假定的前视推导的实测高程是否与记录的实测高程相相等*/
         return StringUtils.isEquals(StringUtils.number2StringZero(levelInfo.getSightHeight() - getQ0L(), levelInfo.getScale()), getSc());
     }
+    public boolean isRealDxMatch(){
+        /*由视线高和假定的前视推导的实测高程是否与记录的实测高程相相等*/
+        return StringUtils.isEquals(StringUtils.number2StringZero(getSc0L()-getSj0L(), levelInfo.getScale()), getDx0L());
+    }
     /*转点验证*/
     public boolean isRealZdMatch(){
         return StringUtils.isEquals(StringUtils.number2StringZero(levelInfo.getSightHeight()+getH0L()-getQ0L(), levelInfo.getScale()), getBmd());
@@ -266,7 +270,7 @@ public class TurnPoint implements Serializable {
         return BaseUtils.isNumber(this.bmd)&&BaseUtils.isNumber(this.q)&&BaseUtils.isNumber(this.h);
     }
     public boolean isCompletedCe(){
-        return BaseUtils.isNumber(this.sc)&&BaseUtils.isNumber(this.q)&&BaseUtils.isNumber(this.dx);
+        return BaseUtils.isNumber(this.sc)&&BaseUtils.isNumber(this.q)&&BaseUtils.isNumber(this.dx)&&BaseUtils.isNumber(this.sj);
     }
     public boolean isCompletedBm(){
         return BaseUtils.isNumber(this.bmd)&&BaseUtils.isNumber(this.h)&&BaseUtils.isNumber(this.sj);

+ 3 - 5
blade-service/blade-manager/src/main/java/org/springblade/manager/formula/impl/TableElementConverter.java

@@ -23,16 +23,12 @@ import org.springblade.manager.formula.FormulaLog;
 import org.springblade.manager.formula.ITableElementConverter;
 import org.springblade.manager.formula.KeyMapper;
 import org.springblade.manager.formula.NodeTable;
-import org.springblade.manager.vo.AppWbsTreeContractVO;
 import org.springblade.manager.vo.CurrentNode;
-import org.springframework.beans.BeanUtils;
+import org.springframework.jdbc.core.JdbcTemplate;
 
 import java.util.*;
-import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
 
 /**
  * @author yangyj
@@ -107,6 +103,8 @@ public class TableElementConverter implements ITableElementConverter {
     /**是否使用新流程,默认关闭*/
     public  boolean isNew=false;
 
+    public JdbcTemplate  jdbcTemplate;
+
     /**根据表名获取对应表页对象*/
     public LinkedList<NodeTable> getTableByName(String initTableName){
         LinkedList<NodeTable> list = new LinkedList<>();

+ 4 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/IFormulaService.java

@@ -112,4 +112,8 @@ public interface IFormulaService extends BaseService<Formula> {
     R<Object> evaluate(Long pkeyId);
     /**获取当前节点的参数wbsNodeId:WBS级id ,wtpPkeyId:项目级PkeyId*/
     List<WbsParam> getNodeWps(Long wbsNodeId,Long wtpPkeyId);
+
+    IWbsParamService wpService();
+
+
 }

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

@@ -50,4 +50,7 @@ public interface IWbsParamService extends BaseService<WbsParam> {
      R<Object> treeCodeUpdateProjectAllForJob();
     /**指定合同段或者项目 ,合同段id,projectId项目id , R<Object> 返回信息*/
      R<Object> treeCode(Long contractId,Long projectId);
+
+
+
 }

+ 1 - 0
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/ExcelTabServiceImpl.java

@@ -376,6 +376,7 @@ public class ExcelTabServiceImpl extends BaseServiceImpl<ExcelTabMapper, ExcelTa
                         tec.setContractId(contractId);
                         tec.setProjectId(projectId);
                         if (tec.isPresent()) {
+                            tec.setJdbcTemplate(this.jdbcTemplate);
                             tec.before();
                             this.formulaService.execute(tec);
                             tec.after();

+ 14 - 9
blade-service/blade-manager/src/main/java/org/springblade/manager/service/impl/FormulaServiceImpl.java

@@ -17,6 +17,7 @@ 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.*;
@@ -678,15 +679,6 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
     public Map<String,Object> getWpMap(Long wbsNodeId,Long wtpPkeyId){
         Map<String,Object> result = new HashMap<>(100);
         List<WbsParam> total=this.getNodeWps(wbsNodeId,wtpPkeyId);
-    /*    List<WbsParam> total = new ArrayList<>();
-        List<WbsParam> wpsPublic = this.wpService.list(Wrappers.<WbsParam>lambdaQuery().eq(WbsParam::getNodeId,wbsNodeId).eq(WbsParam::getType,1));
-        if(Func.isNotEmpty(wpsPublic)){
-            total.addAll(wpsPublic);
-        }
-        List<WbsParam> wpsPrivate = this.wpService.list(Wrappers.<WbsParam>lambdaQuery().eq(WbsParam::getNodeId,wtpPkeyId).eq(WbsParam::getType,1));
-        if(Func.isNotEmpty(wpsPrivate)){
-            total.addAll(wpsPrivate);
-        }*/
         Map<String,String> logMap =new HashMap<>();
         if(CollectionUtil.isNotEmpty(total)){
             /*同名参数私有覆盖公用*/
@@ -2485,6 +2477,19 @@ public class FormulaServiceImpl extends BaseServiceImpl<FormulaMapper, Formula>
         return R.fail("无数据");
     }
 
+    @Override
+    public IWbsParamService wpService() {
+        return this.wpService;
+    }
+
+    public IWbsTreeContractService wbsTreeContractService(){
+        return this.wbsTreeContractService;
+    }
+
+    public void execute3(TableElementConverter tableElementConverter){
+        FormulaExecutor formulaExecutor = new FormulaExecutor(this,tableElementConverter);
+    }
+
 
 
 }

+ 5 - 0
blade-service/blade-meter/pom.xml

@@ -27,6 +27,11 @@
             <groupId>org.springblade</groupId>
             <artifactId>blade-starter-swagger</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springblade</groupId>
+            <artifactId>blade-manager-api</artifactId>
+            <version>${bladex.project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 36 - 27
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/ContractInventoryFormController.java

@@ -25,7 +25,9 @@ import org.springblade.core.mp.support.Condition;
 import org.springblade.core.mp.support.Query;
 import org.springblade.core.tool.api.R;
 import org.springblade.core.tool.utils.Func;
+import org.springblade.meter.vo.ContractInventoryFormVO;
 import org.springblade.meter.vo.FormTreeVO;
+import org.springblade.meter.vo.InventoryFormDetailVO;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.RequestParam;
 import com.baomidou.mybatisplus.core.metadata.IPage;
@@ -76,25 +78,28 @@ public class ContractInventoryFormController extends BladeController {
 	}
 
 	/**
-	 * 详情
+	 * 获取节点详情
 	 */
 	@GetMapping("/detail")
 	@ApiOperationSupport(order = 2)
-	@ApiOperation(value = "详情", notes = "传入contractInventoryForm")
-	public R<ContractInventoryForm> detail(ContractInventoryForm contractInventoryForm) {
-		ContractInventoryForm detail = contractInventoryFormService.getOne(Condition.getQueryWrapper(contractInventoryForm));
-		return R.data(detail);
+	@ApiOperation(value = "获取节点详情", notes = "传入当前节点id")
+	public R<InventoryFormDetailVO> detail(Long id) {
+		InventoryFormDetailVO vo = contractInventoryFormService.detail(id);
+		return R.data(vo);
 	}
 
 	/**
-	 * 分页 合同工程清单表
+	 * 锁定节点
 	 */
-	@GetMapping("/list")
+	@GetMapping("/lockNode")
 	@ApiOperationSupport(order = 3)
-	@ApiOperation(value = "分页", notes = "传入contractInventoryForm")
-	public R<IPage<ContractInventoryForm>> list(ContractInventoryForm contractInventoryForm, Query query) {
-		IPage<ContractInventoryForm> pages = contractInventoryFormService.page(Condition.getPage(query), Condition.getQueryWrapper(contractInventoryForm));
-		return R.data(pages);
+	@ApiOperation(value = "锁定节点", notes = "传入当前节点id,和状态0为锁定,1为解锁")
+	@ApiImplicitParams(value = {
+			@ApiImplicitParam(name = "id", value = "当前节点id", required = true),
+			@ApiImplicitParam(name = "status", value = "状态0为锁定,1为解锁", required = true)
+	})
+	public R<String> lockNode(Long id,Long contractId,Integer status) {
+		return contractInventoryFormService.lockNode(id,contractId,status);
 	}
 
 
@@ -102,42 +107,46 @@ public class ContractInventoryFormController extends BladeController {
 	/**
 	 * 新增 合同工程清单表
 	 */
-	@PostMapping("/save")
+	@PostMapping("/add")
 	@ApiOperationSupport(order = 4)
-	@ApiOperation(value = "新增", notes = "传入contractInventoryForm")
-	public R save(@Valid @RequestBody ContractInventoryForm contractInventoryForm) {
-		return R.status(contractInventoryFormService.save(contractInventoryForm));
+	@ApiOperation(value = "新增", notes = "传入整个节点信息,普通新增isSupplement传0,增补传1")
+	public R<String> add(@Valid @RequestBody ContractInventoryForm form) {
+		contractInventoryFormService.add(form);
+		return R.success("新增成功");
 	}
 
 	/**
 	 * 修改 合同工程清单表
 	 */
-	@PostMapping("/update")
+	@PostMapping("/updateForm")
 	@ApiOperationSupport(order = 5)
 	@ApiOperation(value = "修改", notes = "传入contractInventoryForm")
-	public R update(@Valid @RequestBody ContractInventoryForm contractInventoryForm) {
-		return R.status(contractInventoryFormService.updateById(contractInventoryForm));
+	public R<String> updateForm(@Valid @RequestBody ContractInventoryForm form) {
+		contractInventoryFormService.updateForm(form);
+		return R.success("修改成功");
 	}
 
 	/**
-	 * 新增或修改 合同工程清单表
+	 * 排序 合同工程清单表
 	 */
-	@PostMapping("/submit")
+	@PostMapping("/sort")
 	@ApiOperationSupport(order = 6)
-	@ApiOperation(value = "新增或修改", notes = "传入contractInventoryForm")
-	public R submit(@Valid @RequestBody ContractInventoryForm contractInventoryForm) {
-		return R.status(contractInventoryFormService.saveOrUpdate(contractInventoryForm));
+	@ApiOperation(value = "排序", notes = "传入排序后的id,字符串拼接逗号分隔")
+	public R submit(@Valid @RequestParam String ids) {
+		contractInventoryFormService.sort(ids);
+		return R.success("排序完成");
 	}
 
 	
 	/**
 	 * 删除 合同工程清单表
 	 */
-	@PostMapping("/remove")
+	@GetMapping("/delete")
 	@ApiOperationSupport(order = 7)
-	@ApiOperation(value = "逻辑删除", notes = "传入ids")
-	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
-		return R.status(contractInventoryFormService.deleteLogic(Func.toLongList(ids)));
+	@ApiOperation(value = "删除节点", notes = "传入id")
+	public R delete(@ApiParam(value = "id", required = true) @RequestParam Long id,@RequestParam Long contractId) {
+		contractInventoryFormService.delete(id,contractId);
+		return R.success("删除成功");
 	}
 
 	

+ 26 - 43
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/ContractMaterialController.java

@@ -16,9 +16,7 @@
  */
 package org.springblade.meter.controller;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.*;
 import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
 import lombok.AllArgsConstructor;
 import javax.validation.Valid;
@@ -48,15 +46,15 @@ public class ContractMaterialController extends BladeController {
 
 	private final IContractMaterialService contractMaterialService;
 
+
 	/**
-	 * 详情
+	 * 新增或修改 合同材料表
 	 */
-	@GetMapping("/detail")
+	@PostMapping("/submit")
 	@ApiOperationSupport(order = 1)
-	@ApiOperation(value = "详情", notes = "传入contractMaterial")
-	public R<ContractMaterial> detail(ContractMaterial contractMaterial) {
-		ContractMaterial detail = contractMaterialService.getOne(Condition.getQueryWrapper(contractMaterial));
-		return R.data(detail);
+	@ApiOperation(value = "新增或修改", notes = "传入contractMaterial")
+	public R submit(@Valid @RequestBody ContractMaterial contractMaterial) {
+		return R.status(contractMaterialService.saveOrUpdate(contractMaterial));
 	}
 
 	/**
@@ -64,51 +62,36 @@ public class ContractMaterialController extends BladeController {
 	 */
 	@GetMapping("/list")
 	@ApiOperationSupport(order = 2)
-	@ApiOperation(value = "分页", notes = "传入contractMaterial")
+	@ApiOperation(value = "分页", notes = "传入项目id合同段id和分页信息")
+	@ApiImplicitParams(value = {
+			@ApiImplicitParam(name = "projectId", value = "项目id", required = true),
+			@ApiImplicitParam(name = "contractId", value = "合同id", required = true),
+			@ApiImplicitParam(name = "current", value = "当前页", required = true),
+			@ApiImplicitParam(name = "size", value = "每页数量", required = true)
+	})
 	public R<IPage<ContractMaterial>> list(ContractMaterial contractMaterial, Query query) {
 		IPage<ContractMaterial> pages = contractMaterialService.page(Condition.getPage(query), Condition.getQueryWrapper(contractMaterial));
 		return R.data(pages);
 	}
 
-
-
-	/**
-	 * 新增 合同材料表
-	 */
-	@PostMapping("/save")
-	@ApiOperationSupport(order = 4)
-	@ApiOperation(value = "新增", notes = "传入contractMaterial")
-	public R save(@Valid @RequestBody ContractMaterial contractMaterial) {
-		return R.status(contractMaterialService.save(contractMaterial));
-	}
-
-	/**
-	 * 修改 合同材料表
-	 */
-	@PostMapping("/update")
-	@ApiOperationSupport(order = 5)
-	@ApiOperation(value = "修改", notes = "传入contractMaterial")
-	public R update(@Valid @RequestBody ContractMaterial contractMaterial) {
-		return R.status(contractMaterialService.updateById(contractMaterial));
-	}
-
-	/**
-	 * 新增或修改 合同材料表
-	 */
-	@PostMapping("/submit")
-	@ApiOperationSupport(order = 6)
-	@ApiOperation(value = "新增或修改", notes = "传入contractMaterial")
-	public R submit(@Valid @RequestBody ContractMaterial contractMaterial) {
-		return R.status(contractMaterialService.saveOrUpdate(contractMaterial));
-	}
+//	/**
+//	 * 详情
+//	 */
+//	@GetMapping("/detail")
+//	@ApiOperationSupport(order = 3)
+//	@ApiOperation(value = "详情", notes = "传入contractMaterial")
+//	public R<ContractMaterial> detail(ContractMaterial contractMaterial) {
+//		ContractMaterial detail = contractMaterialService.getOne(Condition.getQueryWrapper(contractMaterial));
+//		return R.data(detail);
+//	}
 
 	
 	/**
 	 * 删除 合同材料表
 	 */
 	@PostMapping("/remove")
-	@ApiOperationSupport(order = 7)
-	@ApiOperation(value = "逻辑删除", notes = "传入ids")
+	@ApiOperationSupport(order = 4)
+	@ApiOperation(value = "删除", notes = "传入ids")
 	public R remove(@ApiParam(value = "主键集合", required = true) @RequestParam String ids) {
 		return R.status(contractMaterialService.deleteLogic(Func.toLongList(ids)));
 	}

+ 677 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MeterTreeController.java

@@ -0,0 +1,677 @@
+package org.springblade.meter.controller;
+
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import net.logstash.logback.encoder.org.apache.commons.lang3.ObjectUtils;
+import org.springblade.core.boot.ctrl.BladeController;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.support.Condition;
+import org.springblade.core.mp.support.Query;
+import org.springblade.core.redis.cache.BladeRedis;
+import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.core.tool.utils.ObjectUtil;
+import org.springblade.manager.entity.ContractInfo;
+import org.springblade.manager.entity.ProjectInfo;
+import org.springblade.meter.dto.MeterTreeContractDTO;
+import org.springblade.meter.dto.MeterTreeContractSaveBatchDTO;
+import org.springblade.meter.entity.MeterTreeContract;
+import org.springblade.meter.entity.MeterTreeProject;
+import org.springblade.meter.entity.MeterTreeSystem;
+import org.springblade.meter.entity.MeterTreeTemplateInfo;
+import org.springblade.meter.service.MeterTreeContractService;
+import org.springblade.meter.service.MeterTreeProjectService;
+import org.springblade.meter.service.MeterTreeSystemService;
+import org.springblade.meter.service.MeterTreeTemplateInfoService;
+import org.springblade.meter.vo.MeterTreeContractVO;
+import org.springframework.jdbc.core.BeanPropertyRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@RestController
+@AllArgsConstructor
+@RequestMapping("/tree")
+@Api(value = "计量单元(树)接口", tags = "计量单元(树)接口")
+public class MeterTreeController extends BladeController {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final BladeRedis bladeRedis;
+    private final MeterTreeTemplateInfoService meterTreeTemplateInfoService;
+    private final MeterTreeSystemService meterTreeSystemService;
+    private final MeterTreeProjectService meterTreeProjectService;
+    private final MeterTreeContractService meterTreeContractService;
+
+    @GetMapping("/template/detail")
+    @ApiOperationSupport(order = 1)
+    @ApiOperation(value = "树模板详情", notes = "传入id")
+    public R<MeterTreeTemplateInfo> templateDetail(@RequestParam String id) {
+        return R.data(meterTreeTemplateInfoService.getById(id));
+    }
+
+    @PostMapping("/template/submit")
+    @ApiOperationSupport(order = 2)
+    @ApiOperation(value = "树模板新增、修改", notes = "传入MeterTreeTemplateInfo")
+    public R<Object> templateSubmit(@RequestBody MeterTreeTemplateInfo obj) {
+        if (ObjectUtil.isNotEmpty(obj.getId())) {
+            /*修改模板信息*/
+            if (meterTreeTemplateInfoService.updateById(obj)) {
+                /*修改详情根节点名称*/
+                return R.data(meterTreeSystemService.update(Wrappers.<MeterTreeSystem>lambdaUpdate()
+                        .eq(MeterTreeSystem::getId, obj.getId())
+                        .set(MeterTreeSystem::getNodeName, obj.getName())));
+            }
+        } else {
+            /*创建模板信息*/
+            if (meterTreeTemplateInfoService.save(obj)) {
+                /*创建详情根节点*/
+                MeterTreeSystem systemTree = new MeterTreeSystem();
+                systemTree.setId(obj.getId());
+                systemTree.setTemplateId(obj.getId());
+                systemTree.setNodeName(obj.getName());
+                systemTree.setParentId(0L);
+                systemTree.setAncestor("0");
+                systemTree.setSort(1);
+                return R.data(meterTreeSystemService.save(systemTree));
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/template/remove")
+    @ApiOperationSupport(order = 3)
+    @ApiOperation(value = "树模板删除", notes = "传入ids")
+    public R<Object> templateRemove(@RequestParam String ids) {
+        String[] split = ids.split(",");
+        for (String id : split) {
+            /*删除模板*/
+            if (meterTreeTemplateInfoService.removeById(id)) {
+
+                /*判断项目引用情况,项目引用中无法删除(项目节点sourceNodeId指向就是系统级节点的id*/
+                List<MeterTreeProject> meterTreeProjects = meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                        .select(MeterTreeProject::getProjectId)
+                        .eq(MeterTreeProject::getTemplateId, id)
+                        .eq(MeterTreeProject::getStatus, 1)
+                        .groupBy(MeterTreeProject::getProjectId)
+                );
+                if (meterTreeProjects.size() > 0) {
+                    List<Long> projectIds = meterTreeProjects.stream().map(MeterTreeProject::getProjectId).collect(Collectors.toList());
+                    List<ProjectInfo> projectInfoList = jdbcTemplate.query("SELECT project_name FROM m_project_info WHERE id in(" + org.apache.commons.lang.StringUtils.join(projectIds, ",") + ")", new BeanPropertyRowMapper<>(ProjectInfo.class));
+                    List<String> names = projectInfoList.stream().map(ProjectInfo::getProjectName).collect(Collectors.toList());
+                    throw new ServiceException("【" + org.apache.commons.lang.StringUtils.join(names, "、") + "】项目引用中,无法删除该节点");
+                }
+
+                /*删除详情*/
+                meterTreeSystemService.remove(Wrappers.<MeterTreeSystem>lambdaQuery().eq(MeterTreeSystem::getTemplateId, id));
+            }
+        }
+        return R.success("操作成功");
+    }
+
+    @PostMapping("/template/page")
+    @ApiOperationSupport(order = 4)
+    @ApiOperation(value = "树模板分页", notes = "传入MeterTreeTemplateInfo、Query")
+    public R<IPage<MeterTreeTemplateInfo>> templatePage(@RequestBody MeterTreeTemplateInfo meterTreeTemplateInfo, @RequestBody Query query) {
+        IPage<MeterTreeTemplateInfo> pages = meterTreeTemplateInfoService.page(Condition.getPage(query), Condition.getQueryWrapper(meterTreeTemplateInfo));
+        List<MeterTreeTemplateInfo> sortResult = pages.getRecords().stream()
+                .sorted(Comparator.comparing(MeterTreeTemplateInfo::getCreateTime))
+                .collect(Collectors.toList());
+        return R.data(pages.setRecords(sortResult));
+    }
+
+    @GetMapping("/template/list")
+    @ApiOperationSupport(order = 4)
+    @ApiOperation(value = "树模板列表", notes = "")
+    public R<List<MeterTreeTemplateInfo>> templateList() {
+        return R.data(meterTreeTemplateInfoService.getBaseMapper().selectList(Wrappers.<MeterTreeTemplateInfo>lambdaQuery().select(MeterTreeTemplateInfo::getName, MeterTreeTemplateInfo::getId)));
+    }
+
+    @GetMapping("/system/detail")
+    @ApiOperationSupport(order = 5)
+    @ApiOperation(value = "系统树节点详情", notes = "传入id")
+    public R<MeterTreeSystem> systemDetail(@RequestParam String id) {
+        return R.data(meterTreeSystemService.getById(id));
+    }
+
+    @PostMapping("/system/save")
+    @ApiOperationSupport(order = 6)
+    @ApiOperation(value = "系统树节点新增", notes = "传入MeterTreeSystem(上级的id赋值到parentId字段上提交)")
+    public R<Object> systemSave(@RequestBody MeterTreeSystem obj) {
+        /*计算parentId、ancestor*/
+        if (ObjectUtil.isEmpty(obj.getParentId()) || ObjectUtil.isEmpty(obj.getAncestor())) {
+            throw new ServiceException("未获取到父节点id或祖级id");
+        }
+        MeterTreeSystem parentNode = meterTreeSystemService.getById(obj.getParentId());
+        if (ObjectUtil.isEmpty(parentNode)) {
+            throw new ServiceException("未获取到父节点信息");
+        }
+        obj.setAncestor(parentNode.getAncestor() + "," + parentNode.getId());
+
+        /*获取最大sort*/
+        Integer maxSort = meterTreeSystemService.selectMaxSort(obj.getParentId());
+        obj.setSort(ObjectUtils.defaultIfNull(maxSort, 0) + 1);
+
+        return R.data(meterTreeSystemService.save(obj));
+    }
+
+    @PostMapping("/system/update")
+    @ApiOperationSupport(order = 7)
+    @ApiOperation(value = "系统树节点修改", notes = "传入MeterTreeSystem")
+    public R<Object> systemUpdate(@RequestBody MeterTreeSystem obj) {
+        return R.data(meterTreeSystemService.updateById(obj));
+    }
+
+    @GetMapping("/system/remove")
+    @ApiOperationSupport(order = 8)
+    @ApiOperation(value = "系统树节点删除", notes = "传入id")
+    public R<Object> systemRemove(@RequestParam String id) {
+        if (StringUtils.isNotEmpty(id)) {
+            MeterTreeSystem obj = meterTreeSystemService.getById(id);
+            if (obj != null) {
+                if (obj.getParentId().equals(0L) && obj.getAncestor().equals("0")) {
+                    throw new ServiceException("根节点无法删除");
+                }
+
+                /*子节点判断*/
+                Long countChild = meterTreeSystemService.getBaseMapper().selectCount(Wrappers.<MeterTreeSystem>lambdaQuery()
+                        .eq(MeterTreeSystem::getTemplateId, obj.getTemplateId())
+                        .eq(MeterTreeSystem::getStatus, 1)
+                        .like(MeterTreeSystem::getAncestor, id)
+                );
+                if (countChild > 0) {
+                    throw new ServiceException("该节点下存在子节点,无法删除");
+                }
+
+                /*判断项目引用情况,项目引用中无法删除(项目节点sourceNodeId指向就是系统级节点的id*/
+                List<MeterTreeProject> meterTreeProjects = meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                        .select(MeterTreeProject::getProjectId)
+                        .eq(MeterTreeProject::getTemplateId, obj.getTemplateId())
+                        .eq(MeterTreeProject::getSourceNodeId, obj.getId())
+                        .eq(MeterTreeProject::getStatus, 1)
+                        .groupBy(MeterTreeProject::getProjectId)
+                );
+                if (meterTreeProjects.size() > 0) {
+                    List<Long> projectIds = meterTreeProjects.stream().map(MeterTreeProject::getProjectId).collect(Collectors.toList());
+                    List<ProjectInfo> projectInfoList = jdbcTemplate.query("SELECT project_name FROM m_project_info WHERE id in(" + org.apache.commons.lang.StringUtils.join(projectIds, ",") + ")", new BeanPropertyRowMapper<>(ProjectInfo.class));
+                    List<String> names = projectInfoList.stream().map(ProjectInfo::getProjectName).collect(Collectors.toList());
+                    throw new ServiceException("【" + org.apache.commons.lang.StringUtils.join(names, "、") + "】项目引用中,无法删除该节点");
+                }
+                return R.data(meterTreeSystemService.removeById(id));
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/system/same-list")
+    @ApiOperationSupport(order = 9)
+    @ApiOperation(value = "系统树节点同级列表", notes = "传入parentId")
+    public R<List<MeterTreeSystem>> systemSameList(@RequestParam String parentId) {
+        if (StringUtils.isNotEmpty(parentId)) {
+            return R.data(meterTreeSystemService.getBaseMapper().selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                    .eq(MeterTreeSystem::getParentId, parentId)
+                    .eq(MeterTreeSystem::getStatus, 1)
+                    .orderByAsc(MeterTreeSystem::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @PostMapping("/system/sort")
+    @ApiOperationSupport(order = 10)
+    @ApiOperation(value = "系统树节点同级排序", notes = "传入节点ids逗号拼接字符串,按照顺序从上到下")
+    public R<Object> systemSort(@RequestBody String ids) {
+        if (StringUtils.isNotEmpty(ids)) {
+            String[] split = ids.split(",");
+            int sort = 1;
+            for (String id : split) {
+                meterTreeSystemService.update(Wrappers.<MeterTreeSystem>lambdaUpdate()
+                        .set(MeterTreeSystem::getSort, sort++)
+                        .eq(MeterTreeSystem::getId, id)
+                );
+            }
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/system/lazy")
+    @ApiOperationSupport(order = 11)
+    @ApiOperation(value = "系统树节点懒加载", notes = "传入templateId、节点id(根节点id=0)")
+    public R<List<MeterTreeSystem>> systemLazy(@RequestParam String templateId, @RequestParam String id) {
+        if (StringUtils.isNotEmpty(id) && StringUtils.isNotEmpty(templateId)) {
+            return R.data(meterTreeSystemService.getBaseMapper().selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                    .eq(MeterTreeSystem::getParentId, id)
+                    .eq(MeterTreeSystem::getTemplateId, templateId)
+                    .eq(MeterTreeSystem::getStatus, 1)
+                    .orderByAsc(MeterTreeSystem::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @GetMapping("/system/child-list")
+    @ApiOperationSupport(order = 12)
+    @ApiOperation(value = "系统树节点下级节点列表", notes = "传入id")
+    public R<List<MeterTreeSystem>> systemChildList(@RequestParam String id) {
+        if (StringUtils.isNotEmpty(id)) {
+            return R.data(meterTreeSystemService.getBaseMapper().selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                    .like(MeterTreeSystem::getAncestor, id)
+                    .eq(MeterTreeSystem::getStatus, 1)
+                    .orderByAsc(MeterTreeSystem::getCreateTime)
+                    .orderByAsc(MeterTreeSystem::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @GetMapping("/project/refresh")
+    @ApiOperationSupport(order = 13)
+    @ApiOperation(value = "项目树初始化(新增、增量同步)", notes = "传入项目projectId")
+    public R<Object> projectRefresh(@RequestParam String projectId) {
+        if (StringUtils.isEmpty(projectId)) {
+            throw new ServiceException("未获取到项目信息");
+        }
+        ProjectInfo projectInfo = jdbcTemplate.query("SELECT * FROM m_project_info WHERE id = " + projectId, new BeanPropertyRowMapper<>(ProjectInfo.class)).stream().findAny().orElse(null);
+        if (projectInfo != null && ObjectUtil.isNotEmpty(projectInfo.getMeterTemplateId())) {
+
+            /*加锁*/
+            String redisKey = "meter:project:refresh-tree:" + projectId;
+            String redisValue = bladeRedis.get(redisKey);
+            if (StringUtils.isNotEmpty(redisValue) && redisValue.equals("1")) {
+                return R.fail(400, "请勿重复提交,60秒后再尝试");
+            }
+            bladeRedis.set(redisKey, "1");
+            bladeRedis.expire(redisKey, 60);
+
+            /*初始化 或 增量同步 项目计量树*/
+            boolean result = meterTreeProjectService.projectTreeInitOrSync(projectInfo.getMeterTemplateId(), Long.parseLong(projectId));
+            if (result) {
+                return R.success("操作成功");
+            } else {
+                return R.fail("操作失败");
+            }
+        } else {
+            throw new ServiceException("未获取到项目关联的系统单元信息,操作失败");
+        }
+    }
+
+    @GetMapping("/project/detail")
+    @ApiOperationSupport(order = 14)
+    @ApiOperation(value = "项目树节点详情", notes = "传入id")
+    public R<MeterTreeProject> projectDetail(@RequestParam String id) {
+        return R.data(meterTreeProjectService.getById(id));
+    }
+
+    @PostMapping("/project/save")
+    @ApiOperationSupport(order = 15)
+    @ApiOperation(value = "项目树节点新增", notes = "传入MeterTreeProject(上级的id赋值到parentId字段上提交)")
+    public R<Object> projectSave(@RequestBody MeterTreeProject obj) {
+        /*计算parentId、ancestor*/
+        if (ObjectUtil.isEmpty(obj.getParentId()) || ObjectUtil.isEmpty(obj.getAncestor())) {
+            throw new ServiceException("未获取到父节点id或祖级id");
+        }
+        MeterTreeProject parentNode = meterTreeProjectService.getById(obj.getParentId());
+        if (ObjectUtil.isEmpty(parentNode)) {
+            throw new ServiceException("未获取到父节点信息");
+        }
+        obj.setAncestor(parentNode.getAncestor() + "," + parentNode.getId());
+
+        /*获取最大sort*/
+        Integer maxSort = meterTreeProjectService.selectMaxSort(obj.getParentId());
+        obj.setSort(ObjectUtils.defaultIfNull(maxSort, 0) + 1);
+
+        obj.setDataSourceType(2); //客户手动新增
+        obj.setSourceNodeId(null); //手动新增无源节点id
+        obj.setUpdateStatus(0); //非编辑
+
+        return R.data(meterTreeProjectService.save(obj));
+    }
+
+    @PostMapping("/project/update")
+    @ApiOperationSupport(order = 16)
+    @ApiOperation(value = "项目树节点修改", notes = "传入MeterTreeProject")
+    public R<Object> projectUpdate(@RequestBody MeterTreeProject obj) {
+        obj.setUpdateStatus(1); //编辑
+        return R.data(meterTreeProjectService.updateById(obj));
+    }
+
+    @GetMapping("/project/remove")
+    @ApiOperationSupport(order = 17)
+    @ApiOperation(value = "项目树节点删除", notes = "传入id")
+    public R<Object> projectRemove(@RequestParam String id) {
+        if (StringUtils.isNotEmpty(id)) {
+            MeterTreeProject obj = meterTreeProjectService.getById(id);
+            if (obj != null) {
+                if (obj.getParentId().equals(0L) && obj.getAncestor().equals("0")) {
+                    throw new ServiceException("根节点无法删除");
+                }
+
+                /*子节点判断*/
+                Long countChild = meterTreeProjectService.getBaseMapper().selectCount(Wrappers.<MeterTreeProject>lambdaQuery()
+                        .eq(MeterTreeProject::getTemplateId, obj.getTemplateId())
+                        .eq(MeterTreeProject::getProjectId, obj.getProjectId())
+                        .eq(MeterTreeProject::getStatus, 1)
+                        .like(MeterTreeProject::getAncestor, id)
+                );
+                if (countChild > 0) {
+                    throw new ServiceException("该节点下存在子节点,无法删除");
+                }
+
+                /*判断合同段引用情况,合同段引用中无法删除(合同段节点sourceNodeId指向就是项目节点的id*/
+                List<MeterTreeContract> meterTreeContracts = meterTreeContractService.getBaseMapper().selectList(Wrappers.<MeterTreeContract>lambdaQuery()
+                        .select(MeterTreeContract::getContractId)
+                        .eq(MeterTreeContract::getTemplateId, obj.getTemplateId())
+                        .eq(MeterTreeContract::getProjectId, obj.getProjectId())
+                        .eq(MeterTreeContract::getSourceNodeId, obj.getId())
+                        .eq(MeterTreeContract::getStatus, 1)
+                        .groupBy(MeterTreeContract::getContractId)
+                );
+                if (meterTreeContracts.size() > 0) {
+                    List<Long> contractIds = meterTreeContracts.stream().map(MeterTreeContract::getContractId).collect(Collectors.toList());
+                    List<ContractInfo> contractInfoList = jdbcTemplate.query("SELECT contract_name FROM m_contract_info WHERE id in(" + org.apache.commons.lang.StringUtils.join(contractIds, ",") + ")", new BeanPropertyRowMapper<>(ContractInfo.class));
+                    List<String> names = contractInfoList.stream().map(ContractInfo::getContractName).collect(Collectors.toList());
+                    throw new ServiceException("【" + org.apache.commons.lang.StringUtils.join(names, "、") + "】合同段引用中,无法删除该节点");
+                }
+                return R.data(meterTreeProjectService.removeById(id));
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/project/same-list")
+    @ApiOperationSupport(order = 18)
+    @ApiOperation(value = "项目树节点同级列表", notes = "传入parentId")
+    public R<List<MeterTreeProject>> projectSameList(@RequestParam String parentId) {
+        if (StringUtils.isNotEmpty(parentId)) {
+            return R.data(meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                    .eq(MeterTreeProject::getParentId, parentId)
+                    .eq(MeterTreeProject::getStatus, 1)
+                    .orderByAsc(MeterTreeProject::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @PostMapping("/project/sort")
+    @ApiOperationSupport(order = 19)
+    @ApiOperation(value = "项目树节点同级排序", notes = "传入节点ids逗号拼接字符串,按照顺序从上到下")
+    public R<Object> projectSort(@RequestBody String ids) {
+        if (StringUtils.isNotEmpty(ids)) {
+            String[] split = ids.split(",");
+            int sort = 1;
+            for (String id : split) {
+                meterTreeProjectService.update(Wrappers.<MeterTreeProject>lambdaUpdate()
+                        .set(MeterTreeProject::getSort, sort++)
+                        .eq(MeterTreeProject::getId, id)
+                );
+            }
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/project/lazy")
+    @ApiOperationSupport(order = 20)
+    @ApiOperation(value = "项目树节点懒加载", notes = "传入projectId、节点id(根节点id=0)")
+    public R<List<MeterTreeProject>> projectLazy(@RequestParam String projectId, @RequestParam String id) {
+        if (StringUtils.isNotEmpty(id) && StringUtils.isNotEmpty(projectId)) {
+            return R.data(meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                    .eq(MeterTreeProject::getParentId, id)
+                    .eq(MeterTreeProject::getProjectId, projectId)
+                    .eq(MeterTreeProject::getStatus, 1)
+                    .orderByAsc(MeterTreeProject::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @GetMapping("/contract/refresh")
+    @ApiOperationSupport(order = 21)
+    @ApiOperation(value = "合同段树初始化(新增、增量同步)", notes = "传入项目projectId、合同段contractId")
+    public R<Object> contractRefresh(@RequestParam String projectId, @RequestParam String contractId) {
+        boolean result = false;
+        if (StringUtils.isNotEmpty(projectId) && StringUtils.isNotEmpty(contractId)) {
+            /*加锁*/
+            String redisKey = "meter:contract:refresh-tree:" + projectId + "_" + contractId;
+            String redisValue = bladeRedis.get(redisKey);
+            if (StringUtils.isNotEmpty(redisValue) && redisValue.equals("1")) {
+                return R.fail(400, "请勿重复提交,60秒后再尝试");
+            }
+            bladeRedis.set(redisKey, "1");
+            bladeRedis.expire(redisKey, 60);
+
+            /*首先判断是否存在项目树*/
+            MeterTreeProject rootNode = meterTreeProjectService.getBaseMapper().selectOne(Wrappers.<MeterTreeProject>lambdaQuery()
+                    .select(MeterTreeProject::getTemplateId)
+                    .eq(MeterTreeProject::getProjectId, projectId)
+                    .eq(MeterTreeProject::getParentId, 0L)
+                    .eq(MeterTreeProject::getAncestor, "0")
+                    .eq(MeterTreeProject::getStatus, 1));
+
+            if (rootNode != null) {
+                /*判断合同段是否存在树,不存在为初始化,存在则为同步*/
+                Long count = meterTreeContractService.getBaseMapper().selectCount(Wrappers.<MeterTreeContract>lambdaQuery()
+                        .eq(MeterTreeContract::getTemplateId, rootNode.getTemplateId())
+                        .eq(MeterTreeContract::getContractId, contractId)
+                        .eq(MeterTreeContract::getParentId, 0L)
+                        .eq(MeterTreeContract::getAncestor, "0")
+                        .eq(MeterTreeContract::getStatus, 1));
+
+                if (count == 0) {
+                    /*当前项目所有树*/
+                    List<MeterTreeProject> meterTreeProjects = meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                            .eq(MeterTreeProject::getTemplateId, rootNode.getTemplateId())
+                            .eq(MeterTreeProject::getProjectId, projectId)
+                            .eq(MeterTreeProject::getStatus, 1));
+
+                    /*初始化 合同段计量树*/
+                    result = meterTreeContractService.contractTreeInit(meterTreeProjects, Long.parseLong(contractId));
+
+                } else {
+                    /*当前项目所有树*/
+                    List<MeterTreeProject> meterTreeProjects = meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                            .select(MeterTreeProject::getId, MeterTreeProject::getParentId)
+                            .eq(MeterTreeProject::getTemplateId, rootNode.getTemplateId())
+                            .eq(MeterTreeProject::getProjectId, projectId)
+                            .eq(MeterTreeProject::getStatus, 1));
+
+                    /*增量同步 合同段计量树*/
+                    result = meterTreeContractService.contractTreeSync(meterTreeProjects, rootNode.getTemplateId(), Long.parseLong(contractId), Long.parseLong(projectId));
+                }
+            } else {
+                throw new ServiceException("未获取到项目计量单元信息!");
+            }
+        }
+        if (result) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/contract/left-list")
+    @ApiOperationSupport(order = 22)
+    @ApiOperation(value = "合同段-新增/增补单元-左边节点列表", notes = "传入id")
+    public R<List<MeterTreeProject>> leftList(@RequestParam String id) {
+        if (StringUtils.isNotEmpty(id)) {
+            MeterTreeContract contractNode = meterTreeContractService.getById(id);
+            if (contractNode != null) {
+                /*根据数据源id获取到对应项目的节点*/
+                MeterTreeProject projectNode = meterTreeProjectService.getById(contractNode.getSourceNodeId());
+                if (projectNode != null) {
+                    /*获取项目树子级(即左边节点列表)*/
+                    return R.data(meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                            .eq(MeterTreeProject::getParentId, projectNode.getId())
+                            .eq(MeterTreeProject::getStatus, 1)
+                    ));
+                } else {
+                    throw new ServiceException("未获取到项目计量单元信息");
+                }
+            }
+        }
+        return R.data(null);
+    }
+
+    @PostMapping("/contract/save")
+    @ApiOperationSupport(order = 23)
+    @ApiOperation(value = "合同段树节点新增", notes = "传入MeterTreeContractSaveBatchDTO")
+    public R<Object> contractSave(@RequestBody MeterTreeContractSaveBatchDTO dto) {
+        if (ObjectUtil.isNotEmpty(dto.getDataList()) && dto.getDataList().size() > 0 && (dto.getRequestType().equals(1) || dto.getRequestType().equals(2))) {
+            if (meterTreeContractService.contractSave(dto)) {
+                return R.success("操作成功");
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+    @PostMapping("/contract/update")
+    @ApiOperationSupport(order = 24)
+    @ApiOperation(value = "合同段树节点修改", notes = "传入MeterTreeContractDTO")
+    public R<Object> contractUpdate(@RequestBody MeterTreeContractDTO dto) {
+        if (ObjectUtil.isNotEmpty(dto.getDecompositionList()) && dto.getDecompositionList().size() > 0) {
+            /*最底层节点修改*/
+            dto.setUpdateStatus(1); //编辑
+            boolean b1 = meterTreeContractService.updateById(dto);
+            boolean b2 = true; //TODO 修改分解清单列表信息
+            if (b1 && b2) {
+                return R.success("操作成功");
+            }
+        } else if (ObjectUtil.isEmpty(dto.getDecompositionList()) || dto.getDecompositionList().size() == 0) {
+            /*非最底层节点修改*/
+            dto.setUpdateStatus(1); //编辑
+            if (meterTreeContractService.updateById(dto)) {
+                return R.success("操作成功");
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/contract/detail")
+    @ApiOperationSupport(order = 25)
+    @ApiOperation(value = "合同段树节点详情", notes = "传入id")
+    public R<MeterTreeContractVO> contractDetail(@RequestParam String id) {
+        MeterTreeContract basicInfo = meterTreeContractService.getById(id);
+        if (basicInfo != null) {
+            MeterTreeContractVO vo = new MeterTreeContractVO();
+            BeanUtil.copyProperties(basicInfo, vo);
+
+            //TODO 此处为分解列表信息,查询赋值
+            vo.setDecompositionList(null);
+            return R.data(vo);
+        }
+        return R.data(null);
+    }
+
+    @GetMapping("/contract/lazy")
+    @ApiOperationSupport(order = 26)
+    @ApiOperation(value = "合同段树节点懒加载", notes = "传入contractId、节点id(根节点id=0)")
+    public R<List<MeterTreeContract>> contractLazy(@RequestParam String contractId, @RequestParam String id) {
+        if (StringUtils.isNotEmpty(id) && StringUtils.isNotEmpty(contractId)) {
+            return R.data(meterTreeContractService.getBaseMapper().selectList(Wrappers.<MeterTreeContract>lambdaQuery()
+                    .eq(MeterTreeContract::getParentId, id)
+                    .eq(MeterTreeContract::getContractId, contractId)
+                    .eq(MeterTreeContract::getStatus, 1)
+                    .orderByAsc(MeterTreeContract::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @GetMapping("/contract/same-list")
+    @ApiOperationSupport(order = 27)
+    @ApiOperation(value = "合同段树节点同级列表", notes = "传入parentId")
+    public R<List<MeterTreeContract>> contractSameList(@RequestParam String parentId) {
+        if (StringUtils.isNotEmpty(parentId)) {
+            return R.data(meterTreeContractService.getBaseMapper().selectList(Wrappers.<MeterTreeContract>lambdaQuery()
+                    .eq(MeterTreeContract::getParentId, parentId)
+                    .eq(MeterTreeContract::getStatus, 1)
+                    .orderByAsc(MeterTreeContract::getSort)));
+        }
+        return R.data(null);
+    }
+
+    @PostMapping("/contract/sort")
+    @ApiOperationSupport(order = 28)
+    @ApiOperation(value = "合同段树节点同级排序", notes = "传入节点ids逗号拼接字符串,按照顺序从上到下")
+    public R<Object> contractSort(@RequestBody String ids) {
+        if (StringUtils.isNotEmpty(ids)) {
+            String[] split = ids.split(",");
+            int sort = 1;
+            for (String id : split) {
+                meterTreeContractService.update(Wrappers.<MeterTreeContract>lambdaUpdate()
+                        .set(MeterTreeContract::getSort, sort++)
+                        .eq(MeterTreeContract::getId, id)
+                );
+            }
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/contract/remove")
+    @ApiOperationSupport(order = 29)
+    @ApiOperation(value = "合同段树节点删除", notes = "传入id")
+    public R<Object> contractRemove(@RequestParam String id) {
+        if (StringUtils.isNotEmpty(id)) {
+            MeterTreeContract obj = meterTreeContractService.getById(id);
+            if (obj != null) {
+                if (obj.getParentId().equals(0L) && obj.getAncestor().equals("0")) {
+                    throw new ServiceException("根节点无法删除");
+                }
+
+                /*子节点判断*/
+                Long countChild = meterTreeContractService.getBaseMapper().selectCount(Wrappers.<MeterTreeContract>lambdaQuery()
+                        .eq(MeterTreeContract::getTemplateId, obj.getTemplateId())
+                        .eq(MeterTreeContract::getProjectId, obj.getProjectId())
+                        .eq(MeterTreeContract::getContractId, obj.getContractId())
+                        .eq(MeterTreeContract::getStatus, 1)
+                        .like(MeterTreeContract::getAncestor, id)
+                );
+                if (countChild > 0) {
+                    throw new ServiceException("该节点下存在子节点,无法删除");
+                }
+
+                //TODO 判断分解清单列表引用情况,是否确认要删除
+
+                return R.data(meterTreeContractService.removeById(id));
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/contract/lock")
+    @ApiOperationSupport(order = 30)
+    @ApiOperation(value = "合同段树节点锁定/解锁", notes = "传入id、lockStatus=1(锁定),lockStatus=0(解锁)")
+    public R<Object> contractLock(@RequestParam String id, @RequestParam Integer lockStatus) {
+        if (StringUtils.isNotEmpty(id) && (lockStatus.equals(0) || lockStatus.equals(1))) {
+            MeterTreeContract obj = meterTreeContractService.getById(id);
+            if (obj != null) {
+                /*获取所有子级信息*/
+                List<MeterTreeContract> meterTreeContracts = meterTreeContractService.getBaseMapper().selectList(Wrappers.<MeterTreeContract>lambdaQuery()
+                        .select(MeterTreeContract::getId)
+                        .eq(MeterTreeContract::getTemplateId, obj.getTemplateId())
+                        .eq(MeterTreeContract::getProjectId, obj.getProjectId())
+                        .eq(MeterTreeContract::getContractId, obj.getContractId())
+                        .eq(MeterTreeContract::getStatus, 1)
+                        .like(MeterTreeContract::getAncestor, id)
+                );
+                if (meterTreeContracts.size() > 0) {
+                    /*添加选择的顶级节点*/
+                    meterTreeContracts.add(obj);
+                    /*批量修改*/
+                    List<Long> contractNodeIds = meterTreeContracts.stream().map(MeterTreeContract::getId).collect(Collectors.toList());
+                    UpdateWrapper<MeterTreeContract> updateWrapper = new UpdateWrapper<>();
+                    updateWrapper.in("id", contractNodeIds);
+                    MeterTreeContract updateEntity = new MeterTreeContract();
+                    updateEntity.setIsLock(lockStatus);
+                    return R.data(meterTreeContractService.update(updateEntity, updateWrapper));
+                }
+            }
+        }
+        return R.fail("操作失败");
+    }
+
+
+}

+ 0 - 4
blade-service/blade-meter/src/main/java/org/springblade/meter/controller/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.controller;
-
-public class test {
-}

+ 11 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/feign/MeterTreeSystemClientImpl.java

@@ -0,0 +1,11 @@
+package org.springblade.meter.feign;
+
+
+import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@AllArgsConstructor
+public class MeterTreeSystemClientImpl implements MeterTreeSystemClient {
+
+}

+ 10 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/ContractInventoryFormMapper.java

@@ -19,7 +19,9 @@ package org.springblade.meter.mapper;
 import org.apache.ibatis.annotations.Param;
 import org.springblade.meter.entity.ContractInventoryForm;
 import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.meter.vo.ContractInventoryFormVO;
 import org.springblade.meter.vo.FormTreeVO;
+import org.springblade.meter.vo.InventoryFormDetailVO;
 
 import java.util.List;
 
@@ -33,4 +35,12 @@ public interface ContractInventoryFormMapper extends BaseMapper<ContractInventor
 
 
     List<FormTreeVO> getAllNode(@Param("projectId") Long projectId,@Param("contractId") Long contractId);
+
+    InventoryFormDetailVO getById(@Param("id") Long id);
+
+    List<ContractInventoryFormVO> getChildList(@Param("id") Long id,@Param("contractId") Long contractId);
+
+    void updateNodeLock(@Param("id") Long id,@Param("contractId") Long contractId,@Param("isLock") Integer isLock);
+
+    void updateNode(@Param("id") Long id,@Param("contractId") Long contractId);
 }

+ 25 - 2
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/ContractInventoryFormMapper.xml

@@ -35,13 +35,36 @@
         <result column="is_lock" property="isLock"/>
         <result column="is_supplement" property="isSupplement"/>
     </resultMap>
+    <update id="updateNodeLock">
+        update s_contract_inventory_form set is_lock = #{isLock} where
+            contract_id = #{contractId} and (id = #{id} or FIND_IN_SET(#{id},ancestors) > 0)
+    </update>
+    <update id="updateNode">
+        update s_contract_inventory_form set is_deleted = 1 where
+            contract_id = #{contractId} and (id = #{id} or FIND_IN_SET(#{id},ancestors) > 0)
+    </update>
     <select id="getAllNode" resultType="org.springblade.meter.vo.FormTreeVO">
-        select id,parent_id,
-               concat(scif.form_number, scif.form_name) as form_name,
+        select id,parent_id,is_lock,chapter_number,
+               if(scif.is_supplement=0,concat(scif.form_number, scif.form_name),concat(scif.form_number, scif.form_name,'[增补]')) as form_name,
                (select COUNT(1) from s_contract_inventory_form
                     WHERE contract_id = #{contractId} and is_deleted=0 and parent_id = scif.id) as hasChild
         from s_contract_inventory_form scif
         where project_id = #{projectId} and contract_id = #{contractId} and is_deleted = 0
+        order by sort
+    </select>
+    <select id="getById" resultType="org.springblade.meter.vo.InventoryFormDetailVO">
+        select * from s_contract_inventory_form where id = #{id}
+    </select>
+    <select id="getChildList" resultType="org.springblade.meter.vo.ContractInventoryFormVO">
+        select form_number,unit,current_price,contract_total,change_price,change_total,if(is_supplement=0,'否','是') as isSupplementName,
+               if(is_supplement=0,form_name,concat(form_name,'[增补]')) as form_name,
+               (select sum(contract_money) from s_contract_inventory_form sci where sci.is_deleted=0 and sci.contract_id = #{contractId}
+                        and (sci.id = scif.id or FIND_IN_SET(scif.id,ancestors) > 0)) as contract_money,
+               (select sum(change_money) from s_contract_inventory_form sci where sci.is_deleted=0 and sci.contract_id = #{contractId}
+                        and (sci.id = scif.id or FIND_IN_SET(scif.id,ancestors) > 0)) as change_money
+        from s_contract_inventory_form scif
+        where is_deleted=0 and contract_id = #{contractId} and parent_id = #{id}
+        order by sort
     </select>
 
 

+ 13 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeContractMapper.java

@@ -0,0 +1,13 @@
+package org.springblade.meter.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springblade.meter.entity.MeterTreeContract;
+
+public interface MeterTreeContractMapper extends BaseMapper<MeterTreeContract> {
+
+    @Select("SELECT MAX(sort) FROM s_meter_tree_contract WHERE parent_id = #{parentId} AND status = 1 AND is_deleted = 0")
+    Integer selectMaxSort(@Param("parentId") Long parentId);
+
+}

+ 5 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeContractMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.meter.mapper.MeterTreeContractMapper">
+
+</mapper>

+ 13 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeProjectMapper.java

@@ -0,0 +1,13 @@
+package org.springblade.meter.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springblade.meter.entity.MeterTreeProject;
+
+public interface MeterTreeProjectMapper extends BaseMapper<MeterTreeProject> {
+
+    @Select("SELECT MAX(sort) FROM s_meter_tree_project WHERE parent_id = #{parentId} AND status = 1 AND is_deleted = 0")
+    Integer selectMaxSort(@Param("parentId") Long parentId);
+
+}

+ 5 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeProjectMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.meter.mapper.MeterTreeProjectMapper">
+
+</mapper>

+ 13 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeSystemMapper.java

@@ -0,0 +1,13 @@
+package org.springblade.meter.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.springblade.meter.entity.MeterTreeSystem;
+
+public interface MeterTreeSystemMapper extends BaseMapper<MeterTreeSystem> {
+
+    @Select("SELECT MAX(sort) FROM s_meter_tree_system WHERE parent_id = #{parentId} AND status = 1 AND is_deleted = 0")
+    Integer selectMaxSort(@Param("parentId") Long parentId);
+
+}

+ 5 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeSystemMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.meter.mapper.MeterTreeSystemMapper">
+
+</mapper>

+ 8 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeTemplateInfoMapper.java

@@ -0,0 +1,8 @@
+package org.springblade.meter.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.springblade.meter.entity.MeterTreeTemplateInfo;
+
+public interface MeterTreeTemplateInfoMapper extends BaseMapper<MeterTreeTemplateInfo> {
+
+}

+ 5 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeTemplateInfoMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.springblade.meter.mapper.MeterTreeTemplateInfoMapper">
+
+</mapper>

+ 0 - 4
blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.mapper;
-
-public class test {
-}

+ 14 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/IContractInventoryFormService.java

@@ -19,7 +19,9 @@ package org.springblade.meter.service;
 import org.springblade.core.tool.api.R;
 import org.springblade.meter.entity.ContractInventoryForm;
 import org.springblade.core.mp.base.BaseService;
+import org.springblade.meter.vo.ContractInventoryFormVO;
 import org.springblade.meter.vo.FormTreeVO;
+import org.springblade.meter.vo.InventoryFormDetailVO;
 import org.springframework.web.multipart.MultipartFile;
 
 import java.util.List;
@@ -36,4 +38,16 @@ public interface IContractInventoryFormService extends BaseService<ContractInven
     R<String> importExcel(MultipartFile file,Long projectId, Long contractId);
 
     List<FormTreeVO> getFormTree(Long projectId, Long contractId);
+
+    InventoryFormDetailVO detail(Long id);
+
+    R<String> lockNode(Long id,Long contractId,Integer status);
+
+    void add(ContractInventoryForm form);
+
+    void updateForm(ContractInventoryForm form);
+
+    void sort(String ids);
+
+    void delete(Long id,Long contractId);
 }

+ 20 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeContractService.java

@@ -0,0 +1,20 @@
+package org.springblade.meter.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.meter.dto.MeterTreeContractSaveBatchDTO;
+import org.springblade.meter.entity.MeterTreeContract;
+import org.springblade.meter.entity.MeterTreeProject;
+
+import java.util.List;
+
+public interface MeterTreeContractService extends BaseService<MeterTreeContract> {
+
+    Integer selectMaxSort(Long parentId);
+
+    boolean contractTreeInit(List<MeterTreeProject> meterTreeProjects, Long contractId);
+
+    boolean contractTreeSync(List<MeterTreeProject> meterTreeProjects, Long templateId, Long contractId, Long projectId);
+
+    boolean contractSave(MeterTreeContractSaveBatchDTO dto);
+
+}

+ 12 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeProjectService.java

@@ -0,0 +1,12 @@
+package org.springblade.meter.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.meter.entity.MeterTreeProject;
+
+public interface MeterTreeProjectService extends BaseService<MeterTreeProject> {
+
+    Integer selectMaxSort(Long parentId);
+
+    boolean projectTreeInitOrSync(Long meterTemplateId, Long projectId);
+
+}

+ 10 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeSystemService.java

@@ -0,0 +1,10 @@
+package org.springblade.meter.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.meter.entity.MeterTreeSystem;
+
+public interface MeterTreeSystemService extends BaseService<MeterTreeSystem> {
+
+    Integer selectMaxSort(Long parentId);
+
+}

+ 8 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeTemplateInfoService.java

@@ -0,0 +1,8 @@
+package org.springblade.meter.service;
+
+import org.springblade.core.mp.base.BaseService;
+import org.springblade.meter.entity.MeterTreeTemplateInfo;
+
+public interface MeterTreeTemplateInfoService extends BaseService<MeterTreeTemplateInfo> {
+
+}

+ 296 - 66
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/ContractInventoryFormServiceImpl.java

@@ -17,17 +17,28 @@
 package org.springblade.meter.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import lombok.AllArgsConstructor;
 import org.apache.commons.lang.StringUtils;
 import org.springblade.common.utils.SnowFlakeUtil;
 import org.springblade.core.excel.util.ExcelUtil;
 import org.springblade.core.log.exception.ServiceException;
 import org.springblade.core.tool.api.R;
+import org.springblade.core.tool.utils.Func;
+import org.springblade.meter.entity.ChangeTokenInventory;
 import org.springblade.meter.entity.ContractInventoryForm;
+import org.springblade.meter.entity.InventoryFormMeter;
 import org.springblade.meter.mapper.ContractInventoryFormMapper;
+import org.springblade.meter.service.IChangeTokenFormService;
+import org.springblade.meter.service.IChangeTokenInventoryService;
 import org.springblade.meter.service.IContractInventoryFormService;
 import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.meter.service.IInventoryFormMeterService;
 import org.springblade.meter.utils.ForestNodeMerger;
+import org.springblade.meter.vo.ContractInventoryFormVO;
 import org.springblade.meter.vo.FormTreeVO;
+import org.springblade.meter.vo.InventoryFormDetailVO;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.multipart.MultipartFile;
@@ -46,8 +57,12 @@ import java.util.stream.Collectors;
  * @since 2023-11-29
  */
 @Service
+@AllArgsConstructor
 public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractInventoryFormMapper, ContractInventoryForm> implements IContractInventoryFormService {
 
+        private final IInventoryFormMeterService formMeterService;
+
+        private final IChangeTokenInventoryService tokenInventoryService;
 
     /**
      * 导入excel
@@ -60,41 +75,19 @@ public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractIn
         //校验文件类型
         String filename = file.getOriginalFilename();
         String fileSuffix = filename.substring(filename.lastIndexOf(".")+1);
-        if (!"xls,xlsx".contains(fileSuffix)){
-            throw new ServiceException("请传入excel文件");
-        }
-        List<ContractInventoryForm> excels = ExcelUtil.read(file, ContractInventoryForm.class);
-        List<Integer> firstNode = new ArrayList<>();
-        //校验必填字段是否为空
-        for (int i = 0; i < excels.size(); i++) {
-            ContractInventoryForm excel = excels.get(i);
-            if (StringUtils.isBlank(excel.getImportNumber()) || StringUtils.isBlank(excel.getFormNumber() ) || StringUtils.isBlank(excel.getFormName())){
-                throw new ServiceException("excel中有必填项未填写,请检查后重新导入");
-            }
-            excel.setProjectId(projectId);
-            excel.setContractId(contractId);
-            excel.setId(SnowFlakeUtil.getId());
-            //保存每个首节点位置
-            if (!excel.getImportNumber().contains("-")){
-                firstNode.add(i);
+        Integer allTotal = 0;
+        Integer nodes = 0;
+        Integer endTotal = 0;
+        try {
+            if (!"xls,xlsx".contains(fileSuffix)){
+                throw new ServiceException("请传入excel文件");
             }
-        }
-        int size = firstNode.size();
-        if (size == 0){
-            throw new ServiceException("未找到首节点,请检查后重新导入");
-        }
-        //根据首节点分成多个数组
-        List<List<ContractInventoryForm>> lists = new ArrayList<>();
-        //只有一个节点,直接单独处理
-        if (size == 1){
-            lists.add(excels);
-        }else {
-            for (int i = 0; i < size-1; i++) {
-                lists.add(excels.subList(firstNode.get(i),firstNode.get(i+1)));
+            List<ContractInventoryForm> excels = ExcelUtil.read(file, ContractInventoryForm.class);
+            if (excels == null || excels.size() == 0){
+                throw new ServiceException("未识别到excel中数据,请检查后重新导入");
             }
-            lists.add(excels.subList(firstNode.get(size-1),excels.size()));
-        }
-        try {
+            allTotal = excels.size();
+            List<Integer> firstNode = new ArrayList<>();
             //获取当前合同的首节点
             ContractInventoryForm one = this.getOne(new LambdaQueryWrapper<ContractInventoryForm>()
                     .eq(ContractInventoryForm::getContractId, contractId)
@@ -103,6 +96,53 @@ public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractIn
                 throw new ServiceException("未找到当前合同根节点,请联系管理员");
             }
             Long id = one.getId();
+            //查询出当前合同下所有的节点,并按照导入编号分组
+            List<ContractInventoryForm> allNode = this.list(new LambdaQueryWrapper<ContractInventoryForm>()
+                    .eq(ContractInventoryForm::getContractId, contractId)
+                    .isNotNull(ContractInventoryForm::getImportNumber));
+            nodes = allNode.size();
+            Map<String, ContractInventoryForm> map = allNode.stream().collect(Collectors.toMap(ContractInventoryForm::getImportNumber, l -> l));
+            //校验必填字段是否为空,设置通用字段,并且通过导入编号对已经存在的节点做标识
+            for (int i = 0; i < excels.size(); i++) {
+                ContractInventoryForm excel = excels.get(i);
+                if (StringUtils.isBlank(excel.getImportNumber()) || StringUtils.isBlank(excel.getFormNumber() ) || StringUtils.isBlank(excel.getFormName())){
+                    throw new ServiceException("excel中有必填项未填写,请检查后重新导入");
+                }
+                excel.setProjectId(projectId);
+                excel.setContractId(contractId);
+                //判断是否已经存在于合同段
+                ContractInventoryForm form = map.get(excel.getImportNumber());
+                if (form == null) {
+                    excel.setId(SnowFlakeUtil.getId());
+                    excel.setStatus(0);
+                }else {
+                    excel.setId(form.getId());
+                    excel.setParentId(form.getParentId());
+                    excel.setAncestors(form.getAncestors());
+                    excel.setParentNumber(form.getParentNumber());
+                    excel.setIsFormNode(form.getIsFormNode());
+                    excel.setStatus(1);
+                }
+                //保存每个首节点位置
+                if (!excel.getImportNumber().contains("-")){
+                    firstNode.add(i);
+                }
+            }
+            int size = firstNode.size();
+            if (size == 0){
+                throw new ServiceException("未找到首节点,请检查后重新导入");
+            }
+            //根据首节点分成多个数组
+            List<List<ContractInventoryForm>> lists = new ArrayList<>();
+            //只有一个节点,直接单独处理
+            if (size == 1){
+                lists.add(excels);
+            }else {
+                for (int i = 0; i < size-1; i++) {
+                    lists.add(excels.subList(firstNode.get(i),firstNode.get(i+1)));
+                }
+                lists.add(excels.subList(firstNode.get(size-1),excels.size()));
+            }
             Map<String,ContractInventoryForm> lastMap = new HashMap<>();
             Map<String,ContractInventoryForm> thisMap = new HashMap<>();
             //循环保存
@@ -117,30 +157,33 @@ public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractIn
                     String num = fo.getImportNumber();
                     int i = num.split("-").length - 1;
                     fo.setNodeTier(i);
-                    //判断单位,数量,单价是否存在数据,如果有一个存在数据,则为清单节点
-                    if (fo.getContractTotal() != null || StringUtils.isNotBlank(fo.getUnit()) || fo.getBidPrice() != null){
-                        fo.setIsFormNode(1);
-                        if (fo.getContractTotal() != null){
-                            fo.setContractTotal(fo.getContractTotal());
-                            fo.setChangeTotal(fo.getContractTotal());
-                        }
-                        if (fo.getBidPrice() != null){
-                            fo.setCurrentPrice(fo.getBidPrice());
-                            fo.setChangePrice(fo.getBidPrice());
+                    //如果是已经存在的节点则不设置这些值和父编号
+                    if (fo.getStatus() == 0) {
+                        //判断数量,单价是否存在数据,如果有一个存在数据,则为清单节点
+                        if (fo.getContractTotal() != null ||  fo.getBidPrice() != null) {
+                            fo.setIsFormNode(1);
+                            if (fo.getContractTotal() != null) {
+                                fo.setContractTotal(fo.getContractTotal());
+                                fo.setChangeTotal(fo.getContractTotal());
+                            }
+                            if (fo.getBidPrice() != null) {
+                                fo.setCurrentPrice(fo.getBidPrice());
+                                fo.setChangePrice(fo.getBidPrice());
+                            }
+                            if (fo.getContractTotal() != null && fo.getBidPrice() != null) {
+                                fo.setContractMoney(new BigDecimal(fo.getContractTotal()).multiply(fo.getBidPrice()));
+                                fo.setChangeMoney(fo.getContractMoney());
+                            }
+                        } else {
+                            fo.setIsFormNode(0);
                         }
-                        if (fo.getContractTotal() != null && fo.getBidPrice() != null){
-                            fo.setContractMoney(new BigDecimal(fo.getContractTotal()).multiply(fo.getBidPrice()));
-                            fo.setChangeMoney(fo.getChangeMoney());
+                        if (i == 0) {
+                            continue;
+                        } else if (i == 1) {
+                            fo.setParentNumber(number);
+                        } else {
+                            fo.setParentNumber(num.substring(0, num.lastIndexOf("-")));
                         }
-                    }else {
-                        fo.setIsFormNode(0);
-                    }
-                    if (i == 0){
-                        continue;
-                    }else if (i == 1){
-                        fo.setParentNumber(number);
-                    }else{
-                        fo.setParentNumber(num.substring(0, num.lastIndexOf("-")));
                     }
                 }
                 Map<Integer, List<ContractInventoryForm>> listMap = list.parallelStream().collect(Collectors.groupingBy(ContractInventoryForm::getNodeTier));
@@ -153,15 +196,20 @@ public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractIn
                     }
                     for (ContractInventoryForm f : forms) {
                         ContractInventoryForm fo = lastMap.get(f.getParentNumber());
+                        if (fo == null){
+                            throw new ServiceException("清单节点:"+f.getFormName()+"未找到上级节点");
+                        }
                         if (fo.getIsFormNode() == 1){
                             throw new ServiceException("清单节点:"+fo.getFormName()+"下还有节点,检查后重新上传excel");
                         }
-                        //设置章编号和清单类型,必须根父节点一致
-                        f.setChapterNumber(fo.getChapterNumber());
-                        f.setFormType(fo.getFormType());
-                        //设置父id和祖籍id
-                        f.setParentId(fo.getId());
-                        f.setAncestors(fo.getAncestors()+","+fo.getId());
+                        if (f.getStatus() == 0) {
+                            //设置章编号和清单类型,必须根父节点一致
+                            f.setChapterNumber(fo.getChapterNumber());
+                            f.setFormType(fo.getFormType());
+                            //设置父id和祖籍id
+                            f.setParentId(fo.getId());
+                            f.setAncestors(fo.getAncestors() + "," + fo.getId());
+                        }
                         thisMap.put(f.getImportNumber(),f);
                     }
                     lastMap = thisMap;
@@ -179,15 +227,19 @@ public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractIn
             if (sb.length() > 0){
                 throw new ServiceException("以下清单名称:"+sb.deleteCharAt(sb.length()-1)+"。未找到上级节点,请修改excel后重新导入");
             }
-            //所有节点设置好数据,比对已经存在的节点,然后移除集合,只保存不存在的
-
-            this.saveBatch(excels);
+            //移除所有已经存在的节点,保存
+            List<ContractInventoryForm> list = excels.stream().filter(l -> l.getStatus() == 0).collect(Collectors.toList());
+            endTotal = list.size();
+            this.saveBatch(list);
 
         }catch (Exception e){
             throw new ServiceException(e.getMessage());
         }
-
-        return R.data("导入成功");
+        if (nodes == 0) {
+            return R.data("成功导入" + allTotal + "条数据");
+        }else {
+            return R.data("成功新增" + endTotal + "条数据");
+        }
     }
 
     /**
@@ -214,4 +266,182 @@ public class ContractInventoryFormServiceImpl extends BaseServiceImpl<ContractIn
         }
         return ForestNodeMerger.merge(vos);
     }
+
+    /**
+     * 获取节点详情
+     */
+    @Override
+    public InventoryFormDetailVO detail(Long id) {
+        //查询当前节点详情
+        InventoryFormDetailVO vo = baseMapper.getById(id);
+        //如果是清单节点就直接返回
+        if (vo.getIsFormNode() == 1){
+            return vo;
+        }
+        //查询下级节点集合,并统计合同金额,变更后金额
+        List<ContractInventoryFormVO> list = baseMapper.getChildList(id,vo.getContractId());
+        vo.setList(list);
+        return vo;
+    }
+
+    /**
+     * 锁定节点
+     */
+    @Override
+    public R<String> lockNode(Long id,Long contractId,Integer status) {
+        if (status == 0){
+            //锁定节点
+            baseMapper.updateNodeLock(id,contractId,1);
+            return R.data("当前节点以及子节点已锁定");
+        }else {
+            //解锁节点
+            baseMapper.updateNodeLock(id,contractId,0);
+            return R.data("当前节点以及子节点已解除锁定");
+        }
+
+    }
+
+    /**
+     * 新增 合同工程清单表
+     */
+    @Override
+    public void add(ContractInventoryForm form) {
+        //如果为增补清单,那么节点类型必须为清单节点
+        if (form.getIsSupplement() == 1){
+            if (form.getIsFormNode() == 0){
+                throw new ServiceException("增补清单必须为清单节点");
+            }
+        }
+        //获取父级节点信息,判断是否已经锁定,判断是否为清单节点
+        ContractInventoryForm parentNode = this.getById(form.getParentId());
+        if (parentNode.getIsLock() == 1){
+            throw new ServiceException("当前节点已经被锁定不能新增");
+        }
+        if (parentNode.getIsFormNode() == 1){
+            throw new ServiceException("当前节点为清单节点,不能新增子节点");
+        }
+        //如果父节点有章编号,则判断当前节点章编号是否与父级节点一致
+        if (StringUtils.isNotBlank(parentNode.getChapterNumber())){
+            if (!parentNode.getChapterNumber().equals(form.getChapterNumber())){
+                throw new ServiceException("子节点的章编号,必须与父节点的章编号相同");
+            }
+        }
+        //如果父节点有清单类型,则判断当前节点清单类型是否与父节点一致
+        if (parentNode.getFormType() != null){
+            if (!parentNode.getFormType().equals(form.getFormType())){
+                throw new ServiceException("子节点的清单类型,必须与父节点的清单类型相同");
+            }
+        }
+        //设置祖级节点
+        form.setAncestors(parentNode.getAncestors()+","+parentNode.getId());
+        //如果不是根节点则设置层级
+        if (parentNode.getNodeTier() != null) {
+            int i = form.getFormNumber().split("-").length - 1;
+            if (i != (parentNode.getNodeTier() + 1)){
+                throw new ServiceException("清单编号规则错误,请修改后保存");
+            }
+            form.setNodeTier(i);
+        }
+        this.save(form);
+    }
+
+    /**
+     * 修改 合同工程清单表
+     */
+    @Override
+    public void updateForm(ContractInventoryForm form) {
+        //获取当前节点信息
+        ContractInventoryForm nodeInfo = this.getById(form.getId());
+        //判断节点是否已经锁定
+        if (nodeInfo.getIsLock() == 1){
+            throw new ServiceException("当前节点已经被锁定不能修改");
+        }
+        //如果当前节点类型和修改后的节点类型相同,则不校验
+        if (nodeInfo.getIsFormNode().equals(form.getIsFormNode())) {
+            //相同则先判断修改后节点类型是否为清单类型
+            if (form.getIsFormNode().equals(1)){
+                //判断是否已经被分解或者被变更,包括零号变更
+                Boolean isChange = this.nodeIsChange(nodeInfo);
+                //发生过变更,就去判断单价和数量。没被变更或者修改过,就不去判断单价和数量是否发生改变
+                if (isChange) {
+                    //判断单价和数量是否发生改变
+                    if (nodeInfo.getBidPrice() != null) {
+                        if (!nodeInfo.getBidPrice().equals(form.getBidPrice())) {
+                            throw new ServiceException("当前节点已经做过分解或变更,不能修改中标单价");
+                        }
+                    }
+                    if (nodeInfo.getContractTotal() != null) {
+                        if (!nodeInfo.getContractTotal().equals(form.getContractTotal())) {
+                            throw new ServiceException("当前节点已经做过分解或变更,不能修改合同数量");
+                        }
+                    }
+                }
+            }else {
+                form.setContractMoney(null);
+                form.setChangeMoney(null);
+            }
+        }else {
+            //如果不同则判断修改后节点类型是否为清单节点
+            if (form.getIsFormNode() == 1){
+                //为清单节点:判断当前节点是否还有下级节点
+                long count = this.count(new LambdaQueryWrapper<ContractInventoryForm>()
+                        .eq(ContractInventoryForm::getParentId, nodeInfo.getId()));
+                if (count > 0){
+                    throw new ServiceException("当前节点下存在节点,不能修改为清单节点");
+                }
+            }else {
+                //不为清单节点:判断当前节点是否被分解或者被变更,包括零号变更
+                Boolean isChange = this.nodeIsChange(nodeInfo);
+                if (isChange){
+                    throw new ServiceException("当前节点已被分解或变更,不能修改为非清单节点");
+                }
+                form.setContractMoney(null);
+                form.setChangeMoney(null);
+            }
+        }
+        this.updateById(form);
+    }
+
+    /**
+     * 排序 合同工程清单表
+     */
+    @Override
+    public void sort(String ids) {
+        List<Long> longs = Func.toLongList(ids);
+        for (int i = 0; i < longs.size(); i++) {
+            this.update(new LambdaUpdateWrapper<ContractInventoryForm>()
+                .eq(ContractInventoryForm::getId,longs.get(i))
+                .set(ContractInventoryForm::getSort,i));
+        }
+    }
+
+    /**
+     * 删除单个节点 合同工程清单表
+     */
+    @Override
+    public void delete(Long id,Long contractId) {
+        baseMapper.updateNode(id,contractId);
+    }
+
+    /**
+     * 判断当前节点是否已经分解或变更过,变更过返回true
+     */
+    private Boolean nodeIsChange(ContractInventoryForm form) {
+        if (form.getBuildChangeTotal() != null) {
+            return true;
+        }
+        long count = formMeterService.count(new LambdaQueryWrapper<InventoryFormMeter>()
+                .eq(InventoryFormMeter::getContractFormId, form.getId()));
+        if (count > 0) {
+            return true;
+        }
+        long count1 = tokenInventoryService.count(new LambdaQueryWrapper<ChangeTokenInventory>()
+                .eq(ChangeTokenInventory::getContractFormId, form.getId()));
+        if (count1 > 0) {
+            return true;
+        }
+        return false;
+    }
+
+
 }

+ 432 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeContractServiceImpl.java

@@ -0,0 +1,432 @@
+package org.springblade.meter.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.meter.dto.MeterTreeContractSaveBatchDTO;
+import org.springblade.meter.dto.MeterTreeContractSaveDTO;
+import org.springblade.meter.entity.MeterTreeContract;
+import org.springblade.meter.entity.MeterTreeProject;
+import org.springblade.meter.mapper.MeterTreeContractMapper;
+import org.springblade.meter.mapper.MeterTreeProjectMapper;
+import org.springblade.meter.service.MeterTreeContractService;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@AllArgsConstructor
+public class MeterTreeContractServiceImpl extends BaseServiceImpl<MeterTreeContractMapper, MeterTreeContract> implements MeterTreeContractService {
+
+    private final JdbcTemplate jdbcTemplate;
+    private final MeterTreeProjectMapper meterTreeProjectMapper;
+
+    @Override
+    public Integer selectMaxSort(Long parentId) {
+        return baseMapper.selectMaxSort(parentId);
+    }
+
+    @Override
+    public boolean contractTreeInit(List<MeterTreeProject> meterTreeProjects, Long contractId) {
+        /*创建旧节点ID和新节点ID的映射newNodeIdMap*/
+        List<Long> projectNodeIdList = meterTreeProjects.stream().map(MeterTreeProject::getId).collect(Collectors.toList());
+        Map<Long, Long> newNodeIdMap = new HashMap<>(projectNodeIdList.size());
+        for (Long oldNodeId : projectNodeIdList) {
+            newNodeIdMap.put(oldNodeId, SnowFlakeUtil.getId());
+        }
+
+        /*把项目节点id、parentId、ancestor替换成新的,构造成MeterTreeContract对象*/
+        List<MeterTreeContract> meterTreeContractResultData = new ArrayList<>(projectNodeIdList.size());
+        for (MeterTreeProject pObj : meterTreeProjects) {
+            MeterTreeContract cObj = new MeterTreeContract();
+            BeanUtil.copyProperties(pObj, cObj);
+            cObj.setContractId(contractId);
+
+            /*数据源id*/
+            cObj.setSourceNodeId(pObj.getId());
+            cObj.setDataSourceType(1); //项目引用
+            cObj.setUpdateStatus(0); //非编辑
+
+            /*id*/
+            cObj.setId(newNodeIdMap.get(pObj.getId()));
+            /*parentId、ancestor*/
+            Long parentId = newNodeIdMap.getOrDefault(pObj.getParentId(), null);
+            if (parentId == null) {
+                /*根节点*/
+                cObj.setParentId(0L);
+                cObj.setAncestor("0");
+            } else {
+                /*非根节点*/
+                cObj.setParentId(parentId);
+                cObj.setAncestor(null); //后面重新计算子级ancestor
+            }
+
+            meterTreeContractResultData.add(cObj);
+        }
+
+        /*层级结构排序*/
+        List<MeterTreeContract> sortedList = this.sortMeterTreeContracts(meterTreeContractResultData);
+
+        /*构造ancestor*/
+        for (MeterTreeContract contract : sortedList) {
+            if (contract.getAncestor() == null) {
+                //如果当前元素是子级(ancestor为null),找到对应的父级
+                Long parentId = contract.getParentId();
+                String parentAncestor = this.getAncestorForParentId(meterTreeContractResultData, parentId);
+                //构造新的ancestor字段
+                String newAncestor = parentAncestor + "," + parentId;
+                contract.setAncestor(newAncestor);
+            }
+        }
+
+        if (sortedList.size() == projectNodeIdList.size()) {
+            return this.saveBatch(sortedList, 1000);
+        } else {
+            throw new ServiceException("数据构造异常!");
+        }
+    }
+
+    @Override
+    public boolean contractTreeSync(List<MeterTreeProject> meterTreeProjects, Long templateId, Long contractId, Long projectId) {
+        /*获取合同段树*/
+        List<MeterTreeContract> meterTreeContracts = baseMapper.selectList(Wrappers.<MeterTreeContract>lambdaQuery()
+                .select(MeterTreeContract::getSourceNodeId)
+                .eq(MeterTreeContract::getTemplateId, templateId)
+                .eq(MeterTreeContract::getContractId, contractId)
+                .eq(MeterTreeContract::getDataSourceType, 1) //此处只判断引用的(因为是靠引用id来获取父节点信息)
+                .eq(MeterTreeContract::getStatus, 1));
+
+        /*获取meterTreeProjects差集*/
+        List<MeterTreeProject> difference = meterTreeProjects.stream()
+                .filter(project -> meterTreeContracts.stream()
+                        .noneMatch(contract -> contract.getSourceNodeId().equals(project.getId())))
+                .collect(Collectors.toList());
+
+        /*增量同步(该接口逻辑与contractSave接口中的划分子级一样)*/
+        if (!difference.isEmpty()) {
+            /*构造差集节点id=parentId关系Maps*/
+            Map<Long, Long> idAndParentIdMaps = difference.stream().collect(Collectors.toMap(MeterTreeProject::getId, MeterTreeProject::getParentId));
+
+            /*获取所有要新增的项目级的根节点ids*/
+            Set<Long> rootNodeIds = this.getRootNodeIds(difference);
+            /*获取根节点的父级节点ids*/
+            List<MeterTreeProject> meterTreeProjectsParentNodes = meterTreeProjectMapper.selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                    .select(MeterTreeProject::getParentId)
+                    .eq(MeterTreeProject::getStatus, 1)
+                    .eq(MeterTreeProject::getTemplateId, templateId)
+                    .eq(MeterTreeProject::getProjectId, projectId)
+                    .in(MeterTreeProject::getId, rootNodeIds)
+            );
+            Set<Long> parentRootNodeIds = meterTreeProjectsParentNodes.stream().map(MeterTreeProject::getParentId).collect(Collectors.toSet());
+
+            /*获取父级节点(即contractSave接口的合同段计量树选择新增节点meterTreeContractNode对象)*/
+            List<MeterTreeContract> meterTreeContractNodesList = baseMapper.selectList(Wrappers.<MeterTreeContract>lambdaQuery()
+                    .eq(MeterTreeContract::getStatus, 1)
+                    .eq(MeterTreeContract::getContractId, contractId)
+                    .eq(MeterTreeContract::getTemplateId, templateId)
+                    .in(MeterTreeContract::getSourceNodeId, parentRootNodeIds) //项目根节点父级 指向合同段原始id字段
+            );
+            Map<Long, List<MeterTreeContract>> meterTreeContractNodesMaps = meterTreeContractNodesList.stream().collect(Collectors.groupingBy(MeterTreeContract::getSourceNodeId));
+
+            for (Long parentRootId : parentRootNodeIds) {
+                /*新增到合同段处的根节点的父级节点信息,由于合同段树新增是从项目选择的(相当于复制),那么sourceNodeId就有相同的,就有N个,所以是多份父级节点*/
+                List<MeterTreeContract> contractParentRootNodeList = meterTreeContractNodesMaps.get(parentRootId);
+                for (MeterTreeContract meterTreeContractNode : contractParentRootNodeList) {
+
+                    for (Long rootNodeId : rootNodeIds) {
+                        /*归纳到自己节点下 TODO 待测试,这里是否需要,项目同步是需要判断的*/
+                        Long rootIdRecordParentId = idAndParentIdMaps.get(rootNodeId);
+                        if (!rootIdRecordParentId.equals(parentRootId)) {
+                            continue;
+                        }
+
+                        /*获取新增节点的所有子级相关节点*/
+                        List<MeterTreeProject> projectNode = this.getMeterTreeProjectNode(projectId, templateId, rootNodeId);
+
+                        /*构造数据*/
+                        /*创建旧节点ID和新节点ID的映射Map*/
+                        Map<Long, Long> newRootAndChildNodeIdMap = new HashMap<>(projectNode.size());
+                        List<Long> projectNodeIdList = projectNode.stream().map(MeterTreeProject::getId).collect(Collectors.toList());
+                        for (Long oldNodeId : projectNodeIdList) {
+                            newRootAndChildNodeIdMap.put(oldNodeId, SnowFlakeUtil.getId());
+                        }
+                        /*把项目节点id、parentId、ancestor替换成新的,构造成MeterTreeContract对象*/
+                        List<MeterTreeContract> meterTreeContractResultData = new ArrayList<>(projectNodeIdList.size());
+                        for (MeterTreeProject pObj : projectNode) {
+                            MeterTreeContract cObj = new MeterTreeContract();
+                            BeanUtil.copyProperties(pObj, cObj);
+                            cObj.setContractId(contractId);
+
+                            /*数据源id*/
+                            cObj.setSourceNodeId(pObj.getId());
+                            cObj.setDataSourceType(1); //项目引用
+                            cObj.setUpdateStatus(0); //非编辑
+
+                            /*id*/
+                            cObj.setId(newRootAndChildNodeIdMap.get(pObj.getId()));
+                            /*parentId*/
+                            Long parentId = newRootAndChildNodeIdMap.getOrDefault(pObj.getParentId(), null);
+                            if (parentId == null) {
+                                /*根节点*/
+                                cObj.setParentId(meterTreeContractNode.getId());
+                                /*ancestor(根节点ancestor指向就是 外部合同段树 处选择的节点)*/
+                                cObj.setAncestor(meterTreeContractNode.getAncestor() + "," + cObj.getParentId());
+                            } else {
+                                /*非根节点*/
+                                cObj.setParentId(parentId);
+                                cObj.setAncestor(null); //后面重新计算子级ancestor
+                            }
+
+                            meterTreeContractResultData.add(cObj);
+                        }
+
+                        /*层级结构排序*/
+                        List<MeterTreeContract> sortedList = this.sortMeterTreeContracts(meterTreeContractResultData);
+
+                        /*构造ancestor*/
+                        for (MeterTreeContract contract : sortedList) {
+                            if (contract.getAncestor() == null) {
+                                //如果当前元素是子级(ancestor为null),找到对应的父级
+                                Long parentId = contract.getParentId();
+                                String parentAncestor = this.getAncestorForParentId(meterTreeContractResultData, parentId);
+                                //构造新的ancestor字段
+                                String newAncestor = parentAncestor + "," + parentId;
+                                contract.setAncestor(newAncestor);
+                            }
+                        }
+
+                        /*入库*/
+                        this.saveBatch(sortedList, 1000);
+                    }
+                }
+            }
+            return true;
+        } else {
+            throw new ServiceException("未获取到需要同步的项目计量单元信息!");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean contractSave(MeterTreeContractSaveBatchDTO dto) {
+        List<MeterTreeContractSaveDTO> dataList = dto.getDataList();
+        /*合同段计量树选择新增节点*/
+        MeterTreeContract meterTreeContractNode = baseMapper.selectOne(Wrappers.<MeterTreeContract>lambdaQuery()
+                .eq(MeterTreeContract::getId, dto.getContractNodeId())
+                .eq(MeterTreeContract::getStatus, 1)
+        );
+
+        try {
+            for (MeterTreeContractSaveDTO selectNode : dataList) {
+                if (selectNode.getIsAddChildNode().equals(0)) {
+                    /*================== 不划分子级 ==================*/
+                    MeterTreeProject pObj = meterTreeProjectMapper.selectById(selectNode.getLeftNodeId());
+                    if (pObj != null) {
+                        MeterTreeContract cObj = new MeterTreeContract();
+                        BeanUtil.copyProperties(pObj, cObj);
+                        cObj.setContractId(dto.getContractId());
+                        cObj.setNodeName(selectNode.getNodeName());
+
+                        /*增补单元操作,名称拼接*/
+                        if (dto.getRequestType().equals(2)) {
+                            cObj.setNodeName(cObj.getNodeName() + "【增补】");
+                            cObj.setIsSupplement(1); //增补
+                        }
+
+                        cObj.setStartStake(selectNode.getStartStake());
+                        cObj.setEndStake(selectNode.getEndStake());
+                        cObj.setContractPicture(selectNode.getContractPicture());
+
+                        /*数据源id=左边树节点id*/
+                        cObj.setSourceNodeId(pObj.getId());
+                        cObj.setDataSourceType(2); //用户手动新增
+                        cObj.setUpdateStatus(0); //非编辑
+
+                        /*id*/
+                        cObj.setId(SnowFlakeUtil.getId());
+                        /*parentId=合同段计量树选择节点的id,新增到选择节点下面*/
+                        cObj.setParentId(dto.getContractNodeId());
+                        /*ancestor*/
+                        cObj.setAncestor(meterTreeContractNode.getAncestor() + "," + cObj.getParentId());
+
+                        baseMapper.insert(cObj);
+                    }
+
+                } else if (selectNode.getIsAddChildNode().equals(1)) {
+                    /*================== 划分子级 ==================*/
+                    /*获取所有相关节点*/
+                    List<MeterTreeProject> projectNode = this.getMeterTreeProjectNode(dto.getProjectId(), dto.getTemplateId(), selectNode.getLeftNodeId());
+
+                    /*构造数据*/
+                    /*创建旧节点ID和新节点ID的映射Map*/
+                    Map<Long, Long> newRootAndChildNodeIdMap = new HashMap<>(projectNode.size());
+                    List<Long> projectNodeIdList = projectNode.stream().map(MeterTreeProject::getId).collect(Collectors.toList());
+                    for (Long oldNodeId : projectNodeIdList) {
+                        newRootAndChildNodeIdMap.put(oldNodeId, SnowFlakeUtil.getId());
+                    }
+                    /*把项目节点id、parentId、ancestor替换成新的,构造成MeterTreeContract对象*/
+                    List<MeterTreeContract> meterTreeContractResultData = new ArrayList<>(projectNodeIdList.size());
+                    for (MeterTreeProject pObj : projectNode) {
+                        MeterTreeContract cObj = new MeterTreeContract();
+                        BeanUtil.copyProperties(pObj, cObj);
+                        cObj.setContractId(dto.getContractId());
+
+                        /*根节点名称、桩号、合同图号*/
+                        if (pObj.getId().equals(selectNode.getLeftNodeId())) {
+                            cObj.setNodeName(selectNode.getNodeName());
+                            cObj.setStartStake(selectNode.getStartStake());
+                            cObj.setEndStake(selectNode.getEndStake());
+                            cObj.setContractPicture(selectNode.getContractPicture());
+                        }
+
+                        /*增补单元操作,名称拼接*/
+                        if (dto.getRequestType().equals(2)) {
+                            cObj.setNodeName(cObj.getNodeName() + "【增补】");
+                            cObj.setIsSupplement(1); //增补
+                        }
+
+                        /*数据源id*/
+                        cObj.setSourceNodeId(pObj.getId());
+                        cObj.setDataSourceType(2); //用户手动新增
+                        cObj.setUpdateStatus(0); //非编辑
+
+                        /*id*/
+                        cObj.setId(newRootAndChildNodeIdMap.get(pObj.getId()));
+                        /*parentId*/
+                        Long parentId = newRootAndChildNodeIdMap.getOrDefault(pObj.getParentId(), null);
+                        if (parentId == null) {
+                            /*根节点*/
+                            cObj.setParentId(meterTreeContractNode.getId());
+                            /*ancestor(根节点ancestor指向就是 外部合同段树 处选择的节点)*/
+                            cObj.setAncestor(meterTreeContractNode.getAncestor() + "," + cObj.getParentId());
+
+                        } else {
+                            /*非根节点*/
+                            cObj.setParentId(parentId);
+                            cObj.setAncestor(null); //后面重新计算子级ancestor
+                        }
+
+                        meterTreeContractResultData.add(cObj);
+                    }
+
+                    /*层级结构排序*/
+                    List<MeterTreeContract> sortedList = this.sortMeterTreeContracts(meterTreeContractResultData);
+
+                    /*构造ancestor*/
+                    for (MeterTreeContract contract : sortedList) {
+                        if (contract.getAncestor() == null) {
+                            //如果当前元素是子级(ancestor为null),找到对应的父级
+                            Long parentId = contract.getParentId();
+                            String parentAncestor = this.getAncestorForParentId(meterTreeContractResultData, parentId);
+                            //构造新的ancestor字段
+                            String newAncestor = parentAncestor + "," + parentId;
+                            contract.setAncestor(newAncestor);
+                        }
+                    }
+
+                    /*入库*/
+                    this.saveBatch(sortedList, 1000);
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new ServiceException("操作失败,errorMsg:" + e.getMessage());
+        }
+
+        return true;
+    }
+
+    /**
+     * 获取对应的父级的ancestor字段
+     *
+     * @param contracts
+     * @param parentId
+     * @return
+     */
+    private String getAncestorForParentId(List<MeterTreeContract> contracts, Long parentId) {
+        for (MeterTreeContract contract : contracts) {
+            if (contract.getId().equals(parentId)) {
+                return contract.getAncestor();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 按照层级结构排序
+     *
+     * @param inputList
+     * @return
+     */
+    private List<MeterTreeContract> sortMeterTreeContracts(List<MeterTreeContract> inputList) {
+        List<MeterTreeContract> sortedList = new ArrayList<>();
+        //找到根节点并递归构建树
+        for (MeterTreeContract contract : inputList) {
+            if (contract.getAncestor() != null) {
+                //ancestor有值说明是根节点
+                buildTree(contract, inputList, sortedList, 1);
+            }
+        }
+        return sortedList;
+    }
+
+    private void buildTree(MeterTreeContract currentContract, List<MeterTreeContract> inputList, List<MeterTreeContract> sortedList, int level) {
+        sortedList.add(currentContract);
+        //找到当前节点的子节点并递归构建树
+        for (MeterTreeContract child : inputList) {
+            if (currentContract.getId().equals(child.getParentId())) {
+                buildTree(child, inputList, sortedList, level + 1);
+            }
+        }
+    }
+
+    /**
+     * 获取项目单元节点下的所有相关节点信息
+     *
+     * @return
+     */
+    private List<MeterTreeProject> getMeterTreeProjectNode(Long projectId, Long templateId, Long rootId) {
+        return meterTreeProjectMapper.selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                .eq(MeterTreeProject::getStatus, 1)
+                .eq(MeterTreeProject::getProjectId, projectId)
+                .eq(MeterTreeProject::getTemplateId, templateId)
+                .like(MeterTreeProject::getAncestor, rootId) /*所有子级节点*/
+                .or()
+                .eq(MeterTreeProject::getId, rootId) /*左侧选择的节点视为根节点节点*/
+        );
+    }
+
+    /**
+     * 获取当前树形结构的所有根节点IDS
+     *
+     * @param meterTreeProjects
+     * @return
+     */
+    private Set<Long> getRootNodeIds(List<MeterTreeProject> meterTreeProjects) {
+        Set<Long> rootIds = new HashSet<>();
+        for (MeterTreeProject node : meterTreeProjects) {
+            //如果父节点id为null或者不存在于列表中的id,则说明是根节点
+            if (node.getParentId() == null || !containsId(meterTreeProjects, node.getParentId())) {
+                rootIds.add(node.getId());
+            }
+        }
+        return rootIds;
+    }
+
+    //判断列表中是否包含指定id的节点
+    private boolean containsId(List<MeterTreeProject> meterTreeProjects, Long id) {
+        for (MeterTreeProject node : meterTreeProjects) {
+            if (node.getId().equals(id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 321 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeProjectServiceImpl.java

@@ -0,0 +1,321 @@
+package org.springblade.meter.service.impl;
+
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.AllArgsConstructor;
+import org.springblade.common.utils.SnowFlakeUtil;
+import org.springblade.core.log.exception.ServiceException;
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.core.tool.utils.BeanUtil;
+import org.springblade.meter.entity.MeterTreeProject;
+import org.springblade.meter.entity.MeterTreeSystem;
+import org.springblade.meter.mapper.MeterTreeProjectMapper;
+import org.springblade.meter.mapper.MeterTreeSystemMapper;
+import org.springblade.meter.service.MeterTreeProjectService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+@AllArgsConstructor
+public class MeterTreeProjectServiceImpl extends BaseServiceImpl<MeterTreeProjectMapper, MeterTreeProject> implements MeterTreeProjectService {
+
+    private final MeterTreeSystemMapper meterTreeSystemMapper;
+
+    @Override
+    public Integer selectMaxSort(Long parentId) {
+        return baseMapper.selectMaxSort(parentId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean projectTreeInitOrSync(Long meterTemplateId, Long projectId) {
+        /*首先判断是否存在项目树*/
+        MeterTreeProject rootNode = baseMapper.selectOne(Wrappers.<MeterTreeProject>lambdaQuery()
+                .select(MeterTreeProject::getTemplateId)
+                .eq(MeterTreeProject::getProjectId, projectId)
+                .eq(MeterTreeProject::getParentId, 0L)
+                .eq(MeterTreeProject::getAncestor, "0")
+                .eq(MeterTreeProject::getStatus, 1));
+
+        if (rootNode == null) {
+            /*================== 新增初始化 ==================*/
+            /*获取系统单元树信息*/
+            List<MeterTreeSystem> meterTreeSystems = meterTreeSystemMapper.selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                    .eq(MeterTreeSystem::getTemplateId, meterTemplateId)
+                    .eq(MeterTreeSystem::getStatus, 1)
+            );
+
+            /*创建旧节点ID和新节点ID的映射newNodeIdMap*/
+            List<Long> systemNodeIds = meterTreeSystems.stream().map(MeterTreeSystem::getId).collect(Collectors.toList());
+            Map<Long, Long> newNodeIdMap = new HashMap<>(systemNodeIds.size());
+            for (Long oldNodeId : systemNodeIds) {
+                newNodeIdMap.put(oldNodeId, SnowFlakeUtil.getId());
+            }
+
+            /*把项目节点id、parentId、ancestor替换成新的,构造成MeterTreeProject对象*/
+            List<MeterTreeProject> meterTreeProjectsResultData = new ArrayList<>(systemNodeIds.size());
+            for (MeterTreeSystem sObj : meterTreeSystems) {
+                MeterTreeProject pObj = new MeterTreeProject();
+                BeanUtil.copyProperties(sObj, pObj);
+                pObj.setProjectId(projectId);
+
+                /*数据源id*/
+                pObj.setSourceNodeId(sObj.getId());
+                pObj.setDataSourceType(1); //系统引用
+                pObj.setUpdateStatus(0); //非编辑
+
+                /*id*/
+                pObj.setId(newNodeIdMap.get(sObj.getId()));
+                /*parentId、ancestor*/
+                Long parentId = newNodeIdMap.getOrDefault(sObj.getParentId(), null);
+                if (parentId == null) {
+                    /*根节点*/
+                    pObj.setParentId(0L);
+                    pObj.setAncestor("0");
+                } else {
+                    /*非根节点*/
+                    pObj.setParentId(parentId);
+                    pObj.setAncestor(null); //后面重新计算子级ancestor
+                }
+
+                meterTreeProjectsResultData.add(pObj);
+            }
+
+            /*层级结构排序*/
+            List<MeterTreeProject> sortedList = this.sortMeterTreeProjects(meterTreeProjectsResultData);
+
+            /*构造ancestor*/
+            for (MeterTreeProject project : sortedList) {
+                if (project.getAncestor() == null) {
+                    //如果当前元素是子级(ancestor为null),找到对应的父级
+                    Long parentId = project.getParentId();
+                    String parentAncestor = this.getAncestorForParentId(meterTreeProjectsResultData, parentId);
+                    //构造新的ancestor字段
+                    String newAncestor = parentAncestor + "," + parentId;
+                    project.setAncestor(newAncestor);
+                }
+            }
+
+            if (sortedList.size() == systemNodeIds.size()) {
+                return this.saveBatch(sortedList, 1000);
+            } else {
+                throw new ServiceException("数据构造异常!");
+            }
+
+        } else {
+            /*================== 增量同步 ==================*/
+            /*获取系统单元树信息*/
+            List<MeterTreeSystem> meterTreeSystems = meterTreeSystemMapper.selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                    .select(MeterTreeSystem::getId, MeterTreeSystem::getParentId)
+                    .eq(MeterTreeSystem::getTemplateId, meterTemplateId)
+                    .eq(MeterTreeSystem::getStatus, 1)
+            );
+
+            /*获取项目单元树信息*/
+            List<MeterTreeProject> meterTreeProjects = baseMapper.selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                    .select(MeterTreeProject::getSourceNodeId)
+                    .eq(MeterTreeProject::getProjectId, projectId)
+                    .eq(MeterTreeProject::getTemplateId, meterTemplateId)
+                    .eq(MeterTreeProject::getDataSourceType, 1) //只对初始化引用的节点做处理,项目用户手动新增的节点没有原始id
+                    .eq(MeterTreeProject::getStatus, 1));
+
+            /*获取meterTreeSystems差集*/
+            List<MeterTreeSystem> difference = meterTreeSystems.stream()
+                    .filter(system -> meterTreeProjects.stream()
+                            .noneMatch(project -> project.getSourceNodeId().equals(system.getId())))
+                    .collect(Collectors.toList());
+
+            /*增量同步(该接口逻辑与MeterTreeContractServiceImpl.contractSave()接口中的划分子级一样)*/
+            if (!difference.isEmpty()) {
+                /*构造差集节点id=parentId关系Maps*/
+                Map<Long, Long> idAndParentIdMaps = difference.stream().collect(Collectors.toMap(MeterTreeSystem::getId, MeterTreeSystem::getParentId));
+
+                /*获取所有要新增的系统级的根节点ids*/
+                Set<Long> rootNodeIds = this.getRootNodeIds(difference);
+                /*获取根节点的父级节点ids*/
+                List<MeterTreeSystem> meterTreeSystemsParentNodes = meterTreeSystemMapper.selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                        .select(MeterTreeSystem::getParentId)
+                        .eq(MeterTreeSystem::getStatus, 1)
+                        .eq(MeterTreeSystem::getTemplateId, meterTemplateId)
+                        .in(MeterTreeSystem::getId, rootNodeIds)
+                );
+                Set<Long> parentRootNodeIds = meterTreeSystemsParentNodes.stream().map(MeterTreeSystem::getParentId).collect(Collectors.toSet());
+
+                /*获取父级节点(即contractSave接口的合同段计量树选择新增节点meterTreeContractNode对象,对应项目级就是meterTreeProjectNode对象)*/
+                List<MeterTreeProject> meterTreeProjectNodesList = baseMapper.selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                        .eq(MeterTreeProject::getStatus, 1)
+                        .eq(MeterTreeProject::getProjectId, projectId)
+                        .eq(MeterTreeProject::getTemplateId, meterTemplateId)
+                        .in(MeterTreeProject::getSourceNodeId, parentRootNodeIds) //系统级根节点父级 指向项目原始id字段
+                );
+                Map<Long, List<MeterTreeProject>> meterTreeProjectsNodesMaps = meterTreeProjectNodesList.stream().collect(Collectors.groupingBy(MeterTreeProject::getSourceNodeId));
+
+                for (Long parentRootId : parentRootNodeIds) {
+                    /*项目与合同段不同之处,合同段有N份(合同段新增就是复制,sourceNodeId存在多份一样的),在项目中sourceNodeId源节点id应该是对应一个父级节点*/
+                    List<MeterTreeProject> projectParentRootNodeList = meterTreeProjectsNodesMaps.get(parentRootId);
+                    for (MeterTreeProject meterTreeProjectNode : projectParentRootNodeList) {
+
+                        for (Long rootNodeId : rootNodeIds) {
+                            /*归纳到自己节点下*/
+                            Long rootIdRecordParentId = idAndParentIdMaps.get(rootNodeId);
+                            if (!rootIdRecordParentId.equals(parentRootId)) {
+                                continue;
+                            }
+
+                            /*获取新增节点的所有子级相关节点*/
+                            List<MeterTreeSystem> systemNode = this.getMeterTreeSystemNode(meterTemplateId, rootNodeId);
+
+                            /*构造数据*/
+                            /*创建旧节点ID和新节点ID的映射Map*/
+                            Map<Long, Long> newRootAndChildNodeIdMap = new HashMap<>(systemNode.size());
+                            List<Long> systemNodeIdList = systemNode.stream().map(MeterTreeSystem::getId).collect(Collectors.toList());
+                            for (Long oldNodeId : systemNodeIdList) {
+                                newRootAndChildNodeIdMap.put(oldNodeId, SnowFlakeUtil.getId());
+                            }
+
+                            /*把项目节点id、parentId、ancestor替换成新的,构造成MeterTreeProject对象*/
+                            List<MeterTreeProject> meterTreeProjectsResultData = new ArrayList<>(systemNodeIdList.size());
+                            for (MeterTreeSystem sObj : systemNode) {
+                                MeterTreeProject pObj = new MeterTreeProject();
+                                BeanUtil.copyProperties(sObj, pObj);
+                                pObj.setProjectId(projectId);
+
+                                /*数据源id*/
+                                pObj.setSourceNodeId(sObj.getId());
+                                pObj.setDataSourceType(1); //系统引用
+                                pObj.setUpdateStatus(0); //非编辑
+
+                                /*id*/
+                                pObj.setId(newRootAndChildNodeIdMap.get(sObj.getId()));
+                                /*parentId、ancestor*/
+                                Long parentId = newRootAndChildNodeIdMap.getOrDefault(sObj.getParentId(), null);
+                                if (parentId == null) {
+                                    /*根节点*/
+                                    pObj.setParentId(meterTreeProjectNode.getId());
+                                    /*ancestor(根节点ancestor指向就是 项目 的父级节点)*/
+                                    pObj.setAncestor(meterTreeProjectNode.getAncestor() + "," + pObj.getParentId());
+                                } else {
+                                    /*非根节点*/
+                                    pObj.setParentId(parentId);
+                                    pObj.setAncestor(null); //后面重新计算子级ancestor
+                                }
+
+                                meterTreeProjectsResultData.add(pObj);
+                            }
+
+                            /*层级结构排序*/
+                            List<MeterTreeProject> sortedList = this.sortMeterTreeProjects(meterTreeProjectsResultData);
+
+                            /*构造ancestor*/
+                            for (MeterTreeProject project : sortedList) {
+                                if (project.getAncestor() == null) {
+                                    //如果当前元素是子级(ancestor为null),找到对应的父级
+                                    Long parentId = project.getParentId();
+                                    String parentAncestor = this.getAncestorForParentId(meterTreeProjectsResultData, parentId);
+                                    //构造新的ancestor字段
+                                    String newAncestor = parentAncestor + "," + parentId;
+                                    project.setAncestor(newAncestor);
+                                }
+                            }
+
+                            /*入库*/
+                            this.saveBatch(sortedList, 1000);
+                        }
+                    }
+                }
+                return true;
+            } else {
+                throw new ServiceException("未获取到需要同步的系统计量单元信息!");
+            }
+        }
+    }
+
+    /**
+     * 获取对应的父级的ancestor字段
+     *
+     * @param projects
+     * @param parentId
+     * @return
+     */
+    private String getAncestorForParentId(List<MeterTreeProject> projects, Long parentId) {
+        for (MeterTreeProject project : projects) {
+            if (project.getId().equals(parentId)) {
+                return project.getAncestor();
+            }
+        }
+        return "";
+    }
+
+    /**
+     * 按照层级结构排序
+     *
+     * @param inputList
+     * @return
+     */
+    private List<MeterTreeProject> sortMeterTreeProjects(List<MeterTreeProject> inputList) {
+        List<MeterTreeProject> sortedList = new ArrayList<>();
+        //找到根节点并递归构建树
+        for (MeterTreeProject project : inputList) {
+            if (project.getAncestor() != null) {
+                //ancestor有值说明是根节点
+                buildTree(project, inputList, sortedList, 1);
+            }
+        }
+        return sortedList;
+    }
+
+    private void buildTree(MeterTreeProject projectContract, List<MeterTreeProject> inputList, List<MeterTreeProject> sortedList, int level) {
+        sortedList.add(projectContract);
+        //找到当前节点的子节点并递归构建树
+        for (MeterTreeProject child : inputList) {
+            if (projectContract.getId().equals(child.getParentId())) {
+                buildTree(child, inputList, sortedList, level + 1);
+            }
+        }
+    }
+
+    /**
+     * 获取系统单元节点下的所有相关节点信息
+     *
+     * @return
+     */
+    private List<MeterTreeSystem> getMeterTreeSystemNode(Long templateId, Long rootId) {
+        return meterTreeSystemMapper.selectList(Wrappers.<MeterTreeSystem>lambdaQuery()
+                .eq(MeterTreeSystem::getStatus, 1)
+                .eq(MeterTreeSystem::getTemplateId, templateId)
+                .like(MeterTreeSystem::getAncestor, rootId) /*所有子级节点*/
+                .or()
+                .eq(MeterTreeSystem::getId, rootId) /*左侧选择的节点视为根节点节点*/
+        );
+    }
+
+    /**
+     * 获取当前树形结构的所有根节点IDS
+     *
+     * @param meterTreeSystems
+     * @return
+     */
+    private Set<Long> getRootNodeIds(List<MeterTreeSystem> meterTreeSystems) {
+        Set<Long> rootIds = new HashSet<>();
+        for (MeterTreeSystem node : meterTreeSystems) {
+            //如果父节点id为null或者不存在于列表中的id,则说明是根节点
+            if (node.getParentId() == null || !containsId(meterTreeSystems, node.getParentId())) {
+                rootIds.add(node.getId());
+            }
+        }
+        return rootIds;
+    }
+
+    //判断列表中是否包含指定id的节点
+    private boolean containsId(List<MeterTreeSystem> meterTreeSystems, Long id) {
+        for (MeterTreeSystem node : meterTreeSystems) {
+            if (node.getId().equals(id)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}

+ 17 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeSystemServiceImpl.java

@@ -0,0 +1,17 @@
+package org.springblade.meter.service.impl;
+
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.meter.entity.MeterTreeSystem;
+import org.springblade.meter.mapper.MeterTreeSystemMapper;
+import org.springblade.meter.service.MeterTreeSystemService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MeterTreeSystemServiceImpl extends BaseServiceImpl<MeterTreeSystemMapper, MeterTreeSystem> implements MeterTreeSystemService {
+
+    @Override
+    public Integer selectMaxSort(Long parentId) {
+        return baseMapper.selectMaxSort(parentId);
+    }
+
+}

+ 12 - 0
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeTemplateInfoServiceImpl.java

@@ -0,0 +1,12 @@
+package org.springblade.meter.service.impl;
+
+import org.springblade.core.mp.base.BaseServiceImpl;
+import org.springblade.meter.entity.MeterTreeTemplateInfo;
+import org.springblade.meter.mapper.MeterTreeTemplateInfoMapper;
+import org.springblade.meter.service.MeterTreeTemplateInfoService;
+import org.springframework.stereotype.Service;
+
+@Service
+public class MeterTreeTemplateInfoServiceImpl extends BaseServiceImpl<MeterTreeTemplateInfoMapper, MeterTreeTemplateInfo> implements MeterTreeTemplateInfoService {
+
+}

+ 0 - 4
blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.service.impl;
-
-public class test {
-}

+ 0 - 4
blade-service/blade-meter/src/main/java/org/springblade/meter/service/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.service;
-
-public class test {
-}

+ 0 - 4
blade-service/blade-meter/src/main/java/org/springblade/meter/utils/test.java

@@ -1,4 +0,0 @@
-package org.springblade.meter.utils;
-
-public class test {
-}