package jnpf.dmsService.impl; import jnpf.base.ActionResult; import jnpf.base.BillRuleApi; import jnpf.base.Pagination; import jnpf.base.UserInfo; import jnpf.dmsEntity.fenfa.DmsWenjianDayinJiluEntity; import jnpf.dmsEntity.fenfa.DmsWenjianDayinRenwuEntity; import jnpf.dmsEntity.fenfa.DmsWenjianFenfaFanweiEntity; import jnpf.dmsEntity.fenfa.DmsWenjianFenfaRenwuEntity; import jnpf.dmsEntity.fenfa.DmsWenjianHuishouEntity; import jnpf.dmsEntity.fenfa.DmsWenjianHuishouMingxiEntity; import jnpf.dmsEntity.fenfa.form.DmsWenjianBupinghengForm; import jnpf.dmsEntity.fenfa.form.DmsWenjianHuishouForm; import jnpf.dmsEntity.fenfa.form.DmsWenjianHuishouMingxiForm; import jnpf.dmsEntity.fenfa.form.DmsWenjianQianshouFafangForm; import jnpf.dmsEntity.fenfa.vo.DmsWenjianDayinJiluVO; import jnpf.dmsEntity.fenfa.vo.DmsWenjianDayinRenwuVO; import jnpf.dmsEntity.fenfa.vo.DmsWenjianFenfaRenwuVO; import jnpf.dmsEntity.fenfa.vo.DmsWenjianJiaojieXiangqingVO; import jnpf.dmsEntity.fenfa.vo.DmsWenjianHuishouMingxiVO; import jnpf.dmsEntity.fenfa.vo.DmsWenjianHuishouResultVO; import jnpf.dmsEntity.fenfa.vo.DmsWenjianXiaohuiMingxiVO; import jnpf.dmsIntegration.audit.DmsAuditAdapter; import jnpf.dmsIntegration.audit.DmsAuditRequestSignResolver; import jnpf.dmsIntegration.audit.DmsWenjianHuishouAuditEvent; import jnpf.dmsMapper.DmsWenjianFenfaMapper; import jnpf.dmsPermission.DmsCurrentIdentity; import jnpf.dmsService.DmsWenjianFenfaService; import jnpf.dmsService.DmsWenjianHuishouPolicy; import jnpf.exception.DataException; import jnpf.permission.OrganizeApi; import jnpf.permission.UserApi; import jnpf.permission.entity.OrganizeEntity; import jnpf.permission.entity.UserEntity; import jnpf.permission.model.user.UserRelationIds; import jnpf.util.RandomUtil; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; @Service @RequiredArgsConstructor public class DmsWenjianFenfaServiceImpl implements DmsWenjianFenfaService { private static final String NEW = "xinzeng"; private static final String REVISION = "xiuding"; private static final String PENDING_PRINT = "daidayin"; private static final String PARTIALLY_PRINTED = "bufendayin"; private static final String PRINTED = "yidayin"; private static final String CANCELLED = "yiquxiao"; private static final String PENDING_ISSUE = "daifafang"; private static final String PENDING_RECEIPT = "daiqianshou"; private static final String PENDING_RECYCLE = "daihuishou"; private static final String PENDING_DESTRUCTION = "daixiaohui"; private static final String PENDING_ARCHIVE = "daiguidang"; private static final String ARCHIVED = "yiguidang"; private static final String DESTROYED = "yixiaohui"; private static final String LEGACY_RECYCLED = "yihuishou"; private static final String NEW_AUDIT_SOURCE = "wenjian_xinzeng_shenhe"; private static final String REVISION_AUDIT_SOURCE = "wenjian_xiuding_shenhe"; private static final String PENDING_DISTRIBUTION = "daifafang"; private static final String COMPLETED_DISTRIBUTION = "yiwancheng"; private static final String AUTO_DISTRIBUTION = "zidongfenfa"; private static final String PRINT_BILL_RULE = "wenjianDayinLiushuihao"; private static final String DISTRIBUTION_BILL_RULE = "wenjianFenfaBianhao"; private final DmsCurrentIdentity identity; private final jnpf.dmsService.DmsWenjianChongdaGuard reprintGuard; private final DmsWenjianFenfaMapper mapper; private final BillRuleApi billRuleApi; private final jnpf.dmsService.DmsWenjianDayinHaomaService numbers; private final OrganizeApi organizeApi; private final UserApi userApi; private final DmsAuditAdapter auditAdapter; @Override @Transactional(rollbackFor = Exception.class) public void ensureNewPrintTasks(String tenantId, String auditId, String wenjianId, String creatorUserId) throws DataException { ensurePrintTasks(NEW, NEW_AUDIT_SOURCE, tenantId, auditId, wenjianId, creatorUserId, mapper.selectNewScopes(tenantId, auditId)); } @Override @Transactional(rollbackFor = Exception.class) public void ensureRevisionPrintTasks(String tenantId, String auditId, String wenjianId, String creatorUserId) throws DataException { ensurePrintTasks(REVISION, REVISION_AUDIT_SOURCE, tenantId, auditId, wenjianId, creatorUserId, mapper.selectRevisionScopes(tenantId, auditId)); } private void ensurePrintTasks(String auditType, String sourceType, String tenantId, String auditId, String fileId, String creatorUserId, List scopes) throws DataException { Date now = new Date(); String resolvedTenantId = requireText(tenantId, "租户ID不能为空"); String resolvedAuditId = requireText(auditId, "审核单ID不能为空"); String resolvedFileId = requireText(fileId, "文件ID不能为空"); String resolvedCreatorUserId = requireText(creatorUserId, "任务创建人不能为空"); DmsWenjianDayinRenwuEntity snapshot = mapper.selectAuditPrintSnapshot( resolvedTenantId, auditType, resolvedAuditId); if (snapshot == null) { throw new DataException("未找到打印任务对应的审核文件快照"); } // 新增/修订审核共用:先校验整批分发需求,避免前面的任务已写入后才发现非法数量。 for (DmsWenjianFenfaFanweiEntity scope : safe(scopes)) { validateScope(scope); } for (DmsWenjianFenfaFanweiEntity scope : safe(scopes)) { DmsWenjianDayinRenwuEntity printTask = new DmsWenjianDayinRenwuEntity(); printTask.setId(RandomUtil.uuId()); printTask.setTenantId(resolvedTenantId); printTask.setLaiyuanLeixing(sourceType); printTask.setLaiyuanDanjuId(resolvedAuditId); printTask.setLaiyuanMingxiId(scope.getId()); printTask.setWenjianId(resolvedFileId); printTask.setWenjianBianhao(requireText( snapshot.getWenjianBianhao(), "审核文件编号不能为空")); printTask.setWenjianMingcheng(requireText( snapshot.getWenjianMingcheng(), "审核文件名称不能为空")); printTask.setBanbenhao(requireText(snapshot.getBanbenhao(), "审核文件版本不能为空")); printTask.setShenheWanchengRiqi(snapshot.getShenheWanchengRiqi()); printTask.setPeixunWanchengRiqi(snapshot.getPeixunWanchengRiqi()); printTask.setShengxiaoRiqi(snapshot.getShengxiaoRiqi()); printTask.setJieshouBumenId(scope.getBumenId()); printTask.setWenjianLeibie(scope.getWenjianLeibie()); printTask.setDayinFenshu(requireCopies(scope.getFenshu())); printTask.setBizStatus(PENDING_PRINT); printTask.setCreatorUserId(resolvedCreatorUserId); printTask.setCreatorTime(now); if (mapper.insertPrintTaskIfAbsent(printTask) == 1) numbers.ensureTaskNumbers(resolvedTenantId, printTask, 0); } } @Override public List tasks(Pagination pagination, String bizStatus) throws DataException { requirePagination(pagination); String status = trim(bizStatus); if (!status.isEmpty() && !PENDING_PRINT.equals(status) && !PARTIALLY_PRINTED.equals(status) && !PRINTED.equals(status) && !CANCELLED.equals(status)) { throw new DataException("打印任务状态无效:" + status); } return mapper.selectTaskPage(identity.tenantId(), pagination, status); } @Override public List distributionTasks( Pagination pagination, String bizStatus) throws DataException { requirePagination(pagination); String status = trim(bizStatus); if (!status.isEmpty() && !PENDING_DISTRIBUTION.equals(status) && !PENDING_RECEIPT.equals(status) && !COMPLETED_DISTRIBUTION.equals(status) && !CANCELLED.equals(status)) { throw new DataException("分发任务状态无效:" + status); } return mapper.selectDistributionPage(identity.tenantId(), pagination, status); } @Override public List issueLedger( Pagination pagination, String issueStatus) throws DataException { requirePagination(pagination); String status = trim(issueStatus); if (!status.isEmpty() && !"unissued".equals(status) && !"issued".equals(status)) { throw new DataException("发放状态无效:" + status); } return mapper.selectIssueLedgerPage(identity.tenantId(), pagination, status); } @Override public List receiptLedger( Pagination pagination, String receiptStatus) throws DataException { requirePagination(pagination); String status = trim(receiptStatus); if (!status.isEmpty() && !"pending".equals(status) && !"received".equals(status)) { throw new DataException("签收状态无效:" + status); } return mapper.selectReceiptLedgerPage(identity.tenantId(), pagination, status); } @Override public DmsWenjianJiaojieXiangqingVO handoverDetail(String recordId) throws DataException { DmsWenjianJiaojieXiangqingVO detail = mapper.selectHandoverDetail( identity.tenantId(), requireText(recordId, "打印记录ID不能为空")); if (detail == null) { throw new DataException("未找到对应的有效打印记录"); } String receivingDepartmentId = trim(detail.getJieshouBumenId()); if (!receivingDepartmentId.isEmpty()) { OrganizeEntity organization = organizeApi.getInfoById(receivingDepartmentId); if (organization != null && receivingDepartmentId.equals(trim(organization.getId())) && !trim(organization.getOrgNameTree()).isEmpty()) { detail.setJieshouBumen(organization.getOrgNameTree()); } } return detail; } @Override @Transactional(rollbackFor = Exception.class) public List print(String taskId, int copies) throws DataException { if (copies <= 0) { throw new DataException("打印份数必须大于0"); } UserInfo user = identity.requireUser(); String tenantId = identity.tenantId(user); PrintExecution execution = printCopies(user, tenantId, taskId, copies); return execution.output; } @Override @Transactional(rollbackFor = Exception.class) public List printReserved(String taskId, int copies, List numbers) throws DataException { UserInfo user = identity.requireUser(); String tenant = identity.tenantId(user); DmsWenjianDayinRenwuEntity task = selectPrintableTask(tenant, taskId); if (numbers == null || numbers.size() != copies || copies < 1 || copies > 999 || copies > task.getDayinFenshu() - mapper.countConsumedPrints(tenant, taskId)) throw new DataException("冻结号码或打印份数不匹配"); this.numbers.requireReservedNumbers(tenant, task, mapper.maxPrintSequence(tenant, taskId), numbers); PrintExecution execution = createPrintRecords(user, tenant, task, copies, numbers); return execution.output; } @Override @Transactional(rollbackFor = Exception.class) public List printRemaining(List taskIds) throws DataException { if (taskIds == null || taskIds.isEmpty()) { throw new DataException("打印任务不能为空"); } List resolvedIds = new ArrayList<>(taskIds.size()); Set uniqueIds = new HashSet<>(); for (String taskId : taskIds) { String resolvedId = requireText(taskId, "打印任务ID不能为空"); if (!uniqueIds.add(resolvedId)) { throw new DataException("打印任务不能重复:" + resolvedId); } resolvedIds.add(resolvedId); } // Stable lock order prevents concurrent batches from deadlocking each other. Collections.sort(resolvedIds); UserInfo user = identity.requireUser(); String tenantId = identity.tenantId(user); List result = new ArrayList<>(); List executions = new ArrayList<>(resolvedIds.size()); for (String taskId : resolvedIds) { DmsWenjianDayinRenwuEntity task = selectPrintableTask(tenantId, taskId); int requiredCopies = task.getDayinFenshu(); int printedCopies = mapper.countConsumedPrints(tenantId, task.getId()); PrintExecution execution = printCopies(user, tenantId, taskId, requiredCopies - printedCopies); executions.add(execution); result.addAll(execution.output); } return result; } private PrintExecution printCopies( UserInfo user, String tenantId, String taskId, int copies) throws DataException { DmsWenjianDayinRenwuEntity task = selectPrintableTask(tenantId, taskId); int requiredCopies = task.getDayinFenshu(); int printedCopies = mapper.countConsumedPrints(tenantId, task.getId()); int remainingCopies = requiredCopies - printedCopies; if (copies < 1 || copies > 999 || copies > remainingCopies) { throw new DataException("本次打印份数超过剩余份数或无效,剩余:" + remainingCopies); } return createPrintRecords(user, tenantId, task, copies, null); } private DmsWenjianDayinRenwuEntity selectPrintableTask(String tenantId, String taskId) throws DataException { DmsWenjianDayinRenwuEntity task = mapper.selectPrintTaskForUpdate( tenantId, requireText(taskId, "打印任务ID不能为空")); if (task == null) { throw new DataException("打印任务不存在或已删除"); } if (CANCELLED.equals(task.getBizStatus())) { throw new DataException("已取消的打印任务不能打印"); } if (!PENDING_PRINT.equals(task.getBizStatus()) && !PARTIALLY_PRINTED.equals(task.getBizStatus())) { throw new DataException("当前打印任务状态不允许打印"); } if (task.getDayinFenshu() == null || task.getDayinFenshu() <= 0) { throw new DataException("打印任务份数无效"); } return task; } private PrintExecution createPrintRecords( UserInfo user, String tenantId, DmsWenjianDayinRenwuEntity task, int copies, List numbers) throws DataException { requireText(task.getJieshouBumenId(), "打印任务未设置接收部门"); String printingDepartmentId = trimToNull(user.getOrganizeId()); int consumedCopies = mapper.countConsumedPrints(tenantId, task.getId()); int sequence = mapper.maxPrintSequence(tenantId, task.getId()); Date now = new Date(); List result = new ArrayList<>(copies); List records = new ArrayList<>(copies); for (int i = 0; i < copies; i++) { jnpf.dmsEntity.fenfa.DmsWenjianDayinHaoma number = numbers == null ? null : numbers.get(i); String serialNumber = number == null ? nextPrintSerialNumber() : number.getSerialNumber(); DmsWenjianDayinJiluEntity record = new DmsWenjianDayinJiluEntity(); record.setId(RandomUtil.uuId()); record.setTenantId(tenantId); record.setDayinRenwuId(task.getId()); record.setDayinXuhao(++sequence); record.setDayinLiushuihao(serialNumber); if (number != null) { record.setDayinJishuId(number.getCounterId()); record.setDayinJishuXuhao(number.getSequence()); record.setDayinJishuLiushuihao(number.getCountSerialNumber()); } record.setDayinRenId(user.getUserId()); record.setDayinBumenId(printingDepartmentId); record.setDayinShijian(now); record.setBizStatus(PENDING_ISSUE); record.setCreatorUserId(user.getUserId()); record.setCreatorTime(now); requireSingleUpdate(mapper.insertPrintRecord(record), "生成打印记录失败"); records.add(record); DmsWenjianDayinJiluVO vo = new DmsWenjianDayinJiluVO(); vo.setId(record.getId()); vo.setDayinRenwuId(record.getDayinRenwuId()); vo.setDayinXuhao(record.getDayinXuhao()); vo.setDayinLiushuihao(serialNumber); vo.setDayinJishuLiushuihao(record.getDayinJishuLiushuihao()); vo.setDayinRenId(record.getDayinRenId()); vo.setDayinBumenId(record.getDayinBumenId()); vo.setDayinShijian(now); vo.setBizStatus(PENDING_ISSUE); result.add(vo); } boolean complete = consumedCopies + copies >= task.getDayinFenshu(); requireSingleUpdate(mapper.updateTaskStatus( tenantId, task.getId(), complete ? PRINTED : PARTIALLY_PRINTED, user.getUserId(), now), "更新打印任务状态失败"); DmsWenjianFenfaRenwuEntity existingDistribution = mapper.selectDistributionTaskByPrintTaskForUpdate(tenantId, task.getId()); if (existingDistribution != null) { if (!PENDING_DISTRIBUTION.equals(existingDistribution.getBizStatus()) && !PENDING_RECEIPT.equals(existingDistribution.getBizStatus())) { throw new DataException("现有分发任务状态不允许追加打印记录"); } return new PrintExecution(task, records, null, result, complete); } DmsWenjianFenfaRenwuEntity distributionTask = new DmsWenjianFenfaRenwuEntity(); distributionTask.setId(RandomUtil.uuId()); distributionTask.setTenantId(tenantId); distributionTask.setDayinRenwuId(task.getId()); distributionTask.setFenfaBianhao(nextDistributionNumber()); distributionTask.setFafangLeixing(AUTO_DISTRIBUTION); distributionTask.setBizStatus(PENDING_DISTRIBUTION); distributionTask.setCreatorUserId(user.getUserId()); distributionTask.setCreatorTime(now); requireSingleUpdate(mapper.insertDistributionTaskIfAbsent(distributionTask), "生成分发任务失败"); return new PrintExecution(task, records, distributionTask, result, complete); } // DMS 打印审计暂关闭;正式副本、发送控制和分发业务记录仍按原流程保存。 private static final class PrintExecution { private final DmsWenjianDayinRenwuEntity task; private final List records; private final DmsWenjianFenfaRenwuEntity distributionTask; private final boolean complete; private final List output; private PrintExecution(DmsWenjianDayinRenwuEntity task, List records, DmsWenjianFenfaRenwuEntity distributionTask, List output, boolean complete) { this.task = task; this.records = records; this.complete = complete; this.distributionTask = distributionTask; this.output = output; } } @Override @Transactional(rollbackFor = Exception.class) public void handoverByRecordId(String recordId, DmsWenjianQianshouFafangForm form) throws DataException { handover(requireText(recordId, "打印记录ID不能为空"), null, form); } @Override @Transactional(rollbackFor = Exception.class) public void handoverBySerialNumber( String serialNumber, DmsWenjianQianshouFafangForm form) throws DataException { handover(null, requireText(serialNumber, "打印流水号不能为空"), form); } private void handover(String recordId, String serialNumber, DmsWenjianQianshouFafangForm form) throws DataException { UserInfo user = identity.requireUser(); String tenantId = identity.tenantId(user); DmsWenjianDayinJiluEntity record = mapper.selectPrintForUpdate( tenantId, recordId, serialNumber); if (record == null) { throw new DataException("未找到对应的有效打印记录"); } reprintGuard.requireUnlocked(tenantId, record.getId()); DmsWenjianDayinRenwuEntity printTask = mapper.selectPrintTaskForUpdate( tenantId, requireText(record.getDayinRenwuId(), "打印任务ID不能为空")); if (printTask == null) { throw new DataException("分发任务对应的打印任务不存在或已删除"); } DmsWenjianFenfaRenwuEntity task = mapper.selectDistributionTaskByPrintTaskForUpdate( tenantId, record.getDayinRenwuId()); if (task == null) { throw new DataException("打印记录对应的分发任务不存在或已删除"); } if (isPostReceiptStatus(record.getBizStatus())) { return; } if (COMPLETED_DISTRIBUTION.equals(task.getBizStatus())) { throw new DataException("分发任务已完成,但当前打印记录尚未签收"); } if (!PENDING_DISTRIBUTION.equals(task.getBizStatus()) && !PENDING_RECEIPT.equals(task.getBizStatus())) { throw new DataException("当前分发任务状态不允许签收/发放"); } requireText(printTask.getJieshouBumenId(), "打印任务未设置接收部门"); String issuerFormUserId = requireText(form.getBizUserId(), "发放人不能为空"); if (!user.getUserId().equals(issuerFormUserId)) { throw new DataException("发放人必须是当前操作人"); } String issuerDepartmentId = resolveRelatedDepartment( user.getUserId(), user.getOrganizeId()); if (!Boolean.TRUE.equals(user.getIsAdministrator())) { issuerDepartmentId = requireText( issuerDepartmentId, "当前发放人未设置所属部门"); } String receiverId = requireText(form.getBizReviewUser(), "接收人不能为空"); String signerDepartmentId = resolveUserDepartment(receiverId); String bizSign = requireText(form.getBizSign(), "发放人签名不能为空"); String bizReviewSign = requireText(form.getBizReviewSign(), "接收人复核签名不能为空"); String remark = trimToNull(form.getBeizhuShuoming()); Date now = new Date(); requireSingleUpdate(mapper.handoverPrintRecord( tenantId, record.getId(), user.getUserId(), issuerDepartmentId, receiverId, signerDepartmentId, bizSign, bizReviewSign, remark, now), "签收/发放失败"); int completed = mapper.completeDistributionTaskHandoverIfFinished( tenantId, task.getId(), printTask.getId(), receiverId, user.getUserId(), issuerDepartmentId, remark, now); if (completed < 0 || completed > 1) { throw new DataException("更新分发任务失败"); } } private String resolveUserDepartment(String userId) throws DataException { List users = userApi.getUserName(Collections.singletonList(userId)); for (UserEntity user : safe(users)) { if (user != null && userId.equals(trim(user.getId()))) { if (Integer.valueOf(0).equals(user.getEnabledMark())) { throw new DataException("所选签收人不存在或已停用"); } return requireText(resolveRelatedDepartment(userId, user.getOrganizeId()), "所选签收人未设置所属部门"); } } throw new DataException("所选签收人不存在或已停用"); } private String resolveRelatedDepartment(String userId, String defaultOrganizationId) { UserRelationIds relationIds = userApi.getUserObjectIdList(userId); List organizationIds = relationIds == null ? Collections.emptyList() : safe(relationIds.getOrganize()); String defaultId = trim(defaultOrganizationId); for (String organizationId : organizationIds) { if (defaultId.equals(trim(organizationId))) { return defaultId; } } for (String organizationId : organizationIds) { String resolved = trimToNull(organizationId); if (resolved != null) { return resolved; } } return null; } @Override public String signerDepartmentId(String userId) throws DataException { return resolveUserDepartment(requireText(userId, "签收人ID不能为空")); } @Override @Transactional(rollbackFor = Exception.class) public void registerImbalance(String recordId, DmsWenjianBupinghengForm form) throws DataException { // 旧的单副本入口不能绕过原生登记单据。 throw new DataException("请通过文件不平衡登记表单提交,旧登记入口已停用"); } @Override public DmsWenjianHuishouMingxiVO recycleCandidate(String serialNumber) throws DataException { DmsWenjianHuishouMingxiVO candidate = mapper.selectRecycleCandidateBySerialNumber( identity.tenantId(), requireText(serialNumber, "打印流水号不能为空")); if (candidate == null) { throw new DataException("未找到满足回收条件的有效文件副本"); } return candidate; } @Override @Transactional(rollbackFor = Exception.class) public DmsWenjianHuishouResultVO recycle(DmsWenjianHuishouForm form) throws DataException { if (form == null) { throw new DataException("回收单不能为空"); } String recycleId = RandomUtil.uuId(); String signature = requireText(form.getBizSign(), "回收人签名不能为空"); List recordIds = recycleRecordIds(form.getHuishouMingxi()); UserInfo user = identity.requireUser(); String tenantId = identity.tenantId(user); Date now = new Date(); DmsWenjianHuishouEntity recycle = new DmsWenjianHuishouEntity(); recycle.setId(recycleId); recycle.setTenantId(tenantId); recycle.setBizSign(signature); // 此接口只处理转待销毁;确认归档由独立归档接口处理。 recycle.setChuliFangshi("xiaohui"); recycle.setBeizhuShuoming(trimToNull(form.getBeizhuShuoming())); recycle.setDeleteMark(null); requireSingleUpdate(mapper.insertRecycle(recycle), "保存回收单失败"); List records = mapper.selectRecyclePrintsForUpdate( tenantId, recordIds); if (records.size() != recordIds.size()) { throw new DataException("存在不存在或已删除的打印记录,无法回收"); } for (DmsWenjianHuishouMingxiVO record : records) { DmsWenjianHuishouPolicy.requireEligible(record); reprintGuard.requireUnlocked(tenantId, record.getDayinJiluId()); } requireUpdateCount(mapper.markPrintsRecycled(tenantId, recordIds, user.getUserId(), now), records.size(), "更新打印记录回收状态失败"); DmsWenjianHuishouPolicy.completeDistributions(mapper, tenantId, records, user.getUserId(), now); java.util.Map beforeStatuses = new java.util.LinkedHashMap<>(); for (DmsWenjianHuishouMingxiVO record : records) beforeStatuses.put(record.getDayinJiluId(), record.getBizStatus()); List recycledDetails = new ArrayList<>(records.size()); for (DmsWenjianHuishouMingxiVO record : records) { DmsWenjianHuishouMingxiEntity detail = new DmsWenjianHuishouMingxiEntity(); detail.setId(RandomUtil.uuId()); detail.setForeignId(recycleId); detail.setTenantId(tenantId); detail.setDayinJiluId(record.getDayinJiluId()); detail.setDayinLiushuihao(record.getDayinLiushuihao()); detail.setWenjianBianhao(record.getWenjianBianhao()); detail.setWenjianMingcheng(record.getWenjianMingcheng()); detail.setBanbenhao(record.getBanbenhao()); detail.setQianshouBumen(record.getQianshouBumen()); detail.setQianshouRen(record.getQianshouRen()); detail.setQianshouShijian(record.getQianshouShijian()); detail.setHuishouRenId(user.getUserId()); detail.setHuishouShijian(now); detail.setDeleteMark(null); requireSingleUpdate(mapper.insertRecycleDetail(detail), "保存回收明细失败"); recycledDetails.add(detail); } auditAdapter.recycle(DmsWenjianHuishouAuditEvent.builder() .huishouDanjuId(recycleId) .beizhuShuoming(recycle.getBeizhuShuoming()) .huishouRenId(user.getUserId()) .huishouShijian(now) .details(recycledDetails) .beforeStatuses(beforeStatuses) .relatedSigns(DmsAuditRequestSignResolver.resolve( Collections.singletonMap("biz_sign", signature))) .build()); DmsWenjianHuishouResultVO result = new DmsWenjianHuishouResultVO(); result.setId(recycleId); result.setHuishouFenshu(records.size()); result.setHuishouShijian(now); result.setRepeated(false); return result; } @Override public DmsWenjianXiaohuiMingxiVO destructionCandidate(String serialNumber) throws DataException { DmsWenjianXiaohuiMingxiVO candidate = mapper.selectDestructionCandidateBySerialNumber( identity.tenantId(), requireText(serialNumber, "打印流水号不能为空")); if (candidate == null) { throw new DataException("未找到待销毁的文件副本"); } return candidate; } private boolean isPostReceiptStatus(String status) { return "yiqianshou".equals(status) || PENDING_RECYCLE.equals(status) || PENDING_DESTRUCTION.equals(status) || PENDING_ARCHIVE.equals(status) || ARCHIVED.equals(status) || DESTROYED.equals(status) || LEGACY_RECYCLED.equals(status); } private List recycleRecordIds(List details) throws DataException { if (details == null || details.isEmpty()) { throw new DataException("至少需要一条待回收明细"); } List result = new ArrayList<>(details.size()); Set uniqueIds = new HashSet<>(); for (DmsWenjianHuishouMingxiForm detail : details) { if (detail == null) { throw new DataException("回收明细不能为空"); } String recordId = requireText(detail.getDayinJiluId(), "打印记录ID不能为空"); if (!uniqueIds.add(recordId)) { throw new DataException("打印记录不能重复回收:" + recordId); } result.add(recordId); } Collections.sort(result); return result; } private void validateScope(DmsWenjianFenfaFanweiEntity scope) throws DataException { if (scope == null) { throw new DataException("分发范围数据为空"); } requireText(scope.getId(), "分发范围行ID不能为空"); requireText(scope.getBumenId(), "分发部门不能为空"); requireText(scope.getWenjianLeibie(), "分发文件类别不能为空"); requireCopies(scope.getFenshu()); } private int requireCopies(Integer value) throws DataException { if (value == null) { throw new DataException("分发份数不能为空"); } if (value <= 0) { throw new DataException("分发份数必须大于0:" + value); } return value; } private String nextPrintSerialNumber() throws DataException { ActionResult result = billRuleApi.getBillNumber(PRINT_BILL_RULE); String number = result == null ? null : trim(result.getData()); if (number == null || number.isEmpty()) { throw new DataException("打印流水号取号失败:单据规则[" + PRINT_BILL_RULE + "]不可用"); } return number; } private String nextDistributionNumber() throws DataException { ActionResult result = billRuleApi.getBillNumber(DISTRIBUTION_BILL_RULE); String number = result == null ? null : trim(result.getData()); if (number == null || number.isEmpty()) { throw new DataException("分发编号取号失败:单据规则[" + DISTRIBUTION_BILL_RULE + "]不可用"); } return number; } private void requirePagination(Pagination pagination) throws DataException { if (pagination == null || pagination.getCurrentPage() <= 0 || pagination.getPageSize() <= 0) { throw new DataException("分页参数无效"); } } private void requireSingleUpdate(int count, String message) throws DataException { requireUpdateCount(count, 1, message); } private void requireUpdateCount(int count, int expected, String message) throws DataException { if (count != expected) { throw new DataException(message); } } private String requireText(String value, String message) throws DataException { String resolved = trim(value); if (resolved.isEmpty()) { throw new DataException(message); } return resolved; } private String trim(String value) { return value == null ? "" : value.trim(); } private String trimToNull(String value) { String resolved = trim(value); return resolved.isEmpty() ? null : resolved; } private List safe(List values) { return values == null ? java.util.Collections.emptyList() : values; } }