刘光辉
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
package jnpf.dmsService;
 
import jnpf.audit.sdk.annotation.AuditLog;
import jnpf.audit.AuditConsts;
import jnpf.audit.model.AuditEventDTO;
import jnpf.audit.model.AuditRelatedSignDTO;
import jnpf.dmsIntegration.audit.DmsSystemAudit;
import jnpf.base.UserInfo;
import jnpf.dmsEntity.fenfa.DmsWenjianDayinJiluEntity;
import jnpf.dmsEntity.fenfa.DmsWenjianDayinRenwuEntity;
import jnpf.dmsEntity.fenfa.DmsWenjianFenfaRenwuEntity;
import jnpf.dmsEntity.fenfa.DmsWenjianBupinghengExecution;
import jnpf.dmsEntity.fenfa.vo.DmsWenjianBupinghengResultVO;
import jnpf.dmsMapper.DmsWenjianBupinghengMapper;
import jnpf.dmsMapper.DmsWenjianFenfaMapper;
import jnpf.dmsPermission.DmsCurrentIdentity;
import jnpf.exception.DataException;
import jnpf.util.RandomUtil;
import jnpf.util.JsonUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
 
@Service
@RequiredArgsConstructor
public class DmsWenjianBupinghengService {
    private final DmsCurrentIdentity identity;
    private final DmsWenjianChongdaGuard reprintGuard;
    private final DmsWenjianFenfaMapper fenfaMapper;
    private final DmsWenjianDayinHaomaService numbers;
    private final DmsWenjianBupinghengMapper mapper;
    private final DmsSystemAudit systemAudit;
 
    /** 只执行已由原生表单保存的措施;失败时原单据保留,业务副作用全部回滚。 */
    @Transactional(rollbackFor = Exception.class)
    @AuditLog(action = "FILE_DISTRIBUTION_IMBALANCE", label = "执行文件不平衡措施", bizModule = "文件分发",
            bizType = "DMS_FILE_IMBALANCE", bizCodeExpr = "", bizCodeRequired = false,
            targetTable = "dms_wenjian_bupingheng_dengji", targetIdExpr = "#p0",
            extraExpr = "{'results': #result, 'after_status': 'yizuofei'}",
            diff = false, entityClass = DmsWenjianBupinghengExecution.class)
    public List<DmsWenjianBupinghengResultVO> execute(String documentId) throws DataException {
        if (blank(documentId)) throw new DataException("登记单ID不能为空");
        UserInfo user = identity.requireUser();
        String tenantId = identity.tenantId(user);
        return executeStored(documentId, tenantId, user.getUserId(), null);
    }
 
    /** 仅供服务内定时补偿调用,不暴露为 HTTP 接口。租户和版本来自已保存登记单。 */
    @Transactional(rollbackFor = Exception.class)
    @AuditLog(action = "FILE_DISTRIBUTION_IMBALANCE_RETRY", bizCodeExpr = "#p1", recordSuccess = false,
            targetTable = "dms_wenjian_bupingheng_dengji", targetIdExpr = "#p1")
    public List<DmsWenjianBupinghengResultVO> executeScheduled(String tenantId, String documentId, Long expectedVersion)
            throws DataException {
        if (blank(tenantId) || blank(documentId) || expectedVersion == null)
            throw new DataException("补偿执行缺少租户、登记单或版本");
        // 显式系统身份;不改变交互式 execute 的 requireUser 契约。
        return executeStored(documentId, tenantId, "0", expectedVersion);
    }
 
