Commit 9f71f5b637502a499a25c799a89b2f172a0b7daa

Authored by 房远帅
1 parent 23f5f6ac

合同:合同变更单

Showing 20 changed files with 1766 additions and 1 deletions
@@ -1465,3 +1465,29 @@ ALTER TABLE tbl_purchase_order_line ADD COLUMN production_process text COMMENT ' @@ -1465,3 +1465,29 @@ ALTER TABLE tbl_purchase_order_line ADD COLUMN production_process text COMMENT '
1465 1465
1466 ALTER TABLE tbl_contract_distributor_standard 1466 ALTER TABLE tbl_contract_distributor_standard
1467 ADD COLUMN change_reason varchar(50) DEFAULT NULL COMMENT '变更原因'; 1467 ADD COLUMN change_reason varchar(50) DEFAULT NULL COMMENT '变更原因';
  1468 +
  1469 +CREATE TABLE IF NOT EXISTS `tbl_contract_change_application`
  1470 +(
  1471 + `id` varchar(32) NOT NULL COMMENT 'ID',
  1472 + `contract_id` varchar(32) NOT NULL COMMENT '合同ID',
  1473 + `root_contract_id` varchar(32) NOT NULL COMMENT '根合同ID',
  1474 + `contract_code` varchar(50) NOT NULL COMMENT '合同编号',
  1475 + `contract_type` varchar(50) NOT NULL COMMENT '合同类型',
  1476 + `contract_stage` varchar(20) NOT NULL COMMENT '合同阶段(FORMAL:正式合同,STANDARD:标准合同)',
  1477 + `change_reason` varchar(200) DEFAULT NULL COMMENT '变更原因',
  1478 + `before_data_json` longtext DEFAULT NULL COMMENT '变更前数据JSON',
  1479 + `after_data_json` longtext DEFAULT NULL COMMENT '变更后数据JSON',
  1480 + `approval_status` varchar(20) NOT NULL COMMENT '审核状态',
  1481 + `approver_id` varchar(32) DEFAULT NULL COMMENT '审核人ID',
  1482 + `approved_at` datetime DEFAULT NULL COMMENT '审核时间',
  1483 + `create_by_id` varchar(32) DEFAULT NULL COMMENT '创建人ID',
  1484 + `create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
  1485 + `update_by_id` varchar(32) DEFAULT NULL COMMENT '更新人ID',
  1486 + `update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
  1487 + `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  1488 + `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  1489 + PRIMARY KEY (`id`),
  1490 + KEY `idx_contract_change_application_contract_id` (`contract_id`),
  1491 + KEY `idx_contract_change_application_root_contract_id` (`root_contract_id`),
  1492 + KEY `idx_contract_change_application_approval_status` (`approval_status`)
  1493 +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同变更申请单';
  1 +package com.lframework.xingyun.sc.bo.contract;
  2 +
  3 +import com.lframework.starter.web.core.bo.BaseBo;
  4 +import com.lframework.xingyun.sc.entity.ContractChangeApplication;
  5 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractDistributorStandardVo;
  6 +import io.swagger.annotations.ApiModelProperty;
  7 +import lombok.Data;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +/**
  12 + * <p>
  13 + * 合同变更申请单 GetBo
  14 + * </p>
  15 + */
  16 +@Data
  17 +public class GetContractChangeApplicationBo extends BaseBo<ContractChangeApplication> {
  18 +
  19 + /**
  20 + * ID
  21 + */
  22 + @ApiModelProperty("ID")
  23 + private String id;
  24 +
  25 + /**
  26 + * 合同ID
  27 + */
  28 + @ApiModelProperty("合同ID")
  29 + private String contractId;
  30 +
  31 + /**
  32 + * 根合同ID
  33 + */
  34 + @ApiModelProperty("根合同ID")
  35 + private String rootContractId;
  36 +
  37 + /**
  38 + * 合同编号
  39 + */
  40 + @ApiModelProperty("合同编号")
  41 + private String contractCode;
  42 +
  43 + /**
  44 + * 合同类型
  45 + */
  46 + @ApiModelProperty("合同类型")
  47 + private String contractType;
  48 +
  49 + /**
  50 + * 合同阶段
  51 + */
  52 + @ApiModelProperty("合同阶段")
  53 + private String contractStage;
  54 +
  55 + /**
  56 + * 变更原因
  57 + */
  58 + @ApiModelProperty("变更原因")
  59 + private String changeReason;
  60 +
  61 + /**
  62 + * 审核状态
  63 + */
  64 + @ApiModelProperty("审核状态")
  65 + private String approvalStatus;
  66 +
  67 + /**
  68 + * 审核人ID
  69 + */
  70 + @ApiModelProperty("审核人ID")
  71 + private String approverId;
  72 +
  73 + /**
  74 + * 审核时间
  75 + */
  76 + @ApiModelProperty("审核时间")
  77 + private LocalDateTime approvedAt;
  78 +
  79 + /**
  80 + * 是否展示审核按钮
  81 + */
  82 + @ApiModelProperty("是否展示审核按钮")
  83 + private Boolean showExamine;
  84 +
  85 + /**
  86 + * 变更后的合同数据
  87 + */
  88 + @ApiModelProperty("变更后的合同数据")
  89 + private UpdateContractDistributorStandardVo contractData;
  90 +
  91 + public GetContractChangeApplicationBo() {
  92 + }
  93 +
  94 + public GetContractChangeApplicationBo(ContractChangeApplication dto) {
  95 + super(dto);
  96 + }
  97 +}
@@ -480,6 +480,30 @@ public class GetContractDistributorStandardBo extends BaseBo<ContractDistributor @@ -480,6 +480,30 @@ public class GetContractDistributorStandardBo extends BaseBo<ContractDistributor
480 private Boolean canSplit; 480 private Boolean canSplit;
481 481
482 /** 482 /**
  483 + * 是否可发起合同变更
  484 + */
  485 + @ApiModelProperty(value = "是否可发起合同变更")
  486 + private Boolean canCreateChange;
  487 +
  488 + /**
  489 + * 是否可锁价
  490 + */
  491 + @ApiModelProperty(value = "是否可锁价")
  492 + private Boolean canPriceLock;
  493 +
  494 + /**
  495 + * 是否可锁规
  496 + */
  497 + @ApiModelProperty(value = "是否可锁规")
  498 + private Boolean canSpecLock;
  499 +
  500 + /**
  501 + * 是否可发起锁规延期申请
  502 + */
  503 + @ApiModelProperty(value = "是否可发起锁规延期申请")
  504 + private Boolean canCreateSpecLockDelay;
  505 +
  506 + /**
483 * 创建人 507 * 创建人
484 */ 508 */
485 @ApiModelProperty(value = "创建人") 509 @ApiModelProperty(value = "创建人")
  1 +package com.lframework.xingyun.sc.bo.contract;
  2 +
  3 +import com.lframework.starter.web.core.bo.BaseBo;
  4 +import com.lframework.xingyun.sc.entity.ContractChangeApplication;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +/**
  11 + * <p>
  12 + * 合同变更申请单 QueryBo
  13 + * </p>
  14 + */
  15 +@Data
  16 +public class QueryContractChangeApplicationBo extends BaseBo<ContractChangeApplication> {
  17 +
  18 + /**
  19 + * ID
  20 + */
  21 + @ApiModelProperty("ID")
  22 + private String id;
  23 +
  24 + /**
  25 + * 合同ID
  26 + */
  27 + @ApiModelProperty("合同ID")
  28 + private String contractId;
  29 +
  30 + /**
  31 + * 根合同ID
  32 + */
  33 + @ApiModelProperty("根合同ID")
  34 + private String rootContractId;
  35 +
  36 + /**
  37 + * 合同编号
  38 + */
  39 + @ApiModelProperty("合同编号")
  40 + private String contractCode;
  41 +
  42 + /**
  43 + * 合同类型
  44 + */
  45 + @ApiModelProperty("合同类型")
  46 + private String contractType;
  47 +
  48 + /**
  49 + * 合同阶段
  50 + */
  51 + @ApiModelProperty("合同阶段")
  52 + private String contractStage;
  53 +
  54 + /**
  55 + * 变更原因
  56 + */
  57 + @ApiModelProperty("变更原因")
  58 + private String changeReason;
  59 +
  60 + /**
  61 + * 审核状态
  62 + */
  63 + @ApiModelProperty("审核状态")
  64 + private String approvalStatus;
  65 +
  66 + /**
  67 + * 是否展示审核按钮
  68 + */
  69 + @ApiModelProperty("是否展示审核按钮")
  70 + private Boolean showExamine;
  71 +
  72 + /**
  73 + * 创建人
  74 + */
  75 + @ApiModelProperty("创建人")
  76 + private String createBy;
  77 +
  78 + /**
  79 + * 创建时间
  80 + */
  81 + @ApiModelProperty("创建时间")
  82 + private LocalDateTime createTime;
  83 +
  84 + public QueryContractChangeApplicationBo() {
  85 + }
  86 +
  87 + public QueryContractChangeApplicationBo(ContractChangeApplication dto) {
  88 + super(dto);
  89 + }
  90 +}
@@ -153,6 +153,18 @@ public class QueryContractDistributorStandardBo extends BaseBo<ContractDistribut @@ -153,6 +153,18 @@ public class QueryContractDistributorStandardBo extends BaseBo<ContractDistribut
153 private BigDecimal totalAmountIncludingTax; 153 private BigDecimal totalAmountIncludingTax;
154 154
155 /** 155 /**
  156 + * 生产厂ID
  157 + */
  158 + @ApiModelProperty("生产厂ID")
  159 + private String workshopId;
  160 +
  161 + /**
  162 + * 生产厂名称
  163 + */
  164 + @ApiModelProperty("生产厂名称")
  165 + private String workshopName;
  166 +
  167 + /**
156 * 合计数量 168 * 合计数量
157 */ 169 */
158 @ApiModelProperty("合计数量") 170 @ApiModelProperty("合计数量")
@@ -240,6 +252,30 @@ public class QueryContractDistributorStandardBo extends BaseBo<ContractDistribut @@ -240,6 +252,30 @@ public class QueryContractDistributorStandardBo extends BaseBo<ContractDistribut
240 @ApiModelProperty(value = "是否可拆分") 252 @ApiModelProperty(value = "是否可拆分")
241 private Boolean canSplit; 253 private Boolean canSplit;
242 254
  255 + /**
  256 + * 是否可发起合同变更
  257 + */
  258 + @ApiModelProperty(value = "是否可发起合同变更")
  259 + private Boolean canCreateChange;
  260 +
  261 + /**
  262 + * 是否可锁价
  263 + */
  264 + @ApiModelProperty(value = "是否可锁价")
  265 + private Boolean canPriceLock;
  266 +
  267 + /**
  268 + * 是否可锁规
  269 + */
  270 + @ApiModelProperty(value = "是否可锁规")
  271 + private Boolean canSpecLock;
  272 +
  273 + /**
  274 + * 是否可发起锁规延期申请
  275 + */
  276 + @ApiModelProperty(value = "是否可发起锁规延期申请")
  277 + private Boolean canCreateSpecLockDelay;
  278 +
243 279
244 public QueryContractDistributorStandardBo() { 280 public QueryContractDistributorStandardBo() {
245 281
  1 +package com.lframework.xingyun.sc.controller.contract;
  2 +
  3 +import com.lframework.starter.common.exceptions.impl.DefaultClientException;
  4 +import com.lframework.starter.web.core.components.resp.InvokeResult;
  5 +import com.lframework.starter.web.core.components.resp.InvokeResultBuilder;
  6 +import com.lframework.starter.web.core.components.resp.PageResult;
  7 +import com.lframework.starter.web.core.controller.DefaultBaseController;
  8 +import com.lframework.starter.web.core.utils.JsonUtil;
  9 +import com.lframework.starter.web.core.utils.PageResultUtil;
  10 +import com.lframework.starter.web.inner.entity.SysDept;
  11 +import com.lframework.starter.web.inner.service.system.SysDeptService;
  12 +import com.lframework.xingyun.basedata.entity.Customer;
  13 +import com.lframework.xingyun.basedata.entity.Workshop;
  14 +import com.lframework.xingyun.basedata.service.customer.CustomerService;
  15 +import com.lframework.xingyun.basedata.service.workshop.WorkshopService;
  16 +import com.lframework.xingyun.sc.bo.contract.GetContractChangeApplicationBo;
  17 +import com.lframework.xingyun.sc.bo.contract.QueryContractChangeApplicationBo;
  18 +import com.lframework.xingyun.sc.bo.contract.QueryContractDistributorStandardBo;
  19 +import com.lframework.xingyun.sc.entity.ContractChangeApplication;
  20 +import com.lframework.xingyun.sc.entity.ContractDistributorStandard;
  21 +import com.lframework.xingyun.sc.service.contract.ContractChangeApplicationService;
  22 +import com.lframework.xingyun.sc.vo.contract.createVo.CreateContractChangeApplicationVo;
  23 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeApplicationVo;
  24 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeSelectableContractVo;
  25 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractChangeApplicationVo;
  26 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractDistributorStandardVo;
  27 +import io.swagger.annotations.Api;
  28 +import io.swagger.annotations.ApiImplicitParam;
  29 +import io.swagger.annotations.ApiOperation;
  30 +import org.apache.commons.collections4.CollectionUtils;
  31 +import org.springframework.beans.factory.annotation.Autowired;
  32 +import org.springframework.validation.annotation.Validated;
  33 +import org.springframework.web.bind.annotation.*;
  34 +
  35 +import javax.validation.Valid;
  36 +import javax.validation.constraints.NotBlank;
  37 +import java.util.Map;
  38 +import java.util.function.Function;
  39 +import java.util.List;
  40 +import java.util.stream.Collectors;
  41 +
  42 +/**
  43 + * 合同变更申请单 Controller
  44 + */
  45 +@Api(tags = "合同变更申请单")
  46 +@Validated
  47 +@RestController
  48 +@RequestMapping("/contract/contractChangeApplication")
  49 +public class ContractChangeApplicationController extends DefaultBaseController {
  50 +
  51 + @Autowired
  52 + private ContractChangeApplicationService contractChangeApplicationService;
  53 + @Autowired
  54 + private CustomerService customerService;
  55 + @Autowired
  56 + private SysDeptService sysDeptService;
  57 + @Autowired
  58 + private WorkshopService workshopService;
  59 +
  60 + /**
  61 + * 查询列表
  62 + */
  63 + @ApiOperation("查询列表")
  64 + @GetMapping("/query")
  65 + public InvokeResult<PageResult<QueryContractChangeApplicationBo>> query(QueryContractChangeApplicationVo vo) {
  66 + PageResult<ContractChangeApplication> pageResult = contractChangeApplicationService.query(getPageIndex(vo), getPageSize(vo), vo);
  67 + List<QueryContractChangeApplicationBo> results = null;
  68 + if (CollectionUtils.isNotEmpty(pageResult.getDatas())) {
  69 + results = pageResult.getDatas().stream().map(QueryContractChangeApplicationBo::new).collect(Collectors.toList());
  70 + }
  71 + return InvokeResultBuilder.success(PageResultUtil.rebuild(pageResult, results));
  72 + }
  73 +
  74 + /**
  75 + * 根据ID查询
  76 + */
  77 + @ApiOperation("根据ID查询")
  78 + @ApiImplicitParam(value = "id", name = "id", paramType = "query", required = true)
  79 + @GetMapping
  80 + public InvokeResult<GetContractChangeApplicationBo> get(@NotBlank(message = "id不能为空!") String id) {
  81 + ContractChangeApplication data = contractChangeApplicationService.findById(id);
  82 + if (data == null) {
  83 + throw new DefaultClientException("合同变更申请单不存在!");
  84 + }
  85 +
  86 + GetContractChangeApplicationBo result = new GetContractChangeApplicationBo(data);
  87 + if (org.apache.commons.lang3.StringUtils.isNotBlank(data.getAfterDataJson())) {
  88 + result.setContractData(JsonUtil.parseObject(data.getAfterDataJson(), UpdateContractDistributorStandardVo.class));
  89 + }
  90 + return InvokeResultBuilder.success(result);
  91 + }
  92 +
  93 + /**
  94 + * 查询可发起变更的合同
  95 + */
  96 + @ApiOperation("查询可发起变更的合同")
  97 + @GetMapping("/querySelectableContracts")
  98 + public InvokeResult<PageResult<QueryContractDistributorStandardBo>> querySelectableContracts(QueryContractChangeSelectableContractVo vo) {
  99 + PageResult<ContractDistributorStandard> pageResult = contractChangeApplicationService
  100 + .querySelectableContracts(getPageIndex(vo), getPageSize(vo), vo);
  101 + List<QueryContractDistributorStandardBo> results = null;
  102 + if (CollectionUtils.isNotEmpty(pageResult.getDatas())) {
  103 + results = pageResult.getDatas().stream().map(QueryContractDistributorStandardBo::new).collect(Collectors.toList());
  104 + Map<String, Customer> customerMap = customerService.listByIds(results.stream()
  105 + .map(QueryContractDistributorStandardBo::getBuyer)
  106 + .collect(Collectors.toSet()))
  107 + .stream()
  108 + .collect(Collectors.toMap(Customer::getId, Function.identity()));
  109 + Map<String, SysDept> deptMap = sysDeptService.listByIds(results.stream()
  110 + .map(QueryContractDistributorStandardBo::getDeptId)
  111 + .collect(Collectors.toSet()))
  112 + .stream()
  113 + .collect(Collectors.toMap(SysDept::getId, Function.identity()));
  114 + Map<String, Workshop> workshopMap = workshopService.listByIds(results.stream()
  115 + .map(QueryContractDistributorStandardBo::getWorkshopId)
  116 + .collect(Collectors.toSet()))
  117 + .stream()
  118 + .collect(Collectors.toMap(Workshop::getId, Function.identity()));
  119 + results.forEach(item -> {
  120 + Customer customer = customerMap.get(item.getBuyer());
  121 + if (customer != null) {
  122 + item.setBuyerName(customer.getName());
  123 + }
  124 + SysDept dept = deptMap.get(item.getDeptId());
  125 + if (dept != null) {
  126 + item.setDeptName(dept.getName());
  127 + }
  128 + Workshop workshop = workshopMap.get(item.getWorkshopId());
  129 + if (workshop != null) {
  130 + item.setWorkshopName(workshop.getName());
  131 + }
  132 + });
  133 + }
  134 + return InvokeResultBuilder.success(PageResultUtil.rebuild(pageResult, results));
  135 + }
  136 +
  137 + /**
  138 + * 新增
  139 + */
  140 + @ApiOperation("新增")
  141 + @PostMapping
  142 + public InvokeResult<Void> create(@Valid @RequestBody CreateContractChangeApplicationVo vo) throws Exception {
  143 + contractChangeApplicationService.create(vo);
  144 + return InvokeResultBuilder.success();
  145 + }
  146 +
  147 + /**
  148 + * 修改
  149 + */
  150 + @ApiOperation("修改")
  151 + @PutMapping
  152 + public InvokeResult<Void> update(@Valid @RequestBody UpdateContractChangeApplicationVo vo) throws Exception {
  153 + contractChangeApplicationService.update(vo);
  154 + return InvokeResultBuilder.success();
  155 + }
  156 +
  157 + /**
  158 + * 取消
  159 + */
  160 + @ApiOperation("取消")
  161 + @ApiImplicitParam(value = "id", name = "id", paramType = "query", required = true)
  162 + @DeleteMapping
  163 + public InvokeResult<Void> cancel(@NotBlank(message = "id不能为空!") String id) {
  164 + contractChangeApplicationService.cancelApplication(id);
  165 + return InvokeResultBuilder.success();
  166 + }
  167 +}
@@ -24,6 +24,7 @@ import com.lframework.xingyun.sc.entity.ContractDistributorStandard; @@ -24,6 +24,7 @@ import com.lframework.xingyun.sc.entity.ContractDistributorStandard;
24 import com.lframework.xingyun.sc.entity.SpecLockDelayApplication; 24 import com.lframework.xingyun.sc.entity.SpecLockDelayApplication;
25 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus; 25 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus;
26 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService; 26 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService;
  27 +import com.lframework.xingyun.sc.service.contract.ContractChangeApplicationService;
27 import com.lframework.xingyun.sc.service.contract.SpecLockDelayApplicationService; 28 import com.lframework.xingyun.sc.service.contract.SpecLockDelayApplicationService;
28 import com.lframework.xingyun.sc.vo.contract.createVo.CreateSpecLockDelayApplicationVo; 29 import com.lframework.xingyun.sc.vo.contract.createVo.CreateSpecLockDelayApplicationVo;
29 import com.lframework.xingyun.sc.vo.contract.queryVo.QuerySpecLockDelayApplicationVo; 30 import com.lframework.xingyun.sc.vo.contract.queryVo.QuerySpecLockDelayApplicationVo;
@@ -65,6 +66,8 @@ public class SpecLockDelayApplicationController extends DefaultBaseController { @@ -65,6 +66,8 @@ public class SpecLockDelayApplicationController extends DefaultBaseController {
65 private CustomerService customerService; 66 private CustomerService customerService;
66 @Autowired 67 @Autowired
67 private WorkshopService workshopService; 68 private WorkshopService workshopService;
  69 + @Autowired
  70 + private ContractChangeApplicationService contractChangeApplicationService;
68 71
69 public static final String AUDIT_STATUS_DIC_CODE = "AUDIT_STATUS"; // 审核状态 72 public static final String AUDIT_STATUS_DIC_CODE = "AUDIT_STATUS"; // 审核状态
70 73
@@ -225,6 +228,11 @@ public class SpecLockDelayApplicationController extends DefaultBaseController { @@ -225,6 +228,11 @@ public class SpecLockDelayApplicationController extends DefaultBaseController {
225 if (contractDistributorStandard == null) { 228 if (contractDistributorStandard == null) {
226 throw new DefaultClientException("对应锁价无规格合同不存在!"); 229 throw new DefaultClientException("对应锁价无规格合同不存在!");
227 } 230 }
  231 + String rootContractId = StringUtils.isBlank(contractDistributorStandard.getParentId())
  232 + ? contractDistributorStandard.getId() : contractDistributorStandard.getParentId();
  233 + if (contractChangeApplicationService.hasUnfinishedApplication(rootContractId)) {
  234 + throw new DefaultClientException("该合同存在未结束的合同变更流程,不允许发起锁规延期申请!");
  235 + }
228 236
229 QuerySpecLockDelayApplicationVo vo = new QuerySpecLockDelayApplicationVo(); 237 QuerySpecLockDelayApplicationVo vo = new QuerySpecLockDelayApplicationVo();
230 vo.setContractId(id); 238 vo.setContractId(id);
  1 +package com.lframework.xingyun.sc.entity;
  2 +
  3 +import com.baomidou.mybatisplus.annotation.FieldFill;
  4 +import com.baomidou.mybatisplus.annotation.TableField;
  5 +import com.baomidou.mybatisplus.annotation.TableName;
  6 +import com.lframework.starter.web.core.dto.BaseDto;
  7 +import com.lframework.starter.web.core.entity.BaseEntity;
  8 +import lombok.Data;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +/**
  13 + * <p>
  14 + * 合同变更申请单
  15 + * </p>
  16 + */
  17 +@Data
  18 +@TableName("tbl_contract_change_application")
  19 +public class ContractChangeApplication extends BaseEntity implements BaseDto {
  20 +
  21 + private static final long serialVersionUID = 1L;
  22 +
  23 + public static final String CACHE_NAME = "ContractChangeApplication";
  24 +
  25 + /**
  26 + * ID
  27 + */
  28 + private String id;
  29 +
  30 + /**
  31 + * 合同ID
  32 + */
  33 + private String contractId;
  34 +
  35 + /**
  36 + * 根合同ID
  37 + */
  38 + private String rootContractId;
  39 +
  40 + /**
  41 + * 合同编号
  42 + */
  43 + private String contractCode;
  44 +
  45 + /**
  46 + * 合同类型
  47 + */
  48 + private String contractType;
  49 +
  50 + /**
  51 + * 合同阶段(FORMAL:正式合同,STANDARD:标准合同)
  52 + */
  53 + private String contractStage;
  54 +
  55 + /**
  56 + * 变更原因
  57 + */
  58 + private String changeReason;
  59 +
  60 + /**
  61 + * 变更前数据JSON
  62 + */
  63 + private String beforeDataJson;
  64 +
  65 + /**
  66 + * 变更后数据JSON
  67 + */
  68 + private String afterDataJson;
  69 +
  70 + /**
  71 + * 审核状态
  72 + */
  73 + private String approvalStatus;
  74 +
  75 + /**
  76 + * 审核人ID
  77 + */
  78 + private String approverId;
  79 +
  80 + /**
  81 + * 审核时间
  82 + */
  83 + private LocalDateTime approvedAt;
  84 +
  85 + /**
  86 + * 办事处编码(非持久化字段)
  87 + */
  88 + @TableField(exist = false)
  89 + private String deptCode;
  90 +
  91 + /**
  92 + * 生产厂编码(非持久化字段)
  93 + */
  94 + @TableField(exist = false)
  95 + private String workshopCode;
  96 +
  97 + /**
  98 + * 是否展示审核按钮(非持久化字段)
  99 + */
  100 + @TableField(exist = false)
  101 + private Boolean showExamine;
  102 +
  103 + /**
  104 + * 创建人ID
  105 + */
  106 + @TableField(fill = FieldFill.INSERT)
  107 + private String createById;
  108 +
  109 + /**
  110 + * 创建人
  111 + */
  112 + @TableField(fill = FieldFill.INSERT)
  113 + private String createBy;
  114 +
  115 + /**
  116 + * 更新人ID
  117 + */
  118 + @TableField(fill = FieldFill.INSERT_UPDATE)
  119 + private String updateById;
  120 +
  121 + /**
  122 + * 更新人
  123 + */
  124 + @TableField(fill = FieldFill.INSERT_UPDATE)
  125 + private String updateBy;
  126 +
  127 + /**
  128 + * 创建时间
  129 + */
  130 + @TableField(fill = FieldFill.INSERT)
  131 + private LocalDateTime createTime;
  132 +
  133 + /**
  134 + * 更新时间
  135 + */
  136 + @TableField(fill = FieldFill.INSERT_UPDATE)
  137 + private LocalDateTime updateTime;
  138 +}
@@ -369,6 +369,30 @@ public class ContractDistributorStandard extends BaseEntity implements BaseDto { @@ -369,6 +369,30 @@ public class ContractDistributorStandard extends BaseEntity implements BaseDto {
369 private Boolean canSplit; 369 private Boolean canSplit;
370 370
371 /** 371 /**
  372 + * 是否可发起合同变更
  373 + */
  374 + @TableField(exist = false)
  375 + private Boolean canCreateChange;
  376 +
  377 + /**
  378 + * 是否可锁价
  379 + */
  380 + @TableField(exist = false)
  381 + private Boolean canPriceLock;
  382 +
  383 + /**
  384 + * 是否可锁规
  385 + */
  386 + @TableField(exist = false)
  387 + private Boolean canSpecLock;
  388 +
  389 + /**
  390 + * 是否可发起锁规延期申请
  391 + */
  392 + @TableField(exist = false)
  393 + private Boolean canCreateSpecLockDelay;
  394 +
  395 + /**
372 * 剩余锁规数量 396 * 剩余锁规数量
373 */ 397 */
374 @TableField(exist = false) 398 @TableField(exist = false)
@@ -19,6 +19,7 @@ import com.lframework.xingyun.basedata.service.workshop.WorkshopService; @@ -19,6 +19,7 @@ import com.lframework.xingyun.basedata.service.workshop.WorkshopService;
19 import com.lframework.xingyun.sc.entity.*; 19 import com.lframework.xingyun.sc.entity.*;
20 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus; 20 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus;
21 import com.lframework.xingyun.sc.service.contract.ContractDistributorLineService; 21 import com.lframework.xingyun.sc.service.contract.ContractDistributorLineService;
  22 +import com.lframework.xingyun.sc.service.contract.ContractChangeApplicationService;
22 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService; 23 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService;
23 import com.lframework.xingyun.sc.service.contract.ContractStdProcessingLineService; 24 import com.lframework.xingyun.sc.service.contract.ContractStdProcessingLineService;
24 import com.lframework.xingyun.sc.service.contract.SpecLockDelayApplicationService; 25 import com.lframework.xingyun.sc.service.contract.SpecLockDelayApplicationService;
@@ -106,6 +107,8 @@ public class BusinessDataHandlerServiceImpl implements BusinessDataHandlerServic @@ -106,6 +107,8 @@ public class BusinessDataHandlerServiceImpl implements BusinessDataHandlerServic
106 @Resource 107 @Resource
107 private SpecLockDelayApplicationService specLockDelayApplicationService; 108 private SpecLockDelayApplicationService specLockDelayApplicationService;
108 @Resource 109 @Resource
  110 + private ContractChangeApplicationService contractChangeApplicationService;
  111 + @Resource
109 private DraftRequestCarTicketService draftRequestCarTicketService; 112 private DraftRequestCarTicketService draftRequestCarTicketService;
110 @Resource 113 @Resource
111 private RequestCarTicketService requestCarTicketService; 114 private RequestCarTicketService requestCarTicketService;
@@ -200,6 +203,9 @@ public class BusinessDataHandlerServiceImpl implements BusinessDataHandlerServic @@ -200,6 +203,9 @@ public class BusinessDataHandlerServiceImpl implements BusinessDataHandlerServic
200 case "SPEC_LOCK_DELAY": 203 case "SPEC_LOCK_DELAY":
201 handleSpecLockDelayAuditData(flowStatus, businessId); 204 handleSpecLockDelayAuditData(flowStatus, businessId);
202 break; 205 break;
  206 + case "CONTRACT_CHANGE":
  207 + handleContractChangeAuditData(flowStatus, businessId);
  208 + break;
203 case "DRAFT_REQUEST_CAR": 209 case "DRAFT_REQUEST_CAR":
204 handleDraftRequestCarTicketData(flowStatus, businessId, variable); 210 handleDraftRequestCarTicketData(flowStatus, businessId, variable);
205 break; 211 break;
@@ -276,6 +282,19 @@ public class BusinessDataHandlerServiceImpl implements BusinessDataHandlerServic @@ -276,6 +282,19 @@ public class BusinessDataHandlerServiceImpl implements BusinessDataHandlerServic
276 } 282 }
277 } 283 }
278 284
  285 + private void handleContractChangeAuditData(String flowStatus, String businessId) {
  286 + if (FlowInstanceStatus.APPROVE_PASS.getCode().equals(flowStatus)
  287 + || FlowInstanceStatus.FINISH.getCode().equals(flowStatus)) {
  288 + contractChangeApplicationService.updateAuditStatus(businessId, "PASS");
  289 + } else if (FlowInstanceStatus.REVOKE.getCode().equals(flowStatus)
  290 + || FlowInstanceStatus.REFUSE.getCode().equals(flowStatus)
  291 + || FlowInstanceStatus.TERMINATION.getCode().equals(flowStatus)) {
  292 + contractChangeApplicationService.updateAuditStatus(businessId, "REFUSE");
  293 + } else if (FlowInstanceStatus.UNDO.getCode().equals(flowStatus)) {
  294 + contractChangeApplicationService.updateAuditStatus(businessId, "CANCEL");
  295 + }
  296 + }
  297 +
279 /** 298 /**
280 * 客户开发业务数据处理 299 * 客户开发业务数据处理
281 * 300 *
  1 +package com.lframework.xingyun.sc.impl.contract;
  2 +
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import com.baomidou.mybatisplus.core.conditions.Wrapper;
  5 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  6 +import com.github.pagehelper.PageInfo;
  7 +import com.lframework.starter.bpm.dto.FlowTaskDto;
  8 +import com.lframework.starter.bpm.mappers.FlowTaskWrapperMapper;
  9 +import com.lframework.starter.bpm.service.FlowInstanceWrapperService;
  10 +import com.lframework.starter.bpm.vo.flow.task.QueryTodoTaskListVo;
  11 +import com.lframework.starter.common.exceptions.impl.DefaultClientException;
  12 +import com.lframework.starter.common.utils.Assert;
  13 +import com.lframework.starter.common.utils.ObjectUtil;
  14 +import com.lframework.starter.web.core.annotations.oplog.OpLog;
  15 +import com.lframework.starter.web.core.components.resp.PageResult;
  16 +import com.lframework.starter.web.core.components.security.SecurityUtil;
  17 +import com.lframework.starter.web.core.impl.BaseMpServiceImpl;
  18 +import com.lframework.starter.web.core.utils.IdUtil;
  19 +import com.lframework.starter.web.core.utils.JsonUtil;
  20 +import com.lframework.starter.web.core.utils.OpLogUtil;
  21 +import com.lframework.starter.web.core.utils.PageHelperUtil;
  22 +import com.lframework.starter.web.core.utils.PageResultUtil;
  23 +import com.lframework.starter.web.inner.components.oplog.OtherOpLogType;
  24 +import com.lframework.starter.web.inner.entity.SysDept;
  25 +import com.lframework.starter.web.inner.service.system.SysDeptService;
  26 +import com.lframework.xingyun.basedata.entity.Workshop;
  27 +import com.lframework.xingyun.basedata.service.workshop.WorkshopService;
  28 +import com.lframework.xingyun.sc.entity.ContractChangeApplication;
  29 +import com.lframework.xingyun.sc.entity.ContractDistributorLine;
  30 +import com.lframework.xingyun.sc.entity.ContractDistributorStandard;
  31 +import com.lframework.xingyun.sc.entity.ContractStdProcessingLine;
  32 +import com.lframework.xingyun.sc.entity.SpecLockDelayApplication;
  33 +import com.lframework.xingyun.sc.enums.CustomerDevelopStatus;
  34 +import com.lframework.xingyun.sc.mappers.ContractChangeApplicationMapper;
  35 +import com.lframework.xingyun.sc.mappers.ContractDistributorStandardMapper;
  36 +import com.lframework.xingyun.sc.mappers.SpecLockDelayApplicationMapper;
  37 +import com.lframework.xingyun.sc.service.contract.ContractChangeApplicationService;
  38 +import com.lframework.xingyun.sc.service.contract.ContractDistributorLineService;
  39 +import com.lframework.xingyun.sc.service.contract.ContractStdProcessingLineService;
  40 +import com.lframework.xingyun.sc.vo.contract.createVo.CreateContractChangeApplicationVo;
  41 +import com.lframework.xingyun.sc.vo.contract.createVo.CreateContractDistributorLineVo;
  42 +import com.lframework.xingyun.sc.vo.contract.createVo.CreateContractStdProcessingLineVo;
  43 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeApplicationVo;
  44 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeSelectableContractVo;
  45 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractChangeApplicationVo;
  46 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractDistributorLineVo;
  47 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractDistributorStandardVo;
  48 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractStdProcessingLineVo;
  49 +import lombok.extern.slf4j.Slf4j;
  50 +import org.apache.commons.collections4.CollectionUtils;
  51 +import org.apache.commons.lang3.StringUtils;
  52 +import org.springframework.beans.factory.annotation.Autowired;
  53 +import org.springframework.cache.annotation.CacheEvict;
  54 +import org.springframework.stereotype.Service;
  55 +import org.springframework.transaction.annotation.Transactional;
  56 +
  57 +import javax.annotation.Resource;
  58 +import java.io.Serializable;
  59 +import java.time.LocalDateTime;
  60 +import java.util.ArrayList;
  61 +import java.util.Arrays;
  62 +import java.util.List;
  63 +import java.util.Set;
  64 +import java.util.stream.Collectors;
  65 +
  66 +/**
  67 + * 合同变更申请单 Service 实现
  68 + */
  69 +@Service
  70 +@Slf4j
  71 +public class ContractChangeApplicationServiceImpl extends BaseMpServiceImpl<ContractChangeApplicationMapper, ContractChangeApplication>
  72 + implements ContractChangeApplicationService {
  73 +
  74 + private static final String BPM_FLAG = "CONTRACT_CHANGE";
  75 +
  76 + @Autowired
  77 + private FlowInstanceWrapperService flowInstanceWrapperService;
  78 + @Resource
  79 + private FlowTaskWrapperMapper flowTaskWrapperMapper;
  80 + @Resource
  81 + private ContractDistributorStandardMapper contractDistributorStandardMapper;
  82 + @Resource
  83 + private ContractDistributorLineService contractDistributorLineService;
  84 + @Resource
  85 + private ContractStdProcessingLineService contractStdProcessingLineService;
  86 + @Resource
  87 + private SpecLockDelayApplicationMapper specLockDelayApplicationMapper;
  88 + @Resource
  89 + private WorkshopService workshopService;
  90 + @Resource
  91 + private SysDeptService sysDeptService;
  92 +
  93 + @Override
  94 + public PageResult<ContractChangeApplication> query(Integer pageIndex, Integer pageSize, QueryContractChangeApplicationVo vo) {
  95 + Assert.greaterThanZero(pageIndex);
  96 + Assert.greaterThanZero(pageSize);
  97 +
  98 + PageHelperUtil.startPage(pageIndex, pageSize);
  99 + Wrapper<ContractChangeApplication> wrapper = Wrappers.lambdaQuery(ContractChangeApplication.class)
  100 + .like(StringUtils.isNotBlank(vo.getContractCode()), ContractChangeApplication::getContractCode, vo.getContractCode())
  101 + .eq(StringUtils.isNotBlank(vo.getContractType()), ContractChangeApplication::getContractType, vo.getContractType())
  102 + .eq(StringUtils.isNotBlank(vo.getContractStage()), ContractChangeApplication::getContractStage, vo.getContractStage())
  103 + .eq(StringUtils.isNotBlank(vo.getApprovalStatus()), ContractChangeApplication::getApprovalStatus, vo.getApprovalStatus())
  104 + .orderByDesc(ContractChangeApplication::getCreateTime);
  105 + List<ContractChangeApplication> datas = list(wrapper);
  106 + fillShowExamine(datas);
  107 + return PageResultUtil.convert(new PageInfo<>(datas));
  108 + }
  109 +
  110 + @Override
  111 + public ContractChangeApplication findById(String id) {
  112 + ContractChangeApplication data = getBaseMapper().selectById(id);
  113 + if (data == null) {
  114 + return null;
  115 + }
  116 + fillShowExamine(Arrays.asList(data));
  117 + return data;
  118 + }
  119 +
  120 + @Override
  121 + @Transactional(rollbackFor = Exception.class)
  122 + @OpLog(type = OtherOpLogType.class, name = "新增合同变更申请单,ID:{}", params = {"#id"})
  123 + public String create(CreateContractChangeApplicationVo vo) throws Exception {
  124 + ContractDistributorStandard contract = getContractOrThrow(vo.getContractId());
  125 + validateContractCanChange(contract);
  126 +
  127 + UpdateContractDistributorStandardVo beforeData = buildContractData(contract);
  128 + UpdateContractDistributorStandardVo afterData = normalizeContractData(contract, vo.getContractData(), vo.getChangeReason());
  129 +
  130 + ContractChangeApplication data = new ContractChangeApplication();
  131 + data.setId(IdUtil.getUUID());
  132 + data.setContractId(contract.getId());
  133 + data.setRootContractId(getRootContractId(contract));
  134 + data.setContractCode(contract.getCode());
  135 + data.setContractType(contract.getType());
  136 + data.setContractStage(contract.getStatus());
  137 + data.setChangeReason(StringUtils.defaultIfBlank(vo.getChangeReason(), afterData.getChangeReason()));
  138 + data.setBeforeDataJson(JsonUtil.toJsonString(beforeData));
  139 + data.setAfterDataJson(JsonUtil.toJsonString(afterData));
  140 + data.setApprovalStatus(CustomerDevelopStatus.AUDIT.name());
  141 +
  142 + fillFlowVariables(data, contract);
  143 + getBaseMapper().insert(data);
  144 +
  145 + OpLogUtil.setVariable("id", data.getId());
  146 + OpLogUtil.setExtra(vo);
  147 +
  148 + data.setCreateById(SecurityUtil.getCurrentUser().getId());
  149 + flowInstanceWrapperService.startInstance(BPM_FLAG, data.getId(), BPM_FLAG, data);
  150 + return data.getId();
  151 + }
  152 +
  153 + @Override
  154 + @Transactional(rollbackFor = Exception.class)
  155 + @OpLog(type = OtherOpLogType.class, name = "修改合同变更申请单,ID:{}", params = {"#vo.id"})
  156 + public void update(UpdateContractChangeApplicationVo vo) throws Exception {
  157 + ContractChangeApplication data = getBaseMapper().selectById(vo.getId());
  158 + if (data == null) {
  159 + throw new DefaultClientException("合同变更申请单不存在!");
  160 + }
  161 + if (!CustomerDevelopStatus.REFUSE.name().equals(data.getApprovalStatus())) {
  162 + throw new DefaultClientException(CustomerDevelopStatus.getDescByNameSafe(data.getApprovalStatus()) + "的合同变更申请单不允许修改!");
  163 + }
  164 +
  165 + ContractDistributorStandard contract = getContractOrThrow(data.getContractId());
  166 + UpdateContractDistributorStandardVo beforeData = buildContractData(contract);
  167 + UpdateContractDistributorStandardVo afterData = normalizeContractData(contract, vo.getContractData(), vo.getChangeReason());
  168 +
  169 + Wrapper<ContractChangeApplication> wrapper = Wrappers.lambdaUpdate(ContractChangeApplication.class)
  170 + .set(ContractChangeApplication::getChangeReason, StringUtils.defaultIfBlank(vo.getChangeReason(), afterData.getChangeReason()))
  171 + .set(ContractChangeApplication::getBeforeDataJson, JsonUtil.toJsonString(beforeData))
  172 + .set(ContractChangeApplication::getAfterDataJson, JsonUtil.toJsonString(afterData))
  173 + .set(ContractChangeApplication::getApprovalStatus, CustomerDevelopStatus.AUDIT.name())
  174 + .eq(ContractChangeApplication::getId, vo.getId());
  175 + update(wrapper);
  176 +
  177 + fillFlowVariables(data, contract);
  178 + data.setAfterDataJson(JsonUtil.toJsonString(afterData));
  179 + data.setChangeReason(StringUtils.defaultIfBlank(vo.getChangeReason(), afterData.getChangeReason()));
  180 + flowInstanceWrapperService.startInstance(BPM_FLAG, data.getId(), BPM_FLAG, data);
  181 +
  182 + OpLogUtil.setVariable("id", data.getId());
  183 + OpLogUtil.setExtra(vo);
  184 + cleanCacheByKey(data.getId());
  185 + }
  186 +
  187 + @Override
  188 + @Transactional(rollbackFor = Exception.class)
  189 + @OpLog(type = OtherOpLogType.class, name = "取消合同变更申请单,ID:{}", params = {"#id"})
  190 + public void cancelApplication(String id) {
  191 + ContractChangeApplication data = getBaseMapper().selectById(id);
  192 + if (data == null) {
  193 + throw new DefaultClientException("合同变更申请单不存在!");
  194 + }
  195 + if (CustomerDevelopStatus.CANCEL.name().equals(data.getApprovalStatus())) {
  196 + throw new DefaultClientException("合同变更申请单已取消!");
  197 + }
  198 + if (!CustomerDevelopStatus.REFUSE.name().equals(data.getApprovalStatus())) {
  199 + throw new DefaultClientException(CustomerDevelopStatus.getDescByNameSafe(data.getApprovalStatus()) + "的合同变更申请单不允许取消!");
  200 + }
  201 +
  202 + Wrapper<ContractChangeApplication> wrapper = Wrappers.lambdaUpdate(ContractChangeApplication.class)
  203 + .set(ContractChangeApplication::getApprovalStatus, CustomerDevelopStatus.CANCEL.name())
  204 + .set(ContractChangeApplication::getApprovedAt, LocalDateTime.now())
  205 + .set(ContractChangeApplication::getApproverId, SecurityUtil.getCurrentUser().getId())
  206 + .eq(ContractChangeApplication::getId, id);
  207 + update(wrapper);
  208 +
  209 + cleanCacheByKey(id);
  210 + }
  211 +
  212 + @Override
  213 + @Transactional(rollbackFor = Exception.class)
  214 + @OpLog(type = OtherOpLogType.class, name = "合同变更申请单审批完成,ID:{},审批状态:{}", params = {"#id", "#approvalStatus"})
  215 + public void updateAuditStatus(String id, String approvalStatus) {
  216 + if (StringUtils.isBlank(id) || StringUtils.isBlank(approvalStatus)) {
  217 + return;
  218 + }
  219 +
  220 + ContractChangeApplication data = getBaseMapper().selectById(id);
  221 + if (data == null) {
  222 + return;
  223 + }
  224 +
  225 + Wrapper<ContractChangeApplication> wrapper = Wrappers.lambdaUpdate(ContractChangeApplication.class)
  226 + .set(ContractChangeApplication::getApprovalStatus, approvalStatus)
  227 + .set(ContractChangeApplication::getApprovedAt, LocalDateTime.now())
  228 + .set(ContractChangeApplication::getApproverId, SecurityUtil.getCurrentUser().getId())
  229 + .eq(ContractChangeApplication::getId, id);
  230 + update(wrapper);
  231 +
  232 + if (CustomerDevelopStatus.PASS.name().equals(approvalStatus)) {
  233 + applyContractChange(data);
  234 + }
  235 +
  236 + cleanCacheByKey(id);
  237 + }
  238 +
  239 + @Override
  240 + public PageResult<ContractDistributorStandard> querySelectableContracts(Integer pageIndex, Integer pageSize,
  241 + QueryContractChangeSelectableContractVo vo) {
  242 + Assert.greaterThanZero(pageIndex);
  243 + Assert.greaterThanZero(pageSize);
  244 +
  245 + PageHelperUtil.startPage(pageIndex, pageSize);
  246 + List<ContractDistributorStandard> datas = getBaseMapper().querySelectableContracts(vo);
  247 + return PageResultUtil.convert(new PageInfo<>(datas));
  248 + }
  249 +
  250 + @Override
  251 + public boolean hasUnfinishedApplication(String rootContractId) {
  252 + if (StringUtils.isBlank(rootContractId)) {
  253 + return false;
  254 + }
  255 + Wrapper<ContractChangeApplication> wrapper = Wrappers.lambdaQuery(ContractChangeApplication.class)
  256 + .eq(ContractChangeApplication::getRootContractId, rootContractId)
  257 + .notIn(ContractChangeApplication::getApprovalStatus,
  258 + Arrays.asList(CustomerDevelopStatus.PASS.name(), CustomerDevelopStatus.CANCEL.name()));
  259 + return count(wrapper) > 0;
  260 + }
  261 +
  262 + @CacheEvict(value = ContractChangeApplication.CACHE_NAME, key = "@cacheVariables.tenantId() + #key")
  263 + @Override
  264 + public void cleanCacheByKey(Serializable key) {
  265 + }
  266 +
  267 + /**
  268 + * 填充审核待办标识
  269 + *
  270 + * @param datas 数据列表
  271 + */
  272 + private void fillShowExamine(List<ContractChangeApplication> datas) {
  273 + if (CollectionUtils.isEmpty(datas)) {
  274 + return;
  275 + }
  276 +
  277 + List<FlowTaskDto> flowTaskList = flowTaskWrapperMapper.queryTodoList(new QueryTodoTaskListVo(),
  278 + SecurityUtil.getCurrentUser().getId());
  279 + if (CollectionUtils.isEmpty(flowTaskList)) {
  280 + datas.forEach(item -> item.setShowExamine(false));
  281 + return;
  282 + }
  283 +
  284 + Set<String> ids = flowTaskList.stream()
  285 + .filter(task -> BPM_FLAG.equals(task.getMode()))
  286 + .map(FlowTaskDto::getBusinessId)
  287 + .collect(Collectors.toSet());
  288 + datas.forEach(item -> item.setShowExamine(ids.contains(item.getId())));
  289 + }
  290 +
  291 + /**
  292 + * 获取合同,不存在则抛异常
  293 + *
  294 + * @param contractId 合同ID
  295 + * @return 合同
  296 + */
  297 + private ContractDistributorStandard getContractOrThrow(String contractId) {
  298 + ContractDistributorStandard contract = contractDistributorStandardMapper.selectById(contractId);
  299 + if (contract == null) {
  300 + throw new DefaultClientException("合同不存在!");
  301 + }
  302 + return contract;
  303 + }
  304 +
  305 + /**
  306 + * 校验合同是否允许发起变更
  307 + *
  308 + * @param contract 合同
  309 + */
  310 + private void validateContractCanChange(ContractDistributorStandard contract) {
  311 + if (contract == null) {
  312 + throw new DefaultClientException("合同不存在!");
  313 + }
  314 + if (!isSupportedChangeContract(contract)) {
  315 + throw new DefaultClientException("该合同当前不允许发起变更!");
  316 + }
  317 +
  318 + String rootContractId = getRootContractId(contract);
  319 + if (hasUnfinishedApplication(rootContractId)) {
  320 + throw new DefaultClientException("该合同存在未结束的合同变更流程,不允许重复发起变更!");
  321 + }
  322 + if (hasUnfinishedSpecLockDelay(rootContractId)) {
  323 + throw new DefaultClientException("该合同存在未结束的锁规延期申请,不允许发起合同变更!");
  324 + }
  325 + }
  326 +
  327 + /**
  328 + * 判断是否为支持发起变更的合同
  329 + *
  330 + * @param contract 合同
  331 + * @return 是否支持
  332 + */
  333 + private boolean isSupportedChangeContract(ContractDistributorStandard contract) {
  334 + if (contract == null || StringUtils.isBlank(contract.getType()) || StringUtils.isBlank(contract.getStatus())) {
  335 + return false;
  336 + }
  337 +
  338 + if ("DISTRIB_STD".equals(contract.getType())
  339 + || "DRAFT_DIST_AGMT".equals(contract.getType())
  340 + || "INTL_STD_CONTRACT".equals(contract.getType())
  341 + || "INTL_OPEN_SPEC_AGMT".equals(contract.getType())
  342 + || "PROCESS_STD_AGMT".equals(contract.getType())) {
  343 + return "STANDARD".equals(contract.getStatus()) && CustomerDevelopStatus.PASS.name().equals(contract.getStandardApproved());
  344 + }
  345 +
  346 + if ("DIST_STOCK_CONTRACT".equals(contract.getType()) || "INTL_INVENTORY_AGMT".equals(contract.getType())) {
  347 + if ("FORMAL".equals(contract.getStatus())) {
  348 + return CustomerDevelopStatus.PASS.name().equals(contract.getFormalApproved());
  349 + }
  350 + if ("STANDARD".equals(contract.getStatus())) {
  351 + return CustomerDevelopStatus.PASS.name().equals(contract.getStandardApproved());
  352 + }
  353 + }
  354 + return false;
  355 + }
  356 +
  357 + /**
  358 + * 构建合同变更数据快照
  359 + *
  360 + * @param contract 合同
  361 + * @return 合同变更数据
  362 + */
  363 + private UpdateContractDistributorStandardVo buildContractData(ContractDistributorStandard contract) {
  364 + UpdateContractDistributorStandardVo result = JsonUtil.parseObject(JsonUtil.toJsonString(contract),
  365 + UpdateContractDistributorStandardVo.class);
  366 +
  367 + Wrapper<ContractDistributorLine> distributorLineWrapper = Wrappers.lambdaQuery(ContractDistributorLine.class)
  368 + .eq(ContractDistributorLine::getContractId, contract.getId())
  369 + .orderByAsc(ContractDistributorLine::getShowOrder);
  370 + List<ContractDistributorLine> distributorLineList = contractDistributorLineService.list(distributorLineWrapper);
  371 + if (CollectionUtils.isNotEmpty(distributorLineList)) {
  372 + result.setContractDistributorLineList(JsonUtil.parseList(JsonUtil.toJsonString(distributorLineList),
  373 + UpdateContractDistributorLineVo.class));
  374 + } else {
  375 + result.setContractDistributorLineList(new ArrayList<>());
  376 + }
  377 +
  378 + Wrapper<ContractStdProcessingLine> processingLineWrapper = Wrappers.lambdaQuery(ContractStdProcessingLine.class)
  379 + .eq(ContractStdProcessingLine::getContractId, contract.getId())
  380 + .orderByAsc(ContractStdProcessingLine::getShowOrder);
  381 + List<ContractStdProcessingLine> processingLineList = contractStdProcessingLineService.list(processingLineWrapper);
  382 + if (CollectionUtils.isNotEmpty(processingLineList)) {
  383 + result.setContractStdProcessingLineList(JsonUtil.parseList(JsonUtil.toJsonString(processingLineList),
  384 + UpdateContractStdProcessingLineVo.class));
  385 + } else {
  386 + result.setContractStdProcessingLineList(new ArrayList<>());
  387 + }
  388 + return result;
  389 + }
  390 +
  391 + /**
  392 + * 规范化变更后的合同数据
  393 + *
  394 + * @param contract 原合同
  395 + * @param contractData 变更后的合同数据
  396 + * @param changeReason 变更原因
  397 + * @return 规范化后的合同数据
  398 + */
  399 + private UpdateContractDistributorStandardVo normalizeContractData(ContractDistributorStandard contract,
  400 + UpdateContractDistributorStandardVo contractData,
  401 + String changeReason) {
  402 + if (contractData == null) {
  403 + throw new DefaultClientException("变更后的合同数据不能为空!");
  404 + }
  405 +
  406 + UpdateContractDistributorStandardVo result = JsonUtil.parseObject(JsonUtil.toJsonString(contractData),
  407 + UpdateContractDistributorStandardVo.class);
  408 + result.setId(contract.getId());
  409 + result.setType(contract.getType());
  410 + result.setStatus(contract.getStatus());
  411 + result.setParentId(contract.getParentId());
  412 + result.setChangeReason(StringUtils.defaultIfBlank(changeReason, result.getChangeReason()));
  413 + if (result.getContractDistributorLineList() == null) {
  414 + result.setContractDistributorLineList(new ArrayList<>());
  415 + }
  416 + if (result.getContractStdProcessingLineList() == null) {
  417 + result.setContractStdProcessingLineList(new ArrayList<>());
  418 + }
  419 + return result;
  420 + }
  421 +
  422 + /**
  423 + * 填充流程变量
  424 + *
  425 + * @param data 申请单
  426 + * @param contract 合同
  427 + */
  428 + private void fillFlowVariables(ContractChangeApplication data, ContractDistributorStandard contract) {
  429 + if (data == null || contract == null) {
  430 + return;
  431 + }
  432 +
  433 + Workshop workshop = workshopService.findById(contract.getWorkshopId());
  434 + data.setWorkshopCode(workshop == null ? "" : workshop.getCode());
  435 + SysDept sysDept = sysDeptService.findById(contract.getDeptId());
  436 + data.setDeptCode(sysDept == null ? "" : sysDept.getCode());
  437 + }
  438 +
  439 + /**
  440 + * 获取根合同ID
  441 + *
  442 + * @param contract 合同
  443 + * @return 根合同ID
  444 + */
  445 + private String getRootContractId(ContractDistributorStandard contract) {
  446 + if (contract == null) {
  447 + return null;
  448 + }
  449 + return StringUtils.isBlank(contract.getParentId()) ? contract.getId() : contract.getParentId();
  450 + }
  451 +
  452 + /**
  453 + * 是否存在未结束的锁规延期申请
  454 + *
  455 + * @param rootContractId 根合同ID
  456 + * @return 是否存在未结束的锁规延期申请
  457 + */
  458 + private boolean hasUnfinishedSpecLockDelay(String rootContractId) {
  459 + if (StringUtils.isBlank(rootContractId)) {
  460 + return false;
  461 + }
  462 +
  463 + Wrapper<ContractDistributorStandard> contractWrapper = Wrappers.lambdaQuery(ContractDistributorStandard.class)
  464 + .select(ContractDistributorStandard::getId)
  465 + .eq(ContractDistributorStandard::getId, rootContractId)
  466 + .or()
  467 + .eq(ContractDistributorStandard::getParentId, rootContractId);
  468 + List<ContractDistributorStandard> contractList = contractDistributorStandardMapper.selectList(contractWrapper);
  469 + List<String> contractIds = contractList.stream().map(ContractDistributorStandard::getId).collect(Collectors.toList());
  470 + if (!contractIds.contains(rootContractId)) {
  471 + contractIds.add(rootContractId);
  472 + }
  473 +
  474 + Wrapper<SpecLockDelayApplication> wrapper = Wrappers.lambdaQuery(SpecLockDelayApplication.class)
  475 + .in(SpecLockDelayApplication::getContractId, contractIds)
  476 + .notIn(SpecLockDelayApplication::getApprovalStatus,
  477 + Arrays.asList(CustomerDevelopStatus.PASS.name(), CustomerDevelopStatus.CANCEL.name()));
  478 + return specLockDelayApplicationMapper.selectCount(wrapper) > 0;
  479 + }
  480 +
  481 + /**
  482 + * 应用合同变更
  483 + *
  484 + * @param application 变更申请单
  485 + */
  486 + private void applyContractChange(ContractChangeApplication application) {
  487 + if (application == null || StringUtils.isBlank(application.getAfterDataJson())) {
  488 + return;
  489 + }
  490 +
  491 + UpdateContractDistributorStandardVo vo = JsonUtil.parseObject(application.getAfterDataJson(),
  492 + UpdateContractDistributorStandardVo.class);
  493 + ContractDistributorStandard data = contractDistributorStandardMapper.selectById(application.getContractId());
  494 + if (data == null) {
  495 + throw new DefaultClientException("对应合同不存在!");
  496 + }
  497 +
  498 + Wrapper<ContractDistributorStandard> wrapper = Wrappers.lambdaUpdate(ContractDistributorStandard.class)
  499 + .set(ContractDistributorStandard::getCode, vo.getCode())
  500 + .set(ContractDistributorStandard::getSupplier, vo.getSupplier())
  501 + .set(ContractDistributorStandard::getBuyer, vo.getBuyer())
  502 + .set(ContractDistributorStandard::getDeptId, vo.getDeptId())
  503 + .set(ContractDistributorStandard::getRegion, vo.getRegion())
  504 + .set(ContractDistributorStandard::getStockUpCompanyId, vo.getStockUpCompanyId())
  505 + .set(ContractDistributorStandard::getOrderDate, vo.getOrderDate())
  506 + .set(ContractDistributorStandard::getUnit, vo.getUnit())
  507 + .set(ContractDistributorStandard::getWorkshopId, vo.getWorkshopId())
  508 + .set(ContractDistributorStandard::getTotalAmountCapital, vo.getTotalAmountCapital())
  509 + .set(ContractDistributorStandard::getDepositInfo, vo.getDepositInfo())
  510 + .set(ContractDistributorStandard::getPackagingRequirements, vo.getPackagingRequirements())
  511 + .set(ContractDistributorStandard::getPaymentTerms, vo.getPaymentTerms())
  512 + .set(ContractDistributorStandard::getExecutionStandard, vo.getExecutionStandard())
  513 + .set(ContractDistributorStandard::getExecutionStandardRemarks, vo.getExecutionStandardRemarks())
  514 + .set(ContractDistributorStandard::getTransportMode, vo.getTransportMode())
  515 + .set(ContractDistributorStandard::getDestination, vo.getDestination())
  516 + .set(ContractDistributorStandard::getForeignDestination, vo.getForeignDestination())
  517 + .set(ContractDistributorStandard::getIncludesPackagingFee, vo.getIncludesPackagingFee())
  518 + .set(ContractDistributorStandard::getIncludesTransportFee, vo.getIncludesTransportFee())
  519 + .set(ContractDistributorStandard::getPieceWeightHead, vo.getPieceWeightHead())
  520 + .set(ContractDistributorStandard::getSurface, vo.getSurface())
  521 + .set(ContractDistributorStandard::getTolerance, vo.getTolerance())
  522 + .set(ContractDistributorStandard::getPerformance, vo.getPerformance())
  523 + .set(ContractDistributorStandard::getComponent, vo.getComponent())
  524 + .set(ContractDistributorStandard::getPackaging, vo.getPackaging())
  525 + .set(ContractDistributorStandard::getSpecialTerms, vo.getSpecialTerms())
  526 + .set(ContractDistributorStandard::getDesignatedConsignee, vo.getDesignatedConsignee())
  527 + .set(ContractDistributorStandard::getSpecialInstructions, vo.getSpecialInstructions())
  528 + .set(ContractDistributorStandard::getRemarks, vo.getRemarks())
  529 + .set(ContractDistributorStandard::getTotalQuantity, vo.getTotalQuantity())
  530 + .set(ContractDistributorStandard::getTotalAmountExcludingTax, vo.getTotalAmountExcludingTax())
  531 + .set(ContractDistributorStandard::getTotalAmountIncludingTax, vo.getTotalAmountIncludingTax())
  532 + .set(ContractDistributorStandard::getChangeReason, vo.getChangeReason())
  533 + .eq(ContractDistributorStandard::getId, application.getContractId());
  534 + contractDistributorStandardMapper.update(wrapper);
  535 +
  536 + overwriteDistributorLines(application.getContractId(), vo);
  537 + overwriteProcessingLines(application.getContractId(), vo);
  538 + }
  539 +
  540 + /**
  541 + * 覆盖普通合同行
  542 + *
  543 + * @param contractId 合同ID
  544 + * @param vo 合同数据
  545 + */
  546 + private void overwriteDistributorLines(String contractId, UpdateContractDistributorStandardVo vo) {
  547 + Wrapper<ContractDistributorLine> removeWrapper = Wrappers.lambdaQuery(ContractDistributorLine.class)
  548 + .eq(ContractDistributorLine::getContractId, contractId);
  549 + contractDistributorLineService.remove(removeWrapper);
  550 +
  551 + if (CollectionUtils.isEmpty(vo.getContractDistributorLineList())) {
  552 + return;
  553 + }
  554 +
  555 + for (int i = 0; i < vo.getContractDistributorLineList().size(); i++) {
  556 + UpdateContractDistributorLineVo lineVo = vo.getContractDistributorLineList().get(i);
  557 + CreateContractDistributorLineVo createVo = new CreateContractDistributorLineVo();
  558 + BeanUtil.copyProperties(lineVo, createVo);
  559 + createVo.setContractId(contractId);
  560 + createVo.setShowOrder(i);
  561 + contractDistributorLineService.create(createVo);
  562 + }
  563 + }
  564 +
  565 + /**
  566 + * 覆盖加工合同行
  567 + *
  568 + * @param contractId 合同ID
  569 + * @param vo 合同数据
  570 + */
  571 + private void overwriteProcessingLines(String contractId, UpdateContractDistributorStandardVo vo) {
  572 + Wrapper<ContractStdProcessingLine> removeWrapper = Wrappers.lambdaQuery(ContractStdProcessingLine.class)
  573 + .eq(ContractStdProcessingLine::getContractId, contractId);
  574 + contractStdProcessingLineService.remove(removeWrapper);
  575 +
  576 + if (CollectionUtils.isEmpty(vo.getContractStdProcessingLineList())) {
  577 + return;
  578 + }
  579 +
  580 + for (int i = 0; i < vo.getContractStdProcessingLineList().size(); i++) {
  581 + UpdateContractStdProcessingLineVo lineVo = vo.getContractStdProcessingLineList().get(i);
  582 + CreateContractStdProcessingLineVo createVo = new CreateContractStdProcessingLineVo();
  583 + BeanUtil.copyProperties(lineVo, createVo);
  584 + createVo.setContractId(contractId);
  585 + createVo.setShowOrder(i);
  586 + contractStdProcessingLineService.create(createVo);
  587 + }
  588 + }
  589 +}
@@ -35,7 +35,9 @@ import com.lframework.starter.common.utils.ObjectUtil; @@ -35,7 +35,9 @@ import com.lframework.starter.common.utils.ObjectUtil;
35 import com.lframework.starter.common.utils.Assert; 35 import com.lframework.starter.common.utils.Assert;
36 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus; 36 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus;
37 import com.lframework.xingyun.sc.handlers.TransactorHandler; 37 import com.lframework.xingyun.sc.handlers.TransactorHandler;
  38 +import com.lframework.xingyun.sc.mappers.SpecLockDelayApplicationMapper;
38 import com.lframework.xingyun.sc.mappers.ContractDistributorStandardMapper; 39 import com.lframework.xingyun.sc.mappers.ContractDistributorStandardMapper;
  40 +import com.lframework.xingyun.sc.service.contract.ContractChangeApplicationService;
39 import com.lframework.xingyun.sc.service.contract.ContractDistributorLineService; 41 import com.lframework.xingyun.sc.service.contract.ContractDistributorLineService;
40 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService; 42 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService;
41 import com.lframework.xingyun.sc.service.contract.ContractFrameworkService; 43 import com.lframework.xingyun.sc.service.contract.ContractFrameworkService;
@@ -110,6 +112,10 @@ public class ContractDistributorStandardServiceImpl extends @@ -110,6 +112,10 @@ public class ContractDistributorStandardServiceImpl extends
110 private PurchaseOrderLineService purchaseOrderLineService; 112 private PurchaseOrderLineService purchaseOrderLineService;
111 @Resource 113 @Resource
112 private TransactorHandler transactorHandler; 114 private TransactorHandler transactorHandler;
  115 + @Resource
  116 + private ContractChangeApplicationService contractChangeApplicationService;
  117 + @Resource
  118 + private SpecLockDelayApplicationMapper specLockDelayApplicationMapper;
113 119
114 @Override 120 @Override
115 public PageResult<ContractDistributorStandard> query(Integer pageIndex, Integer pageSize, QueryContractDistributorStandardVo vo) { 121 public PageResult<ContractDistributorStandard> query(Integer pageIndex, Integer pageSize, QueryContractDistributorStandardVo vo) {
@@ -218,7 +224,7 @@ public class ContractDistributorStandardServiceImpl extends @@ -218,7 +224,7 @@ public class ContractDistributorStandardServiceImpl extends
218 public List<ContractDistributorStandard> query(QueryContractDistributorStandardVo vo) { 224 public List<ContractDistributorStandard> query(QueryContractDistributorStandardVo vo) {
219 225
220 List<ContractDistributorStandard> datas = getBaseMapper().query(vo); 226 List<ContractDistributorStandard> datas = getBaseMapper().query(vo);
221 - CollectionUtils.emptyIfNull(datas).forEach(data -> data.setCanSplit(isCanSplit(data))); 227 + CollectionUtils.emptyIfNull(datas).forEach(this::fillContractOperationPermissions);
222 return datas; 228 return datas;
223 } 229 }
224 230
@@ -239,6 +245,7 @@ public class ContractDistributorStandardServiceImpl extends @@ -239,6 +245,7 @@ public class ContractDistributorStandardServiceImpl extends
239 } 245 }
240 data.setCanEdit(canEdit); 246 data.setCanEdit(canEdit);
241 fillRemainingQuantity(data); 247 fillRemainingQuantity(data);
  248 + fillContractOperationPermissions(data);
242 249
243 // 获取当前人员的待办任务数据 250 // 获取当前人员的待办任务数据
244 List<FlowTaskDto> flowTaskList = flowTaskWrapperMapper.queryTodoList(new QueryTodoTaskListVo(), SecurityUtil.getCurrentUser().getId()); 251 List<FlowTaskDto> flowTaskList = flowTaskWrapperMapper.queryTodoList(new QueryTodoTaskListVo(), SecurityUtil.getCurrentUser().getId());
@@ -308,6 +315,167 @@ public class ContractDistributorStandardServiceImpl extends @@ -308,6 +315,167 @@ public class ContractDistributorStandardServiceImpl extends
308 return true; 315 return true;
309 } 316 }
310 317
  318 + /**
  319 + * 填充合同操作权限字段
  320 + *
  321 + * @param data 合同
  322 + */
  323 + private void fillContractOperationPermissions(ContractDistributorStandard data) {
  324 + if (data == null) {
  325 + return;
  326 + }
  327 + data.setCanSplit(isCanSplit(data));
  328 + data.setCanCreateChange(isCanCreateChange(data));
  329 + data.setCanPriceLock(isCanPriceLock(data));
  330 + data.setCanSpecLock(isCanSpecLock(data));
  331 + data.setCanCreateSpecLockDelay(isCanCreateSpecLockDelay(data));
  332 + }
  333 +
  334 + /**
  335 + * 判断是否可发起合同变更
  336 + *
  337 + * @param data 合同
  338 + * @return 是否可发起合同变更
  339 + */
  340 + private boolean isCanCreateChange(ContractDistributorStandard data) {
  341 + if (data == null || StringUtils.isBlank(data.getType()) || StringUtils.isBlank(data.getStatus())) {
  342 + return false;
  343 + }
  344 + if (hasUnfinishedContractChange(data) || hasUnfinishedSpecLockDelay(data)) {
  345 + return false;
  346 + }
  347 + if ("DISTRIB_STD".equals(data.getType())
  348 + || "DRAFT_DIST_AGMT".equals(data.getType())
  349 + || "INTL_STD_CONTRACT".equals(data.getType())
  350 + || "INTL_OPEN_SPEC_AGMT".equals(data.getType())
  351 + || "PROCESS_STD_AGMT".equals(data.getType())) {
  352 + return "STANDARD".equals(data.getStatus()) && CustomerDevelopStatus.PASS.name().equals(data.getStandardApproved());
  353 + }
  354 + if ("DIST_STOCK_CONTRACT".equals(data.getType()) || "INTL_INVENTORY_AGMT".equals(data.getType())) {
  355 + if ("FORMAL".equals(data.getStatus())) {
  356 + return CustomerDevelopStatus.PASS.name().equals(data.getFormalApproved());
  357 + }
  358 + if ("STANDARD".equals(data.getStatus())) {
  359 + return CustomerDevelopStatus.PASS.name().equals(data.getStandardApproved());
  360 + }
  361 + }
  362 + return false;
  363 + }
  364 +
  365 + /**
  366 + * 判断是否可锁价
  367 + *
  368 + * @param data 合同
  369 + * @return 是否可锁价
  370 + */
  371 + private boolean isCanPriceLock(ContractDistributorStandard data) {
  372 + if (data == null) {
  373 + return false;
  374 + }
  375 + if (!"DIST_STOCK_CONTRACT".equals(data.getType()) && !"INTL_INVENTORY_AGMT".equals(data.getType())) {
  376 + return false;
  377 + }
  378 + if (!"FORMAL".equals(data.getStatus())) {
  379 + return false;
  380 + }
  381 + if (!CustomerDevelopStatus.PASS.name().equals(data.getFormalApproved())) {
  382 + return false;
  383 + }
  384 + if (Boolean.TRUE.equals(data.getPriceSpecLocked())) {
  385 + return false;
  386 + }
  387 + if (hasUnfinishedContractChange(data)) {
  388 + return false;
  389 + }
  390 + Wrapper<PurchaseOrderInfo> orderInfoWrapper = Wrappers.lambdaQuery(PurchaseOrderInfo.class)
  391 + .eq(PurchaseOrderInfo::getContractId, data.getId())
  392 + .eq(PurchaseOrderInfo::getType, "PRODUCTION")
  393 + .eq(PurchaseOrderInfo::getExamineStatus, CustomerDevelopStatus.PASS.name());
  394 + return purchaseOrderInfoService.count(orderInfoWrapper) > 0;
  395 + }
  396 +
  397 + /**
  398 + * 判断是否可锁规
  399 + *
  400 + * @param data 合同
  401 + * @return 是否可锁规
  402 + */
  403 + private boolean isCanSpecLock(ContractDistributorStandard data) {
  404 + if (data == null) {
  405 + return false;
  406 + }
  407 + if (!"DRAFT_DIST_AGMT".equals(data.getType()) && !"INTL_OPEN_SPEC_AGMT".equals(data.getType())) {
  408 + return false;
  409 + }
  410 + if (!"STANDARD".equals(data.getStatus())) {
  411 + return false;
  412 + }
  413 + if (!CustomerDevelopStatus.PASS.name().equals(data.getStandardApproved())) {
  414 + return false;
  415 + }
  416 + if (Boolean.TRUE.equals(data.getPriceSpecLocked())) {
  417 + return false;
  418 + }
  419 + return !hasUnfinishedContractChange(data);
  420 + }
  421 +
  422 + /**
  423 + * 判断是否可发起锁规延期申请
  424 + *
  425 + * @param data 合同
  426 + * @return 是否可发起锁规延期申请
  427 + */
  428 + private boolean isCanCreateSpecLockDelay(ContractDistributorStandard data) {
  429 + if (!isCanSpecLock(data)) {
  430 + return false;
  431 + }
  432 + return !hasUnfinishedSpecLockDelay(data);
  433 + }
  434 +
  435 + /**
  436 + * 是否存在未结束的合同变更
  437 + *
  438 + * @param data 合同
  439 + * @return 是否存在未结束的合同变更
  440 + */
  441 + private boolean hasUnfinishedContractChange(ContractDistributorStandard data) {
  442 + if (data == null) {
  443 + return false;
  444 + }
  445 + String rootContractId = StringUtils.isBlank(data.getParentId()) ? data.getId() : data.getParentId();
  446 + return contractChangeApplicationService.hasUnfinishedApplication(rootContractId);
  447 + }
  448 +
  449 + /**
  450 + * 是否存在未结束的锁规延期申请
  451 + *
  452 + * @param data 合同
  453 + * @return 是否存在未结束的锁规延期申请
  454 + */
  455 + private boolean hasUnfinishedSpecLockDelay(ContractDistributorStandard data) {
  456 + if (data == null) {
  457 + return false;
  458 + }
  459 + String rootContractId = StringUtils.isBlank(data.getParentId()) ? data.getId() : data.getParentId();
  460 + Wrapper<ContractDistributorStandard> contractWrapper = Wrappers.lambdaQuery(ContractDistributorStandard.class)
  461 + .select(ContractDistributorStandard::getId)
  462 + .eq(ContractDistributorStandard::getId, rootContractId)
  463 + .or()
  464 + .eq(ContractDistributorStandard::getParentId, rootContractId);
  465 + List<String> contractIds = CollectionUtils.emptyIfNull(getBaseMapper().selectList(contractWrapper))
  466 + .stream()
  467 + .map(ContractDistributorStandard::getId)
  468 + .collect(Collectors.toList());
  469 + if (!contractIds.contains(rootContractId)) {
  470 + contractIds.add(rootContractId);
  471 + }
  472 + Wrapper<SpecLockDelayApplication> wrapper = Wrappers.lambdaQuery(SpecLockDelayApplication.class)
  473 + .in(SpecLockDelayApplication::getContractId, contractIds)
  474 + .notIn(SpecLockDelayApplication::getApprovalStatus,
  475 + Arrays.asList(CustomerDevelopStatus.PASS.name(), CustomerDevelopStatus.CANCEL.name()));
  476 + return specLockDelayApplicationMapper.selectCount(wrapper) > 0;
  477 + }
  478 +
311 @OpLog(type = OtherOpLogType.class, name = "新增合同,ID:{}", params = {"#id"}) 479 @OpLog(type = OtherOpLogType.class, name = "新增合同,ID:{}", params = {"#id"})
312 @Transactional(rollbackFor = Exception.class) 480 @Transactional(rollbackFor = Exception.class)
313 @Override 481 @Override
@@ -1291,6 +1459,9 @@ public class ContractDistributorStandardServiceImpl extends @@ -1291,6 +1459,9 @@ public class ContractDistributorStandardServiceImpl extends
1291 if (ObjectUtil.isNull(data)) { 1459 if (ObjectUtil.isNull(data)) {
1292 throw new DefaultClientException("合同不存在!"); 1460 throw new DefaultClientException("合同不存在!");
1293 } 1461 }
  1462 + if (!isCanPriceLock(data)) {
  1463 + throw new DefaultClientException("当前合同存在未结束的合同变更流程,或未满足锁价条件,不允许锁价!");
  1464 + }
1294 1465
1295 List<UpdateContractDistributorLineVo> lineVoList = vo.getContractDistributorLineList(); 1466 List<UpdateContractDistributorLineVo> lineVoList = vo.getContractDistributorLineList();
1296 if (CollectionUtils.isEmpty(lineVoList)) { 1467 if (CollectionUtils.isEmpty(lineVoList)) {
@@ -1418,6 +1589,9 @@ public class ContractDistributorStandardServiceImpl extends @@ -1418,6 +1589,9 @@ public class ContractDistributorStandardServiceImpl extends
1418 if (ObjectUtil.isNull(data)) { 1589 if (ObjectUtil.isNull(data)) {
1419 throw new DefaultClientException("合同不存在!"); 1590 throw new DefaultClientException("合同不存在!");
1420 } 1591 }
  1592 + if (!isCanSpecLock(data)) {
  1593 + throw new DefaultClientException("当前合同存在未结束的合同变更流程,或未满足锁规条件,不允许锁规!");
  1594 + }
1421 1595
1422 List<UpdateContractDistributorLineVo> lineVoList = vo.getContractDistributorLineList(); 1596 List<UpdateContractDistributorLineVo> lineVoList = vo.getContractDistributorLineList();
1423 if (CollectionUtils.isEmpty(lineVoList)) { 1597 if (CollectionUtils.isEmpty(lineVoList)) {
@@ -32,6 +32,7 @@ import com.lframework.xingyun.sc.entity.SpecLockDelayApplication; @@ -32,6 +32,7 @@ import com.lframework.xingyun.sc.entity.SpecLockDelayApplication;
32 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus; 32 import com.lframework.xingyun.sc.enums.CustomerDevelopStatus;
33 import com.lframework.xingyun.sc.handlers.TransactorHandler; 33 import com.lframework.xingyun.sc.handlers.TransactorHandler;
34 import com.lframework.xingyun.sc.mappers.SpecLockDelayApplicationMapper; 34 import com.lframework.xingyun.sc.mappers.SpecLockDelayApplicationMapper;
  35 +import com.lframework.xingyun.sc.service.contract.ContractChangeApplicationService;
35 import com.lframework.xingyun.sc.service.HolidaysService; 36 import com.lframework.xingyun.sc.service.HolidaysService;
36 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService; 37 import com.lframework.xingyun.sc.service.contract.ContractDistributorStandardService;
37 import com.lframework.xingyun.sc.service.contract.SpecLockDelayApplicationService; 38 import com.lframework.xingyun.sc.service.contract.SpecLockDelayApplicationService;
@@ -79,6 +80,8 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL @@ -79,6 +80,8 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL
79 private SysDeptService sysDeptService; 80 private SysDeptService sysDeptService;
80 @Resource 81 @Resource
81 private TransactorHandler transactorHandler; 82 private TransactorHandler transactorHandler;
  83 + @Resource
  84 + private ContractChangeApplicationService contractChangeApplicationService;
82 85
83 @Override 86 @Override
84 public PageResult<SpecLockDelayApplication> query(Integer pageIndex, Integer pageSize, QuerySpecLockDelayApplicationVo vo) { 87 public PageResult<SpecLockDelayApplication> query(Integer pageIndex, Integer pageSize, QuerySpecLockDelayApplicationVo vo) {
@@ -131,6 +134,7 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL @@ -131,6 +134,7 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL
131 @Transactional(rollbackFor = Exception.class) 134 @Transactional(rollbackFor = Exception.class)
132 @Override 135 @Override
133 public String create(CreateSpecLockDelayApplicationVo vo) throws Exception { 136 public String create(CreateSpecLockDelayApplicationVo vo) throws Exception {
  137 + checkNoUnfinishedContractChange(vo.getContractId());
134 checkSpecLockDateLimit(vo.getApplicationCount(), vo.getContractId(), vo.getSpecLockDate()); 138 checkSpecLockDateLimit(vo.getApplicationCount(), vo.getContractId(), vo.getSpecLockDate());
135 139
136 SpecLockDelayApplication data = new SpecLockDelayApplication(); 140 SpecLockDelayApplication data = new SpecLockDelayApplication();
@@ -181,6 +185,7 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL @@ -181,6 +185,7 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL
181 @Transactional(rollbackFor = Exception.class) 185 @Transactional(rollbackFor = Exception.class)
182 @Override 186 @Override
183 public void update(UpdateSpecLockDelayApplicationVo vo) throws Exception { 187 public void update(UpdateSpecLockDelayApplicationVo vo) throws Exception {
  188 + checkNoUnfinishedContractChange(vo.getContractId());
184 checkSpecLockDateLimit(vo.getApplicationCount(), vo.getContractId(), vo.getSpecLockDate()); 189 checkSpecLockDateLimit(vo.getApplicationCount(), vo.getContractId(), vo.getSpecLockDate());
185 190
186 SpecLockDelayApplication data = getBaseMapper().selectById(vo.getId()); 191 SpecLockDelayApplication data = getBaseMapper().selectById(vo.getId());
@@ -286,6 +291,25 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL @@ -286,6 +291,25 @@ public class SpecLockDelayApplicationServiceImpl extends BaseMpServiceImpl<SpecL
286 291
287 } 292 }
288 293
  294 + /**
  295 + * 校验不存在未结束的合同变更
  296 + *
  297 + * @param contractId 合同ID
  298 + */
  299 + private void checkNoUnfinishedContractChange(String contractId) {
  300 + if (StringUtils.isBlank(contractId)) {
  301 + return;
  302 + }
  303 + ContractDistributorStandard contract = contractDistributorStandardService.findById(contractId);
  304 + if (contract == null) {
  305 + return;
  306 + }
  307 + String rootContractId = StringUtils.isBlank(contract.getParentId()) ? contract.getId() : contract.getParentId();
  308 + if (contractChangeApplicationService.hasUnfinishedApplication(rootContractId)) {
  309 + throw new DefaultClientException("该合同存在未结束的合同变更流程,不允许发起锁规延期申请!");
  310 + }
  311 + }
  312 +
289 public void sendDelaySpecLockMessage() { 313 public void sendDelaySpecLockMessage() {
290 log.info("开始执行延迟锁规消息发送任务"); 314 log.info("开始执行延迟锁规消息发送任务");
291 315
  1 +package com.lframework.xingyun.sc.mappers;
  2 +
  3 +import com.lframework.starter.web.core.annotations.permission.DataPermission;
  4 +import com.lframework.starter.web.core.annotations.permission.DataPermissions;
  5 +import com.lframework.starter.web.core.mapper.BaseMapper;
  6 +import com.lframework.starter.web.inner.components.permission.OrderDataPermissionDataPermissionType;
  7 +import com.lframework.xingyun.sc.entity.ContractChangeApplication;
  8 +import com.lframework.xingyun.sc.entity.ContractDistributorStandard;
  9 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeSelectableContractVo;
  10 +import org.apache.ibatis.annotations.Param;
  11 +
  12 +import java.util.List;
  13 +
  14 +/**
  15 + * <p>
  16 + * 合同变更申请单 Mapper 接口
  17 + * </p>
  18 + */
  19 +public interface ContractChangeApplicationMapper extends BaseMapper<ContractChangeApplication> {
  20 +
  21 + /**
  22 + * 查询可发起变更的合同
  23 + *
  24 + * @param vo 查询参数
  25 + * @return 可发起变更的合同
  26 + */
  27 + @DataPermissions(type = OrderDataPermissionDataPermissionType.class, value = {
  28 + @DataPermission(template = "order", alias = "tb")
  29 + })
  30 + List<ContractDistributorStandard> querySelectableContracts(@Param("vo") QueryContractChangeSelectableContractVo vo);
  31 +}
  1 +package com.lframework.xingyun.sc.service.contract;
  2 +
  3 +import com.lframework.starter.web.core.components.resp.PageResult;
  4 +import com.lframework.starter.web.core.service.BaseMpService;
  5 +import com.lframework.xingyun.sc.entity.ContractChangeApplication;
  6 +import com.lframework.xingyun.sc.entity.ContractDistributorStandard;
  7 +import com.lframework.xingyun.sc.vo.contract.createVo.CreateContractChangeApplicationVo;
  8 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeApplicationVo;
  9 +import com.lframework.xingyun.sc.vo.contract.queryVo.QueryContractChangeSelectableContractVo;
  10 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractChangeApplicationVo;
  11 +
  12 +/**
  13 + * 合同变更申请单 Service
  14 + */
  15 +public interface ContractChangeApplicationService extends BaseMpService<ContractChangeApplication> {
  16 +
  17 + /**
  18 + * 查询列表
  19 + *
  20 + * @param pageIndex 页码
  21 + * @param pageSize 页大小
  22 + * @param vo 查询参数
  23 + * @return 分页结果
  24 + */
  25 + PageResult<ContractChangeApplication> query(Integer pageIndex, Integer pageSize, QueryContractChangeApplicationVo vo);
  26 +
  27 + /**
  28 + * 根据ID查询
  29 + *
  30 + * @param id ID
  31 + * @return 合同变更申请单
  32 + */
  33 + ContractChangeApplication findById(String id);
  34 +
  35 + /**
  36 + * 创建
  37 + *
  38 + * @param vo 新增参数
  39 + * @return ID
  40 + * @throws Exception 异常
  41 + */
  42 + String create(CreateContractChangeApplicationVo vo) throws Exception;
  43 +
  44 + /**
  45 + * 修改
  46 + *
  47 + * @param vo 修改参数
  48 + * @throws Exception 异常
  49 + */
  50 + void update(UpdateContractChangeApplicationVo vo) throws Exception;
  51 +
  52 + /**
  53 + * 取消申请
  54 + *
  55 + * @param id ID
  56 + */
  57 + void cancelApplication(String id);
  58 +
  59 + /**
  60 + * 更新审核状态
  61 + *
  62 + * @param id ID
  63 + * @param approvalStatus 审核状态
  64 + */
  65 + void updateAuditStatus(String id, String approvalStatus);
  66 +
  67 + /**
  68 + * 查询可发起变更的合同
  69 + *
  70 + * @param pageIndex 页码
  71 + * @param pageSize 页大小
  72 + * @param vo 查询参数
  73 + * @return 分页结果
  74 + */
  75 + PageResult<ContractDistributorStandard> querySelectableContracts(Integer pageIndex, Integer pageSize,
  76 + QueryContractChangeSelectableContractVo vo);
  77 +
  78 + /**
  79 + * 是否存在未结束的合同变更申请单
  80 + *
  81 + * @param rootContractId 根合同ID
  82 + * @return 是否存在未结束的合同变更申请单
  83 + */
  84 + boolean hasUnfinishedApplication(String rootContractId);
  85 +}
  1 +package com.lframework.xingyun.sc.vo.contract.createVo;
  2 +
  3 +import com.lframework.starter.web.core.vo.BaseVo;
  4 +import com.lframework.xingyun.sc.vo.contract.updateVo.UpdateContractDistributorStandardVo;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.Length;
  8 +
  9 +import javax.validation.Valid;
  10 +import javax.validation.constraints.NotBlank;
  11 +import javax.validation.constraints.NotNull;
  12 +import java.io.Serializable;
  13 +
  14 +/**
  15 + * 合同变更申请单新增参数
  16 + */
  17 +@Data
  18 +public class CreateContractChangeApplicationVo implements BaseVo, Serializable {
  19 +
  20 + private static final long serialVersionUID = 1L;
  21 +
  22 + /**
  23 + * 合同ID
  24 + */
  25 + @ApiModelProperty(value = "合同ID", required = true)
  26 + @NotBlank(message = "合同ID不能为空!")
  27 + private String contractId;
  28 +
  29 + /**
  30 + * 变更原因
  31 + */
  32 + @ApiModelProperty(value = "变更原因")
  33 + @Length(message = "变更原因最多允许200个字符!")
  34 + private String changeReason;
  35 +
  36 + /**
  37 + * 变更后的合同数据
  38 + */
  39 + @ApiModelProperty(value = "变更后的合同数据", required = true)
  40 + @NotNull(message = "变更后的合同数据不能为空!")
  41 + @Valid
  42 + private UpdateContractDistributorStandardVo contractData;
  43 +}
  1 +package com.lframework.xingyun.sc.vo.contract.queryVo;
  2 +
  3 +import com.lframework.starter.web.core.vo.BaseVo;
  4 +import com.lframework.starter.web.core.vo.PageVo;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +
  8 +import java.io.Serializable;
  9 +
  10 +/**
  11 + * 合同变更申请单查询参数
  12 + */
  13 +@Data
  14 +public class QueryContractChangeApplicationVo extends PageVo implements BaseVo, Serializable {
  15 +
  16 + private static final long serialVersionUID = 1L;
  17 +
  18 + /**
  19 + * 合同编号
  20 + */
  21 + @ApiModelProperty("合同编号")
  22 + private String contractCode;
  23 +
  24 + /**
  25 + * 合同类型
  26 + */
  27 + @ApiModelProperty("合同类型")
  28 + private String contractType;
  29 +
  30 + /**
  31 + * 合同阶段(FORMAL:正式合同,STANDARD:标准合同)
  32 + */
  33 + @ApiModelProperty("合同阶段(FORMAL:正式合同,STANDARD:标准合同)")
  34 + private String contractStage;
  35 +
  36 + /**
  37 + * 审核状态
  38 + */
  39 + @ApiModelProperty("审核状态")
  40 + private String approvalStatus;
  41 +}
  1 +package com.lframework.xingyun.sc.vo.contract.queryVo;
  2 +
  3 +import com.lframework.starter.web.core.vo.BaseVo;
  4 +import com.lframework.starter.web.core.vo.PageVo;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +
  8 +import java.io.Serializable;
  9 +
  10 +/**
  11 + * 可发起合同变更合同查询参数
  12 + */
  13 +@Data
  14 +public class QueryContractChangeSelectableContractVo extends PageVo implements BaseVo, Serializable {
  15 +
  16 + private static final long serialVersionUID = 1L;
  17 +
  18 + /**
  19 + * 合同编号
  20 + */
  21 + @ApiModelProperty("合同编号")
  22 + private String code;
  23 +
  24 + /**
  25 + * 合同类型
  26 + */
  27 + @ApiModelProperty("合同类型")
  28 + private String type;
  29 +
  30 + /**
  31 + * 合同阶段(FORMAL:正式合同,STANDARD:标准合同)
  32 + */
  33 + @ApiModelProperty("合同阶段(FORMAL:正式合同,STANDARD:标准合同)")
  34 + private String contractStage;
  35 +}
  1 +package com.lframework.xingyun.sc.vo.contract.updateVo;
  2 +
  3 +import com.lframework.starter.web.core.vo.BaseVo;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +import org.hibernate.validator.constraints.Length;
  7 +
  8 +import javax.validation.Valid;
  9 +import javax.validation.constraints.NotBlank;
  10 +import javax.validation.constraints.NotNull;
  11 +import java.io.Serializable;
  12 +
  13 +/**
  14 + * 合同变更申请单修改参数
  15 + */
  16 +@Data
  17 +public class UpdateContractChangeApplicationVo implements BaseVo, Serializable {
  18 +
  19 + private static final long serialVersionUID = 1L;
  20 +
  21 + /**
  22 + * ID
  23 + */
  24 + @ApiModelProperty(value = "ID", required = true)
  25 + @NotBlank(message = "ID不能为空!")
  26 + private String id;
  27 +
  28 + /**
  29 + * 变更原因
  30 + */
  31 + @ApiModelProperty(value = "变更原因")
  32 + @Length(message = "变更原因最多允许200个字符!")
  33 + private String changeReason;
  34 +
  35 + /**
  36 + * 变更后的合同数据
  37 + */
  38 + @ApiModelProperty(value = "变更后的合同数据", required = true)
  39 + @NotNull(message = "变更后的合同数据不能为空!")
  40 + @Valid
  41 + private UpdateContractDistributorStandardVo contractData;
  42 +}
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3 +<mapper namespace="com.lframework.xingyun.sc.mappers.ContractChangeApplicationMapper">
  4 +
  5 + <select id="querySelectableContracts" resultType="com.lframework.xingyun.sc.entity.ContractDistributorStandard">
  6 + SELECT tb.id,
  7 + tb.code,
  8 + tb.buyer,
  9 + tb.dept_id,
  10 + tb.region,
  11 + tb.stock_up_company_id,
  12 + tb.order_date,
  13 + tb.unit,
  14 + tb.workshop_id,
  15 + tb.total_quantity,
  16 + tb.total_amount_including_tax,
  17 + tb.status,
  18 + tb.type,
  19 + tb.parent_id,
  20 + tb.formal_approved,
  21 + tb.standard_approved,
  22 + tb.price_spec_locked,
  23 + tb.change_reason,
  24 + tb.create_by,
  25 + tb.create_by_id,
  26 + tb.create_time
  27 + FROM tbl_contract_distributor_standard tb
  28 + WHERE tb.status != 'DELETED'
  29 + <if test="vo.code != null and vo.code != ''">
  30 + AND tb.code LIKE CONCAT('%', #{vo.code}, '%')
  31 + </if>
  32 + <if test="vo.type != null and vo.type != ''">
  33 + AND tb.type = #{vo.type}
  34 + </if>
  35 + <if test="vo.contractStage != null and vo.contractStage != ''">
  36 + AND tb.status = #{vo.contractStage}
  37 + </if>
  38 + AND (
  39 + (tb.type = 'DISTRIB_STD' AND tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  40 + OR (tb.type = 'DIST_STOCK_CONTRACT' AND (
  41 + (tb.status = 'FORMAL' AND tb.formal_approved = 'PASS')
  42 + OR (tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  43 + ))
  44 + OR (tb.type = 'DRAFT_DIST_AGMT' AND tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  45 + OR (tb.type = 'INTL_STD_CONTRACT' AND tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  46 + OR (tb.type = 'INTL_INVENTORY_AGMT' AND (
  47 + (tb.status = 'FORMAL' AND tb.formal_approved = 'PASS')
  48 + OR (tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  49 + ))
  50 + OR (tb.type = 'INTL_OPEN_SPEC_AGMT' AND tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  51 + OR (tb.type = 'PROCESS_STD_AGMT' AND tb.status = 'STANDARD' AND tb.standard_approved = 'PASS')
  52 + )
  53 + AND NOT EXISTS (
  54 + SELECT 1
  55 + FROM tbl_contract_change_application cca
  56 + WHERE cca.root_contract_id = IFNULL(NULLIF(tb.parent_id, ''), tb.id)
  57 + AND cca.approval_status NOT IN ('PASS', 'CANCEL')
  58 + )
  59 + AND NOT EXISTS (
  60 + SELECT 1
  61 + FROM tbl_spec_lock_delay_application sda
  62 + LEFT JOIN tbl_contract_distributor_standard rel
  63 + ON rel.id = sda.contract_id
  64 + WHERE sda.approval_status NOT IN ('PASS', 'CANCEL')
  65 + AND (
  66 + sda.contract_id = IFNULL(NULLIF(tb.parent_id, ''), tb.id)
  67 + OR rel.parent_id = IFNULL(NULLIF(tb.parent_id, ''), tb.id)
  68 + )
  69 + )
  70 + ORDER BY tb.create_time DESC
  71 + </select>
  72 +</mapper>