Browse Source

计量单元相关

liuyc 1 year ago
parent
commit
75dd4a8257
39 changed files with 1792 additions and 32 deletions
  1. 18 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractDTO.java
  2. 30 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractSaveBatchDTO.java
  3. 21 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/MeterTreeContractSaveDTO.java
  4. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/dto/test.java
  5. 66 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterContractInfo.java
  6. 63 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemContract.java
  7. 60 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemProject.java
  8. 19 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemRelation.java
  9. 57 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterMidPayItemSystem.java
  10. 92 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeContract.java
  11. 57 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeProject.java
  12. 45 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeSystem.java
  13. 18 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/MeterTreeTemplateInfo.java
  14. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/entity/test.java
  15. 18 0
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/MeterTreeContractVO.java
  16. 0 4
      blade-service-api/blade-meter-api/src/main/java/org/springblade/meter/vo/test.java
  17. 5 0
      blade-service/blade-meter/pom.xml
  18. 640 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/MeterTreeController.java
  19. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/controller/test.java
  20. 13 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeContractMapper.java
  21. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeContractMapper.xml
  22. 13 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeProjectMapper.java
  23. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeProjectMapper.xml
  24. 13 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeSystemMapper.java
  25. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeSystemMapper.xml
  26. 8 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeTemplateInfoMapper.java
  27. 5 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/MeterTreeTemplateInfoMapper.xml
  28. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/mapper/test.java
  29. 20 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeContractService.java
  30. 10 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeProjectService.java
  31. 10 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeSystemService.java
  32. 8 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/MeterTreeTemplateInfoService.java
  33. 422 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeContractServiceImpl.java
  34. 17 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeProjectServiceImpl.java
  35. 17 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeSystemServiceImpl.java
  36. 12 0
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/MeterTreeTemplateInfoServiceImpl.java
  37. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/impl/test.java
  38. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/service/test.java
  39. 0 4
      blade-service/blade-meter/src/main/java/org/springblade/meter/utils/test.java

+ 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 {
-}

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

+ 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 {
-}

+ 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>

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

@@ -0,0 +1,640 @@
+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");
+                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/detail")
+    @ApiOperationSupport(order = 13)
+    @ApiOperation(value = "项目树节点详情", notes = "传入id")
+    public R<MeterTreeProject> projectDetail(@RequestParam String id) {
+        return R.data(meterTreeProjectService.getById(id));
+    }
+
+    @PostMapping("/project/save")
+    @ApiOperationSupport(order = 14)
+    @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 = 15)
+    @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 = 16)
+    @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 = 17)
+    @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 = 18)
+    @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 = 19)
+    @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 = 20)
+    @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秒后再尝试");
+            }
+
+            /*首先判断是否存在项目树*/
+            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) {
+                /*当前项目所有树*/
+                List<MeterTreeProject> meterTreeProjects = meterTreeProjectService.getBaseMapper().selectList(Wrappers.<MeterTreeProject>lambdaQuery()
+                        .eq(MeterTreeProject::getTemplateId, rootNode.getTemplateId())
+                        .eq(MeterTreeProject::getProjectId, projectId)
+                        .eq(MeterTreeProject::getStatus, 1));
+
+                /*判断合同段是否存在树,不存在为初始化,存在则为同步*/
+                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) {
+                    /*初始化新增到合同段*/
+                    result = meterTreeContractService.contractTreeInit(meterTreeProjects, Long.parseLong(contractId));
+
+                } else {
+                    /*同步修改到合同段(增量)*/
+                    result = meterTreeContractService.contractTreeSync(meterTreeProjects, rootNode.getTemplateId(), Long.parseLong(contractId), Long.parseLong(projectId));
+                }
+
+                /*加锁*/
+                bladeRedis.set(redisKey, "1");
+                bladeRedis.expire(redisKey, 60);
+
+            } else {
+                throw new ServiceException("未获取到项目计量单元信息!");
+            }
+        }
+        if (result) {
+            return R.success("操作成功");
+        }
+        return R.fail("操作失败");
+    }
+
+    @GetMapping("/contract/left-list")
+    @ApiOperationSupport(order = 21)
+    @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 = 22)
+    @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 = 23)
+    @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 = 24)
+    @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 = 25)
+    @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 = 26)
+    @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 = 27)
+    @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 = 28)
+    @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 = 29)
+    @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 {
-}

+ 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 {
-}

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

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

@@ -0,0 +1,10 @@
+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);
+
+}

+ 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> {
+
+}

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

@@ -0,0 +1,422 @@
+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());
+            /*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
+            }
+
+            cObj.setUpdateStatus(0); //非编辑
+            cObj.setDataSourceType(1); //项目引用
+
+            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()
+                .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()) {
+            /*获取所有要新增的根节点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) //项目根节点父级 指向合同段
+            );
+            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) {
+                        /*获取新增节点的所有子级相关节点*/
+                        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());
+                            /*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
+                            }
+
+                            cObj.setUpdateStatus(0); //非编辑
+                            cObj.setDataSourceType(1); //项目引用
+
+                            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());
+                        /*id*/
+                        cObj.setId(SnowFlakeUtil.getId());
+                        /*parentId=合同段计量树选择节点的id,新增到选择节点下面*/
+                        cObj.setParentId(dto.getContractNodeId());
+                        /*ancestor*/
+                        cObj.setAncestor(meterTreeContractNode.getAncestor() + "," + cObj.getParentId());
+
+                        cObj.setUpdateStatus(0); //非编辑
+                        cObj.setDataSourceType(2); //用户手动新增
+
+                        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());
+                        /*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
+                        }
+
+                        cObj.setUpdateStatus(0); //非编辑
+                        cObj.setDataSourceType(2); //用户手动新增
+
+                        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;
+    }
+
+}

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

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

+ 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 {
-}