package jnpf.elnService.template.impl;
|
|
import cn.hutool.core.util.IdUtil;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.audit.sdk.AuditClient;
|
import jnpf.base.UserInfo;
|
import jnpf.database.util.TenantDataSourceUtil;
|
import jnpf.elnEntity.ElnDocumentFileVo;
|
import jnpf.elnEntity.field.ElnFieldDefEntity;
|
import jnpf.elnEntity.field.ElnFieldEnums;
|
import jnpf.elnEntity.field.ElnFieldForms;
|
import jnpf.elnEntity.field.ElnFieldVersionEntity;
|
import jnpf.elnEntity.field.ElnFieldVos;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateEnums;
|
import jnpf.elnEntity.template.ElnTemplateForms;
|
import jnpf.elnEntity.template.ElnTemplateVersionEntity;
|
import jnpf.elnEntity.template.ElnTemplateVos;
|
import jnpf.elnMapper.ElnTemplateMapper;
|
import jnpf.elnMapper.ElnFieldDefMapper;
|
import jnpf.elnMapper.ElnFieldVersionMapper;
|
import jnpf.elnMapper.ElnTemplateBindingMapper;
|
import jnpf.elnService.ElnFieldCatalogService;
|
import jnpf.elnService.template.ElnTemplateBindingConfirmationFacade;
|
import jnpf.elnService.template.ElnTemplateBindingDependencyValidator;
|
import jnpf.elnService.template.ElnTemplateBindingLifecycleParticipant;
|
import jnpf.elnService.template.ElnTemplateBindingService;
|
import jnpf.elnService.template.ElnTemplateCheckboxGroupService;
|
import jnpf.elnService.template.ElnTemplateDocumentEvidence;
|
import jnpf.elnService.template.ElnTemplateOperationException;
|
import jnpf.elnService.template.ElnPendingBindingCleanupService;
|
import jnpf.elnService.template.ElnTemplateRepeatGroupService;
|
import jnpf.elnService.template.ElnTemplateVersionCopyParticipant;
|
import jnpf.elnService.template.ElnTemplateStepService;
|
import jnpf.elnService.template.TemplateAnnotationAuthorization;
|
import jnpf.elnService.template.docx.DocxControlConfigMatcher;
|
import jnpf.elnService.template.docx.DocxExpectedControlConfig;
|
import jnpf.elnService.template.docx.DocxControlSnapshot;
|
import jnpf.exception.DataException;
|
import jnpf.model.tenant.TenantVO;
|
import jnpf.util.JsonUtil;
|
import jnpf.util.UserProvider;
|
import org.springframework.beans.factory.ObjectProvider;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.dao.DuplicateKeyException;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
import java.nio.charset.StandardCharsets;
|
import java.security.MessageDigest;
|
import java.security.NoSuchAlgorithmException;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.Comparator;
|
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;
|
import java.util.UUID;
|
import java.util.function.Consumer;
|
import java.util.function.Supplier;
|
|
@Service
|
public class ElnTemplateBindingServiceImpl implements ElnTemplateBindingService {
|
|
private static final String ENABLED = "enabled";
|
private static final String DRAFT = ElnTemplateEnums.TemplateStatus.DRAFT.name();
|
private static final String PENDING = ElnTemplateEnums.BindingStatus.PENDING.name();
|
private static final String ACTIVE = ElnTemplateEnums.BindingStatus.ACTIVE.name();
|
private static final String CANCELLED = ElnTemplateEnums.BindingStatus.CANCELLED.name();
|
private static final String REMOVED = ElnTemplateEnums.BindingStatus.REMOVED.name();
|
private static final String PRIMARY = ElnTemplateEnums.ControlRole.PRIMARY.name();
|
private static final String REFERENCE = ElnTemplateEnums.ControlRole.REFERENCE.name();
|
private static final String REPEAT = ElnTemplateEnums.ControlRole.REPEAT.name();
|
private static final String REPEAT_PROTOTYPE_ROW_ID = "prototype";
|
|
private final BindingStore store;
|
private final ElnFieldCatalogService fieldCatalogService;
|
private final ElnTemplateRepeatGroupService repeatGroupService;
|
private final List<ElnTemplateBindingDependencyValidator> dependencyValidators;
|
private final List<ElnTemplateVersionCopyParticipant> copyParticipants;
|
private final List<ElnTemplateBindingLifecycleParticipant> lifecycleParticipants;
|
private final Supplier<String> bindingIdSupplier;
|
private final Supplier<String> referenceInstanceIdSupplier;
|
private final Consumer<AuditEventDTO> auditRecorder;
|
private final ElnTemplateDocumentEvidence documentEvidence;
|
private final ElnPendingBindingCleanupService pendingCleanupService;
|
private final Supplier<UserContext> userContextSupplier;
|
private final ElnTemplateBindingConfirmationFacade confirmationFacade;
|
private final StepAssignmentResolver stepAssignmentResolver;
|
private final StepConfigurationReader stepConfigurationReader;
|
private ElnTemplateCheckboxGroupService checkboxGroupService;
|
private long pendingTimeoutMinutes;
|
|
@Autowired
|
public ElnTemplateBindingServiceImpl(ElnTemplateMapper templateMapper,
|
ElnFieldDefMapper fieldMapper, ElnFieldVersionMapper fieldVersionMapper,
|
ElnTemplateBindingMapper bindingMapper, ElnFieldCatalogService fieldCatalogService,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
ElnTemplateCheckboxGroupService checkboxGroupService,
|
ElnTemplateStepService stepService,
|
List<ElnTemplateBindingDependencyValidator> dependencyValidators,
|
List<ElnTemplateVersionCopyParticipant> copyParticipants,
|
List<ElnTemplateBindingLifecycleParticipant> lifecycleParticipants,
|
ObjectProvider<AuditClient> auditClientProvider,
|
ObjectProvider<ElnTemplateDocumentEvidence> documentEvidenceProvider,
|
ElnPendingBindingCleanupService pendingCleanupService,
|
@Value("${eln.template-binding.pending-timeout-minutes:30}")
|
long pendingTimeoutMinutes,
|
ElnTemplateBindingConfirmationFacade confirmationFacade) {
|
this(new MyBatisBindingStore(templateMapper, fieldMapper, fieldVersionMapper, bindingMapper),
|
fieldCatalogService, repeatGroupService, dependencyValidators, copyParticipants,
|
lifecycleParticipants,
|
IdUtil::getSnowflakeNextIdStr,
|
() -> UUID.randomUUID().toString().replace("-", ""),
|
event -> {
|
AuditClient client = auditClientProvider.getIfAvailable();
|
if (client != null) {
|
client.record(event);
|
}
|
}, evidence(documentEvidenceProvider), pendingCleanupService,
|
ElnTemplateBindingServiceImpl::currentUserContext, confirmationFacade,
|
stepService::resolveAssignment, stepService::configuration);
|
this.checkboxGroupService = checkboxGroupService;
|
this.pendingTimeoutMinutes = pendingTimeoutMinutes;
|
}
|
|
ElnTemplateBindingServiceImpl(BindingStore store,
|
ElnFieldCatalogService fieldCatalogService,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
List<ElnTemplateBindingDependencyValidator> dependencyValidators,
|
List<ElnTemplateVersionCopyParticipant> copyParticipants,
|
List<ElnTemplateBindingLifecycleParticipant> lifecycleParticipants,
|
Supplier<String> bindingIdSupplier, Supplier<String> referenceInstanceIdSupplier,
|
Consumer<AuditEventDTO> auditRecorder, ElnTemplateDocumentEvidence documentEvidence,
|
Supplier<UserContext> userContextSupplier,
|
ElnTemplateBindingConfirmationFacade confirmationFacade) {
|
this(store, fieldCatalogService, repeatGroupService, dependencyValidators, copyParticipants,
|
lifecycleParticipants,
|
bindingIdSupplier, referenceInstanceIdSupplier, auditRecorder, documentEvidence,
|
null, userContextSupplier, confirmationFacade);
|
}
|
|
ElnTemplateBindingServiceImpl(BindingStore store,
|
ElnFieldCatalogService fieldCatalogService,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
List<ElnTemplateBindingDependencyValidator> dependencyValidators,
|
List<ElnTemplateVersionCopyParticipant> copyParticipants,
|
List<ElnTemplateBindingLifecycleParticipant> lifecycleParticipants,
|
Supplier<String> bindingIdSupplier, Supplier<String> referenceInstanceIdSupplier,
|
Consumer<AuditEventDTO> auditRecorder, ElnTemplateDocumentEvidence documentEvidence,
|
ElnPendingBindingCleanupService pendingCleanupService,
|
Supplier<UserContext> userContextSupplier,
|
ElnTemplateBindingConfirmationFacade confirmationFacade) {
|
this(store, fieldCatalogService, repeatGroupService, dependencyValidators, copyParticipants,
|
lifecycleParticipants,
|
bindingIdSupplier, referenceInstanceIdSupplier, auditRecorder, documentEvidence,
|
pendingCleanupService, userContextSupplier, confirmationFacade,
|
(tenantId, templateId, requestedStepId) -> StringUtils.hasText(requestedStepId)
|
? requestedStepId.trim() : "test-single-step",
|
templateId -> singleStepConfiguration("test-single-step"));
|
}
|
|
ElnTemplateBindingServiceImpl(BindingStore store,
|
ElnFieldCatalogService fieldCatalogService,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
List<ElnTemplateBindingDependencyValidator> dependencyValidators,
|
List<ElnTemplateVersionCopyParticipant> copyParticipants,
|
List<ElnTemplateBindingLifecycleParticipant> lifecycleParticipants,
|
Supplier<String> bindingIdSupplier, Supplier<String> referenceInstanceIdSupplier,
|
Consumer<AuditEventDTO> auditRecorder, ElnTemplateDocumentEvidence documentEvidence,
|
Supplier<UserContext> userContextSupplier,
|
ElnTemplateBindingConfirmationFacade confirmationFacade,
|
ElnTemplateCheckboxGroupService checkboxGroupService) {
|
this(store, fieldCatalogService, repeatGroupService, dependencyValidators, copyParticipants,
|
lifecycleParticipants, bindingIdSupplier, referenceInstanceIdSupplier,
|
auditRecorder, documentEvidence, userContextSupplier, confirmationFacade);
|
this.checkboxGroupService = checkboxGroupService;
|
}
|
|
private ElnTemplateBindingServiceImpl(BindingStore store,
|
ElnFieldCatalogService fieldCatalogService,
|
ElnTemplateRepeatGroupService repeatGroupService,
|
List<ElnTemplateBindingDependencyValidator> dependencyValidators,
|
List<ElnTemplateVersionCopyParticipant> copyParticipants,
|
List<ElnTemplateBindingLifecycleParticipant> lifecycleParticipants,
|
Supplier<String> bindingIdSupplier, Supplier<String> referenceInstanceIdSupplier,
|
Consumer<AuditEventDTO> auditRecorder, ElnTemplateDocumentEvidence documentEvidence,
|
ElnPendingBindingCleanupService pendingCleanupService,
|
Supplier<UserContext> userContextSupplier,
|
ElnTemplateBindingConfirmationFacade confirmationFacade,
|
StepAssignmentResolver stepAssignmentResolver,
|
StepConfigurationReader stepConfigurationReader) {
|
this.store = store;
|
this.fieldCatalogService = fieldCatalogService;
|
this.repeatGroupService = repeatGroupService;
|
this.dependencyValidators = dependencyValidators == null
|
? Collections.emptyList() : new ArrayList<>(dependencyValidators);
|
this.copyParticipants = copyParticipants == null
|
? Collections.emptyList() : new ArrayList<>(copyParticipants);
|
this.lifecycleParticipants = lifecycleParticipants == null
|
? Collections.emptyList() : new ArrayList<>(lifecycleParticipants);
|
this.bindingIdSupplier = bindingIdSupplier;
|
this.referenceInstanceIdSupplier = referenceInstanceIdSupplier;
|
this.auditRecorder = auditRecorder;
|
this.documentEvidence = documentEvidence;
|
this.pendingCleanupService = pendingCleanupService;
|
this.userContextSupplier = userContextSupplier;
|
this.confirmationFacade = confirmationFacade;
|
this.stepAssignmentResolver = stepAssignmentResolver;
|
this.stepConfigurationReader = stepConfigurationReader;
|
this.pendingTimeoutMinutes = 30L;
|
}
|
|
@Override
|
public ElnTemplateVos.AnnotationContext context(TemplateAnnotationAuthorization authorization) {
|
prepareDocumentEvidence(authorization);
|
ElnTemplateVersionEntity template = requireEditableTemplate(authorization);
|
fieldCatalogService.ensureTestSubmissionFields();
|
ElnTemplateVos.AnnotationContext result = new ElnTemplateVos.AnnotationContext();
|
result.setTemplateId(template.getId());
|
result.setTemplateCode(template.getMubanBianhao());
|
result.setUpgradeOperationCode(template.getShengbanCaozuoBianma());
|
result.setStepConfiguration(stepConfigurationReader.read(template.getId()));
|
result.setRepeatGroups(repeatGroupService.list(
|
authorization.getTenantId(), template.getId()));
|
if (checkboxGroupService != null) {
|
result.setCheckboxGroups(checkboxGroupService.list(
|
authorization.getTenantId(), template.getId()));
|
}
|
for (ElnFieldDefEntity field : store.listFields(authorization.getTenantId())) {
|
if (!ENABLED.equals(field.getBizEnabled()) || !fieldVisibleToTemplate(field, template)) {
|
continue;
|
}
|
ElnFieldVos.Detail detail = toFieldDetail(field);
|
for (ElnFieldVersionEntity version
|
: store.listFieldVersions(authorization.getTenantId(), field.getId())) {
|
boolean preRelease = ElnFieldEnums.VersionStatus.PRE_RELEASE.name()
|
.equals(version.getBizStatus());
|
boolean currentPublished = ElnFieldEnums.VersionStatus.PUBLISHED.name()
|
.equals(version.getBizStatus())
|
&& version.getId().equals(field.getDangqianZiduanBanbenId());
|
if (preRelease || currentPublished) {
|
detail.getVersions().add(toFieldVersion(version));
|
}
|
}
|
if (!detail.getVersions().isEmpty()) {
|
result.getFields().add(detail);
|
}
|
}
|
for (ElnTemplateBindingEntity binding
|
: store.listBindings(authorization.getTenantId(), template.getId())) {
|
if (PENDING.equals(binding.getBangdingZhuangtai())
|
|| ACTIVE.equals(binding.getBangdingZhuangtai())) {
|
result.getBindings().add(toBinding(binding));
|
}
|
if (PENDING.equals(binding.getBangdingZhuangtai())
|
&& StringUtils.hasText(binding.getLaiyuanBangdingId())) {
|
result.getCopiedPendingBindingIds().add(binding.getId());
|
}
|
}
|
return result;
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.Binding reservePrimary(TemplateAnnotationAuthorization authorization,
|
ElnTemplateForms.ReserveBinding form) {
|
prepareDocumentEvidence(authorization);
|
return reservePrimaryPrepared(authorization, form);
|
}
|
|
private ElnTemplateVos.Binding reservePrimaryPrepared(
|
TemplateAnnotationAuthorization authorization, ElnTemplateForms.ReserveBinding form) {
|
ElnTemplateVersionEntity template = requireEditableTemplateForUpdate(authorization);
|
requireReserveForm(form);
|
boolean repeat = Boolean.TRUE.equals(form.getRepeatRequest());
|
String stepId = stepAssignmentResolver.resolve(authorization.getTenantId(),
|
template.getId(), form.getStepId());
|
String repeatGroupCode = repeat
|
? requireRepeatToken(form.getRepeatGroupCode(), "重复组编码无效") : null;
|
String prototypeRowId = repeat
|
? requireRepeatToken(form.getRepeatPrototypeRowId(), "原型行标识无效") : null;
|
if (repeat && !REPEAT_PROTOTYPE_ROW_ID.equals(prototypeRowId)) {
|
throw new DataException("当前阶段原型行标识必须为 prototype");
|
}
|
if (repeat) {
|
repeatGroupService.ensureConfigured(authorization.getTenantId(), template.getId(),
|
repeatGroupCode, form.getMinRows(), form.getMaxRows(),
|
stepId, authorization.getUserId());
|
}
|
String role = repeat ? REPEAT : PRIMARY;
|
String checkboxGroupCode = normalizeCheckboxGroupCode(form.getCheckboxGroupCode());
|
String operation = form.getOperationCode().trim();
|
ElnTemplateBindingEntity retried = store.findByOperation(
|
authorization.getTenantId(), template.getId(), operation);
|
if (retried != null) {
|
if (!role.equals(retried.getKongjianJuese())
|
|| !form.getFieldId().trim().equals(retried.getZiduanDingyiId())
|
|| !form.getFieldVersionId().trim().equals(retried.getZiduanBanbenId())
|
|| !Objects.equals(repeatGroupCode, retried.getRepeatGroupCode())
|
|| !Objects.equals(prototypeRowId, retried.getRepeatPrototypeRowId())
|
|| !Objects.equals(checkboxGroupCode, retried.getCheckboxGroupCode())
|
|| !Objects.equals(repeat ? null : stepId, retried.getStepId())) {
|
throw ElnTemplateOperationException.conflict("绑定操作码已用于其他请求");
|
}
|
ensureCheckboxGroupConfigured(authorization, template.getId(), checkboxGroupCode,
|
form.getCheckboxGroupMinSelected(), form.getCheckboxGroupMaxSelected());
|
return toBinding(retried);
|
}
|
ElnFieldDefEntity field = requireField(authorization.getTenantId(), form.getFieldId());
|
ElnFieldVersionEntity version = requireSelectableVersion(
|
authorization.getTenantId(), field, form.getFieldVersionId(), template);
|
validateCheckboxGroupAssignment(authorization, null, checkboxGroupCode,
|
role, repeatGroupCode, repeat ? null : stepId, version);
|
ensureCheckboxGroupConfigured(authorization, template.getId(), checkboxGroupCode,
|
form.getCheckboxGroupMinSelected(), form.getCheckboxGroupMaxSelected());
|
for (ElnTemplateBindingEntity binding
|
: store.listBindings(authorization.getTenantId(), template.getId())) {
|
if (!isLive(binding)) continue;
|
if (!repeat && PRIMARY.equals(binding.getKongjianJuese())
|
&& field.getZiduanBianma().equals(binding.getZiduanBianma())) {
|
throw ElnTemplateOperationException.conflict("同一模板字段只能有一个有效主控件");
|
}
|
if (repeat && REPEAT.equals(binding.getKongjianJuese())) {
|
if (repeatGroupCode.equals(binding.getRepeatGroupCode())
|
&& !prototypeRowId.equals(binding.getRepeatPrototypeRowId())) {
|
throw ElnTemplateOperationException.conflict("同一重复组只能有一条原型行");
|
}
|
if (repeatGroupCode.equals(binding.getRepeatGroupCode())
|
&& field.getZiduanBianma().equals(binding.getZiduanBianma())) {
|
throw ElnTemplateOperationException.conflict("同一重复组字段只能绑定一次");
|
}
|
}
|
}
|
String tag = repeat
|
? "eln.repeat." + repeatGroupCode + "." + prototypeRowId + "."
|
+ field.getZiduanBianma()
|
: "eln.field." + field.getZiduanBianma();
|
ElnTemplateBindingEntity created = newBinding(authorization, template, field, version,
|
role, null, tag, operation);
|
created.setRepeatGroupCode(repeatGroupCode);
|
created.setRepeatPrototypeRowId(prototypeRowId);
|
created.setCheckboxGroupCode(checkboxGroupCode);
|
created.setStepId(repeat ? null : stepId);
|
insert(created);
|
audit("ELN_TEMPLATE_BINDING_RESERVE", authorization.getTenantId(),
|
authorization.getUserId(), operation, template.getId(), created.getId(),
|
null, created, null, "SUCCESS");
|
return toBinding(created);
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.Binding reserveReference(TemplateAnnotationAuthorization authorization,
|
String primaryBindingId, String operationCode) {
|
prepareDocumentEvidence(authorization);
|
ElnTemplateVersionEntity template = requireEditableTemplateForUpdate(authorization);
|
String operation = requireText(operationCode, "操作码不能为空");
|
ElnTemplateBindingEntity retried = store.findByOperation(
|
authorization.getTenantId(), template.getId(), operation);
|
if (retried != null) {
|
if (!REFERENCE.equals(retried.getKongjianJuese())
|
|| !trim(primaryBindingId).equals(retried.getYinyongShiliId())) {
|
throw ElnTemplateOperationException.conflict("绑定操作码已用于其他请求");
|
}
|
return toBinding(retried);
|
}
|
ElnTemplateBindingEntity primary = requireBindingForUpdate(
|
authorization, primaryBindingId);
|
if (!ACTIVE.equals(primary.getBangdingZhuangtai())
|
|| !PRIMARY.equals(primary.getKongjianJuese())) {
|
throw ElnTemplateOperationException.conflict("引用控件必须指向有效主控件");
|
}
|
ElnFieldDefEntity field = requireField(
|
authorization.getTenantId(), primary.getZiduanDingyiId());
|
ElnFieldVersionEntity version = requireSelectableVersion(authorization.getTenantId(), field,
|
primary.getZiduanBanbenId(), template);
|
String instanceId = referenceInstanceIdSupplier.get();
|
if (instanceId == null || !instanceId.matches("[0-9a-fA-F]{32}")) {
|
throw new DataException("引用实例标识生成失败");
|
}
|
String tag = "eln.ref." + field.getZiduanBianma() + "." + instanceId.toLowerCase();
|
ElnTemplateBindingEntity created = newBinding(authorization, template, field, version,
|
REFERENCE, primary.getId(), tag, operation);
|
insert(created);
|
audit("ELN_TEMPLATE_BINDING_RESERVE", authorization.getTenantId(),
|
authorization.getUserId(), operation, template.getId(), created.getId(),
|
null, created, null, "SUCCESS");
|
return toBinding(created);
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.Binding changeCheckboxGroup(
|
TemplateAnnotationAuthorization authorization,
|
String bindingId,
|
ElnTemplateForms.ChangeCheckboxGroup form) {
|
if (form == null || form.getExpectedVersion() == null) {
|
throw new DataException("绑定期望版本不能为空");
|
}
|
String operation = requireText(form.getOperationCode(), "操作码不能为空");
|
String checkboxGroupCode = normalizeCheckboxGroupCode(form.getCheckboxGroupCode());
|
ElnTemplateBindingEntity replayed = store.findByOperation(
|
authorization.getTenantId(), authorization.getTemplateId(), operation);
|
if (replayed != null) {
|
if (replayed.getId().equals(trim(bindingId))
|
&& Objects.equals(checkboxGroupCode, replayed.getCheckboxGroupCode())) {
|
return toBinding(replayed);
|
}
|
throw ElnTemplateOperationException.conflict("选择组操作码已用于其他请求");
|
}
|
requireEditableTemplateForUpdate(authorization);
|
ElnTemplateBindingEntity current = requireBindingForUpdate(authorization, bindingId);
|
if (!isLive(current)) {
|
throw ElnTemplateOperationException.conflict("只有待确认或有效绑定可以修改选择组");
|
}
|
if (!form.getExpectedVersion().equals(current.getFVersion())) {
|
throw ElnTemplateOperationException.conflict("绑定状态已变化,请刷新后重试");
|
}
|
ElnFieldVersionEntity version = requireFieldVersion(
|
authorization.getTenantId(), current.getZiduanBanbenId());
|
validateCheckboxGroupAssignment(authorization, current.getId(), checkboxGroupCode,
|
current.getKongjianJuese(), current.getRepeatGroupCode(),
|
current.getStepId(), version);
|
ensureCheckboxGroupConfigured(authorization, authorization.getTemplateId(),
|
checkboxGroupCode, form.getCheckboxGroupMinSelected(),
|
form.getCheckboxGroupMaxSelected());
|
if (Objects.equals(checkboxGroupCode, current.getCheckboxGroupCode())) {
|
return toBinding(current);
|
}
|
ElnTemplateBindingEntity after = copy(current);
|
after.setCheckboxGroupCode(checkboxGroupCode);
|
after.setCaozuoBianma(operation);
|
casExpected(current, after, current.getBangdingZhuangtai(), form.getExpectedVersion());
|
ElnTemplateBindingEntity persisted = requireBinding(authorization, current.getId());
|
removeUnusedCheckboxGroups(authorization.getTenantId(),
|
authorization.getTemplateId(), authorization.getUserId());
|
audit("ELN_TEMPLATE_BINDING_CHECKBOX_GROUP_CHANGE", authorization.getTenantId(),
|
authorization.getUserId(), operation, authorization.getTemplateId(),
|
current.getId(), current, persisted, null, "SUCCESS");
|
return toBinding(persisted);
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.CheckboxGroup changeCheckboxGroupRule(
|
TemplateAnnotationAuthorization authorization, String groupCode,
|
ElnTemplateForms.ChangeCheckboxGroupRule form) {
|
if (form == null || form.getExpectedVersion() == null) {
|
throw new DataException("选择组期望版本不能为空");
|
}
|
String operation = requireText(form.getOperationCode(), "操作码不能为空");
|
requireEditableTemplateForUpdate(authorization);
|
if (checkboxGroupService == null) throw new DataException("选择组服务不可用");
|
ElnTemplateVos.CheckboxGroup before = checkboxGroupService.list(
|
authorization.getTenantId(), authorization.getTemplateId()).stream()
|
.filter(item -> Objects.equals(item.getGroupCode(), trim(groupCode)))
|
.findFirst().orElse(null);
|
ElnTemplateVos.CheckboxGroup changed = checkboxGroupService.change(
|
authorization.getTenantId(), authorization.getTemplateId(), groupCode,
|
form.getMinSelected(), form.getMaxSelected(), form.getExpectedVersion(),
|
authorization.getUserId());
|
audit("ELN_TEMPLATE_CHECKBOX_GROUP_RULE_CHANGE", authorization.getTenantId(),
|
authorization.getUserId(), operation, authorization.getTemplateId(),
|
changed.getGroupCode(), before, changed, null, "SUCCESS");
|
return changed;
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.Binding createPrivateFieldAndReserve(
|
TemplateAnnotationAuthorization authorization, ElnTemplateForms.PrivateField form) {
|
prepareDocumentEvidence(authorization);
|
ElnTemplateVersionEntity template = requireEditableTemplateForUpdate(authorization);
|
if (form == null || !StringUtils.hasText(form.getOperationCode())) {
|
throw new DataException("私有字段操作码不能为空");
|
}
|
ElnTemplateBindingEntity retried = store.findByOperation(authorization.getTenantId(),
|
template.getId(), form.getOperationCode().trim());
|
if (retried != null) {
|
requireSamePrivateFieldRequest(authorization, template, retried, form);
|
ensureCheckboxGroupConfigured(authorization, template.getId(),
|
normalizeCheckboxGroupCode(form.getCheckboxGroupCode()),
|
form.getCheckboxGroupMinSelected(), form.getCheckboxGroupMaxSelected());
|
return toBinding(retried);
|
}
|
ElnFieldForms.TemplateFieldCreate command = new ElnFieldForms.TemplateFieldCreate();
|
command.setTenantId(authorization.getTenantId());
|
command.setTemplateCode(template.getMubanBianhao());
|
command.setOperatorId(authorization.getUserId());
|
command.setBeizhu(form.getBeizhu());
|
command.setZiduanMingcheng(form.getZiduanMingcheng());
|
command.setShujuLeixing(form.getShujuLeixing());
|
command.setDanwei(form.getDanwei());
|
command.setShifouBitian(form.getShifouBitian());
|
command.setPiciMoshi(ElnFieldEnums.BatchMode.RECORD_PRIVATE.name());
|
command.setKongjianPeizhi(form.getKongjianPeizhi());
|
command.setBinding(manualBinding());
|
ElnFieldVos.Detail created = fieldCatalogService.createTemplateFieldAndPreRelease(command);
|
if (created == null || !StringUtils.hasText(created.getId())
|
|| created.getVersions() == null || created.getVersions().size() != 1
|
|| !ElnFieldEnums.VersionStatus.PRE_RELEASE.name().equals(
|
created.getVersions().get(0).getBizStatus())) {
|
throw new DataException("私有字段创建结果无效");
|
}
|
ElnTemplateForms.ReserveBinding reserve = new ElnTemplateForms.ReserveBinding();
|
reserve.setFieldId(created.getId());
|
reserve.setFieldVersionId(created.getVersions().get(0).getId());
|
reserve.setOperationCode(form.getOperationCode());
|
reserve.setStepId(form.getStepId());
|
reserve.setCheckboxGroupCode(form.getCheckboxGroupCode());
|
reserve.setCheckboxGroupMinSelected(form.getCheckboxGroupMinSelected());
|
reserve.setCheckboxGroupMaxSelected(form.getCheckboxGroupMaxSelected());
|
return reservePrimaryPrepared(authorization, reserve);
|
}
|
|
private void ensureCheckboxGroupConfigured(TemplateAnnotationAuthorization authorization,
|
String templateId, String groupCode,
|
Integer minSelected, Integer maxSelected) {
|
if (checkboxGroupService == null || groupCode == null) return;
|
checkboxGroupService.ensureConfigured(authorization.getTenantId(), templateId,
|
groupCode, minSelected, maxSelected, authorization.getUserId());
|
}
|
|
@Override
|
@Transactional
|
public void activate(TemplateAnnotationAuthorization authorization, String bindingId,
|
String operationCode) {
|
requireEditableTemplateForUpdate(authorization);
|
ElnTemplateBindingEntity observed = requireBinding(authorization, bindingId);
|
ElnTemplateBindingEntity lockedPrimary = null;
|
if (REFERENCE.equals(observed.getKongjianJuese())) {
|
lockedPrimary = requireBindingForUpdate(
|
authorization, observed.getYinyongShiliId());
|
}
|
ElnTemplateBindingEntity before = requireBindingForUpdate(authorization, bindingId);
|
if (REFERENCE.equals(before.getKongjianJuese())) {
|
requireValidReferencePrimary(before, lockedPrimary);
|
}
|
rejectCopiedBindingSingleTransition(before);
|
if (ACTIVE.equals(before.getBangdingZhuangtai())) {
|
return;
|
}
|
if (!PENDING.equals(before.getBangdingZhuangtai())) {
|
throw ElnTemplateOperationException.conflict("只有待确认绑定可以激活");
|
}
|
ElnTemplateBindingEntity after = copy(before);
|
after.setBangdingZhuangtai(ACTIVE);
|
after.setZhuangtaiYuanyin(null);
|
cas(before, after, PENDING);
|
ElnTemplateBindingEntity persisted = requireBinding(authorization, bindingId);
|
audit("ELN_TEMPLATE_BINDING_ACTIVATE", authorization.getTenantId(),
|
authorization.getUserId(), requireText(operationCode, "操作码不能为空"),
|
authorization.getTemplateId(), bindingId, before, persisted, null, "SUCCESS");
|
}
|
|
@Override
|
@Transactional
|
public void cancel(TemplateAnnotationAuthorization authorization, String bindingId,
|
String operationCode, String reason) {
|
requireEditableTemplateForUpdate(authorization);
|
ElnTemplateBindingEntity before = requireBinding(authorization, bindingId);
|
rejectCopiedBindingSingleTransition(before);
|
if (CANCELLED.equals(before.getBangdingZhuangtai())) {
|
return;
|
}
|
if (!PENDING.equals(before.getBangdingZhuangtai())) {
|
throw ElnTemplateOperationException.conflict("只有待确认绑定可以取消");
|
}
|
ElnTemplateBindingEntity after = copy(before);
|
after.setBangdingZhuangtai(CANCELLED);
|
after.setZhuangtaiYuanyin(requireText(reason, "取消原因不能为空"));
|
cas(before, after, PENDING);
|
terminateBinding(authorization.getTenantId(), bindingId, authorization.getUserId());
|
ElnTemplateBindingEntity persisted = requireBinding(authorization, bindingId);
|
audit("ELN_TEMPLATE_BINDING_CANCEL", authorization.getTenantId(),
|
authorization.getUserId(), requireText(operationCode, "操作码不能为空"),
|
authorization.getTemplateId(), bindingId, before, persisted, reason, "SUCCESS");
|
}
|
|
@Override
|
public ElnTemplateVos.RepairContext repairContext(TemplateAnnotationAuthorization authorization,
|
String bindingId) {
|
requireEditableTemplate(authorization);
|
ElnTemplateBindingEntity binding = requireActiveBinding(authorization, bindingId);
|
ElnTemplateVos.ChangeCheck check = changeCheck("repair", authorization,
|
Collections.singletonList(binding), activeSnapshots(authorization),
|
Collections.emptyList(), null);
|
ElnTemplateVos.RepairContext result = new ElnTemplateVos.RepairContext();
|
copyCheck(check, result);
|
result.setBinding(toBinding(binding));
|
return result;
|
}
|
|
@Override
|
public void confirmRepair(TemplateAnnotationAuthorization authorization, String bindingId,
|
ElnTemplateForms.RepairConfirm form) {
|
confirmationFacade.execute(() -> {
|
ElnTemplateBindingEntity current = requireBinding(authorization, bindingId);
|
if (ACTIVE.equals(current.getBangdingZhuangtai()) && form != null
|
&& Objects.equals(form.getOperationCode(), current.getCaozuoBianma())) {
|
return;
|
}
|
requireEditableTemplateForUpdate(authorization);
|
current = requireBinding(authorization, bindingId);
|
if (ACTIVE.equals(current.getBangdingZhuangtai()) && form != null
|
&& Objects.equals(form.getOperationCode(), current.getCaozuoBianma())) {
|
return;
|
}
|
ElnTemplateVos.RepairContext check = repairContext(authorization, bindingId);
|
requireConfirmation(check, form);
|
ElnTemplateBindingEntity after = copy(current);
|
after.setCaozuoBianma(form.getOperationCode());
|
casExpected(current, after, ACTIVE,
|
form.getExpectedVersions().get(bindingId));
|
ElnTemplateBindingEntity persisted = requireBinding(authorization, bindingId);
|
audit("ELN_TEMPLATE_BINDING_REPAIR", authorization.getTenantId(),
|
authorization.getUserId(), form.getOperationCode(), authorization.getTemplateId(),
|
bindingId, current, persisted, null, "SUCCESS");
|
}, error -> failureAudit("REPAIR", authorization.getTenantId(), authorization.getUserId(),
|
operation(form), authorization.getTemplateId(), bindingId, error));
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.ChangeCheck checkUpgrade(TemplateAnnotationAuthorization authorization,
|
String bindingId, String targetVersionId) {
|
requireEditableTemplate(authorization);
|
ElnTemplateBindingEntity primary = requireActivePrimaryForUpdate(
|
authorization, bindingId);
|
ElnFieldDefEntity field = requireField(
|
authorization.getTenantId(), primary.getZiduanDingyiId());
|
ElnFieldVersionEntity target = requireSelectableVersion(authorization.getTenantId(), field,
|
targetVersionId, requireTemplate(authorization));
|
List<ElnTemplateBindingEntity> affected = fieldBindingFamily(authorization, primary);
|
List<ElnTemplateVos.Diagnostic> dependencies = validateUpgrade(
|
authorization, ids(affected), target.getId());
|
List<ElnTemplateVos.ControlSnapshot> controls = activeSnapshots(authorization);
|
Set<String> affectedIds = ids(affected);
|
for (ElnTemplateVos.ControlSnapshot control : controls) {
|
if (affectedIds.contains(control.getBindingId())) {
|
control.setFieldVersionId(target.getId());
|
control.setControlAlias(target.getZiduanMingcheng());
|
control.setControlType(controlType(target.getShujuLeixing()));
|
control.setControlConfig(DocxExpectedControlConfig.from(
|
target.getShujuLeixing(), target.getKongjianPeizhi()));
|
}
|
}
|
return changeCheck("upgrade:" + target.getId(), authorization,
|
affected, controls, dependencies, target.getId());
|
}
|
|
@Override
|
public void confirmUpgrade(TemplateAnnotationAuthorization authorization, String bindingId,
|
ElnTemplateForms.UpgradeConfirm form) {
|
confirmationFacade.execute(() -> confirmUpgradeInternal(authorization, bindingId, form),
|
error -> failureAudit("UPGRADE", authorization.getTenantId(),
|
authorization.getUserId(), operation(form), authorization.getTemplateId(),
|
bindingId, error));
|
}
|
|
private void confirmUpgradeInternal(TemplateAnnotationAuthorization authorization,
|
String bindingId, ElnTemplateForms.UpgradeConfirm form) {
|
if (isUpgradeRetry(authorization, bindingId, form)) {
|
return;
|
}
|
requireEditableTemplateForUpdate(authorization);
|
if (isUpgradeRetry(authorization, bindingId, form)) {
|
return;
|
}
|
ElnTemplateVos.ChangeCheck check = checkUpgrade(
|
authorization, bindingId, form == null ? null : form.getTargetFieldVersionId());
|
requireConfirmation(check, form);
|
List<ElnTemplateBindingEntity> beforeSnapshots = new ArrayList<>();
|
List<ElnTemplateBindingEntity> afterSnapshots = new ArrayList<>();
|
for (Map.Entry<String, Integer> entry : check.getExpectedVersions().entrySet()) {
|
ElnTemplateBindingEntity before = requireActiveBinding(authorization, entry.getKey());
|
beforeSnapshots.add(copy(before));
|
ElnTemplateBindingEntity after = copy(before);
|
ElnFieldVersionEntity target = requireFieldVersion(
|
authorization.getTenantId(), form.getTargetFieldVersionId());
|
validateCheckboxGroupAssignment(authorization, before.getId(),
|
before.getCheckboxGroupCode(), before.getKongjianJuese(),
|
before.getRepeatGroupCode(), before.getStepId(), target);
|
after.setZiduanBanbenId(target.getId());
|
after.setKongjianBieming(target.getZiduanMingcheng());
|
after.setKongjianLeixing(controlType(target.getShujuLeixing()));
|
after.setCaozuoBianma(bindingOperationCode(form.getOperationCode(), before.getId()));
|
casExpected(before, after, ACTIVE, entry.getValue());
|
afterSnapshots.add(requireActiveBinding(authorization, before.getId()));
|
}
|
audit("ELN_TEMPLATE_BINDING_UPGRADE", authorization.getTenantId(),
|
authorization.getUserId(), form.getOperationCode(), authorization.getTemplateId(),
|
bindingId, beforeSnapshots, afterSnapshots,
|
null, "SUCCESS");
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.ChangeCheck checkRemove(TemplateAnnotationAuthorization authorization,
|
String bindingId) {
|
requireEditableTemplate(authorization);
|
ElnTemplateBindingEntity binding = requireActiveBindingForUpdate(
|
authorization, bindingId);
|
Set<String> affected = Collections.singleton(binding.getId());
|
if (PRIMARY.equals(binding.getKongjianJuese()) && hasLiveReferences(authorization, binding)) {
|
throw new DataException("主控件仍有引用,不能删除");
|
}
|
List<ElnTemplateVos.Diagnostic> dependencies = validateRemoval(authorization, affected);
|
List<ElnTemplateVos.ControlSnapshot> controls = activeSnapshots(authorization);
|
controls.removeIf(item -> binding.getId().equals(item.getBindingId()));
|
ElnTemplateVos.ChangeCheck result = changeCheck("remove", authorization,
|
Collections.singletonList(binding), controls, dependencies, null);
|
audit("ELN_TEMPLATE_BINDING_REMOVE_CHECK", authorization.getTenantId(),
|
authorization.getUserId(), result.getOperationCode(), authorization.getTemplateId(),
|
bindingId, binding, result, null, "SUCCESS");
|
return result;
|
}
|
|
@Override
|
public void confirmRemove(TemplateAnnotationAuthorization authorization, String bindingId,
|
ElnTemplateForms.RemoveConfirm form) {
|
confirmationFacade.execute(() -> {
|
ElnTemplateBindingEntity current = requireBinding(authorization, bindingId);
|
if (REMOVED.equals(current.getBangdingZhuangtai())
|
&& form != null && Objects.equals(
|
form.getOperationCode(), current.getCaozuoBianma())) {
|
return;
|
}
|
requireEditableTemplateForUpdate(authorization);
|
current = requireBinding(authorization, bindingId);
|
if (REMOVED.equals(current.getBangdingZhuangtai())
|
&& form != null && Objects.equals(
|
form.getOperationCode(), current.getCaozuoBianma())) {
|
return;
|
}
|
ElnTemplateVos.ChangeCheck check = checkRemove(authorization, bindingId);
|
requireConfirmation(check, form);
|
requireObservedControlTags(check, form, current.getKongjianBiaoqian());
|
ElnTemplateBindingEntity after = copy(current);
|
after.setBangdingZhuangtai(REMOVED);
|
after.setCaozuoBianma(form.getOperationCode());
|
casExpected(current, after, ACTIVE, form.getExpectedVersions().get(bindingId));
|
terminateBinding(authorization.getTenantId(), bindingId, authorization.getUserId());
|
removeUnusedCheckboxGroups(authorization.getTenantId(),
|
authorization.getTemplateId(), authorization.getUserId());
|
ElnTemplateBindingEntity persisted = requireBinding(authorization, bindingId);
|
audit("ELN_TEMPLATE_BINDING_REMOVE", authorization.getTenantId(),
|
authorization.getUserId(), form.getOperationCode(), authorization.getTemplateId(),
|
bindingId, current, persisted, null, "SUCCESS");
|
}, error -> failureAudit("REMOVE", authorization.getTenantId(), authorization.getUserId(),
|
operation(form), authorization.getTemplateId(), bindingId, error));
|
}
|
|
@Override
|
public void confirmRemoveMissing(String bindingId,
|
ElnTemplateForms.RemoveMissingConfirm form) {
|
UserContext user = requireUserContext();
|
confirmationFacade.execute(() -> {
|
if (form == null || form.getExpectedVersion() == null) {
|
throw new DataException("绑定期望版本不能为空");
|
}
|
String operation = requireText(form.getOperationCode(), "操作码不能为空");
|
String reason = requireText(form.getReason(), "确认删除原因不能为空");
|
ElnTemplateBindingEntity observed = requireBinding(user.getTenantId(), bindingId);
|
if (REMOVED.equals(observed.getBangdingZhuangtai())
|
&& operation.equals(observed.getCaozuoBianma())) {
|
if (Objects.equals(reason, trim(observed.getZhuangtaiYuanyin()))) {
|
return;
|
}
|
throw ElnTemplateOperationException.conflict(
|
"确认删除操作码已用于其他原因");
|
}
|
ElnTemplateVersionEntity template = requireEditableTemplateForUpdate(
|
user.getTenantId(), observed.getMubanId());
|
ElnTemplateBindingEntity current = requireBindingForUpdate(
|
user.getTenantId(), bindingId);
|
if (REMOVED.equals(current.getBangdingZhuangtai())
|
&& operation.equals(current.getCaozuoBianma())) {
|
if (Objects.equals(reason, trim(current.getZhuangtaiYuanyin()))) {
|
return;
|
}
|
throw ElnTemplateOperationException.conflict(
|
"确认删除操作码已用于其他原因");
|
}
|
if (!ACTIVE.equals(current.getBangdingZhuangtai())) {
|
throw ElnTemplateOperationException.conflict("只有有效绑定可以确认缺失删除");
|
}
|
String fileId = uniqueDocxFileId(template.getMubanFile());
|
if (documentEvidence.containsControlTag(
|
user.getTenantId(), fileId, current.getKongjianBiaoqian())) {
|
throw new DataException("服务端文档证据显示控件仍然存在");
|
}
|
if (PRIMARY.equals(current.getKongjianJuese())
|
&& hasLiveReferences(user.getTenantId(), template.getId(), current)) {
|
throw new DataException("主控件仍有引用,不能删除");
|
}
|
validateRemoval(user.getTenantId(), template.getId(),
|
Collections.singleton(current.getId()));
|
ElnTemplateBindingEntity after = copy(current);
|
after.setBangdingZhuangtai(REMOVED);
|
after.setCaozuoBianma(operation);
|
after.setZhuangtaiYuanyin(reason);
|
casExpected(current, after, ACTIVE, form.getExpectedVersion());
|
terminateBinding(user.getTenantId(), bindingId, user.getUserId());
|
removeUnusedCheckboxGroups(user.getTenantId(), template.getId(), user.getUserId());
|
ElnTemplateBindingEntity persisted = requireBinding(user.getTenantId(), bindingId);
|
audit("ELN_TEMPLATE_BINDING_REMOVE", user.getTenantId(), user.getUserId(), operation,
|
template.getId(), bindingId, current, persisted,
|
reason, "SUCCESS");
|
}, error -> failureAudit("REMOVE", user.getTenantId(), user.getUserId(),
|
form == null ? null : form.getOperationCode(),
|
templateIdForFailure(user.getTenantId(), bindingId), bindingId, error));
|
}
|
|
private void terminateBinding(String tenantId, String bindingId, String userId) {
|
for (ElnTemplateBindingLifecycleParticipant participant : lifecycleParticipants) {
|
participant.bindingTerminated(tenantId, bindingId, userId);
|
}
|
}
|
|
private void removeUnusedCheckboxGroups(String tenantId, String templateId,
|
String operatorId) {
|
if (checkboxGroupService == null) return;
|
Set<String> usedGroupCodes = new LinkedHashSet<>();
|
for (ElnTemplateBindingEntity binding : store.listBindings(tenantId, templateId)) {
|
String groupCode = trim(binding.getCheckboxGroupCode());
|
if (isLive(binding) && groupCode != null) usedGroupCodes.add(groupCode);
|
}
|
checkboxGroupService.removeUnused(
|
tenantId, templateId, usedGroupCodes, operatorId);
|
}
|
|
@Override
|
@Transactional
|
public ElnTemplateVos.ChangeCheck checkPromoteReference(
|
TemplateAnnotationAuthorization authorization, String referenceBindingId) {
|
requireEditableTemplate(authorization);
|
ElnTemplateBindingEntity reference = requireActiveBinding(authorization, referenceBindingId);
|
if (!REFERENCE.equals(reference.getKongjianJuese())) {
|
throw new DataException("只能提升引用控件");
|
}
|
ElnTemplateBindingEntity primary = requireActivePrimaryForUpdate(
|
authorization, reference.getYinyongShiliId());
|
reference = requireActiveBindingForUpdate(authorization, referenceBindingId);
|
requireValidReferencePrimary(reference, primary);
|
List<ElnTemplateBindingEntity> affected = fieldBindingFamily(authorization, primary);
|
if (!ids(affected).contains(reference.getId())) {
|
throw new DataException("待提升引用不属于主控件引用集合");
|
}
|
List<ElnTemplateVos.Diagnostic> dependencies = validatePromotion(
|
authorization, primary.getId(), reference.getId());
|
List<ElnTemplateVos.ControlSnapshot> controls = activeSnapshots(authorization);
|
controls.removeIf(item -> primary.getId().equals(item.getBindingId()));
|
for (ElnTemplateVos.ControlSnapshot control : controls) {
|
if (reference.getId().equals(control.getBindingId())) {
|
control.setControlRole(PRIMARY);
|
control.setPrimaryBindingId(null);
|
control.setControlTag("eln.field." + reference.getZiduanBianma());
|
control.setStepId(primary.getStepId());
|
} else if (primary.getId().equals(control.getPrimaryBindingId())) {
|
control.setPrimaryBindingId(reference.getId());
|
}
|
}
|
return changeCheck("promote", authorization,
|
affected, controls, dependencies, null);
|
}
|
|
@Override
|
public void confirmPromoteReference(TemplateAnnotationAuthorization authorization,
|
String referenceBindingId, ElnTemplateForms.PromoteConfirm form) {
|
confirmationFacade.execute(() -> {
|
ElnTemplateBindingEntity possibleRetry = requireBinding(authorization, referenceBindingId);
|
if (PRIMARY.equals(possibleRetry.getKongjianJuese()) && ACTIVE.equals(
|
possibleRetry.getBangdingZhuangtai()) && form != null
|
&& Objects.equals(bindingOperationCode(
|
form.getOperationCode(), possibleRetry.getId()),
|
possibleRetry.getCaozuoBianma())) {
|
return;
|
}
|
requireEditableTemplateForUpdate(authorization);
|
possibleRetry = requireBinding(authorization, referenceBindingId);
|
if (PRIMARY.equals(possibleRetry.getKongjianJuese()) && ACTIVE.equals(
|
possibleRetry.getBangdingZhuangtai()) && form != null
|
&& Objects.equals(bindingOperationCode(
|
form.getOperationCode(), possibleRetry.getId()),
|
possibleRetry.getCaozuoBianma())) {
|
return;
|
}
|
ElnTemplateVos.ChangeCheck check = checkPromoteReference(
|
authorization, referenceBindingId);
|
requireConfirmation(check, form);
|
ElnTemplateBindingEntity reference = requireActiveBinding(
|
authorization, referenceBindingId);
|
requireObservedControlTags(check, form, reference.getKongjianBiaoqian());
|
ElnTemplateBindingEntity primary = requireActiveBinding(
|
authorization, reference.getYinyongShiliId());
|
List<ElnTemplateBindingEntity> family = fieldBindingFamily(authorization, primary);
|
List<ElnTemplateBindingEntity> transitionOrder = new ArrayList<>();
|
transitionOrder.add(primary);
|
transitionOrder.add(reference);
|
for (ElnTemplateBindingEntity member : family) {
|
if (!primary.getId().equals(member.getId())
|
&& !reference.getId().equals(member.getId())) {
|
transitionOrder.add(member);
|
}
|
}
|
List<ElnTemplateBindingEntity> changed = new ArrayList<>();
|
for (ElnTemplateBindingEntity before : transitionOrder) {
|
ElnTemplateBindingEntity after = copy(before);
|
if (primary.getId().equals(before.getId())) {
|
after.setBangdingZhuangtai(REMOVED);
|
} else if (reference.getId().equals(before.getId())) {
|
after.setKongjianJuese(PRIMARY);
|
after.setYinyongShiliId(null);
|
after.setKongjianBiaoqian("eln.field." + reference.getZiduanBianma());
|
after.setStepId(primary.getStepId());
|
} else {
|
after.setYinyongShiliId(reference.getId());
|
}
|
after.setCaozuoBianma(bindingOperationCode(
|
form.getOperationCode(), before.getId()));
|
casExpected(before, after, ACTIVE,
|
form.getExpectedVersions().get(before.getId()));
|
changed.add(requireBinding(authorization, before.getId()));
|
}
|
audit("ELN_TEMPLATE_BINDING_PROMOTE", authorization.getTenantId(),
|
authorization.getUserId(), form.getOperationCode(), authorization.getTemplateId(),
|
referenceBindingId, family, changed,
|
null, "SUCCESS");
|
}, error -> failureAudit("PROMOTE", authorization.getTenantId(), authorization.getUserId(),
|
operation(form), authorization.getTemplateId(), referenceBindingId, error));
|
}
|
|
@Override
|
public void confirmVersionCopy(TemplateAnnotationAuthorization authorization,
|
ElnTemplateForms.VersionCopyConfirm form) {
|
confirmationFacade.execute(() -> confirmVersionCopyInternal(authorization, form),
|
error -> failureAudit("VERSION_COPY_CONFIRM", authorization.getTenantId(),
|
authorization.getUserId(), form == null ? null : form.getUpgradeOperationCode(),
|
authorization.getTemplateId(), null, error));
|
}
|
|
private void confirmVersionCopyInternal(TemplateAnnotationAuthorization authorization,
|
ElnTemplateForms.VersionCopyConfirm form) {
|
ElnTemplateVersionEntity template = requireEditableTemplateForUpdate(authorization);
|
if (form == null || !StringUtils.hasText(form.getUpgradeOperationCode())
|
|| !form.getUpgradeOperationCode().trim().equals(template.getShengbanCaozuoBianma())) {
|
throw new DataException("升版操作码无效");
|
}
|
String upgradeOperation = form.getUpgradeOperationCode().trim();
|
List<ElnTemplateBindingEntity> copies = copiedBindings(authorization);
|
if (copies.isEmpty()) {
|
throw new DataException("本次升版没有可确认的复制绑定");
|
}
|
boolean allPending = true;
|
boolean allActive = true;
|
for (ElnTemplateBindingEntity binding : copies) {
|
String expectedOperation = upgradeOperation + ":" + binding.getLaiyuanBangdingId();
|
if (!expectedOperation.equals(binding.getCaozuoBianma())) {
|
throw new DataException("升版复制绑定操作码无效");
|
}
|
allPending &= PENDING.equals(binding.getBangdingZhuangtai());
|
allActive &= ACTIVE.equals(binding.getBangdingZhuangtai());
|
}
|
if (allActive) {
|
requireVersionCopyRetryVersions(copies, form.getExpectedVersions());
|
requireControls(snapshots(copies), form.getControls());
|
return;
|
}
|
if (!allPending) {
|
throw ElnTemplateOperationException.conflict(
|
"升版复制绑定必须全部为待确认或全部为已激活状态");
|
}
|
for (ElnTemplateBindingEntity binding : copies) {
|
if (binding.getDaoqiShijian() != null) {
|
throw ElnTemplateOperationException.conflict("升版复制绑定状态无效");
|
}
|
ElnFieldVersionEntity version = requireFieldVersion(
|
authorization.getTenantId(), binding.getZiduanBanbenId());
|
ElnFieldDefEntity field = requireField(
|
authorization.getTenantId(), binding.getZiduanDingyiId());
|
if (!field.getId().equals(version.getZiduanDingyiId())) {
|
throw new DataException("升版复制绑定的字段版本关系无效");
|
}
|
boolean published = ElnFieldEnums.VersionStatus.PUBLISHED.name()
|
.equals(version.getBizStatus());
|
boolean privatePreRelease = ElnFieldEnums.VersionStatus.PRE_RELEASE.name()
|
.equals(version.getBizStatus())
|
&& ElnFieldEnums.Scope.TEMPLATE.name().equals(field.getZuoyongFanwei())
|
&& template.getMubanBianhao().equals(field.getMubanBianhao());
|
if (!published && !privatePreRelease) {
|
throw new DataException("升版复制只能继承已发布字段版本或同模板的预发布私有字段版本");
|
}
|
}
|
Map<String, Integer> expected = versions(copies);
|
requireExpectedVersions(expected, form.getExpectedVersions());
|
requireControls(snapshots(copies), form.getControls());
|
Map<String, String> bindingMap = new LinkedHashMap<>();
|
List<ElnTemplateBindingEntity> beforeSnapshots = new ArrayList<>();
|
List<ElnTemplateBindingEntity> afterSnapshots = new ArrayList<>();
|
copies.sort(Comparator.comparing(ElnTemplateBindingEntity::getLaiyuanBangdingId));
|
for (ElnTemplateBindingEntity before : copies) {
|
beforeSnapshots.add(copy(before));
|
ElnTemplateBindingEntity after = copy(before);
|
after.setBangdingZhuangtai(ACTIVE);
|
casExpected(before, after, PENDING, expected.get(before.getId()));
|
afterSnapshots.add(requireActiveBinding(authorization, before.getId()));
|
bindingMap.put(before.getLaiyuanBangdingId(), before.getId());
|
}
|
for (ElnTemplateVersionCopyParticipant participant : copyParticipants) {
|
participant.afterBindingsActivated(authorization.getTenantId(),
|
template.getLaiyuanMubanId(), template.getId(), bindingMap);
|
}
|
audit("ELN_TEMPLATE_BINDING_VERSION_COPY_CONFIRM", authorization.getTenantId(),
|
authorization.getUserId(), form.getUpgradeOperationCode(), template.getId(), null,
|
beforeSnapshots, afterSnapshots, null, "SUCCESS");
|
}
|
|
private ElnTemplateBindingEntity newBinding(TemplateAnnotationAuthorization authorization,
|
ElnTemplateVersionEntity template, ElnFieldDefEntity field,
|
ElnFieldVersionEntity version, String role, String primaryBindingId,
|
String tag, String operation) {
|
ElnTemplateBindingEntity created = new ElnTemplateBindingEntity();
|
created.setId(bindingIdSupplier.get());
|
created.setFTenantId(authorization.getTenantId());
|
created.setMubanId(template.getId());
|
created.setMubanBianhao(template.getMubanBianhao());
|
created.setZiduanDingyiId(field.getId());
|
created.setZiduanBianma(field.getZiduanBianma());
|
created.setZiduanBanbenId(version.getId());
|
created.setKongjianBiaoqian(tag);
|
created.setKongjianBieming(version.getZiduanMingcheng());
|
created.setKongjianLeixing(controlType(version.getShujuLeixing()));
|
created.setKongjianJuese(role);
|
created.setYinyongShiliId(primaryBindingId);
|
created.setBangdingZhuangtai(PENDING);
|
created.setCaozuoBianma(operation);
|
if (pendingTimeoutMinutes <= 0) {
|
throw new DataException("PENDING 绑定超时时间配置无效");
|
}
|
long timeoutMillis;
|
try {
|
timeoutMillis = Math.multiplyExact(pendingTimeoutMinutes, 60L * 1000L);
|
} catch (ArithmeticException e) {
|
throw new DataException("PENDING 绑定超时时间配置无效");
|
}
|
created.setDaoqiShijian(new Date(System.currentTimeMillis() + timeoutMillis));
|
created.setFVersion(0);
|
created.setFDeleteMark(0);
|
created.setFCreatorUserId(authorization.getUserId());
|
created.setFLastModifyUserId(authorization.getUserId());
|
return created;
|
}
|
|
private ElnTemplateVos.ChangeCheck changeCheck(String action,
|
TemplateAnnotationAuthorization authorization,
|
List<ElnTemplateBindingEntity> affected,
|
List<ElnTemplateVos.ControlSnapshot> controls,
|
List<ElnTemplateVos.Diagnostic> dependencies, String targetVersionId) {
|
ElnTemplateVos.ChangeCheck result = new ElnTemplateVos.ChangeCheck();
|
result.setExpectedVersions(versions(affected));
|
result.setControls(sortedSnapshots(controls));
|
result.setDependencies(dependencies == null ? new ArrayList<>() : dependencies);
|
result.setOperationCode(operationCode(action, authorization, affected, targetVersionId));
|
return result;
|
}
|
|
private void requireConfirmation(ElnTemplateVos.ChangeCheck check,
|
ElnTemplateForms.ChangeConfirm form) {
|
if (form == null || !check.getOperationCode().equals(form.getOperationCode())) {
|
throw ElnTemplateOperationException.conflict("预检操作码无效,请重新预检");
|
}
|
requireExpectedVersions(check.getExpectedVersions(), form.getExpectedVersions());
|
requireControls(check.getControls(), form.getControls());
|
}
|
|
private static void requireObservedControlTags(ElnTemplateVos.ChangeCheck check,
|
ElnTemplateForms.ChangeConfirm form,
|
String removedTag) {
|
List<String> observedTags = form.getObservedControlTags();
|
if (observedTags == null) {
|
throw ElnTemplateOperationException.conflict("DOCX 控件标签证据未提交");
|
}
|
Set<String> observed = new LinkedHashSet<>();
|
for (String tag : observedTags) {
|
if (!StringUtils.hasText(tag) || !observed.add(tag)) {
|
throw ElnTemplateOperationException.conflict("DOCX 控件标签证据无效");
|
}
|
}
|
for (ElnTemplateVos.ControlSnapshot control : check.getControls()) {
|
String expectedTag = control.getControlTag();
|
if (Collections.frequency(observedTags, expectedTag) != 1) {
|
throw ElnTemplateOperationException.conflict("DOCX 控件标签证据不完整");
|
}
|
}
|
if (observed.contains(removedTag)) {
|
throw ElnTemplateOperationException.conflict("DOCX 控件标签证据包含已移除控件");
|
}
|
}
|
|
private static void requireExpectedVersions(Map<String, Integer> expected,
|
Map<String, Integer> actual) {
|
if (actual == null || !expected.equals(actual)) {
|
throw ElnTemplateOperationException.conflict("受影响绑定版本集合不完整或已变化");
|
}
|
}
|
|
private static void requireControls(List<ElnTemplateVos.ControlSnapshot> expected,
|
List<ElnTemplateVos.ControlSnapshot> actual) {
|
if (!canonicalControls(expected).equals(canonicalControls(actual))) {
|
throw ElnTemplateOperationException.conflict("控件快照不完整或与预检结果不一致");
|
}
|
Map<String, ElnTemplateVos.ControlSnapshot> actualByBindingId = new LinkedHashMap<>();
|
for (ElnTemplateVos.ControlSnapshot item : actual) {
|
if (item == null || actualByBindingId.put(item.getBindingId(), item) != null) {
|
throw ElnTemplateOperationException.conflict("控件快照不完整或与预检结果不一致");
|
}
|
}
|
for (ElnTemplateVos.ControlSnapshot item : expected) {
|
ElnTemplateVos.ControlSnapshot observed = actualByBindingId.get(item.getBindingId());
|
if (!DocxControlConfigMatcher.matches(item.getControlType(),
|
item.getControlConfig(), observed.getControlConfig())) {
|
throw ElnTemplateOperationException.conflict("控件快照不完整或与预检结果不一致");
|
}
|
}
|
}
|
|
private List<ElnTemplateVos.Diagnostic> validateUpgrade(
|
TemplateAnnotationAuthorization authorization, Set<String> bindingIds,
|
String targetVersionId) {
|
List<ElnTemplateVos.Diagnostic> result = new ArrayList<>();
|
for (ElnTemplateBindingDependencyValidator validator : dependencyValidators) {
|
addDiagnostics(result, validator.validateUpgrade(authorization.getTenantId(),
|
authorization.getTemplateId(), bindingIds, targetVersionId));
|
}
|
rejectDependencyErrors(result);
|
return result;
|
}
|
|
private List<ElnTemplateVos.Diagnostic> validateRemoval(
|
TemplateAnnotationAuthorization authorization, Set<String> bindingIds) {
|
return validateRemoval(authorization.getTenantId(),
|
authorization.getTemplateId(), bindingIds);
|
}
|
|
private List<ElnTemplateVos.Diagnostic> validateRemoval(
|
String tenantId, String templateId, Set<String> bindingIds) {
|
List<ElnTemplateVos.Diagnostic> result = new ArrayList<>();
|
for (ElnTemplateBindingDependencyValidator validator : dependencyValidators) {
|
addDiagnostics(result, validator.validateRemoval(tenantId, templateId, bindingIds));
|
}
|
rejectDependencyErrors(result);
|
return result;
|
}
|
|
private List<ElnTemplateVos.Diagnostic> validatePromotion(
|
TemplateAnnotationAuthorization authorization, String primaryId, String referenceId) {
|
List<ElnTemplateVos.Diagnostic> result = new ArrayList<>();
|
for (ElnTemplateBindingDependencyValidator validator : dependencyValidators) {
|
addDiagnostics(result, validator.validatePromotion(authorization.getTenantId(),
|
authorization.getTemplateId(), primaryId, referenceId));
|
}
|
rejectDependencyErrors(result);
|
return result;
|
}
|
|
private static void rejectDependencyErrors(List<ElnTemplateVos.Diagnostic> diagnostics) {
|
for (ElnTemplateVos.Diagnostic diagnostic : diagnostics) {
|
if (diagnostic != null && "ERROR".equalsIgnoreCase(diagnostic.getSeverity())) {
|
throw new DataException(StringUtils.hasText(diagnostic.getMessage())
|
? diagnostic.getMessage() : "绑定依赖校验失败");
|
}
|
}
|
}
|
|
private static void addDiagnostics(List<ElnTemplateVos.Diagnostic> target,
|
List<ElnTemplateVos.Diagnostic> source) {
|
if (source != null) {
|
target.addAll(source);
|
}
|
}
|
|
private List<ElnTemplateBindingEntity> fieldBindingFamily(
|
TemplateAnnotationAuthorization authorization, ElnTemplateBindingEntity primary) {
|
List<ElnTemplateBindingEntity> result = new ArrayList<>();
|
result.add(primary);
|
for (ElnTemplateBindingEntity item
|
: store.listBindings(authorization.getTenantId(), authorization.getTemplateId())) {
|
if (isLive(item) && REFERENCE.equals(item.getKongjianJuese())
|
&& primary.getId().equals(item.getYinyongShiliId())) {
|
if (PENDING.equals(item.getBangdingZhuangtai())) {
|
throw new DataException("主控件仍有待确认引用,请先完成或取消引用标注");
|
}
|
if (!primary.getZiduanBanbenId().equals(item.getZiduanBanbenId())) {
|
throw new DataException("引用控件字段版本与主控件不一致");
|
}
|
result.add(item);
|
}
|
}
|
result.sort(Comparator.comparing(ElnTemplateBindingEntity::getId));
|
return result;
|
}
|
|
private boolean hasLiveReferences(TemplateAnnotationAuthorization authorization,
|
ElnTemplateBindingEntity primary) {
|
return hasLiveReferences(authorization.getTenantId(),
|
authorization.getTemplateId(), primary);
|
}
|
|
private boolean hasLiveReferences(String tenantId, String templateId,
|
ElnTemplateBindingEntity primary) {
|
for (ElnTemplateBindingEntity item
|
: store.listBindings(tenantId, templateId)) {
|
if (isLive(item) && REFERENCE.equals(item.getKongjianJuese())
|
&& primary.getId().equals(item.getYinyongShiliId())) {
|
return true;
|
}
|
}
|
return false;
|
}
|
|
private List<ElnTemplateBindingEntity> copiedBindings(
|
TemplateAnnotationAuthorization authorization) {
|
List<ElnTemplateBindingEntity> result = new ArrayList<>();
|
for (ElnTemplateBindingEntity binding
|
: store.listBindings(authorization.getTenantId(), authorization.getTemplateId())) {
|
if (StringUtils.hasText(binding.getLaiyuanBangdingId())) {
|
result.add(binding);
|
}
|
}
|
result.sort(Comparator.comparing(ElnTemplateBindingEntity::getId));
|
return result;
|
}
|
|
private static void requireVersionCopyRetryVersions(
|
List<ElnTemplateBindingEntity> copies, Map<String, Integer> requestedVersions) {
|
if (requestedVersions == null || !ids(copies).equals(requestedVersions.keySet())) {
|
throw ElnTemplateOperationException.conflict("升版复制绑定版本集合不完整");
|
}
|
for (ElnTemplateBindingEntity binding : copies) {
|
Integer requested = requestedVersions.get(binding.getId());
|
if (requested == null || binding.getFVersion() == null
|
|| requested == Integer.MAX_VALUE
|
|| binding.getFVersion() != requested + 1) {
|
throw ElnTemplateOperationException.conflict("升版复制绑定幂等版本无效");
|
}
|
}
|
}
|
|
private static void rejectCopiedBindingSingleTransition(
|
ElnTemplateBindingEntity binding) {
|
if (StringUtils.hasText(binding.getLaiyuanBangdingId())) {
|
throw ElnTemplateOperationException.conflict("升版复制绑定只能批量确认");
|
}
|
}
|
|
private boolean isUpgradeRetry(TemplateAnnotationAuthorization authorization,
|
String bindingId, ElnTemplateForms.UpgradeConfirm form) {
|
if (form == null || !StringUtils.hasText(form.getOperationCode())
|
|| !StringUtils.hasText(form.getTargetFieldVersionId())) {
|
return false;
|
}
|
ElnTemplateBindingEntity binding = requireBinding(authorization, bindingId);
|
if (!ACTIVE.equals(binding.getBangdingZhuangtai())
|
|| !PRIMARY.equals(binding.getKongjianJuese())
|
|| !form.getTargetFieldVersionId().equals(binding.getZiduanBanbenId())) {
|
return false;
|
}
|
for (ElnTemplateBindingEntity member : fieldBindingFamily(authorization, binding)) {
|
if (!form.getTargetFieldVersionId().equals(member.getZiduanBanbenId())
|
|| !bindingOperationCode(form.getOperationCode(), member.getId())
|
.equals(member.getCaozuoBianma())) {
|
return false;
|
}
|
}
|
return true;
|
}
|
|
private ElnTemplateVersionEntity requireEditableTemplate(
|
TemplateAnnotationAuthorization authorization) {
|
if (authorization == null || !StringUtils.hasText(authorization.getTenantId())
|
|| !StringUtils.hasText(authorization.getTemplateId())) {
|
throw new DataException("模板标注授权无效");
|
}
|
return requireEditableTemplate(authorization.getTenantId(), authorization.getTemplateId());
|
}
|
|
private ElnTemplateVersionEntity requireEditableTemplate(String tenantId, String templateId) {
|
ElnTemplateVersionEntity template = store.findTemplate(tenantId, trim(templateId));
|
if (template == null || !DRAFT.equals(template.getBizStatus())
|
|| !ENABLED.equals(template.getBizEnabled())) {
|
throw ElnTemplateOperationException.conflict("只有启用的草稿模板可以修改绑定");
|
}
|
return template;
|
}
|
|
private ElnTemplateVersionEntity requireEditableTemplateForUpdate(
|
TemplateAnnotationAuthorization authorization) {
|
if (authorization == null || !StringUtils.hasText(authorization.getTenantId())
|
|| !StringUtils.hasText(authorization.getTemplateId())) {
|
throw new DataException("模板标注授权无效");
|
}
|
return requireEditableTemplateForUpdate(
|
authorization.getTenantId(), authorization.getTemplateId());
|
}
|
|
private ElnTemplateVersionEntity requireEditableTemplateForUpdate(
|
String tenantId, String templateId) {
|
ElnTemplateVersionEntity template = store.findTemplateForUpdate(
|
tenantId, trim(templateId));
|
if (template == null || !DRAFT.equals(template.getBizStatus())
|
|| !ENABLED.equals(template.getBizEnabled())) {
|
throw ElnTemplateOperationException.conflict("只有启用的草稿模板可以修改绑定");
|
}
|
return template;
|
}
|
|
private ElnTemplateVersionEntity requireTemplate(TemplateAnnotationAuthorization authorization) {
|
ElnTemplateVersionEntity template = store.findTemplate(
|
authorization.getTenantId(), authorization.getTemplateId());
|
if (template == null) {
|
throw new DataException("模板不存在或不属于当前租户");
|
}
|
return template;
|
}
|
|
private ElnFieldDefEntity requireField(String tenantId, String fieldId) {
|
ElnFieldDefEntity field = store.findField(tenantId, trim(fieldId));
|
if (field == null || !ENABLED.equals(field.getBizEnabled())) {
|
throw new DataException("字段不存在、已停用或不属于当前租户");
|
}
|
return field;
|
}
|
|
private ElnFieldVersionEntity requireFieldVersion(String tenantId, String versionId) {
|
ElnFieldVersionEntity version = store.findFieldVersion(tenantId, trim(versionId));
|
if (version == null) {
|
throw new DataException("字段版本不存在或不属于当前租户");
|
}
|
return version;
|
}
|
|
private ElnFieldVersionEntity requireSelectableVersion(String tenantId, ElnFieldDefEntity field,
|
String versionId, ElnTemplateVersionEntity template) {
|
ElnFieldVersionEntity version = requireFieldVersion(tenantId, versionId);
|
if (!field.getId().equals(version.getZiduanDingyiId())) {
|
throw new DataException("字段版本不属于所选字段");
|
}
|
if (ElnFieldEnums.Scope.TEMPLATE.name().equals(field.getZuoyongFanwei())
|
&& !template.getMubanBianhao().equals(field.getMubanBianhao())) {
|
throw new DataException("模板私有字段不属于当前模板");
|
}
|
if (!ElnFieldEnums.Scope.GLOBAL.name().equals(field.getZuoyongFanwei())
|
&& !ElnFieldEnums.Scope.TEMPLATE.name().equals(field.getZuoyongFanwei())) {
|
throw new DataException("字段作用范围无效");
|
}
|
boolean preRelease = ElnFieldEnums.VersionStatus.PRE_RELEASE.name()
|
.equals(version.getBizStatus());
|
boolean currentPublished = ElnFieldEnums.VersionStatus.PUBLISHED.name()
|
.equals(version.getBizStatus())
|
&& version.getId().equals(field.getDangqianZiduanBanbenId());
|
if (!preRelease && !currentPublished) {
|
throw new DataException("只能选择预发布或当前已发布字段版本");
|
}
|
return version;
|
}
|
|
private ElnTemplateBindingEntity requireBinding(
|
TemplateAnnotationAuthorization authorization, String bindingId) {
|
ElnTemplateBindingEntity binding = requireBinding(authorization.getTenantId(), bindingId);
|
if (!authorization.getTemplateId().equals(binding.getMubanId())) {
|
throw ElnTemplateOperationException.conflict("绑定不属于授权模板");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireBindingForUpdate(
|
TemplateAnnotationAuthorization authorization, String bindingId) {
|
ElnTemplateBindingEntity binding = requireBindingForUpdate(
|
authorization.getTenantId(), bindingId);
|
if (!authorization.getTemplateId().equals(binding.getMubanId())) {
|
throw ElnTemplateOperationException.conflict("绑定不属于授权模板");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireBinding(String tenantId, String bindingId) {
|
ElnTemplateBindingEntity binding = store.findBinding(tenantId, trim(bindingId));
|
if (binding == null) {
|
throw ElnTemplateOperationException.conflict("绑定不存在或不属于当前租户");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireBindingForUpdate(String tenantId, String bindingId) {
|
ElnTemplateBindingEntity binding = store.findBindingForUpdate(tenantId, trim(bindingId));
|
if (binding == null) {
|
throw ElnTemplateOperationException.conflict("绑定不存在或不属于当前租户");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireActiveBinding(
|
TemplateAnnotationAuthorization authorization, String bindingId) {
|
ElnTemplateBindingEntity binding = requireBinding(authorization, bindingId);
|
if (!ACTIVE.equals(binding.getBangdingZhuangtai())) {
|
throw ElnTemplateOperationException.conflict("绑定不是有效状态");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireActiveBindingForUpdate(
|
TemplateAnnotationAuthorization authorization, String bindingId) {
|
ElnTemplateBindingEntity binding = requireBindingForUpdate(authorization, bindingId);
|
if (!ACTIVE.equals(binding.getBangdingZhuangtai())) {
|
throw ElnTemplateOperationException.conflict("绑定不是有效状态");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireActivePrimary(
|
TemplateAnnotationAuthorization authorization, String bindingId) {
|
ElnTemplateBindingEntity binding = requireActiveBinding(authorization, bindingId);
|
if (!PRIMARY.equals(binding.getKongjianJuese())) {
|
throw new DataException("操作目标必须是主控件");
|
}
|
return binding;
|
}
|
|
private ElnTemplateBindingEntity requireActivePrimaryForUpdate(
|
TemplateAnnotationAuthorization authorization, String bindingId) {
|
ElnTemplateBindingEntity binding = requireActiveBindingForUpdate(
|
authorization, bindingId);
|
if (!PRIMARY.equals(binding.getKongjianJuese())) {
|
throw new DataException("操作目标必须是主控件");
|
}
|
return binding;
|
}
|
|
private UserContext requireUserContext() {
|
UserContext user = userContextSupplier.get();
|
if (user == null || !StringUtils.hasText(user.getTenantId())
|
|| !StringUtils.hasText(user.getUserId())) {
|
throw new DataException("登录态无效");
|
}
|
return user;
|
}
|
|
private void insert(ElnTemplateBindingEntity binding) {
|
try {
|
store.insert(binding);
|
} catch (DuplicateKeyException e) {
|
throw ElnTemplateOperationException.conflict("绑定已存在或发生并发冲突");
|
}
|
}
|
|
private void cas(ElnTemplateBindingEntity before,
|
ElnTemplateBindingEntity after, String expectedStatus) {
|
if (before.getFVersion() == null
|
|| !store.compareAndSet(after, before.getFVersion(), expectedStatus)) {
|
throw ElnTemplateOperationException.conflict("绑定状态已变化,请重新预检");
|
}
|
}
|
|
private void casExpected(ElnTemplateBindingEntity before, ElnTemplateBindingEntity after,
|
String expectedStatus, Integer expectedVersion) {
|
if (expectedVersion == null || before.getFVersion() == null
|
|| !expectedVersion.equals(before.getFVersion())
|
|| !store.compareAndSet(after, expectedVersion, expectedStatus)) {
|
throw ElnTemplateOperationException.conflict("绑定状态已变化,请重新预检");
|
}
|
}
|
|
private List<ElnTemplateVos.ControlSnapshot> activeSnapshots(
|
TemplateAnnotationAuthorization authorization) {
|
List<ElnTemplateVos.ControlSnapshot> result = new ArrayList<>();
|
for (ElnTemplateBindingEntity binding
|
: store.listBindings(authorization.getTenantId(), authorization.getTemplateId())) {
|
if (ACTIVE.equals(binding.getBangdingZhuangtai())) {
|
result.add(snapshotWithConfig(binding));
|
}
|
}
|
return sortedSnapshots(result);
|
}
|
|
private List<ElnTemplateVos.ControlSnapshot> snapshots(
|
List<ElnTemplateBindingEntity> bindings) {
|
List<ElnTemplateVos.ControlSnapshot> result = new ArrayList<>();
|
for (ElnTemplateBindingEntity binding : bindings) {
|
result.add(snapshotWithConfig(binding));
|
}
|
return sortedSnapshots(result);
|
}
|
|
private static List<ElnTemplateVos.ControlSnapshot> sortedSnapshots(
|
List<ElnTemplateVos.ControlSnapshot> controls) {
|
List<ElnTemplateVos.ControlSnapshot> result = controls == null
|
? new ArrayList<>() : new ArrayList<>(controls);
|
result.sort(Comparator.comparing(ElnTemplateVos.ControlSnapshot::getBindingId));
|
return result;
|
}
|
|
public static ElnTemplateVos.ControlSnapshot snapshot(ElnTemplateBindingEntity binding) {
|
ElnTemplateVos.ControlSnapshot result = new ElnTemplateVos.ControlSnapshot();
|
result.setBindingId(binding.getId());
|
result.setControlTag(binding.getKongjianBiaoqian());
|
result.setControlAlias(binding.getKongjianBieming());
|
result.setControlType(binding.getKongjianLeixing());
|
result.setControlRole(binding.getKongjianJuese());
|
result.setFieldVersionId(binding.getZiduanBanbenId());
|
result.setPrimaryBindingId(binding.getYinyongShiliId());
|
result.setRepeatGroupCode(binding.getRepeatGroupCode());
|
result.setRepeatPrototypeRowId(binding.getRepeatPrototypeRowId());
|
result.setStepId(binding.getStepId());
|
return result;
|
}
|
|
private ElnTemplateVos.ControlSnapshot snapshotWithConfig(
|
ElnTemplateBindingEntity binding) {
|
ElnTemplateVos.ControlSnapshot result = snapshot(binding);
|
result.setControlConfig(bindingControlConfig(binding));
|
return result;
|
}
|
|
private ElnTemplateVos.Binding toBinding(ElnTemplateBindingEntity source) {
|
ElnTemplateVos.Binding result = new ElnTemplateVos.Binding();
|
result.setId(source.getId());
|
result.setTemplateId(source.getMubanId());
|
result.setFieldId(source.getZiduanDingyiId());
|
result.setFieldCode(source.getZiduanBianma());
|
result.setFieldVersionId(source.getZiduanBanbenId());
|
result.setControlTag(source.getKongjianBiaoqian());
|
result.setControlAlias(source.getKongjianBieming());
|
result.setControlType(source.getKongjianLeixing());
|
result.setControlRole(source.getKongjianJuese());
|
result.setPrimaryBindingId(source.getYinyongShiliId());
|
result.setRepeatGroupCode(source.getRepeatGroupCode());
|
result.setRepeatPrototypeRowId(source.getRepeatPrototypeRowId());
|
result.setCheckboxGroupCode(source.getCheckboxGroupCode());
|
result.setStepId(source.getStepId());
|
result.setControlConfig(bindingControlConfig(source));
|
result.setStatus(source.getBangdingZhuangtai());
|
result.setVersion(source.getFVersion());
|
return result;
|
}
|
|
private Map<String, Object> bindingControlConfig(ElnTemplateBindingEntity binding) {
|
ElnFieldVersionEntity version = requireFieldVersion(
|
binding.getFTenantId(), binding.getZiduanBanbenId());
|
if (!binding.getZiduanDingyiId().equals(version.getZiduanDingyiId())) {
|
throw new DataException("绑定字段版本关系无效");
|
}
|
return DocxExpectedControlConfig.from(
|
version.getShujuLeixing(), version.getKongjianPeizhi());
|
}
|
|
private static boolean fieldVisibleToTemplate(ElnFieldDefEntity field,
|
ElnTemplateVersionEntity template) {
|
return ElnFieldEnums.Scope.GLOBAL.name().equals(field.getZuoyongFanwei())
|
|| (ElnFieldEnums.Scope.TEMPLATE.name().equals(field.getZuoyongFanwei())
|
&& template.getMubanBianhao().equals(field.getMubanBianhao()));
|
}
|
|
private static ElnFieldVos.Detail toFieldDetail(ElnFieldDefEntity source) {
|
ElnFieldVos.Detail result = new ElnFieldVos.Detail();
|
result.setId(source.getId());
|
result.setZiduanBianma(source.getZiduanBianma());
|
result.setZuoyongFanwei(source.getZuoyongFanwei());
|
result.setMubanBianhao(source.getMubanBianhao());
|
result.setDangqianZiduanBanbenId(source.getDangqianZiduanBanbenId());
|
result.setBizEnabled(source.getBizEnabled());
|
result.setBeizhu(source.getBeizhu());
|
return result;
|
}
|
|
private static ElnFieldVos.Version toFieldVersion(ElnFieldVersionEntity source) {
|
ElnFieldVos.Version result = new ElnFieldVos.Version();
|
result.setId(source.getId());
|
result.setZiduanDingyiId(source.getZiduanDingyiId());
|
result.setBanbenHao(source.getBanbenHao());
|
result.setZiduanMingcheng(source.getZiduanMingcheng());
|
result.setShujuLeixing(source.getShujuLeixing());
|
result.setDanwei(source.getDanwei());
|
result.setShifouBitian(source.getShifouBitian());
|
result.setPiciMoshi(source.getPiciMoshi());
|
result.setBinding(StringUtils.hasText(source.getBangdingPeizhi())
|
? JsonUtil.getJsonToBean(source.getBangdingPeizhi(), ElnFieldForms.Binding.class)
|
: null);
|
result.setKongjianPeizhi(parseMap(source.getKongjianPeizhi()));
|
result.setRecommendedCheckboxGroupCode(source.getRecommendedCheckboxGroupCode());
|
result.setRecommendedCheckboxMinSelected(source.getRecommendedCheckboxMinSelected());
|
result.setRecommendedCheckboxMaxSelected(source.getRecommendedCheckboxMaxSelected());
|
result.setBizStatus(source.getBizStatus());
|
result.setZuofeiYuanyin(source.getZuofeiYuanyin());
|
result.setChuangjianShijian(source.getFCreatorTime());
|
result.setGengxinShijian(source.getFLastModifyTime());
|
return result;
|
}
|
|
private static void copyCheck(ElnTemplateVos.ChangeCheck source,
|
ElnTemplateVos.ChangeCheck target) {
|
target.setOperationCode(source.getOperationCode());
|
target.setExpectedVersions(source.getExpectedVersions());
|
target.setControls(source.getControls());
|
target.setDependencies(source.getDependencies());
|
}
|
|
private static Map<String, Integer> versions(List<ElnTemplateBindingEntity> bindings) {
|
List<ElnTemplateBindingEntity> sorted = new ArrayList<>(bindings);
|
sorted.sort(Comparator.comparing(ElnTemplateBindingEntity::getId));
|
Map<String, Integer> result = new LinkedHashMap<>();
|
for (ElnTemplateBindingEntity binding : sorted) {
|
result.put(binding.getId(), binding.getFVersion());
|
}
|
return result;
|
}
|
|
private static Set<String> ids(List<ElnTemplateBindingEntity> bindings) {
|
Set<String> result = new LinkedHashSet<>();
|
List<ElnTemplateBindingEntity> sorted = new ArrayList<>(bindings);
|
sorted.sort(Comparator.comparing(ElnTemplateBindingEntity::getId));
|
for (ElnTemplateBindingEntity binding : sorted) {
|
result.add(binding.getId());
|
}
|
return result;
|
}
|
|
private static List<ElnTemplateBindingEntity> asList(
|
ElnTemplateBindingEntity first, ElnTemplateBindingEntity second) {
|
List<ElnTemplateBindingEntity> result = new ArrayList<>();
|
result.add(first);
|
result.add(second);
|
return result;
|
}
|
|
private static List<String> canonicalControls(List<ElnTemplateVos.ControlSnapshot> controls) {
|
if (controls == null) {
|
return Collections.singletonList("<null>");
|
}
|
List<String> result = new ArrayList<>();
|
for (ElnTemplateVos.ControlSnapshot item : controls) {
|
if (item == null) {
|
result.add("<null-item>");
|
} else {
|
result.add(value(item.getBindingId()) + "\u0000" + value(item.getControlTag())
|
+ "\u0000" + value(item.getControlAlias()) + "\u0000"
|
+ value(item.getControlType()) + "\u0000" + value(item.getControlRole())
|
+ "\u0000" + value(item.getFieldVersionId()) + "\u0000"
|
+ value(item.getPrimaryBindingId()) + "\u0000"
|
+ value(item.getRepeatGroupCode()) + "\u0000"
|
+ value(item.getRepeatPrototypeRowId()) + "\u0000"
|
+ value(item.getStepId()));
|
}
|
}
|
Collections.sort(result);
|
return result;
|
}
|
|
private static String operationCode(String action,
|
TemplateAnnotationAuthorization authorization,
|
List<ElnTemplateBindingEntity> affected, String targetVersionId) {
|
StringBuilder source = new StringBuilder(action).append('\u0000')
|
.append(authorization.getTenantId()).append('\u0000')
|
.append(authorization.getTemplateId()).append('\u0000');
|
for (Map.Entry<String, Integer> item : versions(affected).entrySet()) {
|
source.append(item.getKey()).append(':').append(item.getValue()).append(';');
|
}
|
source.append('\u0000').append(value(targetVersionId));
|
return "eln-binding-" + action.substring(0, action.indexOf(':') < 0
|
? action.length() : action.indexOf(':')) + "-" + sha256(source.toString());
|
}
|
|
private static String bindingOperationCode(String operation, String bindingId) {
|
String result = requireText(operation, "操作码不能为空") + ":"
|
+ requireText(bindingId, "绑定 ID 不能为空");
|
if (result.length() > 160) {
|
throw new DataException("绑定操作码超过 160 个字符");
|
}
|
return result;
|
}
|
|
private static String sha256(String source) {
|
try {
|
byte[] digest = MessageDigest.getInstance("SHA-256")
|
.digest(source.getBytes(StandardCharsets.UTF_8));
|
StringBuilder result = new StringBuilder();
|
for (byte item : digest) {
|
result.append(String.format("%02x", item & 0xff));
|
}
|
return result.toString();
|
} catch (NoSuchAlgorithmException e) {
|
throw new IllegalStateException("JVM 不支持 SHA-256", e);
|
}
|
}
|
|
private void audit(String actionCode, String tenantId, String userId, String operation,
|
String templateId, String bindingId, Object before, Object after,
|
String reason, String result) {
|
Map<String, Object> snapshot = new LinkedHashMap<>();
|
snapshot.put("operationCode", operation);
|
snapshot.put("templateId", templateId);
|
snapshot.put("bindingId", bindingId);
|
snapshot.put("reason", reason);
|
snapshot.put("result", result);
|
snapshot.put("before", before);
|
snapshot.put("after", after);
|
auditRecorder.accept(AuditEventDTO.builder()
|
.operationId(operation)
|
.tenantId(tenantId)
|
.operatorId(userId)
|
.eventTime(new Date())
|
.eventType("DATA_CHANGE")
|
.actionCode(actionCode)
|
.actionLabel(actionCode)
|
.bizModule("eln")
|
.bizType("eln_template_binding")
|
.targetTable("eln_template_binding")
|
.targetId(bindingId == null ? templateId : bindingId)
|
.reason(reason)
|
.dataSnapshot(JsonUtil.getObjectToString(snapshot))
|
.build());
|
}
|
|
private void failureAudit(String action, String tenantId, String userId, String operation,
|
String templateId, String bindingId, RuntimeException error) {
|
audit("ELN_TEMPLATE_BINDING_CONFIRM_FAILED", tenantId, userId, operation,
|
templateId, bindingId, null, null,
|
action + ": " + error.getMessage(), "FAILED");
|
}
|
|
private String templateIdForFailure(String tenantId, String bindingId) {
|
ElnTemplateBindingEntity binding = store.findBinding(tenantId, trim(bindingId));
|
return binding == null ? null : binding.getMubanId();
|
}
|
|
private void requireSamePrivateFieldRequest(TemplateAnnotationAuthorization authorization,
|
ElnTemplateVersionEntity template, ElnTemplateBindingEntity binding,
|
ElnTemplateForms.PrivateField form) {
|
ElnFieldDefEntity field = requireField(
|
authorization.getTenantId(), binding.getZiduanDingyiId());
|
ElnFieldVersionEntity version = requireFieldVersion(
|
authorization.getTenantId(), binding.getZiduanBanbenId());
|
Integer required = form.getShifouBitian() == null ? 0 : form.getShifouBitian();
|
Map<String, Object> expectedBinding = parseMap(
|
JsonUtil.getObjectToString(manualBinding()));
|
boolean same = PRIMARY.equals(binding.getKongjianJuese())
|
&& field.getId().equals(version.getZiduanDingyiId())
|
&& field.getId().equals(binding.getZiduanDingyiId())
|
&& version.getId().equals(binding.getZiduanBanbenId())
|
&& ElnFieldEnums.Scope.TEMPLATE.name().equals(field.getZuoyongFanwei())
|
&& template.getMubanBianhao().equals(field.getMubanBianhao())
|
&& Objects.equals(trimToNull(form.getBeizhu()), trimToNull(field.getBeizhu()))
|
&& ElnFieldEnums.VersionStatus.PRE_RELEASE.name().equals(version.getBizStatus())
|
&& Objects.equals(trimToNull(form.getZiduanMingcheng()),
|
trimToNull(version.getZiduanMingcheng()))
|
&& Objects.equals(trimToNull(form.getShujuLeixing()),
|
trimToNull(version.getShujuLeixing()))
|
&& Objects.equals(trimToNull(form.getDanwei()), trimToNull(version.getDanwei()))
|
&& Objects.equals(required, version.getShifouBitian())
|
&& ElnFieldEnums.BatchMode.RECORD_PRIVATE.name().equals(version.getPiciMoshi())
|
&& Objects.equals(expectedBinding, parseMap(version.getBangdingPeizhi()))
|
&& Objects.equals(form.getKongjianPeizhi(), parseMap(version.getKongjianPeizhi()))
|
&& Objects.equals(normalizeCheckboxGroupCode(form.getCheckboxGroupCode()),
|
binding.getCheckboxGroupCode())
|
&& Objects.equals(stepAssignmentResolver.resolve(authorization.getTenantId(),
|
template.getId(), form.getStepId()), binding.getStepId());
|
if (!same) {
|
throw ElnTemplateOperationException.conflict("私有字段操作码已用于其他请求");
|
}
|
}
|
|
private static void requireValidReferencePrimary(ElnTemplateBindingEntity reference,
|
ElnTemplateBindingEntity primary) {
|
if (primary == null || !REFERENCE.equals(reference.getKongjianJuese())
|
|| !ACTIVE.equals(primary.getBangdingZhuangtai())
|
|| !PRIMARY.equals(primary.getKongjianJuese())
|
|| !primary.getId().equals(reference.getYinyongShiliId())
|
|| !primary.getMubanId().equals(reference.getMubanId())
|
|| !primary.getZiduanDingyiId().equals(reference.getZiduanDingyiId())
|
|| !primary.getZiduanBanbenId().equals(reference.getZiduanBanbenId())) {
|
throw ElnTemplateOperationException.conflict(
|
"引用控件必须指向同模板、同字段版本的有效主控件");
|
}
|
}
|
|
private static ElnFieldForms.Binding manualBinding() {
|
ElnFieldForms.Read read = new ElnFieldForms.Read();
|
read.setSource(ElnFieldEnums.ReadSource.NONE.name());
|
ElnFieldForms.Write write = new ElnFieldForms.Write();
|
write.setTarget("ELN_RECORD");
|
ElnFieldForms.Binding binding = new ElnFieldForms.Binding();
|
binding.setRead(read);
|
binding.setWrite(write);
|
return binding;
|
}
|
|
@SuppressWarnings("unchecked")
|
private static Map<String, Object> parseMap(String json) {
|
return StringUtils.hasText(json) ? JsonUtil.getJsonToBean(json, Map.class) : null;
|
}
|
|
private static String controlType(String dataType) {
|
if (ElnFieldEnums.DataType.TEXT.name().equals(dataType)
|
|| ElnFieldEnums.DataType.DECIMAL.name().equals(dataType)) {
|
return ElnTemplateEnums.ControlType.TEXT.name();
|
}
|
if (ElnFieldEnums.DataType.TEXTAREA.name().equals(dataType)) {
|
return ElnTemplateEnums.ControlType.MULTILINE.name();
|
}
|
if (ElnFieldEnums.DataType.SELECT.name().equals(dataType)) {
|
return ElnTemplateEnums.ControlType.DROPDOWN.name();
|
}
|
if (ElnFieldEnums.DataType.CHECKBOX.name().equals(dataType)) {
|
return ElnTemplateEnums.ControlType.CHECKBOX.name();
|
}
|
if (ElnFieldEnums.DataType.DATE.name().equals(dataType)
|
|| ElnFieldEnums.DataType.DATETIME.name().equals(dataType)) {
|
return ElnTemplateEnums.ControlType.DATE.name();
|
}
|
throw new DataException("字段数据类型不支持 Word 控件绑定");
|
}
|
|
private static String uniqueDocxFileId(String attachments) {
|
try {
|
List<ElnDocumentFileVo> files = JsonUtil.getJsonToList(attachments, ElnDocumentFileVo.class);
|
String result = null;
|
if (files != null) {
|
for (ElnDocumentFileVo file : files) {
|
if (file != null && StringUtils.hasText(file.getFileId())) {
|
String id = file.getFileId().trim();
|
boolean docx = "docx".equalsIgnoreCase(file.getFileExtension())
|
|| id.toLowerCase().endsWith(".docx");
|
if (docx) {
|
if (result != null) {
|
throw new DataException("模板必须且只能包含一个 DOCX 附件");
|
}
|
result = id;
|
}
|
}
|
}
|
}
|
if (result == null) {
|
throw new DataException("模板必须且只能包含一个 DOCX 附件");
|
}
|
return result;
|
} catch (DataException e) {
|
throw e;
|
} catch (RuntimeException e) {
|
throw new DataException("模板附件无效");
|
}
|
}
|
|
private static boolean isLive(ElnTemplateBindingEntity binding) {
|
return PENDING.equals(binding.getBangdingZhuangtai())
|
|| ACTIVE.equals(binding.getBangdingZhuangtai());
|
}
|
|
private static void requireReserveForm(ElnTemplateForms.ReserveBinding form) {
|
if (form == null || !StringUtils.hasText(form.getFieldId())
|
|| !StringUtils.hasText(form.getFieldVersionId())
|
|| !StringUtils.hasText(form.getOperationCode())) {
|
throw new DataException("字段、字段版本和操作码不能为空");
|
}
|
}
|
|
private static String requireRepeatToken(String value, String message) {
|
String token = requireText(value, message);
|
if (!token.matches("[A-Za-z0-9_-]{1,80}")) {
|
throw new DataException(message);
|
}
|
return token;
|
}
|
|
private static String normalizeCheckboxGroupCode(String value) {
|
String code = trimToNull(value);
|
if (code != null && !code.matches("[A-Za-z0-9_-]{1,80}")) {
|
throw new DataException("选择组编码只能包含字母、数字、下划线和短横线,且不能超过80个字符");
|
}
|
return code;
|
}
|
|
private void validateCheckboxGroupAssignment(
|
TemplateAnnotationAuthorization authorization,
|
String excludedBindingId,
|
String checkboxGroupCode,
|
String role,
|
String repeatGroupCode,
|
String stepId,
|
ElnFieldVersionEntity version) {
|
if (checkboxGroupCode == null) {
|
return;
|
}
|
if ((!PRIMARY.equals(role) && !REPEAT.equals(role))
|
|| !ElnTemplateEnums.ControlType.CHECKBOX.name().equals(
|
controlType(version.getShujuLeixing()))) {
|
throw new DataException("只有可编辑复选框可以加入选择组");
|
}
|
ElnFieldForms.Binding bindingConfig = StringUtils.hasText(version.getBangdingPeizhi())
|
? JsonUtil.getJsonToBean(version.getBangdingPeizhi(), ElnFieldForms.Binding.class)
|
: null;
|
String readSource = bindingConfig == null || bindingConfig.getRead() == null
|
? null : trimToNull(bindingConfig.getRead().getSource());
|
if (readSource != null && !ElnFieldEnums.ReadSource.NONE.name().equals(readSource)) {
|
throw new DataException("只读来源复选框不能加入选择组");
|
}
|
if (excludedBindingId != null) {
|
List<ElnTemplateVos.Diagnostic> diagnostics = new ArrayList<>();
|
for (ElnTemplateBindingDependencyValidator validator : dependencyValidators) {
|
addDiagnostics(diagnostics, validator.validateCheckboxGroup(
|
authorization.getTenantId(), authorization.getTemplateId(),
|
excludedBindingId));
|
}
|
rejectDependencyErrors(diagnostics);
|
}
|
boolean defaultChecked = isDefaultChecked(version);
|
int defaultCheckedMembers = 0;
|
for (ElnTemplateBindingEntity member
|
: store.listBindings(authorization.getTenantId(), authorization.getTemplateId())) {
|
if (!isLive(member) || Objects.equals(excludedBindingId, member.getId())
|
|| !checkboxGroupCode.equals(member.getCheckboxGroupCode())) {
|
continue;
|
}
|
if (!role.equals(member.getKongjianJuese())
|
|| (PRIMARY.equals(role) && !Objects.equals(stepId, member.getStepId()))
|
|| (REPEAT.equals(role)
|
&& !Objects.equals(repeatGroupCode, member.getRepeatGroupCode()))) {
|
throw new DataException("同一选择组必须位于同一步骤和同一普通或重复作用域");
|
}
|
if (isDefaultChecked(requireFieldVersion(
|
authorization.getTenantId(), member.getZiduanBanbenId()))) {
|
defaultCheckedMembers++;
|
}
|
}
|
Integer maxSelected = checkboxGroupMaxSelected(authorization, checkboxGroupCode);
|
if (defaultChecked && maxSelected != null && defaultCheckedMembers >= maxSelected) {
|
throw new DataException("选择组默认勾选数不能超过最多选择数 " + maxSelected);
|
}
|
}
|
|
private Integer checkboxGroupMaxSelected(
|
TemplateAnnotationAuthorization authorization, String groupCode) {
|
if (checkboxGroupService != null) {
|
for (ElnTemplateVos.CheckboxGroup group : checkboxGroupService.list(
|
authorization.getTenantId(), authorization.getTemplateId())) {
|
if (groupCode.equals(group.getGroupCode())) {
|
return group.getMaxSelected();
|
}
|
}
|
}
|
return 1;
|
}
|
|
private static boolean isDefaultChecked(ElnFieldVersionEntity version) {
|
Map<String, Object> config = parseMap(version.getKongjianPeizhi());
|
return config != null && Boolean.TRUE.equals(config.get("defaultChecked"));
|
}
|
|
private static String operation(ElnTemplateForms.ChangeConfirm form) {
|
return form == null ? null : form.getOperationCode();
|
}
|
|
private static String requireText(String text, String message) {
|
if (!StringUtils.hasText(text)) {
|
throw new DataException(message);
|
}
|
return text.trim();
|
}
|
|
private static String trim(String text) {
|
return text == null ? null : text.trim();
|
}
|
|
private static String trimToNull(String text) {
|
return StringUtils.hasText(text) ? text.trim() : null;
|
}
|
|
private static String value(String text) {
|
return text == null ? "" : text;
|
}
|
|
private static ElnTemplateVos.StepConfiguration singleStepConfiguration(String stepId) {
|
ElnTemplateVos.Step step = new ElnTemplateVos.Step();
|
step.setId(stepId);
|
step.setName("实验步骤");
|
step.setOrder(1);
|
ElnTemplateVos.StepConfiguration result = new ElnTemplateVos.StepConfiguration();
|
result.setSingleStep(true);
|
result.setSteps(Collections.singletonList(step));
|
return result;
|
}
|
|
private static ElnTemplateBindingEntity copy(ElnTemplateBindingEntity source) {
|
ElnTemplateBindingEntity copy = new ElnTemplateBindingEntity();
|
copy.setId(source.getId());
|
copy.setMubanId(source.getMubanId());
|
copy.setMubanBianhao(source.getMubanBianhao());
|
copy.setZiduanDingyiId(source.getZiduanDingyiId());
|
copy.setZiduanBianma(source.getZiduanBianma());
|
copy.setZiduanBanbenId(source.getZiduanBanbenId());
|
copy.setKongjianBiaoqian(source.getKongjianBiaoqian());
|
copy.setKongjianBieming(source.getKongjianBieming());
|
copy.setKongjianLeixing(source.getKongjianLeixing());
|
copy.setKongjianJuese(source.getKongjianJuese());
|
copy.setYinyongShiliId(source.getYinyongShiliId());
|
copy.setRepeatGroupCode(source.getRepeatGroupCode());
|
copy.setRepeatPrototypeRowId(source.getRepeatPrototypeRowId());
|
copy.setCheckboxGroupCode(source.getCheckboxGroupCode());
|
copy.setStepId(source.getStepId());
|
copy.setBangdingZhuangtai(source.getBangdingZhuangtai());
|
copy.setCaozuoBianma(source.getCaozuoBianma());
|
copy.setLaiyuanBangdingId(source.getLaiyuanBangdingId());
|
copy.setDaoqiShijian(source.getDaoqiShijian());
|
copy.setZhuangtaiYuanyin(source.getZhuangtaiYuanyin());
|
copy.setFTenantId(source.getFTenantId());
|
copy.setFVersion(source.getFVersion());
|
copy.setFCreatorUserId(source.getFCreatorUserId());
|
copy.setFLastModifyUserId(source.getFLastModifyUserId());
|
copy.setFDeleteMark(source.getFDeleteMark());
|
return copy;
|
}
|
|
private List<DocxControlSnapshot> prepareDocumentEvidence(
|
TemplateAnnotationAuthorization authorization) {
|
if (authorization == null || !StringUtils.hasText(authorization.getTenantId())
|
|| !StringUtils.hasText(authorization.getTemplateId())
|
|| !StringUtils.hasText(authorization.getFileId())) {
|
throw new DataException("模板标注授权无效");
|
}
|
List<DocxControlSnapshot> controls = documentEvidence.readControls(
|
authorization.getTenantId(), authorization.getFileId());
|
if (controls == null) {
|
throw new DataException("DOCX 控件证据无效");
|
}
|
if (pendingCleanupService != null) {
|
pendingCleanupService.reconcileExpired(authorization.getTenantId(),
|
authorization.getTemplateId(), new Date(), controls);
|
}
|
return controls;
|
}
|
|
private static ElnTemplateDocumentEvidence evidence(
|
ObjectProvider<ElnTemplateDocumentEvidence> provider) {
|
ElnTemplateDocumentEvidence evidence = provider.getIfAvailable();
|
return evidence == null ? (tenantId, fileId) -> {
|
throw new DataException("DOCX 控件证据服务不可用");
|
} : evidence;
|
}
|
|
private static UserContext currentUserContext() {
|
UserInfo user = UserProvider.getUser();
|
TenantVO routed = TenantDataSourceUtil.getTenantInfo();
|
String tenantId = routed != null && StringUtils.hasText(routed.getEnCode())
|
? routed.getEnCode().trim()
|
: user != null && StringUtils.hasText(user.getTenantId())
|
? user.getTenantId().trim() : null;
|
String userId = user == null || !StringUtils.hasText(user.getUserId())
|
? null : user.getUserId().trim();
|
return new UserContext(tenantId, userId);
|
}
|
|
public interface BindingStore {
|
ElnTemplateVersionEntity findTemplate(String tenantId, String templateId);
|
ElnTemplateVersionEntity findTemplateForUpdate(String tenantId, String templateId);
|
List<ElnFieldDefEntity> listFields(String tenantId);
|
List<ElnFieldVersionEntity> listFieldVersions(String tenantId, String fieldId);
|
ElnFieldDefEntity findField(String tenantId, String fieldId);
|
ElnFieldVersionEntity findFieldVersion(String tenantId, String versionId);
|
ElnTemplateBindingEntity findBinding(String tenantId, String bindingId);
|
ElnTemplateBindingEntity findBindingForUpdate(String tenantId, String bindingId);
|
ElnTemplateBindingEntity findByOperation(String tenantId, String templateId,
|
String operationCode);
|
List<ElnTemplateBindingEntity> listBindings(String tenantId, String templateId);
|
void insert(ElnTemplateBindingEntity binding);
|
boolean compareAndSet(ElnTemplateBindingEntity replacement,
|
int expectedVersion, String expectedStatus);
|
}
|
|
private static final class MyBatisBindingStore implements BindingStore {
|
private final ElnTemplateMapper templateMapper;
|
private final ElnFieldDefMapper fieldMapper;
|
private final ElnFieldVersionMapper fieldVersionMapper;
|
private final ElnTemplateBindingMapper bindingMapper;
|
|
private MyBatisBindingStore(ElnTemplateMapper templateMapper,
|
ElnFieldDefMapper fieldMapper, ElnFieldVersionMapper fieldVersionMapper,
|
ElnTemplateBindingMapper bindingMapper) {
|
this.templateMapper = templateMapper;
|
this.fieldMapper = fieldMapper;
|
this.fieldVersionMapper = fieldVersionMapper;
|
this.bindingMapper = bindingMapper;
|
}
|
|
@Override
|
public ElnTemplateVersionEntity findTemplate(String tenantId, String templateId) {
|
return templateMapper.selectOne(Wrappers.<ElnTemplateVersionEntity>lambdaQuery()
|
.eq(ElnTemplateVersionEntity::getFTenantId, tenantId)
|
.eq(ElnTemplateVersionEntity::getId, templateId)
|
.and(q -> q.isNull(ElnTemplateVersionEntity::getFDeleteMark)
|
.or().eq(ElnTemplateVersionEntity::getFDeleteMark, 0)));
|
}
|
|
@Override
|
public ElnTemplateVersionEntity findTemplateForUpdate(
|
String tenantId, String templateId) {
|
return templateMapper.selectOne(Wrappers.<ElnTemplateVersionEntity>lambdaQuery()
|
.eq(ElnTemplateVersionEntity::getFTenantId, tenantId)
|
.eq(ElnTemplateVersionEntity::getId, templateId)
|
.and(q -> q.isNull(ElnTemplateVersionEntity::getFDeleteMark)
|
.or().eq(ElnTemplateVersionEntity::getFDeleteMark, 0))
|
.last("FOR UPDATE"));
|
}
|
|
@Override
|
public ElnFieldDefEntity findField(String tenantId, String fieldId) {
|
return fieldMapper.selectOne(Wrappers.<ElnFieldDefEntity>lambdaQuery()
|
.eq(ElnFieldDefEntity::getFTenantId, tenantId)
|
.eq(ElnFieldDefEntity::getId, fieldId)
|
.and(q -> q.isNull(ElnFieldDefEntity::getFDeleteMark)
|
.or().eq(ElnFieldDefEntity::getFDeleteMark, 0)));
|
}
|
|
@Override
|
public List<ElnFieldDefEntity> listFields(String tenantId) {
|
return fieldMapper.selectList(Wrappers.<ElnFieldDefEntity>lambdaQuery()
|
.eq(ElnFieldDefEntity::getFTenantId, tenantId)
|
.and(q -> q.isNull(ElnFieldDefEntity::getFDeleteMark)
|
.or().eq(ElnFieldDefEntity::getFDeleteMark, 0))
|
.orderByAsc(ElnFieldDefEntity::getZiduanBianma));
|
}
|
|
@Override
|
public List<ElnFieldVersionEntity> listFieldVersions(String tenantId, String fieldId) {
|
return fieldVersionMapper.selectList(Wrappers.<ElnFieldVersionEntity>lambdaQuery()
|
.eq(ElnFieldVersionEntity::getFTenantId, tenantId)
|
.eq(ElnFieldVersionEntity::getZiduanDingyiId, fieldId)
|
.and(q -> q.isNull(ElnFieldVersionEntity::getFDeleteMark)
|
.or().eq(ElnFieldVersionEntity::getFDeleteMark, 0))
|
.orderByAsc(ElnFieldVersionEntity::getBanbenHao)
|
.orderByAsc(ElnFieldVersionEntity::getId));
|
}
|
|
@Override
|
public ElnFieldVersionEntity findFieldVersion(String tenantId, String versionId) {
|
return fieldVersionMapper.selectOne(Wrappers.<ElnFieldVersionEntity>lambdaQuery()
|
.eq(ElnFieldVersionEntity::getFTenantId, tenantId)
|
.eq(ElnFieldVersionEntity::getId, versionId)
|
.and(q -> q.isNull(ElnFieldVersionEntity::getFDeleteMark)
|
.or().eq(ElnFieldVersionEntity::getFDeleteMark, 0)));
|
}
|
|
@Override
|
public ElnTemplateBindingEntity findBinding(String tenantId, String bindingId) {
|
return bindingMapper.selectOne(Wrappers.<ElnTemplateBindingEntity>lambdaQuery()
|
.eq(ElnTemplateBindingEntity::getFTenantId, tenantId)
|
.eq(ElnTemplateBindingEntity::getId, bindingId)
|
.and(q -> q.isNull(ElnTemplateBindingEntity::getFDeleteMark)
|
.or().eq(ElnTemplateBindingEntity::getFDeleteMark, 0)));
|
}
|
|
@Override
|
public ElnTemplateBindingEntity findBindingForUpdate(String tenantId, String bindingId) {
|
return bindingMapper.selectOne(Wrappers.<ElnTemplateBindingEntity>lambdaQuery()
|
.eq(ElnTemplateBindingEntity::getFTenantId, tenantId)
|
.eq(ElnTemplateBindingEntity::getId, bindingId)
|
.and(q -> q.isNull(ElnTemplateBindingEntity::getFDeleteMark)
|
.or().eq(ElnTemplateBindingEntity::getFDeleteMark, 0))
|
.last("FOR UPDATE"));
|
}
|
|
@Override
|
public ElnTemplateBindingEntity findByOperation(String tenantId, String templateId,
|
String operationCode) {
|
return bindingMapper.selectOne(Wrappers.<ElnTemplateBindingEntity>lambdaQuery()
|
.eq(ElnTemplateBindingEntity::getFTenantId, tenantId)
|
.eq(ElnTemplateBindingEntity::getMubanId, templateId)
|
.eq(ElnTemplateBindingEntity::getCaozuoBianma, operationCode)
|
.and(q -> q.isNull(ElnTemplateBindingEntity::getFDeleteMark)
|
.or().eq(ElnTemplateBindingEntity::getFDeleteMark, 0)));
|
}
|
|
@Override
|
public List<ElnTemplateBindingEntity> listBindings(String tenantId, String templateId) {
|
return bindingMapper.selectList(Wrappers.<ElnTemplateBindingEntity>lambdaQuery()
|
.eq(ElnTemplateBindingEntity::getFTenantId, tenantId)
|
.eq(ElnTemplateBindingEntity::getMubanId, templateId)
|
.and(q -> q.isNull(ElnTemplateBindingEntity::getFDeleteMark)
|
.or().eq(ElnTemplateBindingEntity::getFDeleteMark, 0))
|
.orderByAsc(ElnTemplateBindingEntity::getId));
|
}
|
|
@Override
|
public void insert(ElnTemplateBindingEntity binding) {
|
bindingMapper.insert(binding);
|
}
|
|
@Override
|
public boolean compareAndSet(ElnTemplateBindingEntity replacement,
|
int expectedVersion, String expectedStatus) {
|
return bindingMapper.update(null,
|
Wrappers.<ElnTemplateBindingEntity>lambdaUpdate()
|
.eq(ElnTemplateBindingEntity::getFTenantId, replacement.getFTenantId())
|
.eq(ElnTemplateBindingEntity::getId, replacement.getId())
|
.eq(ElnTemplateBindingEntity::getFVersion, expectedVersion)
|
.eq(ElnTemplateBindingEntity::getBangdingZhuangtai, expectedStatus)
|
.set(ElnTemplateBindingEntity::getZiduanBanbenId,
|
replacement.getZiduanBanbenId())
|
.set(ElnTemplateBindingEntity::getKongjianBiaoqian,
|
replacement.getKongjianBiaoqian())
|
.set(ElnTemplateBindingEntity::getKongjianBieming,
|
replacement.getKongjianBieming())
|
.set(ElnTemplateBindingEntity::getKongjianLeixing,
|
replacement.getKongjianLeixing())
|
.set(ElnTemplateBindingEntity::getKongjianJuese,
|
replacement.getKongjianJuese())
|
.set(ElnTemplateBindingEntity::getYinyongShiliId,
|
replacement.getYinyongShiliId())
|
.set(ElnTemplateBindingEntity::getRepeatGroupCode,
|
replacement.getRepeatGroupCode())
|
.set(ElnTemplateBindingEntity::getRepeatPrototypeRowId,
|
replacement.getRepeatPrototypeRowId())
|
.set(ElnTemplateBindingEntity::getCheckboxGroupCode,
|
replacement.getCheckboxGroupCode())
|
.set(ElnTemplateBindingEntity::getStepId,
|
replacement.getStepId())
|
.set(ElnTemplateBindingEntity::getBangdingZhuangtai,
|
replacement.getBangdingZhuangtai())
|
.set(ElnTemplateBindingEntity::getCaozuoBianma,
|
replacement.getCaozuoBianma())
|
.set(ElnTemplateBindingEntity::getZhuangtaiYuanyin,
|
replacement.getZhuangtaiYuanyin())
|
.set(ElnTemplateBindingEntity::getFVersion, expectedVersion + 1)) == 1;
|
}
|
}
|
|
public static final class UserContext {
|
private final String tenantId;
|
private final String userId;
|
|
public UserContext(String tenantId, String userId) {
|
this.tenantId = tenantId;
|
this.userId = userId;
|
}
|
|
public String getTenantId() { return tenantId; }
|
public String getUserId() { return userId; }
|
}
|
|
@FunctionalInterface
|
interface StepAssignmentResolver {
|
String resolve(String tenantId, String templateId, String requestedStepId);
|
}
|
|
@FunctionalInterface
|
interface StepConfigurationReader {
|
ElnTemplateVos.StepConfiguration read(String templateId);
|
}
|
}
|