刘光辉
5 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
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<DmsWenjianXinzengShenheJiluMingxiEntity> 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<DmsWenjianXiudingShenheJiluMingxiEntity> records =
                revisionAuditRecordMapper.selectByAuditForUpdate(tenantId, audit.getId());
        Set<String> 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<String> 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<String> 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<DmsWenjianGuanlianMingxiEntity> rows,
                                    boolean requireExisting) throws DataException {
        Set<String> 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<String> retainedFileIds,
                                                     String message) throws DataException {
        List<DmsWenjianGuanxiEntity> 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<DmsWenjianXinzengShenheEntity> 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<DmsWenjianXiudingShenheEntity> 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);
    }
}