diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java b/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java index cbf362f..753d316 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/controller/ContractLaunchController.java @@ -4,6 +4,7 @@ import com.nbport.zgwl.common.PageResult; import com.nbport.zgwl.common.R; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchApproveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchCollectSaveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackRequestDto; @@ -299,6 +300,32 @@ public class ContractLaunchController { return R.success(contractLaunchService.mockCollect(id)); } + /** + * 保存采集信息(用印信息 + 签订信息)。 + * + * 签署完成后,在「合同采集」阶段(pending_collect), + * 用户通过采集弹窗补充/修改用印信息和签订信息后保存到合同记录。 + * 与 mock-collect 分开,允许用户在触发采集前先保存草稿。 + * + * 请求体 ContractLaunchCollectSaveDto 字段: + * sealIsSeal - 是否用印 + * sealApproveSame - 用印和审批文本是否一致 + * sealQuantity - 用印数量 + * sealDate - 用印时间 + * signDate - 签订日期 + * activeDate - 合同生效日期 + * executorAccount - 合同履行执行人 + * signedFileName - 签署文件名 + * signedFilePath - 签署文件路径 + * signedFileType - 签署文件类型 + * signedFileSize - 签署文件大小 + */ + @PostMapping("/{id}/collect/save") + public R saveCollectInfo(@PathVariable("id") Long id, + @RequestBody ContractLaunchCollectSaveDto collectDto) { + return R.success(contractLaunchService.saveCollectInfo(id, collectDto)); + } + /** * 合同归档。 * diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchCollectSaveDto.java b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchCollectSaveDto.java new file mode 100644 index 0000000..f4b0f26 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractlaunch/dto/ContractLaunchCollectSaveDto.java @@ -0,0 +1,58 @@ +package com.nbport.zgwl.contractlaunch.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 合同采集信息保存参数。 + * + * 签署完成后,在「合同采集」阶段(pending_collect → pending_archive), + * 用户通过采集弹窗补充用印信息、签订信息后保存到合同记录, + * 再调用 dataCollectionPushService.collectContract 推送到法务系统。 + */ +@Data +public class ContractLaunchCollectSaveDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 是否用印(1-是,0-否) */ + private String sealIsSeal; + + /** 用印和审批文本是否一致(1-是,0-否) */ + private String sealApproveSame; + + /** 用印数量 */ + private Integer sealQuantity; + + /** 用印时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime sealDate; + + /** 签订日期 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime signDate; + + /** 合同生效日期 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime activeDate; + + /** 合同履行执行人(账号) */ + private String executorAccount; + + /** 签署文件名 */ + private String signedFileName; + + /** 签署文件路径 */ + private String signedFilePath; + + /** 签署文件类型 */ + private String signedFileType; + + /** 签署文件大小 */ + private Long signedFileSize; +} diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java index 2f5cfcc..964b11b 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchService.java @@ -3,6 +3,7 @@ package com.nbport.zgwl.contractlaunch.service; import com.nbport.zgwl.common.PageResult; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchApproveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchCollectSaveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackSampleDto; @@ -50,6 +51,9 @@ public interface ContractLaunchService { /** 签署完成回调(回写签署文件路径,状态变更为待采集) */ ContractLaunchDto markSealCompleted(Long id, ContractLaunchSealDto sealDto); + /** 保存采集信息(用印+签订信息),用于采集弹窗保存草稿 */ + ContractLaunchDto saveCollectInfo(Long id, ContractLaunchCollectSaveDto collectDto); + /** 模拟合同采集(调用数据采集 collectContract 接口,TODO:替换为真实调用) */ ContractLaunchDto mockCollect(Long id); diff --git a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java index 458a0ff..8e6bceb 100644 --- a/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java +++ b/src/main/java/com/nbport/zgwl/contractlaunch/service/ContractLaunchServiceImpl.java @@ -11,6 +11,7 @@ import com.nbport.zgwl.contractlaunch.dto.ContractLaunchApproveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchiveEleDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchArchivePaperDto; +import com.nbport.zgwl.contractlaunch.dto.ContractLaunchCollectSaveDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchFileDto; import com.nbport.zgwl.contractlaunch.dto.ContractLaunchLawCallbackDto; @@ -125,15 +126,11 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { if (securityManager.isSuperAdmin()) { return "admin"; } - String permissionScope = resolveScopeByDataPermission(currentUser.getDataPermission()); - if (permissionScope != null) { - return permissionScope; - } if (authManager.isUserCanAccessSys(currentUser.getId(), SysEnum.MANAGE)) { - return isAdminUser(currentUser) ? "company" : "personal"; + return "company"; } if (authManager.isUserCanAccessSys(currentUser.getId(), SysEnum.BUSINESS)) { - return isAdminUser(currentUser) ? "company" : "personal"; + return "company"; } return "personal"; } @@ -278,6 +275,10 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { } SysUserEntity currentUser = adminSecurityManage.getUser(); boolean singleAgreement = isSingleAgreement(contract); + // 非单方协议:提交法务前做严格校验 + if (!singleAgreement) { + validateSubmitForLaw(contract); + } contract.setStatus(ContractLaunchConstants.STATUS_REVIEWING); contract.setApproveStatus(ContractLaunchConstants.APPROVE_STATUS_PENDING); contract.setApproveMessage(singleAgreement ? "已提交内部审批,等待审批" : "已提交法务,等待审批"); @@ -551,6 +552,54 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { * requestDTO.setPayload(payload); * dataCollectionPushService.collectContract(requestDTO); */ + + /** + * 保存采集信息(用印+签订信息)。 + * + * 在 pending_collect 状态时,用户通过采集弹窗补充/修改 + * 用印信息和签订信息后保存到合同记录。 + * 不触发状态变更,可多次保存;后续再通过 mockCollect(或真实联调的 collect)推送法务。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public ContractLaunchDto saveCollectInfo(Long id, ContractLaunchCollectSaveDto collectDto) { + ContractLaunchDto contract = getDetail(id); + if (isSingleAgreement(contract)) { + throw new ServiceException("单方协议无需执行法务采集"); + } + if (!ContractLaunchConstants.STATUS_PENDING_COLLECT.equals(contract.getStatus())) { + throw new ServiceException("仅待采集状态允许保存采集信息"); + } + SysUserEntity currentUser = adminSecurityManage.getUser(); + + // 用印信息 + contract.setSealIsSeal(collectDto.getSealIsSeal()); + contract.setSealApproveSame(collectDto.getSealApproveSame()); + contract.setSealQuantity(collectDto.getSealQuantity()); + contract.setSealDate(collectDto.getSealDate()); + // 签订信息 + contract.setSignDate(collectDto.getSignDate()); + contract.setActiveDate(collectDto.getActiveDate()); + contract.setExecutorAccount(collectDto.getExecutorAccount()); + // 签署文件 + if (collectDto.getSignedFilePath() != null && !collectDto.getSignedFilePath().isBlank()) { + contract.setSignedFileName(collectDto.getSignedFileName()); + contract.setSignedFilePath(collectDto.getSignedFilePath()); + contract.setSignedFileType(collectDto.getSignedFileType()); + contract.setSignedFileSize(collectDto.getSignedFileSize()); + } + contract.setUpdateBy(currentUser.getUsername()); + contract.setUpdateTime(LocalDateTime.now()); + contractLaunchMapper.updateContract(contract); + + insertLog(id, ContractLaunchConstants.ACTION_COLLECT, "保存采集信息", + ContractLaunchConstants.ACTION_STATUS_SUCCESS, "用户通过采集弹窗保存用印/签订信息", null, currentUser); + return getDetail(id); + } + + /** + * 【测试按钮】模拟合同采集。 + */ @Override @Transactional(rollbackFor = Exception.class) public ContractLaunchDto mockCollect(Long id) { @@ -1159,7 +1208,11 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { } else if (isOppositeParty(item)) { item.setRoleName("相对方"); } - item.setSubjectCode(trim(item.getSubjectCode())); + if (isOppositeParty(item)) { + item.setSubjectCode(null); + } else { + item.setSubjectCode(trim(item.getSubjectCode())); + } item.setCompanyName(trim(item.getCompanyName())); item.setCreditCode(trim(item.getCreditCode())); item.setOppositeId(trim(item.getOppositeId())); @@ -1284,21 +1337,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { if (contract.getContractType() == null || contract.getContractType().isBlank()) { contract.setContractType(contract.getContractTypeCode()); } - if (contract.getValuationMode() == null || contract.getValuationMode().isBlank()) { - throw new ServiceException("计价方式不能为空"); - } - if (contract.getCurrencyName() == null || contract.getCurrencyName().isBlank()) { - throw new ServiceException("币种不能为空"); - } - if (contract.getPaymentMethod() == null || contract.getPaymentMethod().isBlank()) { - throw new ServiceException("付款方式不能为空"); - } - if (contract.getSealType() == null || contract.getSealType().isBlank()) { - throw new ServiceException("签订方式不能为空"); - } - if (contract.getSealQuantity() != null && contract.getSealQuantity() < 1) { - throw new ServiceException("用印数量必须大于等于1"); - } + // 以下字段在提交法务时才强制必填(validateSubmitForLaw),草稿保存时放宽 if (contract.getContractPeriodType() == null || contract.getContractPeriodType().isBlank()) { throw new ServiceException("合同期限类型不能为空"); } @@ -1313,7 +1352,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { // if (contract.getActiveDate() == null) { // throw new ServiceException("合同生效日期不能为空"); // } - validateActiveDateWithinTerm(contract); + // activeDate 已移至采集阶段,草稿保存时不校验 if (contract.getPartyList() == null || contract.getPartyList().isEmpty()) { throw new ServiceException("至少需要维护一个合同方"); } @@ -1364,6 +1403,7 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { && !item.getFilePath().isBlank()))) { throw new ServiceException("当前签订方式必须上传授权委托书附件"); } + // 履行计划在草稿和提交法务时均必填 validatePerformancePlans(contract, false); } @@ -1833,6 +1873,53 @@ public class ContractLaunchServiceImpl implements ContractLaunchService { validatePerformancePlans(contract, true); } + /** + * 提交法务前的严格校验:草稿校验 + pushContract 必填项补全 + 履行计划校验 + */ + private void validateSubmitForLaw(ContractLaunchDto contract) { + // 先跑草稿基本校验(不含已移除的采集字段) + validateDraft(contract); + // 补全 pushContract 必填项 + if (contract.getValuationMode() == null || contract.getValuationMode().isBlank()) { + throw new ServiceException("计价方式不能为空"); + } + if (contract.getCurrencyName() == null || contract.getCurrencyName().isBlank()) { + throw new ServiceException("币种不能为空"); + } + if (contract.getSealType() == null || contract.getSealType().isBlank()) { + throw new ServiceException("签订方式不能为空"); + } + if (contract.getIsElectron() == null || contract.getIsElectron().isBlank()) { + throw new ServiceException("签署方式不能为空"); + } + if (contract.getPaymentDirection() == null || contract.getPaymentDirection().isBlank()) { + throw new ServiceException("收支方向不能为空"); + } + if (contract.getTextSource() == null || contract.getTextSource().isBlank()) { + throw new ServiceException("文本来源不能为空"); + } + if (contract.getContractSource() == null || contract.getContractSource().isBlank()) { + throw new ServiceException("合同来源不能为空"); + } + if (contract.getExecutorAccount() == null || contract.getExecutorAccount().isBlank()) { + throw new ServiceException("合同执行人不能为空"); + } + if (contract.getIsMajorContract() == null) { + throw new ServiceException("是否重大合同不能为空"); + } + if (contract.getIsOpenBidding() == null) { + throw new ServiceException("是否公开招标不能为空"); + } + if (contract.getIsSgat() == null || contract.getIsSgat().isBlank()) { + throw new ServiceException("是否涉及港澳台不能为空"); + } + if (contract.getNeedAmendments() == null || contract.getNeedAmendments().isBlank()) { + throw new ServiceException("是否需修订条款不能为空"); + } + // 履行计划校验(提交法务时必填) + validatePerformancePlans(contract, false); + } + private void validatePerformancePlans(ContractLaunchDto contract, boolean requirePlanId) { if (contract.getPerformancePlans() == null || contract.getPerformancePlans().isEmpty()) { throw new ServiceException("履行计划不能为空");