package jnpf.dmsService; import jnpf.audit.sdk.annotation.AuditLog; import jnpf.dmsEntity.storage.approval.DmsDisableOccupancy; import jnpf.dmsMapper.DmsDisableApplicationMapper; import jnpf.exception.DataException; import jnpf.dmsEntity.storage.form.DmsDisableSubmitValidationForm; import jnpf.flowable.WorkFlowApi; import jnpf.flowable.entity.TaskEntity; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; /** Workflow-event side effect only; native forms still own application persistence. */ @Service @RequiredArgsConstructor public class DmsDisableOccupancyService { private static final Set RELEASED = Set.of(3, 4, 7, 9); private final DmsDisableApplicationMapper mapper; private final WorkFlowApi workflow; @Value("${dms.disable-occupancy.enabled:false}") private boolean enabled; @Transactional(rollbackFor = Exception.class) @AuditLog(action = "DMS_SYNC_DISABLE_OCCUPANCY", bizCodeExpr = "#taskId") public boolean onEvent(String tenantId, String flowId, String taskId, String operation, String eventType) { DmsApprovedDisableService.requireOperation(operation); if (!Set.of("start", "end", "back", "reject", "recall").contains(eventType)) throw new DataException("不支持的文件占用流程事件"); if (!enabled && !"end".equals(eventType)) throw new DataException("文件占用登记尚未启用,需先完成技术迁移及流程事件配置"); TaskEntity task = workflow.getInfoSubmit(taskId); if (task == null || !taskId.equals(task.getId()) || task.getStatus() == null || (!flowId.equals(task.getFlowId()) && !flowId.equals(task.getTemplateId()))) throw new DataException("文件占用事件与真实工作流任务不匹配"); DmsDisableOccupancy application = mapper.application(tenantId, taskId, operation); if (application == null || (!Objects.equals(application.getFlowId(), task.getFlowId()) && !Objects.equals(application.getFlowId(), task.getTemplateId())) || !Objects.equals(application.getFlowState(), task.getStatus())) throw new DataException("文件占用事件与原生申请归属或流程状态不匹配"); if (RELEASED.contains(task.getStatus()) && task.getEndTime() != null) { if (enabled) mapper.releaseTask(tenantId, taskId, operation); return false; } if ("end".equals(eventType)) return task.getStatus() == 2 && task.getEndTime() != null; // Returned or recalled but unfinished workflows keep their existing snapshot. if (!"start".equals(eventType) || !Set.of(1, 5, 6, 10).contains(task.getStatus())) return false; List rows = mapper.applicationFiles(tenantId, application.getApplicationId(), operation); if (rows.isEmpty()) throw new DataException("原生撤销或失效申请没有主文件明细"); Set files = new HashSet<>(); for (DmsDisableOccupancy row : rows) { if (row.getFileId() == null || row.getFileId().isBlank() || row.getFolderId() == null || row.getFolderId().isBlank() || !files.add(row.getFileId())) throw new DataException("申请明细重复或缺少有效主文件/专属目录关联"); } requireAvailable(tenantId, files.stream().toList(), taskId); // One local transaction replaces this task's snapshot; no cross-workflow exclusive lock. mapper.releaseTask(tenantId, taskId, operation); for (DmsDisableOccupancy row : rows) { row.setTenantId(tenantId); row.setOperation(operation); row.setApplicationId(application.getApplicationId()); row.setFlowId(task.getFlowId()); row.setTaskId(taskId); mapper.occupy(row); } return false; } public void validateSubmission(String tenantId, String userId, String operation, DmsDisableSubmitValidationForm form) { DmsApprovedDisableService.requireOperation(operation); if (!enabled) throw new DataException("文件占用登记尚未启用,不能提交撤销或失效申请"); List files = form.getFileIds(); if (files == null || files.isEmpty() || files.stream().anyMatch(id -> id == null || id.isBlank()) || new HashSet<>(files).size() != files.size()) throw new DataException("请至少选择一个主文件,同一文件不能重复申请"); String ownTask = null; if (form.getTaskId() != null && !form.getTaskId().isBlank()) { TaskEntity task = workflow.getInfoSubmit(form.getTaskId()); DmsDisableOccupancy application = mapper.application(tenantId, form.getTaskId(), operation); if (task == null || !form.getTaskId().equals(task.getId()) || application == null || (!form.getFlowId().equals(task.getFlowId()) && !form.getFlowId().equals(task.getTemplateId())) || (!Objects.equals(application.getFlowId(), task.getFlowId()) && !Objects.equals(application.getFlowId(), task.getTemplateId())) || task.getStatus() == null || !Set.of(0,1,5,6,8,9,10).contains(task.getStatus()) || task.getEndTime() != null) throw new DataException("仅可沿用本人未结束的原申请,不能排除其他申请的占用"); if (Set.of(0,8,9).contains(task.getStatus())) { if (userId == null || userId.isBlank() || !Objects.equals(userId, application.getCreatorUserId())) throw new DataException("仅可沿用本人未结束的原申请"); } else { // Native beforeSubmit is also invoked for approval. Only an unchanged // persisted file selection may reuse an in-progress task's snapshot. Set persisted = new HashSet<>(); for (DmsDisableOccupancy row : mapper.applicationFiles(tenantId,application.getApplicationId(),operation)) persisted.add(row.getFileId()); if (!persisted.equals(new HashSet<>(files))) throw new DataException("审批中的文件明细与原申请不一致,不能变更占用范围"); } ownTask = task.getId(); } requireAvailable(tenantId, files, ownTask); } private void requireAvailable(String tenantId, List files, String ownTask) { if (mapper.selectedFiles(tenantId, files).size() != files.size()) throw new DataException("所选文件已停用、不是有效主文件或缺少专属目录,请重新选择"); if (mapper.conflictingFiles(tenantId, files, ownTask) > 0) throw new DataException("所选文件已有未结束的撤销或失效申请,不能重复提交"); } }