package jnpf.dmsService.impl; import com.fasterxml.jackson.databind.ObjectMapper; import jnpf.dmsEntity.storage.approval.DmsApprovedItem; import jnpf.dmsEntity.storage.approval.DmsApprovedBatch; import jnpf.base.UserInfo; import jnpf.dmsEntity.DmsWenjianBanbenEntity; import jnpf.dmsEntity.DmsWenjianBianhaoEntity; import jnpf.dmsEntity.DmsWenjianGuanlianMingxiEntity; import jnpf.dmsEntity.DmsWenjianGuanxiEntity; import jnpf.dmsEntity.DmsWenjianXinzengShenheEntity; import jnpf.dmsEntity.DmsWenjianXinzengShenheJiluMingxiEntity; import jnpf.dmsEntity.DmsWenjianXiudingShenheEntity; import jnpf.dmsEntity.DmsWenjianXiudingShenheJiluMingxiEntity; import jnpf.dmsEntity.DmsWenjianXiudingShenqingEntity; import jnpf.dmsEntity.DmsWenjianXiudingShenqingZixiangEntity; import jnpf.dmsEntity.banben.DmsWenjianBanbenJiesuanModel; import jnpf.dmsEntity.banben.vo.DmsWenjianBanbenQueryVO; import jnpf.dmsEntity.banben.vo.DmsWenjianZhongxinBanbenVO; import jnpf.dmsEntity.wenjianbianhao.DmsFlowEventType; import jnpf.dmsMapper.DmsWenjianBanbenMapper; import jnpf.dmsMapper.DmsWenjianBianhaoMapper; import jnpf.dmsMapper.DmsWenjianGuanlianMingxiMapper; import jnpf.dmsMapper.DmsWenjianGuanxiMapper; import jnpf.dmsMapper.DmsWenjianXinzengShenheJiluMingxiMapper; import jnpf.dmsMapper.DmsWenjianXinzengShenheMapper; import jnpf.dmsMapper.DmsWenjianXiudingShenheJiluMingxiMapper; import jnpf.dmsMapper.DmsWenjianXiudingShenheMapper; import jnpf.dmsMapper.DmsWenjianXiudingShenqingMapper; import jnpf.dmsMapper.DmsWenjianXiudingShenqingZixiangMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsService.DmsWenjianBanbenService; import jnpf.dmsService.DmsWenjianFenfaService; import jnpf.dmsService.DmsWenjianYewuBanbenService; import jnpf.exception.DataException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; @Slf4j @Service @RequiredArgsConstructor public class DmsWenjianBanbenServiceImpl implements DmsWenjianBanbenService { private static final ObjectMapper CONTENT_JSON = new ObjectMapper(); private static final int RUNNING = 1; private static final int PASSED = 2; private static final int EXCEPTION = 10; private static final String USING = "using"; private static final String UPGRADE = "shengji"; private static final String UNCHANGED = "wubianhua"; private static final String CREATE = "xinjian"; private final DmsCurrentIdentity identity; private final jnpf.dmsService.DmsBusinessLifecycleGuard businessOccupancy; private final DmsWenjianBanbenMapper versionMapper; private final DmsWenjianBianhaoMapper numberMapper; private final DmsWenjianXinzengShenheMapper newAuditMapper; private final DmsWenjianXinzengShenheJiluMingxiMapper newAuditRecordMapper; private final DmsWenjianXiudingShenqingMapper revisionRequestMapper; private final DmsWenjianXiudingShenqingZixiangMapper revisionDetailMapper; private final DmsWenjianXiudingShenheMapper revisionAuditMapper; private final DmsWenjianXiudingShenheJiluMingxiMapper revisionAuditRecordMapper; private final DmsWenjianGuanlianMingxiMapper relatedDetailMapper; private final DmsWenjianGuanxiMapper relationMapper; private final DmsWenjianYewuBanbenService businessVersionService; private final DmsWenjianFenfaService distributionService; private final jnpf.dmsService.DmsApprovedFileProjection centerProjection; private final jnpf.dmsMapper.DmsApprovedBatchMapper acceptanceMapper; private final jnpf.dmsService.DmsApprovedAuditSource approvedSource; private final jnpf.dmsService.DmsQianpiyeRenderSnapshotService qianpiyeSnapshots; private final jnpf.dmsService.DmsXiudingKanwuService revisionErrata; private final jnpf.dmsService.DmsExternalApprovedSettlementService externalSettlement; @Override @Transactional(readOnly = true) public DmsWenjianBanbenQueryVO getByWenjianIdAndBanbenhao( String wenjianId, String banbenhao) throws DataException { UserInfo user = identity.requireUser(); String tenantId = identity.tenantId(user); String resolvedWenjianId = requireText(wenjianId, "文件ID不能为空"); String resolvedBanbenhao = requireText(banbenhao, "版本号不能为空"); DmsWenjianBanbenEntity version = versionMapper.selectByFileAndVersionLabel( tenantId, resolvedWenjianId, resolvedBanbenhao); if (version == null) { throw new DataException("未找到对应的业务文件版本"); } DmsWenjianBanbenQueryVO result = new DmsWenjianBanbenQueryVO(); result.setId(version.getId()); result.setWenjianZhongxinBanbenId(version.getWenjianZhongxinBanbenId()); result.setWenjianZhongxinWenjianId(version.getWenjianZhongxinWenjianId()); return result; } @Transactional(rollbackFor = Exception.class) public void applyApproved(jnpf.dmsEntity.storage.approval.DmsApprovedBatch batch) { jnpf.dmsEntity.storage.approval.DmsApprovedBatch receipt = acceptanceMapper.lock(batch.getTenantId(), batch.getId()); if (receipt == null) throw new DataException("文件接收回执不存在"); if ("APPLIED".equals(receipt.getStatus())) return; if (!"EFFECTIVE".equals(receipt.getStatus())) throw new DataException("批准文件尚未生效"); jnpf.dmsService.DmsApprovedAuditSource.Source nativeSource = approvedSource.load(receipt.getTenantId(), receipt.getFlowId(), receipt.getTaskId(), receipt.getAuditType(), receipt.getActorId(), receipt.getApprovedAt()); jnpf.dmsEntity.storage.approval.DmsApprovedBatch source = nativeSource.batch(); var businessReceipts = "wailai_wenjian_xinzeng_shenhe".equals(receipt.getAuditType()) ? businessOccupancy.requireExternalCreation(receipt.getTenantId(), receipt.getAuditId(), receipt.getTaskId(), source.getItems().get(0).getDocumentNo()) : "xiuding_shenhe".equals(receipt.getAuditType()) ? businessOccupancy.requireRevision(receipt.getTenantId(),receipt.getAuditId()) : businessOccupancy.requireCreation(receipt.getTenantId(),receipt.getAuditId()); if(businessOccupancy.isEnabled()) jnpf.dmsService.DmsBusinessLifecycleGuard.verifySourceCoverage(businessReceipts,source.getItems()); qianpiyeSnapshots.attach(source, receipt.getApprovalRenderSnapshot(), nativeSource.preparedBy(), nativeSource.preparedOn()); if (!Objects.equals(source.getSourceDigest(), receipt.getSourceDigest())) throw new DataException("批准内容发生变化,停止业务回写"); UserInfo actor = new UserInfo(); actor.setUserId(receipt.getActorId()); actor.setTenantId(receipt.getTenantId()); if ("wailai_wenjian_xinzeng_shenhe".equals(receipt.getAuditType())) { externalSettlement.apply(receipt); } else if ("xinzeng_shenhe".equals(receipt.getAuditType())) { settleNew(actor, receipt.getTenantId(), receipt.getFlowId(), receipt.getTaskId(), PASSED, DmsFlowEventType.END); } else { settleRevision(actor, receipt.getTenantId(), receipt.getFlowId(), receipt.getTaskId(), PASSED, DmsFlowEventType.END); revisionErrata.complete(receipt.getTenantId(), receipt.getAuditId(), nativeSource.errata()); } requireSingleUpdate(acceptanceMapper.applied(receipt.getTenantId(), receipt.getId()), "标记业务回写完成失败"); businessOccupancy.completeExecution(businessReceipts); } /** * 按当前租户和业务文件版本ID读取文件中心版本ID。 */ @Override @Transactional(readOnly = true) public DmsWenjianZhongxinBanbenVO getWenjianZhongxinBanben(String fileVersionId) throws DataException { UserInfo user = identity.requireUser(); String tenantId = identity.tenantId(user); String resolvedVersionId = requireText(fileVersionId, "文件版本ID不能为空"); DmsWenjianBanbenEntity version = versionMapper.selectById(tenantId, resolvedVersionId); DmsWenjianZhongxinBanbenVO result = new DmsWenjianZhongxinBanbenVO(); if (version != null) { result.setWenjianZhongxinBanbenId(trimToNull(version.getWenjianZhongxinBanbenId())); } return result; } @Override @Transactional(rollbackFor = Exception.class) public void syncNewAuditFlow(String flowId, String taskId, Integer flowState, DmsFlowEventType eventType) throws DataException { if (Integer.valueOf(PASSED).equals(flowState)) throw new DataException("批准流程必须通过统一接收和生效入口结算"); UserInfo user = identity.requireUser(); settleNew(user, identity.tenantId(user), flowId, taskId, flowState, eventType); } private void settleNew(UserInfo user, String tenantId, String flowId, String taskId, Integer flowState, DmsFlowEventType eventType) { String resolvedFlowId = requireText(flowId, "流程ID不能为空"); String resolvedTaskId = requireText(taskId, "流程任务ID不能为空"); requireEvent(eventType); DmsWenjianXinzengShenheEntity audit = uniqueNewAudit( newAuditMapper.selectByFlowForUpdate(tenantId, resolvedFlowId, resolvedTaskId)); verifyPersistedState(audit.getFlowState(), flowState); if (!requiresVersionCheck(flowState)) { return; } if (hasCompleteNewResult(audit)) { distributionService.ensureNewPrintTasks( tenantId, audit.getId(), audit.getWenjianId(), user.getUserId()); return; } String numberId = requireText(audit.getWenjianBianhaoId(), "新增审核单未关联编号申请明细"); DmsWenjianBianhaoEntity number = numberMapper.selectActiveByIdForUpdate(tenantId, numberId); validateNewNumber(number); if (flowState != PASSED) { return; } validateTraining(audit.getShifouPeixun(), audit.getPeixunWanchengRiqi()); DmsWenjianBanbenEntity mainVersion = businessVersionService.createInitialVersion( initialMainModel(tenantId, user.getUserId(), audit, number)); settleNewAuditRecords(tenantId, user.getUserId(), audit, mainVersion.getWenjianId()); settleRelatedFiles(tenantId, user.getUserId(), audit.getId(), mainVersion.getWenjianId(), relatedDetailMapper.selectNewAuditRelations(tenantId, audit.getId()), false); writeNewAuditResult(audit, mainVersion); distributionService.ensureNewPrintTasks( tenantId, audit.getId(), mainVersion.getWenjianId(), user.getUserId()); log.info("文件新增审核生成统一正式版本,auditId={}, wenjianId={}, versionId={}, version={}", audit.getId(), mainVersion.getWenjianId(), mainVersion.getId(), mainVersion.getBanbenhao()); } @Override @Transactional(rollbackFor = Exception.class) public void syncRevisionAuditFlow(String flowId, String taskId, Integer flowState, DmsFlowEventType eventType) throws DataException { if (Integer.valueOf(PASSED).equals(flowState)) throw new DataException("批准流程必须通过统一接收和生效入口结算"); UserInfo user = identity.requireUser(); settleRevision(user, identity.tenantId(user), flowId, taskId, flowState, eventType); } private void settleRevision(UserInfo user, String tenantId, String flowId, String taskId, Integer flowState, DmsFlowEventType eventType) { String resolvedFlowId = requireText(flowId, "流程ID不能为空"); String resolvedTaskId = requireText(taskId, "流程任务ID不能为空"); requireEvent(eventType); DmsWenjianXiudingShenheEntity audit = uniqueRevisionAudit( revisionAuditMapper.selectByFlowForUpdate( tenantId, resolvedFlowId, resolvedTaskId)); verifyPersistedState(audit.getFlowState(), flowState); if (flowState != RUNNING && flowState != PASSED) { releaseRevisionDetail(tenantId, audit); return; } DmsWenjianXiudingShenqingZixiangEntity detail = resolveRevisionDetail( tenantId, audit); String requestId = requireText(detail.getForeignId(), "修订申请子项未关联申请主单"); DmsWenjianXiudingShenqingEntity request = revisionRequestMapper.selectByIdForUpdate(tenantId, requestId); validateRevisionRequest(request); if (hasCompleteRevisionResult(audit)) { validateEffectiveDetail(detail, audit.getId()); distributionService.ensureRevisionPrintTasks( tenantId, audit.getId(), audit.getWenjianId(), user.getUserId()); return; } claimRevisionDetail(tenantId, detail, audit.getId()); if (flowState == RUNNING) { return; } validateChangeType(audit.getBiangengFangshi()); validateTraining(audit.getShifouPeixun(), audit.getPeixunWanchengRiqi()); DmsWenjianBanbenEntity mainVersion = settleRevisionMain( tenantId, user.getUserId(), audit, detail); settleRevisionAuditRecords( tenantId, user.getUserId(), audit, mainVersion.getWenjianId()); settleRelatedFiles(tenantId, user.getUserId(), audit.getId(), mainVersion.getWenjianId(), relatedDetailMapper.selectRevisionAuditRelations(tenantId, audit.getId()), true); writeRevisionAuditResult(audit, detail, mainVersion); requireSingleUpdate(revisionDetailMapper.markEffective( tenantId, detail.getId(), audit.getId()), "修订申请子项生效状态更新失败"); // Independent occupancy is released by applyApproved only after all projections succeed. distributionService.ensureRevisionPrintTasks( tenantId, audit.getId(), mainVersion.getWenjianId(), user.getUserId()); log.info("文件修订审核结算统一正式版本,auditId={}, wenjianId={}, versionId={}, version={}", audit.getId(), mainVersion.getWenjianId(), mainVersion.getId(), mainVersion.getBanbenhao()); } private DmsWenjianBanbenJiesuanModel initialMainModel( String tenantId, String userId, DmsWenjianXinzengShenheEntity audit, DmsWenjianBianhaoEntity number) throws DataException { DmsWenjianBanbenJiesuanModel model = baseModel( tenantId, userId, audit.getId(), null, "xinzeng_shenhe:" + audit.getId() + ":zhuwenjian"); model.setWenjianLeixing(DmsWenjianYewuBanbenService.MAIN_FILE); model.setWenjianId(trimToNull(audit.getWenjianId())); model.setWenjianBianhaoId(number.getId()); model.setWenjianBianhao(requireText(number.getWenjianBianhao(), "编号明细缺少已发放编号")); model.setWenjianMingcheng(firstText( audit.getWenjianMingcheng(), number.getDangqianWenjianMingcheng(), "新增审核单缺少文件名称")); model.setGuishuBumenId(audit.getGuishuBumenId()); model.setWenjianMuluId(audit.getWenjianMuluId()); model.setWenjianNeirong(requireText(audit.getWenjian(), "新增审核单缺少主文件")); return model; } private void settleNewAuditRecords(String tenantId, String userId, DmsWenjianXinzengShenheEntity audit, String mainFileId) throws DataException { List records = newAuditRecordMapper.selectByAuditForUpdate(tenantId, audit.getId()); if (records == null) { return; } for (DmsWenjianXinzengShenheJiluMingxiEntity record : records) { if (!blank(record.getBiangengFangshi()) && !CREATE.equals(record.getBiangengFangshi())) { throw new DataException("新增审核记录的变更方式只能为新建"); } if (!blank(record.getBanbenhao()) && !formatVersion(0).equals(record.getBanbenhao())) { throw new DataException("新增审核记录的版本必须为D(00)"); } DmsWenjianBanbenJiesuanModel model = baseModel( tenantId, userId, audit.getId(), record.getId(), "xinzeng_shenhe:" + audit.getId() + ":jilu:" + record.getId()); model.setWenjianId(trimToNull(record.getJiluWenjianId())); model.setWenjianLeixing(DmsWenjianYewuBanbenService.RECORD_FILE); model.setWenjianBianhao(requireText(record.getFujianBianhao(), "记录编号不能为空")); model.setWenjianMingcheng(requireText(record.getFujianMingcheng(), "记录名称不能为空")); model.setJiluLeixing(record.getJiluLeixing()); model.setDengji(record.getDengji()); requireSameDirectory(audit.getWenjianMuluId(), record.getWenjianMuluId()); model.setWenjianMuluId(record.getWenjianMuluId()); model.setJiluDengjiGuanliId(record.getJiluDengjiGuanliId()); model.setJiluDengjiGuanliBanben(record.getJiluDengjiGuanliBanben()); model.setGuishuBumenId(record.getGuishuBumenId()); model.setWenjianNeirong(requireText(record.getJiluWenjian(), "记录文件不能为空")); DmsWenjianBanbenEntity version = businessVersionService.createInitialVersion(model); businessVersionService.ensureRelation( tenantId, mainFileId, version.getWenjianId(), DmsWenjianYewuBanbenService.RELATION_RECORD, audit.getId(), userId); if (blank(record.getShengxiaoWenjianBanbenId())) { record.setBiangengFangshi(CREATE); record.setBanbenhao(version.getBanbenhao()); record.setJiluWenjianId(version.getWenjianId()); record.setYuanWenjianBanbenId(null); record.setShengxiaoWenjianBanbenId(version.getId()); requireSingleUpdate(newAuditRecordMapper.updateSettlement(record), "回写新增审核记录文件版本失败"); } } } private DmsWenjianBanbenEntity settleRevisionMain( String tenantId, String userId, DmsWenjianXiudingShenheEntity audit, DmsWenjianXiudingShenqingZixiangEntity detail) throws DataException { String fileId = requireText(detail.getWenjianId(), "修订申请子项未关联文件"); String sourceVersionId = requireText( detail.getYuanWenjianBanbenId(), "修订申请子项缺少来源文件版本ID"); if (UPGRADE.equals(audit.getBiangengFangshi())) { DmsWenjianBanbenJiesuanModel model = baseModel( tenantId, userId, audit.getId(), null, "xiuding_shenhe:" + audit.getId() + ":zhuwenjian"); model.setWenjianId(fileId); model.setWenjianLeixing(DmsWenjianYewuBanbenService.MAIN_FILE); model.setYuanWenjianBanbenId(sourceVersionId); model.setWenjianMingcheng(audit.getWenjianMingcheng()); model.setGuishuBumenId(audit.getGuishuBumenId()); model.setWenjianMuluId(audit.getWenjianMuluId()); model.setWenjianNeirong(requireText(audit.getWenjian(), "修订审核单缺少主文件")); return businessVersionService.createRevisionVersion(model); } DmsWenjianBanbenEntity current = businessVersionService.reuseCurrentVersion( tenantId, fileId, sourceVersionId); if (!blank(audit.getWenjian()) && !sameContent( audit.getWenjian().trim(), current.getWenjianNeirong())) { throw new DataException("主文件选择无变化时不能替换文件内容"); } return current; } private void settleRevisionAuditRecords(String tenantId, String userId, DmsWenjianXiudingShenheEntity audit, String mainFileId) throws DataException { List records = revisionAuditRecordMapper.selectByAuditForUpdate(tenantId, audit.getId()); Set retainedFileIds = new HashSet<>(); if (records != null) { for (DmsWenjianXiudingShenheJiluMingxiEntity record : records) { settleRevisionRecord(tenantId, userId, audit, mainFileId, record, retainedFileIds); } } DmsApprovedBatch batch = acceptanceMapper.find(tenantId, audit.getFlowTaskId(), "xiuding_shenhe"); if (batch == null || !"EFFECTIVE".equals(batch.getStatus())) throw new DataException("修订记录尚未完成中心生效"); Set retired = new HashSet<>(); for (DmsApprovedItem item : acceptanceMapper.items(tenantId, batch.getId())) { if (!"RETIRE".equals(item.getChangeType())) continue; if (retainedFileIds.contains(item.getFileId()) || !retired.add(item.getFileId())) throw new DataException("移除记录与批准明细不一致"); businessVersionService.retireRecord(tenantId, mainFileId, item.getFileId(), item.getBusinessVersionId(), userId); } requireAllExistingRelationsRetained(tenantId, mainFileId, DmsWenjianYewuBanbenService.RELATION_RECORD, retainedFileIds, "记录归属已变化,存在未经批准移除的记录"); } private void settleRevisionRecord(String tenantId, String userId, DmsWenjianXiudingShenheEntity audit, String mainFileId, DmsWenjianXiudingShenheJiluMingxiEntity record, Set retainedFileIds) throws DataException { String changeType = requireText(record.getBiangengFangshi(), "记录变更方式不能为空"); DmsWenjianBanbenEntity version; if (CREATE.equals(changeType)) { DmsWenjianBanbenJiesuanModel model = baseModel( tenantId, userId, audit.getId(), record.getId(), "xiuding_shenhe:" + audit.getId() + ":jilu:" + record.getId()); model.setWenjianId(trimToNull(record.getJiluWenjianId())); model.setWenjianLeixing(DmsWenjianYewuBanbenService.RECORD_FILE); model.setWenjianBianhao(requireText(record.getFujianBianhao(), "记录编号不能为空")); model.setWenjianMingcheng(requireText(record.getFujianMingcheng(), "记录名称不能为空")); model.setJiluLeixing(record.getJiluLeixing()); model.setDengji(record.getDengji()); requireSameDirectory(audit.getWenjianMuluId(), record.getWenjianMuluId()); model.setWenjianMuluId(record.getWenjianMuluId()); model.setJiluDengjiGuanliId(record.getJiluDengjiGuanliId()); model.setJiluDengjiGuanliBanben(record.getJiluDengjiGuanliBanben()); model.setGuishuBumenId(record.getGuishuBumenId()); model.setWenjianNeirong(requireText(record.getJiluWenjian(), "记录文件不能为空")); version = businessVersionService.createInitialVersion(model); } else { String recordFileId = requireText(record.getJiluWenjianId(), "现行记录缺少记录文件ID"); String sourceVersionId = requireText( record.getYuanWenjianBanbenId(), "现行记录缺少来源文件版本ID"); if (!retainedFileIds.add(recordFileId)) { throw new DataException("修订审核单包含重复的记录文件"); } if (UPGRADE.equals(changeType)) { DmsWenjianBanbenJiesuanModel model = baseModel( tenantId, userId, audit.getId(), record.getId(), "xiuding_shenhe:" + audit.getId() + ":jilu:" + record.getId()); model.setWenjianId(recordFileId); model.setWenjianLeixing(DmsWenjianYewuBanbenService.RECORD_FILE); model.setYuanWenjianBanbenId(sourceVersionId); model.setWenjianMingcheng(record.getFujianMingcheng()); model.setJiluLeixing(record.getJiluLeixing()); model.setDengji(record.getDengji()); requireSameDirectory(audit.getWenjianMuluId(), record.getWenjianMuluId()); model.setWenjianMuluId(record.getWenjianMuluId()); model.setJiluDengjiGuanliId(record.getJiluDengjiGuanliId()); model.setJiluDengjiGuanliBanben(record.getJiluDengjiGuanliBanben()); model.setGuishuBumenId(record.getGuishuBumenId()); model.setWenjianNeirong(requireText(record.getJiluWenjian(), "记录文件不能为空")); version = businessVersionService.createRevisionVersion(model); } else if (UNCHANGED.equals(changeType)) { version = businessVersionService.reuseCurrentVersion( tenantId, recordFileId, sourceVersionId); if (!blank(record.getJiluWenjian()) && !sameContent( record.getJiluWenjian().trim(), version.getWenjianNeirong())) { throw new DataException("记录选择无变化时不能替换文件内容"); } requireSameDirectory(audit.getWenjianMuluId(), record.getWenjianMuluId()); // applyApproved reloaded this native record and verified its approval digest. // REUSE has no new center registration; old receipts may lack a control snapshot. DmsWenjianBanbenJiesuanModel model = new DmsWenjianBanbenJiesuanModel(); model.setTenantId(tenantId); model.setUserId(userId); model.setLaiyuanDanjuLeixing("xiuding_shenhe"); model.setLaiyuanDanjuId(audit.getId()); model.setLaiyuanZixiangId(record.getId()); model.setCenterVersionId(version.getWenjianZhongxinBanbenId()); model.setShifouShoukong(record.getShifouShoukong()); model.setWenjianId(recordFileId); model.setYuanWenjianBanbenId(sourceVersionId); model.setWenjianLeixing(DmsWenjianYewuBanbenService.RECORD_FILE); model.setWenjianMingcheng(record.getFujianMingcheng()); model.setGuishuBumenId(record.getGuishuBumenId()); model.setWenjianMuluId(record.getWenjianMuluId()); version = businessVersionService.updateReusedRecordMetadata(model); } else { throw new DataException("记录变更方式无效"); } } retainedFileIds.add(version.getWenjianId()); businessVersionService.ensureRelation( tenantId, mainFileId, version.getWenjianId(), DmsWenjianYewuBanbenService.RELATION_RECORD, audit.getId(), userId); if (blank(record.getShengxiaoWenjianBanbenId())) { record.setBanbenhao(version.getBanbenhao()); record.setJiluWenjianId(version.getWenjianId()); record.setShengxiaoWenjianBanbenId(version.getId()); requireSingleUpdate(revisionAuditRecordMapper.updateSettlement(record), "回写修订审核记录文件版本失败"); } } private void requireSameDirectory(String main, String record) throws DataException { if (blank(main) || !Objects.equals(main, record)) { throw new DataException("记录子表目录必须与主表一致"); } } private void settleRelatedFiles(String tenantId, String userId, String auditId, String mainFileId, List rows, boolean requireExisting) throws DataException { Set retainedFileIds = new HashSet<>(); if (rows != null) { for (DmsWenjianGuanlianMingxiEntity row : rows) { String relatedFileId = requireText( row.getGuanlianWenjianId(), "关联文件明细缺少文件ID"); if (!retainedFileIds.add(relatedFileId)) { throw new DataException("审核单包含重复的关联文件"); } businessVersionService.ensureRelation( tenantId, mainFileId, relatedFileId, "guanlian_wenjian", auditId, userId); } } if (requireExisting) { requireAllExistingRelationsRetained( tenantId, mainFileId, "guanlian_wenjian", retainedFileIds, "现行关联文件不能从修订审核单中删除"); } } private void requireAllExistingRelationsRetained(String tenantId, String mainFileId, String relationType, Set retainedFileIds, String message) throws DataException { List existing = relationMapper.selectEffectiveByMainAndType( tenantId, mainFileId, relationType); if (existing == null) { return; } for (DmsWenjianGuanxiEntity relation : existing) { if (!retainedFileIds.contains(relation.getGuanlianWenjianId())) { throw new DataException(message); } } } private DmsWenjianBanbenJiesuanModel baseModel( String tenantId, String userId, String auditId, String rowId, String sourceBusinessKey) { DmsWenjianBanbenJiesuanModel model = new DmsWenjianBanbenJiesuanModel(); model.setTenantId(tenantId); model.setLaiyuanDanjuLeixing(sourceBusinessKey.startsWith("xinzeng_shenhe:") ? "xinzeng_shenhe" : "xiuding_shenhe"); model.setLaiyuanDanjuId(auditId); model.setLaiyuanZixiangId(rowId); model.setLaiyuanYewuJian(sourceBusinessKey); model.setUserId(userId); centerProjection.enrich(model); return model; } private void writeNewAuditResult(DmsWenjianXinzengShenheEntity audit, DmsWenjianBanbenEntity version) throws DataException { audit.setWenjianId(version.getWenjianId()); audit.setShengxiaoWenjianBanbenId(version.getId()); audit.setBanbenShengxiaoShijian(version.getShengxiaoShijian()); requireSingleUpdate(newAuditMapper.updateEffectiveVersion(audit), "回写新增审核生效文件版本失败"); } private void writeRevisionAuditResult(DmsWenjianXiudingShenheEntity audit, DmsWenjianXiudingShenqingZixiangEntity detail, DmsWenjianBanbenEntity version) throws DataException { audit.setWenjianId(version.getWenjianId()); audit.setYuanWenjianBanbenId(detail.getYuanWenjianBanbenId()); audit.setShengxiaoWenjianBanbenId(version.getId()); jnpf.dmsEntity.storage.approval.DmsApprovedBatch receipt = acceptanceMapper.find(audit.getTenantId(), audit.getFlowTaskId(), "xiuding_shenhe"); audit.setBanbenShengxiaoShijian(receipt.getActivatedAt()); requireSingleUpdate(revisionAuditMapper.updateEffectiveVersion(audit), "回写修订审核生效文件版本失败"); } private void validateNewNumber(DmsWenjianBianhaoEntity number) throws DataException { if (number == null) { throw new DataException("未找到新增审核单关联的编号申请明细"); } if (!USING.equals(number.getBizStatus()) || blank(number.getWenjianBianhao())) { throw new DataException("关联编号尚未完成发放"); } if (blank(number.getDangqianWenjianMingcheng())) { throw new DataException("编号申请明细缺少文件名称"); } } private void validateRevisionRequest(DmsWenjianXiudingShenqingEntity request) throws DataException { if (request == null) { throw new DataException("未找到修订审核单关联的修订申请"); } if (!Objects.equals(request.getFlowState(), PASSED)) { throw new DataException("关联的文件修订申请尚未审批通过"); } } private DmsWenjianXiudingShenqingZixiangEntity resolveRevisionDetail( String tenantId, DmsWenjianXiudingShenheEntity audit) throws DataException { String detailId = requireText(audit.getXiudingShenqingZixiangId(), "修订审核单未关联修订申请子项"); DmsWenjianXiudingShenqingZixiangEntity detail = revisionDetailMapper.selectByIdForUpdate(tenantId, detailId); if (detail == null) { throw new DataException("修订审核单关联的修订申请子项无效"); } requireText(detail.getWenjianId(), "修订申请子项未关联文件"); requireText(detail.getYuanWenjianBanbenId(), "修订申请子项缺少来源文件版本ID"); return detail; } private void claimRevisionDetail(String tenantId, DmsWenjianXiudingShenqingZixiangEntity detail, String auditId) throws DataException { if ("occupied".equals(detail.getBizStatus()) && auditId.equals(detail.getZhanYongDanjuId())) { return; } requireSingleUpdate(revisionDetailMapper.claim(tenantId, detail.getId(), auditId), "该修订申请子项已被其他审核单占用"); } private void releaseRevisionDetail(String tenantId, DmsWenjianXiudingShenheEntity audit) { if (!blank(audit.getXiudingShenqingZixiangId())) { revisionDetailMapper.release( tenantId, audit.getXiudingShenqingZixiangId(), audit.getId()); } } private void validateEffectiveDetail(DmsWenjianXiudingShenqingZixiangEntity detail, String auditId) throws DataException { if (!"effective".equals(detail.getBizStatus()) || !auditId.equals(detail.getZhanYongDanjuId())) { throw new DataException("修订审核单与修订申请子项生效状态不一致"); } } private void validateChangeType(String changeType) throws DataException { if (!UPGRADE.equals(changeType) && !UNCHANGED.equals(changeType)) { throw new DataException("修订审核单的主文件变更方式无效"); } } private void validateTraining(String trainingType, Date completedAt) throws DataException { String type = requireText(trainingType, "文件审核单未选择是否培训"); if (!"peixun".equals(type) && !"buPeixun".equals(type)) { throw new DataException("文件审核单的培训选项无效"); } if ("peixun".equals(type) && completedAt == null) { throw new DataException("文件培训尚未完成,不能生效"); } } private boolean hasCompleteNewResult(DmsWenjianXinzengShenheEntity audit) throws DataException { boolean any = !blank(audit.getWenjianId()) || !blank(audit.getShengxiaoWenjianBanbenId()) || audit.getBanbenShengxiaoShijian() != null; if (!any) { return false; } if (blank(audit.getWenjianId()) || blank(audit.getShengxiaoWenjianBanbenId()) || audit.getBanbenShengxiaoShijian() == null) { throw new DataException("新增审核单的生效文件版本结果不完整"); } return true; } private boolean hasCompleteRevisionResult(DmsWenjianXiudingShenheEntity audit) throws DataException { boolean any = !blank(audit.getShengxiaoWenjianBanbenId()) || audit.getBanbenShengxiaoShijian() != null; if (!any) { return false; } if (blank(audit.getWenjianId()) || blank(audit.getYuanWenjianBanbenId()) || blank(audit.getShengxiaoWenjianBanbenId()) || audit.getBanbenShengxiaoShijian() == null) { throw new DataException("修订审核单的生效文件版本结果不完整"); } return true; } @org.springframework.beans.factory.annotation.Autowired private jnpf.file.FileContentApi fileContentApi; private boolean sameContent(String left, String right) { return jnpf.file.FileContentEquality.same(left, right, fileContentApi); } private boolean requiresVersionCheck(Integer flowState) throws DataException { if (flowState == null) { throw new DataException("工作流状态不能为空"); } if (flowState == EXCEPTION) { throw new DataException("文件审核流程处于异常状态,请修复流程后重试"); } return flowState == RUNNING || flowState == PASSED; } private void verifyPersistedState(Integer persistedState, Integer workflowState) throws DataException { if (!Objects.equals(persistedState, workflowState)) { throw new DataException("流程状态尚未同步完成:表单状态=" + persistedState + ",任务状态=" + workflowState + ",请稍后重试"); } } private DmsWenjianXinzengShenheEntity uniqueNewAudit( List rows) throws DataException { if (rows == null || rows.isEmpty()) { throw new DataException("未找到文件新增审核流程关联记录"); } if (rows.size() > 1) { throw new DataException("多个文件新增审核单关联同一流程任务"); } return rows.get(0); } private DmsWenjianXiudingShenheEntity uniqueRevisionAudit( List rows) throws DataException { if (rows == null || rows.isEmpty()) { throw new DataException("未找到文件修订审核流程关联记录"); } if (rows.size() > 1) { throw new DataException("多个文件修订审核单关联同一流程任务"); } return rows.get(0); } private void requireEvent(DmsFlowEventType eventType) throws DataException { if (eventType == null) { throw new DataException("流程事件类型不能为空"); } } private String firstText(String first, String second, String message) throws DataException { return !blank(first) ? first.trim() : requireText(second, message); } private String requireText(String value, String message) throws DataException { if (blank(value)) { throw new DataException(message); } return value.trim(); } private String trimToNull(String value) { return blank(value) ? null : value.trim(); } private boolean blank(String value) { return value == null || value.trim().isEmpty(); } private void requireSingleUpdate(int affected, String message) throws DataException { if (affected != 1) { throw new DataException(message); } } static String formatVersion(int sequence) { return DmsWenjianYewuBanbenService.formatVersion(sequence); } }