package jnpf.elnService.impl;
|
|
import cn.hutool.core.util.IdUtil;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import jnpf.base.ActionResult;
|
import jnpf.base.ActionResultCode;
|
import jnpf.base.UserInfo;
|
import jnpf.database.util.TenantDataSourceUtil;
|
import jnpf.elnEntity.ElnDocumentFileVo;
|
import jnpf.elnEntity.field.ElnFieldForms;
|
import jnpf.elnEntity.field.ElnFieldVersionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFormulaCalculationResult;
|
import jnpf.elnEntity.runtime.ElnRecordForms;
|
import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRepeatValueEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordVos;
|
import jnpf.elnMapper.ElnFieldVersionMapper;
|
import jnpf.elnMapper.ElnRecordFieldValueMapper;
|
import jnpf.elnMapper.ElnRecordMapper;
|
import jnpf.elnMapper.ElnRecordRepeatRowMapper;
|
import jnpf.elnMapper.ElnRecordRepeatValueMapper;
|
import jnpf.elnMapper.ElnRecordRevisionMapper;
|
import jnpf.elnService.ElnTestWorkItemSnapshotProvider;
|
import jnpf.elnService.ElnRecordFormulaCalculationService;
|
import jnpf.elnService.ElnRecordRevisionService;
|
import jnpf.elnService.template.ElnTemplateFileService;
|
import jnpf.exception.DataException;
|
import jnpf.lims.api.model.TestWorkItemSnapshotBatch;
|
import jnpf.lims.api.model.TestWorkItemSnapshot;
|
import jnpf.lims.api.model.TestWorkItemSnapshotProperty;
|
import jnpf.model.tenant.TenantVO;
|
import jnpf.onlyoffice.RecordRevisionFenceApi;
|
import jnpf.util.JsonUtil;
|
import jnpf.util.UserProvider;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.util.StringUtils;
|
|
import java.text.SimpleDateFormat;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
|
@Service
|
public class ElnRecordRevisionServiceImpl implements ElnRecordRevisionService {
|
|
private static final Logger log = LoggerFactory.getLogger(ElnRecordRevisionServiceImpl.class);
|
|
private static final String ACTIVE = "ACTIVE";
|
private static final String PRIMARY = "PRIMARY";
|
private static final String DRAFT = "DRAFT";
|
private static final String HISTORICAL = "HISTORICAL";
|
private static final String PENDING_TEST = "pending_test";
|
private static final String AUDIT_REJECTED = "audit_rejected";
|
private static final String CANCELLED = "cancelled";
|
private static final String CROSS_TEMPLATE = "CROSS_TEMPLATE";
|
private static final String LIMS_TASK = "LIMS_TASK";
|
private static final String SYSTEM = "SYSTEM";
|
|
private final ElnTestWorkItemSnapshotProvider taskProvider;
|
private final ElnRecordMapper recordMapper;
|
private final ElnRecordRevisionMapper revisionMapper;
|
private final ElnRecordFieldValueMapper valueMapper;
|
private final ElnFieldVersionMapper fieldVersionMapper;
|
private final ElnRecordRepeatRowMapper repeatRowMapper;
|
private final ElnRecordRepeatValueMapper repeatValueMapper;
|
private final ElnTemplateFileService fileService;
|
private final ElnRecordFormulaCalculationService calculationService;
|
private final RecordRevisionFence revisionFence;
|
private final CurrentUserProvider currentUserProvider;
|
|
@Autowired
|
public ElnRecordRevisionServiceImpl(
|
ElnTestWorkItemSnapshotProvider taskProvider,
|
ElnRecordMapper recordMapper,
|
ElnRecordRevisionMapper revisionMapper,
|
ElnRecordFieldValueMapper valueMapper,
|
ElnFieldVersionMapper fieldVersionMapper,
|
ElnRecordRepeatRowMapper repeatRowMapper,
|
ElnRecordRepeatValueMapper repeatValueMapper,
|
ElnTemplateFileService fileService,
|
ElnRecordFormulaCalculationService calculationService,
|
RecordRevisionFenceApi revisionFenceApi) {
|
this(taskProvider, recordMapper, revisionMapper, valueMapper, fieldVersionMapper,
|
repeatRowMapper, repeatValueMapper,
|
fileService, calculationService, remoteFence(revisionFenceApi),
|
ElnRecordRevisionServiceImpl::currentUser);
|
}
|
|
ElnRecordRevisionServiceImpl(
|
ElnTestWorkItemSnapshotProvider taskProvider,
|
ElnRecordMapper recordMapper,
|
ElnRecordRevisionMapper revisionMapper,
|
ElnRecordFieldValueMapper valueMapper,
|
ElnFieldVersionMapper fieldVersionMapper,
|
ElnRecordRepeatRowMapper repeatRowMapper,
|
ElnRecordRepeatValueMapper repeatValueMapper,
|
ElnTemplateFileService fileService,
|
ElnRecordFormulaCalculationService calculationService,
|
RecordRevisionFence revisionFence,
|
CurrentUserProvider currentUserProvider) {
|
this.taskProvider = taskProvider;
|
this.recordMapper = recordMapper;
|
this.revisionMapper = revisionMapper;
|
this.valueMapper = valueMapper;
|
this.fieldVersionMapper = fieldVersionMapper;
|
this.repeatRowMapper = repeatRowMapper;
|
this.repeatValueMapper = repeatValueMapper;
|
this.fileService = fileService;
|
this.calculationService = calculationService;
|
this.revisionFence = revisionFence;
|
this.currentUserProvider = currentUserProvider;
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public ElnRecordVos.Created open(ElnRecordForms.Create command) {
|
String testWorkItemId = required(
|
command == null ? null : command.getTestWorkItemId(), "检验项不能为空");
|
String operationCode = required(
|
command == null ? null : command.getOperationCode(), "操作码不能为空");
|
UserContext user = currentUserProvider.get();
|
TestWorkItemSnapshot snapshot = snapshot(user, testWorkItemId);
|
ElnRecordEntity observed = recordMapper.selectLiveByTestWorkItem(
|
user.tenantId, testWorkItemId);
|
if (observed == null) {
|
throw new DataException("电子记录不存在");
|
}
|
ElnRecordEntity record = recordMapper.selectLiveForUpdate(user.tenantId, observed.getId());
|
if (record == null || !testWorkItemId.equals(record.getJianyanXiangId())
|
|| !StringUtils.hasText(record.getDangqianXiudingId())) {
|
throw new DataException("电子记录不存在或当前修订无效");
|
}
|
ElnRecordRevisionEntity current = currentRevision(user.tenantId, record);
|
ElnRecordRevisionEntity idempotent = revisionByOperation(
|
user.tenantId, record.getId(), operationCode);
|
if (idempotent != null) {
|
if (!Objects.equals(current.getId(), idempotent.getId())) {
|
throw new DataException("操作码已用于其他电子记录修订");
|
}
|
return created(record, idempotent, true);
|
}
|
if (PENDING_TEST.equals(snapshot.getTaskStatus())) {
|
if (!DRAFT.equals(current.getBizStatus())) {
|
throw new DataException("电子记录当前修订不是可编辑草稿");
|
}
|
return created(record, current, true);
|
}
|
if (!AUDIT_REJECTED.equals(snapshot.getTaskStatus())
|
&& !CANCELLED.equals(snapshot.getTaskStatus())) {
|
throw new DataException("检验项当前状态不允许创建电子记录修订");
|
}
|
if (DRAFT.equals(current.getBizStatus())
|
&& StringUtils.hasText(current.getLaiyuanXiudingId())) {
|
return created(record, current, true);
|
}
|
return createRevision(record, current, snapshot, operationCode, user);
|
}
|
|
private ElnRecordVos.Created createRevision(
|
ElnRecordEntity record, ElnRecordRevisionEntity source,
|
TestWorkItemSnapshot snapshot, String operationCode, UserContext user) {
|
if (!DRAFT.equals(source.getBizStatus()) && !HISTORICAL.equals(source.getBizStatus())) {
|
throw new DataException("电子记录来源修订状态无效");
|
}
|
if (source.getXiudingHao() == null || source.getXiudingHao() < 1) {
|
throw new DataException("电子记录来源修订号无效");
|
}
|
String sourceFileId = required(source.getJiluWendang(),
|
"电子记录来源修订缺少文档");
|
FenceLease lease = acquireFence(user.tenantId, record.getId(), sourceFileId);
|
boolean completionRegistered = registerFenceCompletion(lease);
|
try {
|
List<ElnRecordFieldValueEntity> sourceValues = sourceValues(
|
user.tenantId, record, source);
|
RepeatSnapshot repeatSnapshot = sourceRepeatSnapshot(
|
user.tenantId, record, source);
|
if (DRAFT.equals(source.getBizStatus())
|
&& revisionMapper.markHistorical(user.tenantId, record.getId(), source.getId(),
|
source.getFVersion(), user.userId) != 1) {
|
throw new DataException("电子记录来源修订状态冲突");
|
}
|
|
String revisionId = IdUtil.getSnowflakeNextIdStr();
|
Date now = new Date();
|
ElnDocumentFileVo document = copiedDocument(fileService.copyDocx(
|
sourceDocument(sourceFileId), revisionId));
|
ElnRecordRevisionEntity revision = revision(
|
revisionId, record.getId(), source, operationCode,
|
document.getFileId(), user, now);
|
if (revisionMapper.insert(revision) != 1) {
|
throw new DataException("电子记录新修订创建冲突");
|
}
|
List<ElnRecordFieldValueEntity> values = new ArrayList<>(sourceValues.size());
|
for (ElnRecordFieldValueEntity sourceValue : sourceValues) {
|
ElnRecordFieldValueEntity value = copyValue(
|
sourceValue, revisionId, snapshot, user, now);
|
if (valueMapper.insert(value) != 1) {
|
throw new DataException("电子记录新修订字段值复制失败");
|
}
|
values.add(value);
|
}
|
copyRepeatSnapshot(repeatSnapshot, revisionId, user, now);
|
if (recordMapper.switchCurrentRevision(
|
user.tenantId, record.getId(), source.getId(), revisionId,
|
record.getFVersion(), user.userId) != 1) {
|
throw new DataException("电子记录当前修订切换冲突");
|
}
|
record.setDangqianXiudingId(revisionId);
|
record.setFVersion(record.getFVersion() + 1);
|
ElnRecordFormulaCalculationResult calculated =
|
calculationService.recalculateAllWithRepeats(
|
user.tenantId, record, revision, values, operationCode, user.userId);
|
for (ElnRecordFieldValueEntity value : calculated.getFieldValues()) {
|
if (valueMapper.updateById(value) != 1) {
|
throw new DataException("电子记录新修订公式结果保存失败");
|
}
|
}
|
for (ElnRecordRepeatValueEntity value : calculated.getRepeatValues()) {
|
if (repeatValueMapper.updateById(value) != 1) {
|
throw new DataException("电子记录新修订重复公式结果保存失败");
|
}
|
}
|
if (!completionRegistered) {
|
revisionFence.complete(lease);
|
}
|
return created(record, revision, false);
|
} catch (RuntimeException e) {
|
if (!completionRegistered) {
|
releaseFence(lease, e);
|
}
|
throw e;
|
}
|
}
|
|
private FenceLease acquireFence(String tenantId, String recordId, String fileId) {
|
String fileType = "annex";
|
String token = revisionFence.acquire(tenantId, recordId, fileId, fileType);
|
if (!StringUtils.hasText(token)) {
|
throw new DataException("无法锁定 OnlyOffice 历史文档,请稍后重试");
|
}
|
return new FenceLease(tenantId, recordId, fileId, fileType, token);
|
}
|
|
private boolean registerFenceCompletion(FenceLease lease) {
|
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
|
return false;
|
}
|
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
@Override
|
public void afterCompletion(int status) {
|
try {
|
if (status == TransactionSynchronization.STATUS_COMMITTED) {
|
revisionFence.complete(lease);
|
} else {
|
revisionFence.release(lease);
|
}
|
} catch (RuntimeException e) {
|
log.error("OnlyOffice 记录修订 fence 收尾失败,record={} file={} committed={}",
|
lease.recordId, lease.fileId,
|
status == TransactionSynchronization.STATUS_COMMITTED, e);
|
}
|
}
|
});
|
return true;
|
}
|
|
private void releaseFence(FenceLease lease, RuntimeException original) {
|
try {
|
revisionFence.release(lease);
|
} catch (RuntimeException releaseError) {
|
original.addSuppressed(releaseError);
|
}
|
}
|
|
private List<ElnRecordFieldValueEntity> sourceValues(
|
String tenantId, ElnRecordEntity record, ElnRecordRevisionEntity revision) {
|
List<ElnRecordFieldValueEntity> values = valueMapper.selectList(
|
Wrappers.<ElnRecordFieldValueEntity>lambdaQuery()
|
.eq(ElnRecordFieldValueEntity::getFTenantId, tenantId)
|
.eq(ElnRecordFieldValueEntity::getDianziJiluId, record.getId())
|
.eq(ElnRecordFieldValueEntity::getXiudingId, revision.getId())
|
.and(q -> q.isNull(ElnRecordFieldValueEntity::getFDeleteMark)
|
.or().eq(ElnRecordFieldValueEntity::getFDeleteMark, 0))
|
.orderByAsc(ElnRecordFieldValueEntity::getId));
|
if (values == null || values.isEmpty()) {
|
throw new DataException("电子记录来源修订没有字段值");
|
}
|
Set<String> bindingIds = new LinkedHashSet<>();
|
for (ElnRecordFieldValueEntity value : values) {
|
if (!Objects.equals(record.getMubanId(), value.getMubanId())
|
|| !Objects.equals(revision.getId(), value.getXiudingId())
|
|| !PRIMARY.equals(value.getBangdingJuese())
|
|| !ACTIVE.equals(value.getBangdingZhuangtai())
|
|| !bindingIds.add(value.getMubanBangdingId())) {
|
throw new DataException("电子记录来源修订字段值与固定模板不一致");
|
}
|
}
|
return values;
|
}
|
|
private RepeatSnapshot sourceRepeatSnapshot(
|
String tenantId, ElnRecordEntity record, ElnRecordRevisionEntity revision) {
|
List<ElnRecordRepeatRowEntity> rows = repeatRowMapper.selectList(
|
Wrappers.<ElnRecordRepeatRowEntity>lambdaQuery()
|
.eq(ElnRecordRepeatRowEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRepeatRowEntity::getDianziJiluId, record.getId())
|
.eq(ElnRecordRepeatRowEntity::getXiudingId, revision.getId())
|
.orderByAsc(ElnRecordRepeatRowEntity::getRepeatGroupCode)
|
.orderByAsc(ElnRecordRepeatRowEntity::getRowOrder)
|
.orderByAsc(ElnRecordRepeatRowEntity::getId));
|
List<ElnRecordRepeatValueEntity> values = repeatValueMapper.selectList(
|
Wrappers.<ElnRecordRepeatValueEntity>lambdaQuery()
|
.eq(ElnRecordRepeatValueEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRepeatValueEntity::getDianziJiluId, record.getId())
|
.eq(ElnRecordRepeatValueEntity::getXiudingId, revision.getId())
|
.orderByAsc(ElnRecordRepeatValueEntity::getRepeatGroupCode)
|
.orderByAsc(ElnRecordRepeatValueEntity::getRepeatRowId)
|
.orderByAsc(ElnRecordRepeatValueEntity::getId));
|
rows = rows == null ? Collections.emptyList() : rows;
|
values = values == null ? Collections.emptyList() : values;
|
|
Map<String, ElnRecordRepeatRowEntity> rowsByIdentity = new LinkedHashMap<>();
|
Set<String> operationCodes = new LinkedHashSet<>();
|
for (ElnRecordRepeatRowEntity row : rows) {
|
String key = repeatRowKey(row == null ? null : row.getRepeatGroupCode(),
|
row == null ? null : row.getRowId());
|
if (row == null
|
|| !Objects.equals(record.getId(), row.getDianziJiluId())
|
|| !Objects.equals(revision.getId(), row.getXiudingId())
|
|| !Objects.equals(record.getMubanId(), row.getMubanId())
|
|| !StringUtils.hasText(row.getRepeatGroupCode())
|
|| !StringUtils.hasText(row.getRowId())
|
|| row.getRowOrder() == null || row.getRowOrder() < 1
|
|| !StringUtils.hasText(row.getOperationCode())
|
|| !validRepeatRowLifecycle(row)
|
|| rowsByIdentity.put(key, row) != null
|
|| !operationCodes.add(row.getOperationCode())) {
|
throw new DataException("电子记录来源修订重复行快照无效");
|
}
|
}
|
|
Set<String> cells = new LinkedHashSet<>();
|
for (ElnRecordRepeatValueEntity value : values) {
|
String rowKey = repeatRowKey(value == null ? null : value.getRepeatGroupCode(),
|
value == null ? null : value.getRepeatRowId());
|
ElnRecordRepeatRowEntity row = rowsByIdentity.get(rowKey);
|
String cellKey = rowKey + "\n" + (value == null ? null : value.getMubanBangdingId());
|
if (value == null || row == null
|
|| !Objects.equals(record.getId(), value.getDianziJiluId())
|
|| !Objects.equals(revision.getId(), value.getXiudingId())
|
|| !Objects.equals(record.getMubanId(), value.getMubanId())
|
|| !Objects.equals("REPEAT", value.getBangdingJuese())
|
|| !Objects.equals(ACTIVE, value.getBangdingZhuangtai())
|
|| !StringUtils.hasText(value.getMubanBangdingId())
|
|| !Objects.equals(deleted(row), deleted(value))
|
|| !cells.add(cellKey)) {
|
throw new DataException("电子记录来源修订重复字段值快照无效");
|
}
|
}
|
return new RepeatSnapshot(rows, values);
|
}
|
|
private void copyRepeatSnapshot(
|
RepeatSnapshot snapshot, String revisionId, UserContext user, Date now) {
|
for (ElnRecordRepeatRowEntity source : snapshot.rows) {
|
ElnRecordRepeatRowEntity row = copyRepeatRow(source, revisionId, user, now);
|
if (repeatRowMapper.insert(row) != 1) {
|
throw new DataException("电子记录新修订重复行复制失败");
|
}
|
}
|
for (ElnRecordRepeatValueEntity source : snapshot.values) {
|
ElnRecordRepeatValueEntity value = copyRepeatValue(source, revisionId, user, now);
|
if (repeatValueMapper.insert(value) != 1) {
|
throw new DataException("电子记录新修订重复字段值复制失败");
|
}
|
}
|
}
|
|
private static ElnRecordRepeatRowEntity copyRepeatRow(
|
ElnRecordRepeatRowEntity source, String revisionId, UserContext user, Date now) {
|
ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity();
|
row.setId(IdUtil.getSnowflakeNextIdStr());
|
row.setDianziJiluId(source.getDianziJiluId());
|
row.setXiudingId(revisionId);
|
row.setMubanId(source.getMubanId());
|
row.setRepeatGroupCode(source.getRepeatGroupCode());
|
row.setRowId(source.getRowId());
|
row.setRowOrder(source.getRowOrder());
|
row.setOperationCode(source.getOperationCode());
|
row.setOperationStatus(source.getOperationStatus());
|
row.setDeleteOperationCode(source.getDeleteOperationCode());
|
row.setDeleteReason(source.getDeleteReason());
|
row.setDeleteRequestedUserId(source.getDeleteRequestedUserId());
|
row.setDeleteRequestedTime(source.getDeleteRequestedTime());
|
row.setFTenantId(user.tenantId);
|
row.setFVersion(0);
|
row.setFCreatorUserId(user.userId);
|
row.setFCreatorTime(now);
|
row.setFLastModifyUserId(user.userId);
|
row.setFLastModifyTime(now);
|
row.setFDeleteUserId(source.getFDeleteUserId());
|
row.setFDeleteTime(source.getFDeleteTime());
|
row.setFDeleteMark(source.getFDeleteMark());
|
return row;
|
}
|
|
private static ElnRecordRepeatValueEntity copyRepeatValue(
|
ElnRecordRepeatValueEntity source, String revisionId, UserContext user, Date now) {
|
ElnRecordRepeatValueEntity value = new ElnRecordRepeatValueEntity();
|
value.setId(IdUtil.getSnowflakeNextIdStr());
|
value.setDianziJiluId(source.getDianziJiluId());
|
value.setXiudingId(revisionId);
|
value.setMubanId(source.getMubanId());
|
value.setRepeatRowId(source.getRepeatRowId());
|
value.setRepeatGroupCode(source.getRepeatGroupCode());
|
value.setMubanBangdingId(source.getMubanBangdingId());
|
value.setZiduanDingyiId(source.getZiduanDingyiId());
|
value.setZiduanBianma(source.getZiduanBianma());
|
value.setZiduanBanbenId(source.getZiduanBanbenId());
|
value.setBangdingJuese(source.getBangdingJuese());
|
value.setBangdingZhuangtai(source.getBangdingZhuangtai());
|
value.setZhiWenben(source.getZhiWenben());
|
value.setZhiShuzhi(source.getZhiShuzhi());
|
value.setZhiRiqi(source.getZhiRiqi());
|
value.setZhiLaiyuan(source.getZhiLaiyuan());
|
value.setJisuanZhuangtai(source.getJisuanZhuangtai());
|
value.setJisuanXiaoxi(source.getJisuanXiaoxi());
|
value.setJisuanShijian(source.getJisuanShijian());
|
value.setFTenantId(user.tenantId);
|
value.setFVersion(0);
|
value.setFCreatorUserId(user.userId);
|
value.setFCreatorTime(now);
|
value.setFLastModifyUserId(user.userId);
|
value.setFLastModifyTime(now);
|
value.setFDeleteUserId(source.getFDeleteUserId());
|
value.setFDeleteTime(source.getFDeleteTime());
|
value.setFDeleteMark(source.getFDeleteMark());
|
return value;
|
}
|
|
private static boolean validRepeatRowLifecycle(ElnRecordRepeatRowEntity row) {
|
String status = row.getOperationStatus();
|
boolean deleted = deleted(row);
|
if ("DELETED".equals(status)) {
|
return deleted && hasDeleteIntent(row)
|
&& StringUtils.hasText(row.getFDeleteUserId())
|
&& row.getFDeleteTime() != null;
|
}
|
if ("DELETE_PREPARED".equals(status)) {
|
return !deleted && hasDeleteIntent(row);
|
}
|
if (ACTIVE.equals(status) || "PREPARED".equals(status)
|
|| "CANCELLED".equals(status)) {
|
return !deleted && !hasAnyDeleteIntent(row);
|
}
|
return false;
|
}
|
|
private static boolean hasDeleteIntent(ElnRecordRepeatRowEntity row) {
|
return StringUtils.hasText(row.getDeleteOperationCode())
|
&& StringUtils.hasText(row.getDeleteReason())
|
&& StringUtils.hasText(row.getDeleteRequestedUserId())
|
&& row.getDeleteRequestedTime() != null;
|
}
|
|
private static boolean hasAnyDeleteIntent(ElnRecordRepeatRowEntity row) {
|
return row.getDeleteOperationCode() != null || row.getDeleteReason() != null
|
|| row.getDeleteRequestedUserId() != null || row.getDeleteRequestedTime() != null;
|
}
|
|
private static boolean deleted(ElnRecordRepeatRowEntity row) {
|
return Objects.equals(row.getFDeleteMark(), 1);
|
}
|
|
private static boolean deleted(ElnRecordRepeatValueEntity value) {
|
return Objects.equals(value.getFDeleteMark(), 1);
|
}
|
|
private static String repeatRowKey(String groupCode, String rowId) {
|
return groupCode + "\n" + rowId;
|
}
|
|
private ElnRecordFieldValueEntity copyValue(
|
ElnRecordFieldValueEntity source, String revisionId,
|
TestWorkItemSnapshot snapshot, UserContext user, Date now) {
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
value.setId(IdUtil.getSnowflakeNextIdStr());
|
value.setDianziJiluId(source.getDianziJiluId());
|
value.setXiudingId(revisionId);
|
value.setMubanId(source.getMubanId());
|
value.setMubanBangdingId(source.getMubanBangdingId());
|
value.setZiduanDingyiId(source.getZiduanDingyiId());
|
value.setZiduanBianma(source.getZiduanBianma());
|
value.setZiduanBanbenId(source.getZiduanBanbenId());
|
value.setBangdingJuese(source.getBangdingJuese());
|
value.setBangdingZhuangtai(source.getBangdingZhuangtai());
|
value.setZhiWenben(source.getZhiWenben());
|
value.setZhiShuzhi(source.getZhiShuzhi());
|
value.setZhiRiqi(source.getZhiRiqi());
|
value.setZhiLaiyuan(source.getZhiLaiyuan());
|
value.setYuanShuxing(source.getYuanShuxing());
|
value.setYuanShuxingBanben(source.getYuanShuxingBanben());
|
value.setYuanYinyongLeixing(source.getYuanYinyongLeixing());
|
value.setYuanYinyongId(source.getYuanYinyongId());
|
value.setYuanHuoquShijian(source.getYuanHuoquShijian());
|
value.setYuanQuzhiZhuangtai(source.getYuanQuzhiZhuangtai());
|
value.setYuanQuzhiYuanyin(source.getYuanQuzhiYuanyin());
|
value.setYuanXueyuan(source.getYuanXueyuan());
|
value.setKuamoLaiyuanJiluId(source.getKuamoLaiyuanJiluId());
|
value.setKuamoLaiyuanXiudingId(source.getKuamoLaiyuanXiudingId());
|
value.setKuamoLaiyuanZhiId(source.getKuamoLaiyuanZhiId());
|
value.setKuamoLaiyuanZiduanBanbenId(source.getKuamoLaiyuanZiduanBanbenId());
|
value.setKuamoHuoquShijian(source.getKuamoHuoquShijian());
|
value.setJisuanZhuangtai(source.getJisuanZhuangtai());
|
value.setJisuanXiaoxi(source.getJisuanXiaoxi());
|
value.setJisuanShijian(source.getJisuanShijian());
|
value.setFTenantId(user.tenantId);
|
value.setFVersion(0);
|
value.setFDeleteMark(0);
|
value.setFCreatorUserId(user.userId);
|
value.setFLastModifyUserId(user.userId);
|
value.setFCreatorTime(now);
|
value.setFLastModifyTime(now);
|
|
if (CROSS_TEMPLATE.equals(source.getZhiLaiyuan())) {
|
return value;
|
}
|
if (source.getYuanShuxingBanben() != null) {
|
return value;
|
}
|
ElnFieldVersionEntity version = fixedVersion(user.tenantId, source);
|
ElnFieldForms.Binding binding = parseBinding(version.getBangdingPeizhi());
|
String provider = binding == null || binding.getRead() == null
|
? null : binding.getRead().getSource();
|
String property = binding == null || binding.getRead() == null
|
? null : binding.getRead().getProperty();
|
if (LIMS_TASK.equals(provider)) {
|
TestWorkItemSnapshotProperty taskValue = taskProperty(snapshot, property);
|
if (!Objects.equals(version.getShujuLeixing(), taskValue.getDataType())) {
|
throw new DataException("LIMS 任务快照字段类型与固定字段版本不一致");
|
}
|
value.setZhiWenben(taskValue.getValue());
|
value.setZhiShuzhi(null);
|
value.setZhiRiqi(null);
|
value.setZhiLaiyuan(LIMS_TASK);
|
value.setYuanShuxing(property);
|
value.setYuanYinyongLeixing(taskValue.getSource());
|
value.setYuanYinyongId(taskValue.getSourceRef());
|
value.setYuanHuoquShijian(now);
|
} else if (SYSTEM.equals(provider)) {
|
applySystemValue(value, property, user, now);
|
}
|
return value;
|
}
|
|
private ElnFieldVersionEntity fixedVersion(
|
String tenantId, ElnRecordFieldValueEntity value) {
|
ElnFieldVersionEntity version = fieldVersionMapper.selectOne(
|
Wrappers.<ElnFieldVersionEntity>lambdaQuery()
|
.eq(ElnFieldVersionEntity::getFTenantId, tenantId)
|
.eq(ElnFieldVersionEntity::getId, value.getZiduanBanbenId())
|
.and(q -> q.isNull(ElnFieldVersionEntity::getFDeleteMark)
|
.or().eq(ElnFieldVersionEntity::getFDeleteMark, 0))
|
.last("limit 1"));
|
if (version == null || !Objects.equals(
|
value.getZiduanDingyiId(), version.getZiduanDingyiId())) {
|
throw new DataException("电子记录固定字段版本无效");
|
}
|
return version;
|
}
|
|
private TestWorkItemSnapshot snapshot(UserContext user, String testWorkItemId) {
|
TestWorkItemSnapshotBatch batch = taskProvider.resolveAsActor(
|
user.tenantId, user.userId,
|
Collections.singletonList(testWorkItemId),
|
Collections.singletonList("test.basis"));
|
if (batch == null || batch.getItems() == null || batch.getItems().size() != 1) {
|
throw new DataException("LIMS 检验任务快照返回无效");
|
}
|
TestWorkItemSnapshot item = batch.getItems().get(0);
|
if (item == null || !testWorkItemId.equals(item.getTestWorkItemId())
|
|| !"SUCCESS".equals(item.getOutcomeCode())) {
|
throw new DataException(item != null && StringUtils.hasText(item.getReason())
|
? item.getReason() : "当前用户或检验项状态不允许打开电子记录");
|
}
|
return item;
|
}
|
|
private ElnRecordRevisionEntity currentRevision(String tenantId, ElnRecordEntity record) {
|
ElnRecordRevisionEntity revision = revisionMapper.selectOne(
|
Wrappers.<ElnRecordRevisionEntity>lambdaQuery()
|
.eq(ElnRecordRevisionEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRevisionEntity::getId, record.getDangqianXiudingId())
|
.eq(ElnRecordRevisionEntity::getDianziJiluId, record.getId())
|
.and(q -> q.isNull(ElnRecordRevisionEntity::getFDeleteMark)
|
.or().eq(ElnRecordRevisionEntity::getFDeleteMark, 0))
|
.last("limit 1"));
|
if (revision == null) {
|
throw new DataException("电子记录当前修订不存在");
|
}
|
return revision;
|
}
|
|
private ElnRecordRevisionEntity revisionByOperation(
|
String tenantId, String recordId, String operationCode) {
|
return revisionMapper.selectOne(Wrappers.<ElnRecordRevisionEntity>lambdaQuery()
|
.eq(ElnRecordRevisionEntity::getFTenantId, tenantId)
|
.eq(ElnRecordRevisionEntity::getDianziJiluId, recordId)
|
.eq(ElnRecordRevisionEntity::getXiudingCaozuoBianma, operationCode)
|
.and(q -> q.isNull(ElnRecordRevisionEntity::getFDeleteMark)
|
.or().eq(ElnRecordRevisionEntity::getFDeleteMark, 0))
|
.last("limit 1"));
|
}
|
|
private static ElnRecordRevisionEntity revision(
|
String revisionId, String recordId, ElnRecordRevisionEntity source,
|
String operationCode, String document, UserContext user, Date now) {
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId(revisionId);
|
revision.setDianziJiluId(recordId);
|
revision.setXiudingHao(source.getXiudingHao() + 1);
|
revision.setLaiyuanXiudingId(source.getId());
|
revision.setXiudingCaozuoBianma(operationCode);
|
revision.setJiluWendang(document);
|
revision.setBizStatus(DRAFT);
|
revision.setFTenantId(user.tenantId);
|
revision.setFVersion(0);
|
revision.setFDeleteMark(0);
|
revision.setFCreatorUserId(user.userId);
|
revision.setFLastModifyUserId(user.userId);
|
revision.setFCreatorTime(now);
|
revision.setFLastModifyTime(now);
|
return revision;
|
}
|
|
private static void applySystemValue(
|
ElnRecordFieldValueEntity value, String property, UserContext user, Date now) {
|
value.setZhiWenben(null);
|
value.setZhiShuzhi(null);
|
value.setZhiRiqi(null);
|
value.setZhiLaiyuan(SYSTEM);
|
value.setYuanShuxing(property);
|
value.setYuanYinyongLeixing(SYSTEM);
|
value.setYuanYinyongId(user.userId);
|
value.setYuanHuoquShijian(now);
|
if ("current_user.id".equals(property)) {
|
value.setZhiWenben(user.userId);
|
} else if ("current_user.real_name".equals(property)) {
|
value.setZhiWenben(user.userName);
|
} else if ("current_date".equals(property)) {
|
value.setZhiWenben(new SimpleDateFormat("yyyy-MM-dd").format(now));
|
value.setZhiRiqi(now);
|
} else if ("current_datetime".equals(property)) {
|
value.setZhiWenben(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(now));
|
value.setZhiRiqi(now);
|
} else {
|
throw new DataException("未支持的 SYSTEM property:" + property);
|
}
|
}
|
|
private static TestWorkItemSnapshotProperty taskProperty(
|
TestWorkItemSnapshot item, String property) {
|
if (item.getProperties() != null) {
|
for (TestWorkItemSnapshotProperty candidate : item.getProperties()) {
|
if (candidate != null && Objects.equals(property, candidate.getProperty())) {
|
if (!"SUCCESS".equals(candidate.getOutcomeCode())) {
|
throw new DataException(StringUtils.hasText(candidate.getReason())
|
? candidate.getReason() : "LIMS 任务快照字段不可用");
|
}
|
return candidate;
|
}
|
}
|
}
|
throw new DataException("LIMS 任务快照缺少字段:" + property);
|
}
|
|
private static ElnFieldForms.Binding parseBinding(String json) {
|
try {
|
return StringUtils.hasText(json)
|
? JsonUtil.getJsonToBean(json, ElnFieldForms.Binding.class) : null;
|
} catch (RuntimeException e) {
|
throw new DataException("字段绑定配置无效");
|
}
|
}
|
|
private static String sourceDocument(String fileId) {
|
if (!StringUtils.hasText(fileId)) {
|
throw new DataException("电子记录来源修订缺少文档");
|
}
|
ElnDocumentFileVo file = new ElnDocumentFileVo();
|
file.setFileId(fileId);
|
file.setName(fileId);
|
file.setFileExtension("docx");
|
return JsonUtil.getObjectToString(Collections.singletonList(file));
|
}
|
|
private static ElnDocumentFileVo copiedDocument(String attachments) {
|
try {
|
List<ElnDocumentFileVo> files = JsonUtil.getJsonToList(attachments, ElnDocumentFileVo.class);
|
if (files == null || files.size() != 1 || files.get(0) == null
|
|| !StringUtils.hasText(files.get(0).getFileId())) {
|
throw new DataException("独占电子记录文档复制结果无效");
|
}
|
return files.get(0);
|
} catch (DataException e) {
|
throw e;
|
} catch (RuntimeException e) {
|
throw new DataException("独占电子记录文档复制结果无效");
|
}
|
}
|
|
private static ElnRecordVos.Created created(
|
ElnRecordEntity record, ElnRecordRevisionEntity revision, boolean existing) {
|
ElnRecordVos.Created result = new ElnRecordVos.Created();
|
result.setRecordId(record.getId());
|
result.setRevisionId(revision.getId());
|
result.setDocument(document(revision.getJiluWendang()));
|
result.setExisting(existing);
|
return result;
|
}
|
|
private static ElnDocumentFileVo document(String fileId) {
|
if (!StringUtils.hasText(fileId)) {
|
throw new DataException("电子记录当前修订缺少文档");
|
}
|
ElnDocumentFileVo document = new ElnDocumentFileVo();
|
document.setFileId(fileId);
|
document.setName(fileId);
|
document.setFileExtension("docx");
|
return document;
|
}
|
|
private static String required(String value, String message) {
|
if (!StringUtils.hasText(value)) {
|
throw new DataException(message);
|
}
|
return value.trim();
|
}
|
|
private static UserContext currentUser() {
|
UserInfo user = UserProvider.getUser();
|
if (user == null || !StringUtils.hasText(user.getUserId())) {
|
throw new DataException("当前登录用户无效");
|
}
|
TenantVO routed = TenantDataSourceUtil.getTenantInfo();
|
String tenantId = routed != null && StringUtils.hasText(routed.getEnCode())
|
? routed.getEnCode().trim() : user.getTenantId();
|
return new UserContext(
|
StringUtils.hasText(tenantId) ? tenantId.trim() : "0",
|
user.getUserId().trim(), user.getUserName());
|
}
|
|
private static RecordRevisionFence remoteFence(RecordRevisionFenceApi api) {
|
return new RecordRevisionFence() {
|
@Override
|
public String acquire(String tenantId, String recordId,
|
String fileId, String fileType) {
|
ActionResult<String> result = api.acquire(tenantId, recordId, fileId, fileType);
|
if (result == null
|
|| !ActionResultCode.SUCCESS.getCode().equals(result.getCode())
|
|| !StringUtils.hasText(result.getData())) {
|
throw new DataException(result == null || !StringUtils.hasText(result.getMsg())
|
? "无法锁定 OnlyOffice 历史文档,请稍后重试" : result.getMsg());
|
}
|
return result.getData();
|
}
|
|
@Override
|
public void complete(FenceLease lease) {
|
requireFenceResult(api.complete(lease.tenantId, lease.recordId,
|
lease.fileId, lease.fileType, lease.token),
|
"OnlyOffice 历史文档标记失败,需人工恢复");
|
}
|
|
@Override
|
public void release(FenceLease lease) {
|
requireFenceResult(api.release(lease.tenantId, lease.recordId,
|
lease.fileId, lease.fileType, lease.token),
|
"OnlyOffice 记录修订锁释放失败,需人工恢复");
|
}
|
};
|
}
|
|
private static void requireFenceResult(ActionResult<Boolean> result, String fallback) {
|
if (result == null
|
|| !ActionResultCode.SUCCESS.getCode().equals(result.getCode())
|
|| !Boolean.TRUE.equals(result.getData())) {
|
throw new DataException(result == null || !StringUtils.hasText(result.getMsg())
|
? fallback : result.getMsg());
|
}
|
}
|
|
private static final class RepeatSnapshot {
|
private final List<ElnRecordRepeatRowEntity> rows;
|
private final List<ElnRecordRepeatValueEntity> values;
|
|
private RepeatSnapshot(List<ElnRecordRepeatRowEntity> rows,
|
List<ElnRecordRepeatValueEntity> values) {
|
this.rows = rows;
|
this.values = values;
|
}
|
}
|
|
@FunctionalInterface
|
interface CurrentUserProvider {
|
UserContext get();
|
}
|
|
interface RecordRevisionFence {
|
String acquire(String tenantId, String recordId, String fileId, String fileType);
|
|
void complete(FenceLease lease);
|
|
void release(FenceLease lease);
|
|
}
|
|
static final class FenceLease {
|
private final String tenantId;
|
private final String recordId;
|
private final String fileId;
|
private final String fileType;
|
private final String token;
|
|
FenceLease(String tenantId, String recordId, String fileId,
|
String fileType, String token) {
|
this.tenantId = tenantId;
|
this.recordId = recordId;
|
this.fileId = fileId;
|
this.fileType = fileType;
|
this.token = token;
|
}
|
}
|
|
static final class UserContext {
|
private final String tenantId;
|
private final String userId;
|
private final String userName;
|
|
UserContext(String tenantId, String userId, String userName) {
|
this.tenantId = tenantId;
|
this.userId = userId;
|
this.userName = userName;
|
}
|
}
|
}
|