    private List<DmsWenjianBupinghengResultVO> executeStored(String documentId, String tenantId,
                                                           String userId, Long expectedVersion) throws DataException {
        DmsWenjianBupinghengExecution document = mapper.lockDocument(tenantId, documentId);
        if (document == null) throw new DataException("请先通过原生表单保存不平衡登记单");
        if (blank(document.getBizSign())) throw new DataException("登记单缺少签名,不能执行措施");
        if (expectedVersion != null && document.getZhixingShijian() == null
                && !expectedVersion.equals(document.getVersion()))
            throw new DataException("登记单已修改,本轮补偿取消,等待重新复查");
        List<DmsWenjianBupinghengExecution.Item> savedItems = mapper.lockItems(tenantId, documentId);
        if (savedItems == null || savedItems.isEmpty() || savedItems.size() > 200)
            throw new DataException("登记单须有1至200条明细");
        Map<String, DmsWenjianBupinghengExecution.Item> items = new TreeMap<>();
        for (DmsWenjianBupinghengExecution.Item item : savedItems) {
            if (blank(item.getId()) || blank(item.getDayinJiluId())
                    || items.put(item.getDayinJiluId().trim(), item) != null)
                throw new DataException("登记单存在空白或重复文件副本");
            if (!Arrays.asList("cuoshi1", "cuoshi2").contains(item.getCuoshi()) || blank(item.getYuanyin()))
                throw new DataException("登记单的措施或原因不完整");
        }
        // 主单行锁串行化同一单据的重复请求;结果在同一业务事务内写回。
        if (document.getZhixingShijian() != null) {
            List<DmsWenjianBupinghengResultVO> completed = new ArrayList<>();
            for (DmsWenjianBupinghengExecution.Item item : items.values())
                completed.add(result(documentId, item));
            return completed;
        }
        Map<String, DmsWenjianDayinJiluEntity> copies = new TreeMap<>();
        Set<String> taskIds = new TreeSet<>();
        // 与交接一致,先锁副本,再锁分发任务、打印任务;批量按ID排序。
        for (String id : items.keySet()) {
            DmsWenjianDayinJiluEntity copy = fenfaMapper.selectPrintForUpdate(tenantId, id, null);
            if (copy == null) throw new DataException("存在无效、已删除或跨租户的文件副本");
            reprintGuard.requireUnlocked(tenantId, id);
            eligible(copy.getBizStatus());
            copies.put(id, copy);
            taskIds.add(copy.getDayinRenwuId());
        }
        Map<String, DmsWenjianDayinRenwuEntity> tasks = new HashMap<>();
        for (String taskId : taskIds) {
            DmsWenjianFenfaRenwuEntity distribution = fenfaMapper.selectDistributionTaskByPrintTaskForUpdate(tenantId, taskId);
            if (distribution == null) throw new DataException("分发任务不存在或已取消");
            eligible(distribution.getBizStatus());
            DmsWenjianDayinRenwuEntity task = fenfaMapper.selectPrintTaskForUpdate(tenantId, taskId);
            if (task == null) throw new DataException("原打印任务不存在或已删除");
            tasks.put(taskId, task);
        }
        Date now = new Date();
        List<DmsWenjianBupinghengResultVO> result = new ArrayList<>();
        List<DmsWenjianDayinRenwuEntity> numberedReplacements = new ArrayList<>();
        for (Map.Entry<String, DmsWenjianBupinghengExecution.Item> entry : items.entrySet()) {
            String id = entry.getKey();
            DmsWenjianBupinghengExecution.Item item = entry.getValue();
            DmsWenjianBupinghengResultVO row = new DmsWenjianBupinghengResultVO();
            row.setDengjiId(documentId);
            String detailId = item.getId();
            row.setDayinJiluId(id);
            row.setCuoshi(item.getCuoshi());
            changed(mapper.voidPrint(tenantId, id, userId, now));
            if ("cuoshi1".equals(item.getCuoshi())) {
                DmsWenjianDayinRenwuEntity replacement = new DmsWenjianDayinRenwuEntity();
                BeanUtils.copyProperties(tasks.get(copies.get(id).getDayinRenwuId()), replacement);
                replacement.setId(RandomUtil.uuId());
                replacement.setLaiyuanLeixing("wenjian_bupingheng");
                replacement.setLaiyuanDanjuId(row.getDengjiId());
                replacement.setLaiyuanMingxiId(detailId);
                replacement.setDayinFenshu(1);
                replacement.setBizStatus("daidayin");
                replacement.setCreatorUserId(userId);
                replacement.setCreatorTime(now);
                replacement.setLastModifyUserId(null);
                replacement.setLastModifyTime(null);
                replacement.setDeleteMark(null);
                changed(fenfaMapper.insertPrintTaskIfAbsent(replacement));
                numberedReplacements.add(replacement);
                row.setXinDayinRenwuId(replacement.getId());
            }
            if (row.getXinDayinRenwuId() != null)
                changed(mapper.linkReplacement(tenantId, detailId, documentId, row.getXinDayinRenwuId()));
            result.add(row);
        }
        numberedReplacements.sort(java.util.Comparator.comparing(DmsWenjianDayinRenwuEntity::getWenjianBianhao).thenComparing(DmsWenjianDayinRenwuEntity::getBanbenhao).thenComparing(DmsWenjianDayinRenwuEntity::getId));
        for (DmsWenjianDayinRenwuEntity replacement : numberedReplacements) numbers.ensureTaskNumbers(tenantId, replacement, 0);
        for (String taskId : taskIds) mapper.finishDistribution(tenantId, taskId, userId, now);
        changed(mapper.completeDocument(tenantId, documentId, now));
        if (expectedVersion != null) {
            Map<String, Object> extra = new LinkedHashMap<>();
            extra.put("dengji_ren_id", document.getDengjiRenId());
            extra.put("results", result);
            extra.put("trigger", "scheduled");
            systemAudit.record(AuditEventDTO.builder().eventType(AuditConsts.TYPE_BIZ_ACTION)
                    .actionCode("FILE_DISTRIBUTION_IMBALANCE_RETRY").actionLabel("定时补偿文件不平衡措施")
                    .tenantId(tenantId).operatorId("0").operatorName("系统定时任务")
                    .bizModule("文件分发").bizType("DMS_FILE_IMBALANCE").bizCode(documentId)
                    .targetTable("dms_wenjian_bupingheng_dengji").targetId(documentId)
                    .relatedSigns(Collections.singletonList(AuditRelatedSignDTO.builder()
                            .signId(document.getBizSign()).role("PRIMARY").build()))
                    .extra(JsonUtil.getObjectToString(extra)).build());
        }
        return result;
    }
 
    private DmsWenjianBupinghengResultVO result(String documentId, DmsWenjianBupinghengExecution.Item item) {
        DmsWenjianBupinghengResultVO row = new DmsWenjianBupinghengResultVO();
        row.setDengjiId(documentId);
        row.setDayinJiluId(item.getDayinJiluId());
        row.setCuoshi(item.getCuoshi());
        row.setXinDayinRenwuId(item.getXinDayinRenwuId());
        return row;
    }
    private void eligible(String status) throws DataException {
        if (!"daifafang".equals(status) && !"daiqianshou".equals(status))
            throw new DataException("仅待发放、待签收文件可执行此操作,请刷新列表");
    }
    private void changed(int count) throws DataException {
        if (count != 1) throw new DataException("文件状态已变化,登记单已保留,本次措施未执行,请核实后重试");
    }
    private boolean blank(String value) { return value == null || value.trim().isEmpty(); }
}