刘光辉
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
package jnpf.dmsService;
 
import jnpf.dmsEntity.DmsWenjianBanbenEntity;
import jnpf.dmsEntity.DmsWenjianGuanxiEntity;
import jnpf.dmsEntity.DmsWenjianZiyuanEntity;
import jnpf.dmsEntity.banben.DmsWenjianBanbenJiesuanModel;
import jnpf.dmsMapper.DmsWenjianBanbenMapper;
import jnpf.dmsMapper.DmsWenjianGuanxiMapper;
import jnpf.dmsMapper.DmsWenjianZiyuanMapper;
import jnpf.exception.DataException;
import jnpf.util.RandomUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.util.Date;
import java.util.Objects;
 
@Service
@RequiredArgsConstructor
public class DmsWenjianYewuBanbenService {
    public static final String MAIN_FILE = "zhuwenjian";
    public static final String RECORD_FILE = "jilu";
    public static final String EXTERNAL_FILE = "wailaiWenjian";
    public static final String RELATION_RECORD = "jilu";
 
    private static final String EFFECTIVE = "effective";
 
    private final DmsWenjianZiyuanMapper resourceMapper;
    private final DmsWenjianBanbenMapper versionMapper;
    private final DmsWenjianGuanxiMapper relationMapper;
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianBanbenEntity createInitialVersion(DmsWenjianBanbenJiesuanModel model)
            throws DataException {
        validateCommon(model);
        validateInitial(model);
        DmsWenjianBanbenEntity repeated = versionMapper.selectBySourceBusinessKey(
                model.getTenantId(), model.getLaiyuanYewuJian());
        if (repeated != null) {
            validateRepeated(repeated, model, null);
            return repeated;
        }
 
        if (resourceMapper.selectByNumberForUpdate(
                model.getTenantId(), model.getWenjianBianhao().trim()) != null
                || (!blank(model.getWenjianBianhaoId())
                && resourceMapper.selectByNumberDetailForUpdate(
                model.getTenantId(), model.getWenjianBianhaoId().trim()) != null)) {
            throw new DataException("文件编号已被占用,历史编号不可复用");
        }
 
        Date now = new Date();
        String fileId = blank(model.getWenjianId()) ? RandomUtil.uuId() : model.getWenjianId();
        DmsWenjianZiyuanEntity resource = new DmsWenjianZiyuanEntity();
        resource.setId(fileId);
        resource.setTenantId(model.getTenantId());
        resource.setWenjianLeixing(model.getWenjianLeixing());
        resource.setWenjianBianhaoId(trimToNull(model.getWenjianBianhaoId()));
        resource.setWenjianBianhao(model.getWenjianBianhao().trim());
        resource.setWenjianMingcheng(model.getWenjianMingcheng().trim());
        resource.setJiluLeixing(trimToNull(model.getJiluLeixing()));
        resource.setDengji(trimToNull(model.getDengji()));
        resource.setWenjianMuluId(model.getWenjianMuluId());
        resource.setShifouShoukong(model.getShifouShoukong());
        resource.setJiluDengjiGuanliId(model.getJiluDengjiGuanliId());
        resource.setJiluDengjiGuanliBanben(model.getJiluDengjiGuanliBanben());
        resource.setGuishuBumenId(trimToNull(model.getGuishuBumenId()));
        resource.setWenjianZhongxinWenjianId(model.getCenterDocumentId());
        applyCenterValidity(resource, model);
        applyProjectionMetadata(resource, model);
        resource.setShouciShengxiaoShijian(model.getEffectiveAt() == null ? now : model.getEffectiveAt());
        resource.setBizStatus(EFFECTIVE);
        resource.setCreatorUserId(model.getUserId());
        resource.setCreatorTime(now);
        try {
            requireSingle(resourceMapper.insertResource(resource), "创建业务文件失败");
        } catch (DuplicateKeyException exception) {
            throw new DataException("文件资源或编号已被占用,历史编号不可复用");
        }
 
        DmsWenjianBanbenEntity version = buildVersion(model, fileId, null, 0, now);
        requireSingle(versionMapper.insertVersion(version), "创建文件初始版本失败");
        resource.setDangqianBanbenId(version.getId());
        resource.setDangqianBanbenhao(version.getBanbenhao());
        resource.setDangqianBanbenXuhao(version.getBanbenXuhao());
        resource.setLastModifyUserId(model.getUserId());
        resource.setLastModifyTime(now);
        requireSingle(resourceMapper.setInitialCurrentVersion(resource), "设置文件初始有效版本失败");
        return version;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianBanbenEntity createRevisionVersion(DmsWenjianBanbenJiesuanModel model)
            throws DataException {
        validateCommon(model);
        requireText(model.getWenjianId(), "待升级文件ID不能为空");
        String sourceVersionId = requireText(
                model.getYuanWenjianBanbenId(), "来源文件版本ID不能为空");
        DmsWenjianBanbenEntity repeated = versionMapper.selectBySourceBusinessKey(
                model.getTenantId(), model.getLaiyuanYewuJian());
        if (repeated != null) {
            validateRepeated(repeated, model, sourceVersionId);
            return repeated;
        }
 
        DmsWenjianZiyuanEntity resource = requireCurrentResource(model, sourceVersionId);
        DmsWenjianBanbenEntity current = versionMapper.selectById(
                model.getTenantId(), sourceVersionId);
        if (current == null || !EFFECTIVE.equals(current.getBizStatus())) {
            throw new DataException("来源文件版本不是当前有效版本");
        }
        int nextSequence;
        try {
            nextSequence = Math.addExact(current.getBanbenXuhao(), 1);
        } catch (ArithmeticException exception) {
            throw new DataException("文件版本序号已达到上限");
        }
 
        if (!Objects.equals(resource.getWenjianMuluId(), model.getWenjianMuluId())) {
            throw new DataException("普通修订不能改变文件目录");
        }
        model.setWenjianBianhaoId(resource.getWenjianBianhaoId());
        model.setWenjianBianhao(resource.getWenjianBianhao());
        model.setBianhaoShiyongRenId(resource.getBianhaoShiyongRenId());
        model.setWenjianFenlei(resource.getWenjianFenlei());
        if (blank(model.getWenjianMingcheng())) model.setWenjianMingcheng(resource.getWenjianMingcheng());
        if (blank(model.getGuishuBumenId())) model.setGuishuBumenId(resource.getGuishuBumenId());
        Date now = new Date();
        DmsWenjianBanbenEntity version = buildVersion(
                model, resource.getId(), sourceVersionId, nextSequence, now);
        requireSingle(versionMapper.markSuperseded(
                model.getTenantId(), sourceVersionId, model.getUserId(),
                "修订升版,由 " + version.getBanbenhao() + " 替代"), "来源文件版本已发生变化");
        requireSingle(versionMapper.insertVersion(version), "创建文件升级版本失败");
 
        applyMetadata(resource, model);
        resource.setWenjianZhongxinWenjianId(model.getCenterDocumentId());
        applyCenterValidity(resource, model);
        applyProjectionMetadata(resource, model);
        resource.setDangqianBanbenId(version.getId());
        resource.setDangqianBanbenhao(version.getBanbenhao());
        resource.setDangqianBanbenXuhao(version.getBanbenXuhao());
        resource.setLastModifyUserId(model.getUserId());
        resource.setLastModifyTime(now);
        requireSingle(resourceMapper.advanceCurrentVersion(
                resource, sourceVersionId), "更新文件当前有效版本失败");
        return version;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianBanbenEntity reuseCurrentVersion(String tenantId, String fileId,
                                                       String expectedVersionId)
            throws DataException {
        DmsWenjianBanbenJiesuanModel model = new DmsWenjianBanbenJiesuanModel();
        model.setTenantId(requireText(tenantId, "租户ID不能为空"));
        model.setWenjianId(requireText(fileId, "文件ID不能为空"));
        DmsWenjianZiyuanEntity resource = requireCurrentResource(
                model, requireText(expectedVersionId, "来源文件版本ID不能为空"));
        DmsWenjianBanbenEntity current = versionMapper.selectById(tenantId, resource.getDangqianBanbenId());
        if (current == null || !EFFECTIVE.equals(current.getBizStatus())) {
            throw new DataException("来源文件版本不是当前有效版本");
        }
        return current;
    }
 
    /** REUSE keeps the attachment identity; approved record metadata can still change. */
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianBanbenEntity updateReusedRecordMetadata(DmsWenjianBanbenJiesuanModel model) {
        if (!"xiuding_shenhe".equals(model.getLaiyuanDanjuLeixing())
                || !RECORD_FILE.equals(model.getWenjianLeixing()))
            throw new DataException("仅修订审核记录允许沿用附件更新信息");
        if (!"yes".equals(model.getShifouShoukong()) && !"no".equals(model.getShifouShoukong()))
            throw new DataException("记录方式缺失或无效");
        requireText(model.getWenjianMingcheng(), "记录名称不能为空");
        requireText(model.getGuishuBumenId(), "记录归属部门不能为空");
        DmsWenjianBanbenEntity current = reuseCurrentVersion(model.getTenantId(),
                model.getWenjianId(), model.getYuanWenjianBanbenId());
        if (!RECORD_FILE.equals(current.getWenjianLeixing())
                || !Objects.equals(current.getWenjianMuluId(), model.getWenjianMuluId())
                || !Objects.equals(current.getWenjianZhongxinBanbenId(), model.getCenterVersionId()))
            throw new DataException("沿用记录目录或附件版本已变化");
        requireSingle(versionMapper.updateReusedRecordMetadata(model), "更新沿用记录版本信息失败");
        requireSingle(resourceMapper.updateReusedRecordMetadata(model), "更新沿用记录资源信息失败");
        current.setShifouShoukong(model.getShifouShoukong());
        current.setWenjianMingcheng(model.getWenjianMingcheng());
        current.setGuishuBumenId(model.getGuishuBumenId());
        return current;
    }
 
    /** Called only after the unified revision batch has completed its center transaction. */
    @Transactional(rollbackFor = Exception.class)
    public void retireRecord(String tenantId, String mainFileId, String fileId,
                             String versionId, String userId) {
        DmsWenjianZiyuanEntity resource = resourceMapper.selectByIdForUpdate(tenantId, fileId);
        if (resource == null || !RECORD_FILE.equals(resource.getWenjianLeixing())
                || !EFFECTIVE.equals(resource.getBizStatus())
                || !Objects.equals(versionId, resource.getDangqianBanbenId())
                || relationMapper.selectEffective(tenantId, mainFileId, fileId, RELATION_RECORD) == null)
            throw new DataException("移除记录的当前版本或归属已改变");
        requireSingle(versionMapper.markSuperseded(tenantId, versionId, userId,
                "修订移除记录,保留历史"), "移除记录的业务版本已改变");
        requireSingle(resourceMapper.retireRecord(tenantId, fileId, versionId, userId), "回写移除记录失败");
        requireSingle(relationMapper.retireRecordRelation(tenantId, mainFileId, fileId, userId), "回写记录关系失败");
    }
 
    public DmsWenjianBanbenEntity resolveCurrentVersion(String tenantId, String fileId)
            throws DataException {
        String resolvedTenantId = requireText(tenantId, "租户ID不能为空");
        String resolvedFileId = requireText(fileId, "文件ID不能为空");
        DmsWenjianBanbenEntity current = versionMapper.selectCurrentByFileId(
                resolvedTenantId, resolvedFileId);
        if (current == null || !EFFECTIVE.equals(current.getBizStatus())) {
            throw new DataException("文件不存在当前有效版本");
        }
        return current;
    }
 
    @Transactional(rollbackFor = Exception.class)
    public DmsWenjianGuanxiEntity ensureRelation(String tenantId, String mainFileId,
                                                  String relatedFileId, String relationType,
                                                  String sourceAuditId, String userId)
            throws DataException {
        requireText(tenantId, "租户ID不能为空");
        requireText(mainFileId, "主文件ID不能为空");
        requireText(relatedFileId, "关联文件ID不能为空");
        if (mainFileId.equals(relatedFileId)) {
            throw new DataException("文件不能关联自身");
        }
        if (!RELATION_RECORD.equals(relationType) && !"guanlian_wenjian".equals(relationType)) {
            throw new DataException("文件关系类型无效");
        }
        DmsWenjianZiyuanEntity main = resourceMapper.selectByIdForUpdate(tenantId, mainFileId);
        DmsWenjianZiyuanEntity related = resourceMapper.selectByIdForUpdate(tenantId, relatedFileId);
        if (main == null || !EFFECTIVE.equals(main.getBizStatus())
                || !MAIN_FILE.equals(main.getWenjianLeixing())) {
            throw new DataException("主文件不存在或不是有效主文件");
        }
        if (related == null || !EFFECTIVE.equals(related.getBizStatus())) {
            throw new DataException("关联文件不存在或不是有效文件");
        }
        if (RELATION_RECORD.equals(relationType)
                && !RECORD_FILE.equals(related.getWenjianLeixing())) {
            throw new DataException("记录关系只能关联有效记录文件");
        }
        if (RELATION_RECORD.equals(relationType)
                && (blank(main.getWenjianMuluId()) || !Objects.equals(main.getWenjianMuluId(), related.getWenjianMuluId()))) {
            throw new DataException("记录必须与主文件在同一目录,不能跨目录复用");
        }
        DmsWenjianGuanxiEntity existing = relationMapper.selectEffective(
                tenantId, mainFileId, relatedFileId, relationType);
        if (existing != null) {
            return existing;
        }
        Date now = new Date();
        DmsWenjianGuanxiEntity relation = new DmsWenjianGuanxiEntity();
        relation.setId(RandomUtil.uuId());
        relation.setTenantId(tenantId);
        relation.setZhuWenjianId(mainFileId);
        relation.setGuanlianWenjianId(relatedFileId);
        relation.setGuanxiLeixing(relationType);
        relation.setLaiyuanDanjuId(requireText(sourceAuditId, "来源审核单ID不能为空"));
        relation.setBizStatus(EFFECTIVE);
        relation.setCreatorUserId(requireText(userId, "操作人ID不能为空"));
        relation.setCreatorTime(now);
        requireSingle(relationMapper.insertRelation(relation), "创建文件关系失败");
        return relation;
    }
 
    private DmsWenjianZiyuanEntity requireCurrentResource(
            DmsWenjianBanbenJiesuanModel model, String expectedVersionId) throws DataException {
        DmsWenjianZiyuanEntity resource = resourceMapper.selectByIdForUpdate(
                model.getTenantId(), model.getWenjianId());
        if (resource == null || !EFFECTIVE.equals(resource.getBizStatus())) {
            throw new DataException("待处理文件不存在或不是有效文件");
        }
        if (!Objects.equals(resource.getDangqianBanbenId(), expectedVersionId)) {
            throw new DataException("来源文件版本已不是当前有效版本,请重新发起申请");
        }
        if (!blank(model.getWenjianLeixing())
                && !Objects.equals(resource.getWenjianLeixing(), model.getWenjianLeixing())) {
            throw new DataException("待处理文件类型与审核内容不一致");
        }
        return resource;
    }
 
    private DmsWenjianBanbenEntity buildVersion(DmsWenjianBanbenJiesuanModel model,
                                                 String fileId, String sourceVersionId,
                                                 int sequence, Date now) {
        DmsWenjianBanbenEntity version = new DmsWenjianBanbenEntity();
        version.setId(RandomUtil.uuId());
        version.setTenantId(model.getTenantId());
        version.setWenjianId(fileId);
        version.setWenjianMuluId(model.getWenjianMuluId());
        version.setShifouShoukong(model.getShifouShoukong());
        version.setJiluDengjiGuanliId(model.getJiluDengjiGuanliId());
        version.setJiluDengjiGuanliBanben(model.getJiluDengjiGuanliBanben());
        version.setWenjianLeixing(model.getWenjianLeixing());
        version.setJiluLeixing(model.getJiluLeixing());
        version.setDengji(model.getDengji());
        version.setYuanWenjianBanbenId(sourceVersionId);
        version.setBanbenXuhao(sequence);
        if (model.getCenterVersionLabel() != null
                && !"wailai_wenjian_xinzeng_shenhe".equals(model.getLaiyuanDanjuLeixing())
                && !formatVersion(sequence).equals(model.getCenterVersionLabel()))
            throw new DataException("中心版本号与批准业务版本序号不一致");
        version.setBanbenhao(model.getCenterVersionLabel() == null ? formatVersion(sequence) : model.getCenterVersionLabel());
        version.setWenjianZhongxinBanbenId(model.getCenterVersionId());
        version.setWenjianNeirong(model.getWenjianNeirong().trim());
        version.setLaiyuanDanjuLeixing(model.getLaiyuanDanjuLeixing());
        version.setLaiyuanDanjuId(model.getLaiyuanDanjuId());
        version.setLaiyuanZixiangId(trimToNull(model.getLaiyuanZixiangId()));
        version.setLaiyuanYewuJian(model.getLaiyuanYewuJian());
        version.setShengxiaoShijian(model.getEffectiveAt() == null ? now : model.getEffectiveAt());
        version.setWenjianBianhaoId(model.getWenjianBianhaoId());
        version.setWenjianBianhao(model.getWenjianBianhao());
        version.setWenjianMingcheng(model.getWenjianMingcheng());
        version.setGuishuBumenId(model.getGuishuBumenId());
        version.setBianhaoShiyongRenId(model.getBianhaoShiyongRenId());
        version.setZhidingRenId(model.getZhidingRenId());
        version.setZhidingrenBumenId(model.getZhidingrenBumenId());
        version.setZhidingRiqi(model.getZhidingRiqi());
        version.setWenjianFenlei(model.getWenjianFenlei());
        version.setWenjianDuixiangId(model.getWenjianDuixiangId());
        version.setShenheLiuchengId(model.getShenheLiuchengId());
        version.setShenheLiuchengRenwuId(model.getShenheLiuchengRenwuId());
        version.setWenjianZhongxinWenjianId(model.getCenterDocumentId());
        version.setYouxiaoQiZhi(model.getEffectiveTo());
        if (model.getEffectiveAt() != null) {
            version.setYouxiaoqiFangshi(model.getEffectiveTo() == null ? "changqi" : "guding_riqi");
            version.setShengxiaoNianxian(null);
        }
        version.setBizStatus(EFFECTIVE);
        version.setCreatorUserId(model.getUserId());
        version.setCreatorTime(now);
        return version;
    }
 
    private void applyProjectionMetadata(DmsWenjianZiyuanEntity resource, DmsWenjianBanbenJiesuanModel model) {
        resource.setBianhaoShiyongRenId(model.getBianhaoShiyongRenId());
        resource.setZhidingRenId(model.getZhidingRenId());
        resource.setZhidingrenBumenId(model.getZhidingrenBumenId());
        resource.setZhidingRiqi(model.getZhidingRiqi());
        resource.setWenjianFenlei(model.getWenjianFenlei());
        resource.setWenjianDuixiangId(model.getWenjianDuixiangId());
        resource.setShenheLiuchengId(model.getShenheLiuchengId());
        resource.setShenheLiuchengRenwuId(model.getShenheLiuchengRenwuId());
        resource.setLaiyuanDanjuLeixing(model.getLaiyuanDanjuLeixing());
        resource.setLaiyuanDanjuId(model.getLaiyuanDanjuId());
        resource.setLaiyuanZixiangId(model.getLaiyuanZixiangId());
        resource.setWenjianZhongxinBanbenId(model.getCenterVersionId());
    }
 
    private void applyCenterValidity(DmsWenjianZiyuanEntity resource, DmsWenjianBanbenJiesuanModel model) {
        resource.setYouxiaoQiZhi(model.getEffectiveTo());
        if (model.getEffectiveAt() != null) {
            resource.setShengxiaoShijian(model.getEffectiveAt());
            // The center returns an explicit expiry boundary, never an inferred year duration.
            resource.setYouxiaoqiFangshi(model.getEffectiveTo() == null ? "changqi" : "guding_riqi");
            resource.setShengxiaoNianxian(null);
        }
    }
 
    private void validateCommon(DmsWenjianBanbenJiesuanModel model) throws DataException {
        if (model == null) {
            throw new DataException("文件版本结算参数不能为空");
        }
        requireText(model.getTenantId(), "租户ID不能为空");
        requireText(model.getWenjianNeirong(), "文件内容不能为空");
        requireText(model.getWenjianMuluId(), "文件目录不能为空,请在表单中选择有效来源");
        if (RECORD_FILE.equals(model.getWenjianLeixing())) {
            // This service only settles new/revision audits, whose control flag is selected directly.
            if (!"yes".equals(model.getShifouShoukong()) && !"no".equals(model.getShifouShoukong())) {
                throw new DataException("记录是否受控快照缺失或无效,请完善表单");
            }
        }
        requireText(model.getLaiyuanDanjuLeixing(), "来源单据类型不能为空");
        if (!"xinzeng_shenhe".equals(model.getLaiyuanDanjuLeixing())
                && !"xiuding_shenhe".equals(model.getLaiyuanDanjuLeixing())
                && !"wailai_wenjian_xinzeng_shenhe".equals(model.getLaiyuanDanjuLeixing())) {
            throw new DataException("来源单据类型无效");
        }
        requireText(model.getLaiyuanDanjuId(), "来源审核单ID不能为空");
        requireText(model.getLaiyuanYewuJian(), "来源业务键不能为空");
        requireText(model.getUserId(), "操作人ID不能为空");
    }
 
    private void validateInitial(DmsWenjianBanbenJiesuanModel model) throws DataException {
        boolean externalSource = "wailai_wenjian_xinzeng_shenhe".equals(model.getLaiyuanDanjuLeixing());
        if (externalSource != EXTERNAL_FILE.equals(model.getWenjianLeixing())
                || (!externalSource && !MAIN_FILE.equals(model.getWenjianLeixing())
                    && !RECORD_FILE.equals(model.getWenjianLeixing()))) {
            throw new DataException("文件类型无效");
        }
        requireText(model.getWenjianBianhao(), "文件编号不能为空");
        requireText(model.getWenjianMingcheng(), "文件名称不能为空");
        if (MAIN_FILE.equals(model.getWenjianLeixing())) {
            requireText(model.getWenjianBianhaoId(), "主文件编号申请明细ID不能为空");
        } else if (RECORD_FILE.equals(model.getWenjianLeixing())) {
            requireText(model.getJiluLeixing(), "记录类型不能为空");
            requireText(model.getDengji(), "记录等级不能为空");
        }
        if (!blank(model.getYuanWenjianBanbenId())) {
            throw new DataException("初始版本不能包含来源版本ID");
        }
    }
 
    private void validateRepeated(DmsWenjianBanbenEntity repeated,
                                  DmsWenjianBanbenJiesuanModel model,
                                  String sourceVersionId) throws DataException {
        if ((!blank(model.getWenjianId()) && !Objects.equals(repeated.getWenjianId(), model.getWenjianId()))
                || !Objects.equals(repeated.getYuanWenjianBanbenId(), sourceVersionId)
                || !Objects.equals(repeated.getWenjianNeirong(), model.getWenjianNeirong().trim())
                || !Objects.equals(repeated.getWenjianMuluId(), model.getWenjianMuluId())
                || !Objects.equals(repeated.getShifouShoukong(), model.getShifouShoukong())
                || !Objects.equals(repeated.getJiluDengjiGuanliId(), model.getJiluDengjiGuanliId())
                || !Objects.equals(repeated.getJiluDengjiGuanliBanben(), model.getJiluDengjiGuanliBanben())
                || !Objects.equals(repeated.getWenjianLeixing(), model.getWenjianLeixing())
                || !Objects.equals(repeated.getJiluLeixing(), model.getJiluLeixing())
                || !Objects.equals(repeated.getDengji(), model.getDengji())) {
            throw new DataException("来源业务键对应的文件版本与本次结算内容不一致");
        }
    }
 
    private void applyMetadata(DmsWenjianZiyuanEntity resource,
                               DmsWenjianBanbenJiesuanModel model) {
        resource.setWenjianMuluId(model.getWenjianMuluId());
        resource.setShifouShoukong(model.getShifouShoukong());
        resource.setJiluDengjiGuanliId(model.getJiluDengjiGuanliId());
        resource.setJiluDengjiGuanliBanben(model.getJiluDengjiGuanliBanben());
        if (!blank(model.getWenjianMingcheng())) {
            resource.setWenjianMingcheng(model.getWenjianMingcheng().trim());
        }
        if (!blank(model.getJiluLeixing())) {
            resource.setJiluLeixing(model.getJiluLeixing().trim());
        }
        if (!blank(model.getDengji())) {
            resource.setDengji(model.getDengji().trim());
        }
        if (!blank(model.getGuishuBumenId())) {
            resource.setGuishuBumenId(model.getGuishuBumenId().trim());
        }
    }
 
    public static String formatVersion(int sequence) {
        return String.format("D(%02d)", sequence);
    }
 
    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 requireSingle(int affected, String message) throws DataException {
        if (affected != 1) {
            throw new DataException(message);
        }
    }
}