package jnpf.elnService.impl;
|
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity;
|
import jnpf.elnEntity.runtime.ElnRecordForms;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordVos;
|
import jnpf.elnEntity.ElnDocumentFileVo;
|
import jnpf.elnEntity.field.ElnFieldVersionEntity;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateVersionEntity;
|
import jnpf.elnMapper.ElnTemplateMapper;
|
import jnpf.elnMapper.ElnFieldVersionMapper;
|
import jnpf.elnMapper.ElnRecordFieldValueMapper;
|
import jnpf.elnMapper.ElnRecordMapper;
|
import jnpf.elnMapper.ElnRecordRevisionMapper;
|
import jnpf.elnMapper.ElnTemplateBindingMapper;
|
import jnpf.elnService.ElnTestWorkItemSnapshotProvider;
|
import jnpf.elnService.ElnCrossTemplateSnapshotResolver;
|
import jnpf.elnService.ElnRecordFormulaCalculationService;
|
import jnpf.elnService.template.ElnTemplateFileService;
|
import jnpf.exception.DataException;
|
import jnpf.lims.api.model.TestWorkItemSnapshotBatch;
|
import jnpf.lims.api.model.TestWorkItemSnapshot;
|
import jnpf.lims.api.model.TestWorkItemSnapshotProperty;
|
import jnpf.lims.api.model.ElnSourceSnapshot;
|
import jnpf.lims.api.model.ElnSourcePropertyRef;
|
import org.junit.Test;
|
|
import java.lang.reflect.Proxy;
|
import java.util.ArrayList;
|
import java.util.Arrays;
|
import java.util.Collections;
|
import java.util.List;
|
import java.util.LinkedHashMap;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicReference;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
|
public class ElnRecordCreationServiceImplTest {
|
|
@Test
|
public void createsSnapshotFromPinnedSourceRevisionDespiteANewerPublishedRevision() {
|
Fixture fixture = new Fixture();
|
fixture.versions.get(0).setBangdingPeizhi(
|
"{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"test.basis\",\"propertyRevision\":1}}");
|
fixture.versions.get(0).setShujuLeixing("TEXT");
|
fixture.configuredValues.put("test.basis@1", "CONFIGURED-V1");
|
fixture.configuredValues.put("test.basis@2", "CONFIGURED-V2");
|
|
fixture.service.create(command());
|
|
ElnRecordFieldValueEntity value = fixture.fieldValues.get(0);
|
assertEquals("CONFIGURED-V1", value.getZhiWenben());
|
assertEquals(Integer.valueOf(1), value.getYuanShuxingBanben());
|
assertEquals("SUCCESS", value.getYuanQuzhiZhuangtai());
|
assertEquals("task.details@1/basis", value.getYuanXueyuan());
|
assertEquals(java.util.Date.from(java.time.Instant.parse("2026-09-06T02:00:00Z")),
|
value.getYuanHuoquShijian());
|
}
|
|
@Test
|
public void createsExclusiveDocumentAndInitialFieldSnapshots() {
|
Fixture fixture = new Fixture();
|
|
ElnRecordVos.Created created = fixture.service.create(command());
|
|
assertFalse(created.isExisting());
|
assertNotNull(created.getRecordId());
|
assertNotNull(created.getRevisionId());
|
assertEquals(created.getRevisionId() + ".docx", created.getDocument().getFileId());
|
assertEquals(1, fixture.copyCalls.get());
|
assertEquals(created.getRevisionId(), fixture.copyTarget.get());
|
assertEquals(1, fixture.recordInsertCalls.get());
|
assertEquals(1, fixture.revisionInsertCalls.get());
|
assertEquals(1, fixture.calculationCalls.get());
|
assertEquals("tenant-1", fixture.snapshotTenant.get());
|
assertEquals("user-1", fixture.snapshotUser.get());
|
assertEquals("HD_jyxm_0008", fixture.templateLookupProjectCode.get());
|
assertEquals(created.getDocument().getFileId(), fixture.revision.get().getJiluWendang());
|
assertFalse(fixture.revision.get().getJiluWendang().startsWith("["));
|
assertEquals(created.getRevisionId(), fixture.record.get().getDangqianXiudingId());
|
assertEquals(2, fixture.fieldValues.size());
|
|
ElnRecordFieldValueEntity lims = fixture.fieldValues.get(0);
|
assertEquals("LIMS_TASK", lims.getZhiLaiyuan());
|
assertEquals("GB/T 123", lims.getZhiWenben());
|
assertEquals("basis-1", lims.getYuanYinyongId());
|
|
ElnRecordFieldValueEntity system = fixture.fieldValues.get(1);
|
assertEquals("SYSTEM", system.getZhiLaiyuan());
|
assertEquals("测试员", system.getZhiWenben());
|
assertEquals("user-1", system.getYuanYinyongId());
|
}
|
|
@Test
|
public void preservesConfiguredDecimalPrecisionInTheTypedSnapshot() {
|
Fixture fixture = new Fixture();
|
fixture.versions.get(0).setBangdingPeizhi(
|
"{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"result.amount\",\"propertyRevision\":1}}");
|
fixture.versions.get(0).setShujuLeixing("DECIMAL");
|
fixture.configuredValues.put("result.amount@1", "123456789012345.123456789012345");
|
fixture.configuredResponse = value -> value.setDataType("DECIMAL");
|
|
fixture.service.create(command());
|
|
assertEquals(new java.math.BigDecimal("123456789012345.123456789012345"),
|
fixture.fieldValues.get(0).getZhiShuzhi());
|
}
|
|
@Test
|
public void rejectsNumbersThatWouldBeRoundedOrOverflowTheSnapshotColumn() {
|
for (String input : Arrays.asList("0.1234567890123456789", "100000000000000000000", "1e100000")) {
|
Fixture fixture = configuredFixture();
|
fixture.versions.get(0).setShujuLeixing("DECIMAL");
|
fixture.configuredValues.put("test.basis@1", input);
|
fixture.configuredResponse = value -> value.setDataType("DECIMAL");
|
try { fixture.service.create(command()); fail("Unrepresentable numeric snapshot must be rejected"); }
|
catch (DataException expected) { fixture.assertNoWrites(); }
|
}
|
}
|
|
@Test
|
public void textSourcesCanFillCompatibleTextControls() {
|
for (String type : Arrays.asList("TEXTAREA", "SELECT")) {
|
Fixture fixture = configuredFixture();
|
fixture.versions.get(0).setShujuLeixing(type);
|
fixture.service.create(command());
|
assertEquals("CONFIGURED-V1", fixture.fieldValues.get(0).getZhiWenben());
|
}
|
}
|
|
@Test
|
public void recordsOptionalMissingAndNotApplicableWithoutInventingAValue() {
|
for (String outcome : Arrays.asList("MISSING", "NOT_APPLICABLE")) {
|
Fixture fixture = configuredFixture();
|
fixture.configuredResponse = value -> {
|
value.setValue(null);
|
value.setOutcomeCode(outcome);
|
value.setReasonCode("MISSING".equals(outcome) ? "NAME_MISSING" : null);
|
};
|
|
fixture.service.create(command());
|
|
ElnRecordFieldValueEntity value = fixture.fieldValues.get(0);
|
assertEquals(null, value.getZhiWenben());
|
assertEquals(outcome, value.getYuanQuzhiZhuangtai());
|
assertEquals(Integer.valueOf(1), value.getYuanShuxingBanben());
|
}
|
}
|
|
private static Fixture configuredFixture() {
|
Fixture fixture = new Fixture();
|
fixture.versions.get(0).setBangdingPeizhi(
|
"{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"test.basis\",\"propertyRevision\":1}}");
|
fixture.versions.get(0).setShujuLeixing("TEXT");
|
fixture.configuredValues.put("test.basis@1", "CONFIGURED-V1");
|
return fixture;
|
}
|
|
@Test
|
public void rejectsRequiredMissingAndSourceFaultsBeforeCreatingAnything() {
|
for (String outcome : Arrays.asList("MISSING", "NOT_APPLICABLE", "SOURCE_INVALID", "SOURCE_FORBIDDEN", "SOURCE_AMBIGUOUS")) {
|
Fixture fixture = configuredFixture();
|
if ("MISSING".equals(outcome) || "NOT_APPLICABLE".equals(outcome)) fixture.versions.get(0).setShifouBitian(1);
|
fixture.configuredResponse = value -> value.setOutcomeCode(outcome);
|
try {
|
fixture.service.create(command());
|
fail("Source failure must prevent creation: " + outcome);
|
} catch (DataException expected) { fixture.assertNoWrites(); }
|
}
|
}
|
|
@Test
|
public void rejectsWrongTaskVersionAndTypeInsteadOfUsingLegacyValues() {
|
List<java.util.function.Consumer<ElnSourceSnapshot>> invalid = Arrays.asList(
|
value -> value.setSourceRef("another-task"),
|
value -> value.setPropertyRevision(2),
|
value -> value.setDataType("DECIMAL"),
|
value -> value.setAcquiredAt("invalid"));
|
for (java.util.function.Consumer<ElnSourceSnapshot> response : invalid) {
|
Fixture fixture = configuredFixture();
|
fixture.configuredResponse = response;
|
try { fixture.service.create(command()); fail("Invalid source response must be rejected"); }
|
catch (DataException expected) { fixture.assertNoWrites(); }
|
}
|
}
|
|
@Test
|
public void storesConfiguredDateInTheDateSlot() {
|
Fixture fixture = configuredFixture();
|
fixture.versions.get(0).setShujuLeixing("DATE");
|
fixture.configuredValues.put("test.basis@1", "2026-09-06");
|
fixture.configuredResponse = value -> value.setDataType("DATE");
|
|
fixture.service.create(command());
|
|
assertEquals(java.sql.Date.valueOf("2026-09-06"), fixture.fieldValues.get(0).getZhiRiqi());
|
}
|
|
@Test
|
public void storesConfiguredDateTimeAndBooleanUsingDeclaredTypes() {
|
Fixture time = configuredFixture();
|
time.versions.get(0).setShujuLeixing("DATETIME");
|
time.configuredValues.put("test.basis@1", "2026-09-06T10:30:00+08:00");
|
time.configuredResponse = value -> value.setDataType("DATETIME");
|
time.service.create(command());
|
assertEquals(java.util.Date.from(java.time.Instant.parse("2026-09-06T02:30:00Z")),
|
time.fieldValues.get(0).getZhiRiqi());
|
|
Fixture checkbox = configuredFixture();
|
checkbox.versions.get(0).setShujuLeixing("CHECKBOX");
|
checkbox.configuredValues.put("test.basis@1", "false");
|
checkbox.configuredResponse = value -> value.setDataType("BOOLEAN");
|
checkbox.service.create(command());
|
assertEquals("false", checkbox.fieldValues.get(0).getZhiWenben());
|
}
|
|
@Test
|
public void acceptsPostgresLocalTimestampWithoutInventingAnOffset() {
|
Fixture fixture = configuredFixture();
|
fixture.versions.get(0).setShujuLeixing("DATETIME");
|
fixture.configuredValues.put("test.basis@1", "2026-09-06T10:30:00");
|
fixture.configuredResponse = value -> value.setDataType("DATETIME");
|
fixture.service.create(command());
|
assertEquals(java.sql.Timestamp.valueOf("2026-09-06 10:30:00"), fixture.fieldValues.get(0).getZhiRiqi());
|
}
|
|
@Test
|
public void rejectsSubsecondSourceTimesInsteadOfRoundingTheSnapshot() {
|
for (String input : Arrays.asList("2026-09-06T10:30:00.123456", "2026-09-06T10:30:00.123456+08:00")) {
|
Fixture fixture = configuredFixture();
|
fixture.versions.get(0).setShujuLeixing("DATETIME");
|
fixture.configuredValues.put("test.basis@1", input);
|
fixture.configuredResponse = value -> value.setDataType("DATETIME");
|
try { fixture.service.create(command()); fail("Subsecond snapshot must not be silently rounded"); }
|
catch (DataException expected) { fixture.assertNoWrites(); }
|
}
|
}
|
|
@Test
|
public void requestsEveryLimsPropertyUsedByActiveTemplateBindings() {
|
Fixture fixture = new Fixture();
|
fixture.bindings.add(binding("binding-3", "field-3", "sample_name", "version-3"));
|
fixture.versions.add(version("version-3", "field-3",
|
"{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"sample.name\"}}"));
|
|
fixture.service.create(command());
|
|
assertEquals(Arrays.asList(
|
Collections.emptyList(),
|
Arrays.asList("test.basis", "sample.name")), fixture.snapshotPropertyRequests);
|
assertEquals(3, fixture.fieldValues.size());
|
assertEquals("sample.name", fixture.fieldValues.get(2).getYuanShuxing());
|
assertEquals("阿司匹林片", fixture.fieldValues.get(2).getZhiWenben());
|
}
|
|
@Test
|
public void createsEmptyValueWhenOptionalLimsPropertyIsMissing() {
|
Fixture fixture = new Fixture();
|
fixture.missingProperty = "test.basis";
|
|
fixture.service.create(command());
|
|
assertEquals(2, fixture.fieldValues.size());
|
ElnRecordFieldValueEntity optional = fixture.fieldValues.get(0);
|
assertEquals("LIMS_TASK", optional.getZhiLaiyuan());
|
assertEquals("test.basis", optional.getYuanShuxing());
|
assertEquals(null, optional.getZhiWenben());
|
}
|
|
@Test
|
public void rejectsMissingRequiredLimsPropertyBeforePersistingOrCopying() {
|
Fixture fixture = new Fixture();
|
fixture.versions.get(0).setShifouBitian(1);
|
fixture.missingProperty = "test.basis";
|
|
expectDataException(() -> fixture.service.create(command()), "检验任务快照中缺少检验依据");
|
|
fixture.assertNoWrites();
|
}
|
|
@Test
|
public void returnsExistingDraftWithoutCopyingOrInsertingAgain() {
|
Fixture fixture = new Fixture();
|
ElnRecordVos.Created first = fixture.service.create(command());
|
|
ElnRecordVos.Created second = fixture.service.create(command());
|
|
assertTrue(second.isExisting());
|
assertEquals(first.getRecordId(), second.getRecordId());
|
assertEquals(first.getRevisionId(), second.getRevisionId());
|
assertEquals(first.getDocument().getFileId(), second.getDocument().getFileId());
|
assertEquals(1, fixture.copyCalls.get());
|
assertEquals(1, fixture.recordInsertCalls.get());
|
assertEquals(1, fixture.revisionInsertCalls.get());
|
assertEquals(2, fixture.fieldValues.size());
|
}
|
|
@Test
|
public void rejectsMissingTemplateBeforePersistingOrCopying() {
|
Fixture fixture = new Fixture();
|
fixture.templates.clear();
|
|
expectDataException(() -> fixture.service.create(command()), "未配置");
|
|
fixture.assertNoWrites();
|
}
|
|
@Test
|
public void rejectsAmbiguousTemplatesBeforePersistingOrCopying() {
|
Fixture fixture = new Fixture();
|
fixture.templates.add(template("template-2"));
|
|
expectDataException(() -> fixture.service.create(command()), "多个");
|
|
fixture.assertNoWrites();
|
}
|
|
@Test
|
public void rejectsSnapshotWithoutTestDefinitionCode() {
|
Fixture fixture = new Fixture();
|
fixture.testDefinitionCode = " ";
|
|
expectDataException(() -> fixture.service.create(command()), "检验项目编码");
|
|
fixture.assertNoWrites();
|
}
|
|
@Test
|
public void rejectsTemplateWithMismatchedTestDefinitionCode() {
|
Fixture fixture = new Fixture();
|
fixture.templates.get(0).setJianyanXiangmuBianma("HD_jyxm_9999");
|
|
expectDataException(() -> fixture.service.create(command()), "编码与任务快照不一致");
|
|
fixture.assertNoWrites();
|
}
|
|
@Test
|
public void createsCrossTemplateScalarSnapshotWithCompleteLineageBeforePersistence() {
|
ElnRecordFieldValueEntity source = new ElnRecordFieldValueEntity();
|
source.setId("source-value-1");
|
source.setDianziJiluId("source-record-1");
|
source.setXiudingId("source-revision-1");
|
source.setZiduanBanbenId("source-version-1");
|
source.setZhiWenben("cross template result");
|
Fixture fixture = new Fixture((tenantId, testWorkItemId, bindings) -> {
|
assertEquals("tenant-1", tenantId);
|
assertEquals("test-work-item-1", testWorkItemId);
|
Map<String, ElnRecordFieldValueEntity> result = new LinkedHashMap<>();
|
result.put("binding-1", source);
|
return result;
|
});
|
|
fixture.service.create(command());
|
|
ElnRecordFieldValueEntity target = fixture.fieldValues.get(0);
|
assertEquals("cross template result", target.getZhiWenben());
|
assertEquals("CROSS_TEMPLATE", target.getZhiLaiyuan());
|
assertEquals("source-record-1", target.getKuamoLaiyuanJiluId());
|
assertEquals("source-revision-1", target.getKuamoLaiyuanXiudingId());
|
assertEquals("source-value-1", target.getKuamoLaiyuanZhiId());
|
assertEquals("source-version-1", target.getKuamoLaiyuanZiduanBanbenId());
|
assertNotNull(target.getKuamoHuoquShijian());
|
}
|
|
@Test
|
public void rejectsCrossTemplateResolutionBeforeAnyDatabaseOrDocumentWrite() {
|
Fixture fixture = new Fixture((tenantId, testWorkItemId, bindings) -> {
|
throw new DataException("SOURCE_FIELD_VALUE_INVALID: source empty");
|
});
|
|
expectDataException(() -> fixture.service.create(command()), "SOURCE_FIELD_VALUE_INVALID");
|
|
fixture.assertNoWrites();
|
}
|
|
private static ElnRecordForms.Create command() {
|
ElnRecordForms.Create command = new ElnRecordForms.Create();
|
command.setTestWorkItemId("test-work-item-1");
|
command.setOperationCode("eln-single-test-work-item-1-1");
|
return command;
|
}
|
|
private static void expectDataException(Runnable action, String messagePart) {
|
try {
|
action.run();
|
fail("预期抛出 DataException");
|
} catch (DataException expected) {
|
assertTrue(expected.getMessage().contains(messagePart));
|
}
|
}
|
|
private static ElnTemplateVersionEntity template(String id) {
|
ElnTemplateVersionEntity template = new ElnTemplateVersionEntity();
|
template.setId(id);
|
template.setFTenantId("tenant-1");
|
template.setMubanBianhao("TPL-001");
|
template.setMubanBanben("V1");
|
template.setJianyanXiangmuBianma("HD_jyxm_0008");
|
template.setMubanFile("[{\"fileId\":\"source.docx\",\"name\":\"source.docx\","
|
+ "\"fileExtension\":\"docx\"}]");
|
return template;
|
}
|
|
private static ElnTemplateBindingEntity binding(String id, String fieldId,
|
String fieldCode, String versionId) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(id);
|
binding.setMubanId("template-1");
|
binding.setZiduanDingyiId(fieldId);
|
binding.setZiduanBianma(fieldCode);
|
binding.setZiduanBanbenId(versionId);
|
binding.setKongjianJuese("PRIMARY");
|
binding.setBangdingZhuangtai("ACTIVE");
|
binding.setFTenantId("tenant-1");
|
return binding;
|
}
|
|
private static ElnFieldVersionEntity version(String id, String fieldId, String bindingConfig) {
|
ElnFieldVersionEntity version = new ElnFieldVersionEntity();
|
version.setId(id);
|
version.setZiduanDingyiId(fieldId);
|
version.setShifouBitian(0);
|
version.setBangdingPeizhi(bindingConfig);
|
version.setFTenantId("tenant-1");
|
return version;
|
}
|
|
@SuppressWarnings("unchecked")
|
private static <T> T mapper(Class<T> type, MapperInvocation invocation) {
|
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class<?>[]{type},
|
(proxy, method, args) -> {
|
if (method.getDeclaringClass() == Object.class) {
|
if ("toString".equals(method.getName())) {
|
return type.getSimpleName() + "Fake";
|
}
|
if ("hashCode".equals(method.getName())) {
|
return System.identityHashCode(proxy);
|
}
|
if ("equals".equals(method.getName())) {
|
return proxy == args[0];
|
}
|
}
|
Object result = invocation.invoke(method.getName(), args);
|
if (result != null || !method.getReturnType().isPrimitive()) {
|
return result;
|
}
|
if (method.getReturnType() == boolean.class) {
|
return false;
|
}
|
return 0;
|
});
|
}
|
|
@FunctionalInterface
|
private interface MapperInvocation {
|
Object invoke(String method, Object[] args);
|
}
|
|
private static final class Fixture {
|
private final List<ElnTemplateVersionEntity> templates = new ArrayList<>();
|
private final List<ElnTemplateBindingEntity> bindings = new ArrayList<>();
|
private final List<ElnFieldVersionEntity> versions = new ArrayList<>();
|
private final List<ElnRecordFieldValueEntity> fieldValues = new ArrayList<>();
|
private final AtomicReference<ElnRecordEntity> record = new AtomicReference<>();
|
private final AtomicReference<ElnRecordRevisionEntity> revision = new AtomicReference<>();
|
private final AtomicReference<String> copyTarget = new AtomicReference<>();
|
private final AtomicReference<String> snapshotTenant = new AtomicReference<>();
|
private final AtomicReference<String> snapshotUser = new AtomicReference<>();
|
private final AtomicReference<String> templateLookupProjectCode = new AtomicReference<>();
|
private final List<List<String>> snapshotPropertyRequests = new ArrayList<>();
|
private final AtomicInteger versionReads = new AtomicInteger();
|
private final AtomicInteger copyCalls = new AtomicInteger();
|
private final AtomicInteger recordInsertCalls = new AtomicInteger();
|
private final AtomicInteger revisionInsertCalls = new AtomicInteger();
|
private final AtomicInteger calculationCalls = new AtomicInteger();
|
private String testDefinitionCode = "HD_jyxm_0008";
|
private String missingProperty;
|
private final Map<String, String> configuredValues = new LinkedHashMap<>();
|
private java.util.function.Consumer<ElnSourceSnapshot> configuredResponse = value -> { };
|
private final ElnRecordCreationServiceImpl service;
|
|
private Fixture() {
|
this(ElnCrossTemplateSnapshotResolver.none());
|
}
|
|
private Fixture(ElnCrossTemplateSnapshotResolver crossTemplateSnapshotResolver) {
|
templates.add(template("template-1"));
|
bindings.add(binding("binding-1", "field-1", "test_basis", "version-1"));
|
bindings.add(binding("binding-2", "field-2", "operator_name", "version-2"));
|
versions.add(version("version-1", "field-1",
|
"{\"read\":{\"source\":\"LIMS_TASK\",\"property\":\"test.basis\"}}"));
|
versions.add(version("version-2", "field-2",
|
"{\"read\":{\"source\":\"SYSTEM\",\"property\":\"current_user.real_name\"}}"));
|
|
ElnTemplateMapper templateMapper = mapper(ElnTemplateMapper.class,
|
(method, args) -> {
|
if ("selectCurrentPublishedByTestDefinition".equals(method)) {
|
templateLookupProjectCode.set((String) args[1]);
|
return new ArrayList<>(templates);
|
}
|
return null;
|
});
|
ElnTemplateBindingMapper bindingMapper = mapper(ElnTemplateBindingMapper.class,
|
(method, args) -> "selectList".equals(method)
|
? new ArrayList<>(bindings) : null);
|
ElnFieldVersionMapper fieldVersionMapper = mapper(ElnFieldVersionMapper.class,
|
(method, args) -> "selectOne".equals(method)
|
? versions.get(versionReads.getAndIncrement()) : null);
|
ElnRecordMapper recordMapper = mapper(ElnRecordMapper.class, (method, args) -> {
|
if ("selectLiveByTestWorkItem".equals(method)) {
|
return record.get();
|
}
|
if ("insertIfAbsent".equals(method)) {
|
recordInsertCalls.incrementAndGet();
|
record.set((ElnRecordEntity) args[0]);
|
return 1;
|
}
|
if ("updateById".equals(method)) {
|
record.set((ElnRecordEntity) args[0]);
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordRevisionMapper revisionMapper = mapper(ElnRecordRevisionMapper.class,
|
(method, args) -> {
|
if ("selectOne".equals(method)) {
|
return revision.get();
|
}
|
if ("insert".equals(method)) {
|
revisionInsertCalls.incrementAndGet();
|
revision.set((ElnRecordRevisionEntity) args[0]);
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordFieldValueMapper fieldValueMapper = mapper(ElnRecordFieldValueMapper.class,
|
(method, args) -> {
|
if ("insert".equals(method)) {
|
fieldValues.add((ElnRecordFieldValueEntity) args[0]);
|
return 1;
|
}
|
return null;
|
});
|
ElnTemplateFileService fileService = new ElnTemplateFileService(null, null) {
|
@Override
|
public String copyDocx(String sourceAttachments, String targetTemplateId) {
|
copyCalls.incrementAndGet();
|
copyTarget.set(targetTemplateId);
|
return "[{\"fileId\":\"" + targetTemplateId + ".docx\","
|
+ "\"name\":\"" + targetTemplateId + ".docx\","
|
+ "\"fileExtension\":\"docx\",\"fileSize\":128}]";
|
}
|
};
|
ElnRecordFormulaCalculationService formulaCalculation =
|
new ElnRecordFormulaCalculationService() {
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAffected(
|
String tenantId, ElnRecordEntity record,
|
ElnRecordRevisionEntity revision,
|
List<ElnRecordFieldValueEntity> values,
|
Set<String> changedBindingIds,
|
String operationCode, String operatorUserId) {
|
calculationCalls.incrementAndGet();
|
return Collections.emptyList();
|
}
|
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAll(
|
String tenantId, ElnRecordEntity record,
|
ElnRecordRevisionEntity revision,
|
List<ElnRecordFieldValueEntity> values,
|
String operationCode, String operatorUserId) {
|
return Collections.emptyList();
|
}
|
};
|
|
ElnTestWorkItemSnapshotProvider taskProvider = new ElnTestWorkItemSnapshotProvider() {
|
@Override
|
public List<ElnSourceSnapshot> resolveConfiguredAsActor(String tenantId, String userId,
|
List<String> testWorkItemIds, List<ElnSourcePropertyRef> references) {
|
List<ElnSourceSnapshot> result = new ArrayList<>();
|
for (ElnSourcePropertyRef reference : references) {
|
ElnSourceSnapshot item = new ElnSourceSnapshot();
|
item.setSourceRef(testWorkItemIds.get(0));
|
item.setPropertyCode(reference.getPropertyCode());
|
item.setPropertyRevision(reference.getRevision());
|
item.setDataType("TEXT");
|
item.setValue(configuredValues.get(reference.getPropertyCode() + "@" + reference.getRevision()));
|
item.setOutcomeCode("SUCCESS");
|
item.setAcquiredAt("2026-09-06T02:00:00Z");
|
item.setLineage("task.details@1/basis");
|
configuredResponse.accept(item);
|
result.add(item);
|
}
|
return result;
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolve(
|
List<String> testWorkItemIds, List<String> properties) {
|
fail("记录创建必须通过显式 actor 快照接口读取 LIMS 任务");
|
return null;
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolveAsActor(
|
String tenantId, String userId,
|
List<String> testWorkItemIds, List<String> properties) {
|
snapshotTenant.set(tenantId);
|
snapshotUser.set(userId);
|
snapshotPropertyRequests.add(new ArrayList<>(properties));
|
return snapshot(properties);
|
}
|
};
|
service = new ElnRecordCreationServiceImpl(taskProvider,
|
templateMapper, bindingMapper, fieldVersionMapper, recordMapper,
|
revisionMapper, fieldValueMapper, fileService, formulaCalculation,
|
command -> {
|
ElnRecordVos.Created existing = new ElnRecordVos.Created();
|
existing.setRecordId(record.get().getId());
|
existing.setRevisionId(revision.get().getId());
|
ElnDocumentFileVo document = new ElnDocumentFileVo();
|
document.setFileId(revision.get().getJiluWendang());
|
document.setName(revision.get().getJiluWendang());
|
document.setFileExtension("docx");
|
existing.setDocument(document);
|
existing.setExisting(true);
|
return existing;
|
},
|
() -> ElnRecordCreationServiceImpl.userContext(
|
"tenant-1", "user-1", "测试员"),
|
crossTemplateSnapshotResolver);
|
}
|
|
private void assertNoWrites() {
|
assertEquals(0, copyCalls.get());
|
assertEquals(0, recordInsertCalls.get());
|
assertEquals(0, revisionInsertCalls.get());
|
assertTrue(fieldValues.isEmpty());
|
}
|
|
private TestWorkItemSnapshotBatch snapshot(List<String> requestedProperties) {
|
List<TestWorkItemSnapshotProperty> properties = new ArrayList<>();
|
for (String requested : requestedProperties) {
|
TestWorkItemSnapshotProperty property = new TestWorkItemSnapshotProperty();
|
property.setProperty(requested);
|
property.setSource("LIMS_TASK");
|
property.setSourceRef("sample.name".equals(requested) ? "sample-1" : "basis-1");
|
if (requested.equals(missingProperty)) {
|
property.setOutcomeCode("MISSING");
|
property.setReason("检验任务快照中缺少检验依据");
|
} else {
|
property.setValue("sample.name".equals(requested) ? "阿司匹林片" : "GB/T 123");
|
property.setOutcomeCode("SUCCESS");
|
}
|
properties.add(property);
|
}
|
|
TestWorkItemSnapshot item = new TestWorkItemSnapshot();
|
item.setTestWorkItemId("test-work-item-1");
|
item.setTestDefinitionId("test-definition-1");
|
item.setTestDefinitionCode(testDefinitionCode);
|
item.setTestDefinitionName("pH值");
|
item.setOutcomeCode("SUCCESS");
|
item.setProperties(properties);
|
|
TestWorkItemSnapshotBatch batch = new TestWorkItemSnapshotBatch();
|
batch.setItems(Arrays.asList(item));
|
return batch;
|
}
|
}
|
}
|