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(); }
|
}
|