package jnpf.elnService.impl;
|
|
import jnpf.audit.model.AuditEventDTO;
|
import jnpf.eln.RecordFillSaveCommand;
|
import jnpf.eln.RecordFillSaveOperation;
|
import jnpf.elnEntity.field.ElnFieldVersionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordEntity;
|
import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity;
|
import jnpf.elnEntity.runtime.ElnRecordSaveOperationEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRevisionEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity;
|
import jnpf.elnEntity.runtime.ElnRecordRepeatValueEntity;
|
import jnpf.elnEntity.template.ElnTemplateBindingEntity;
|
import jnpf.elnEntity.template.ElnTemplateVos;
|
import jnpf.elnMapper.ElnFieldVersionMapper;
|
import jnpf.elnMapper.ElnRecordFieldValueMapper;
|
import jnpf.elnMapper.ElnRecordMapper;
|
import jnpf.elnMapper.ElnRecordSaveOperationMapper;
|
import jnpf.elnMapper.ElnRecordRepeatRowMapper;
|
import jnpf.elnMapper.ElnRecordRepeatValueMapper;
|
import jnpf.elnMapper.ElnRecordRevisionMapper;
|
import jnpf.elnMapper.ElnTemplateBindingMapper;
|
import jnpf.elnMapper.ElnTemplateFormulaBindingMapper;
|
import jnpf.elnService.ElnTestWorkItemSnapshotProvider;
|
import jnpf.elnService.ElnRecordFormulaCalculationService;
|
import jnpf.elnService.ElnRecordSourceSyncDocumentConfirmation;
|
import jnpf.elnService.template.ElnTemplateBindingConfirmationFacade;
|
import jnpf.elnService.template.ElnTemplateDocumentEvidence;
|
import jnpf.elnService.template.ElnTemplateCheckboxGroupService;
|
import jnpf.elnService.template.docx.DocxControlSnapshot;
|
import jnpf.exception.DataException;
|
import jnpf.lims.api.model.TestWorkItemSnapshotBatch;
|
import jnpf.lims.api.model.TestWorkItemSnapshot;
|
import org.junit.Test;
|
|
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Field;
|
import java.lang.reflect.Method;
|
import java.lang.reflect.Proxy;
|
import java.math.BigDecimal;
|
import java.util.Collections;
|
import java.util.Arrays;
|
import java.util.ArrayList;
|
import java.util.Date;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Set;
|
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.function.Consumer;
|
import java.util.function.Supplier;
|
|
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.fail;
|
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.when;
|
|
public class ElnRecordFillAccessServiceImplTest {
|
|
@Test
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
public void confirmsPendingSourceSyncBeforeCompletingTheDocumentSaveOperation() {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setJianyanXiangId("item-1");
|
record.setMubanId("template-1");
|
record.setDangqianXiudingId("revision-1");
|
record.setFVersion(4);
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
revision.setDianziJiluId("record-1");
|
revision.setJiluWendang("revision-1.docx");
|
revision.setBizStatus("DRAFT");
|
ElnRecordSaveOperationEntity operation = new ElnRecordSaveOperationEntity();
|
operation.setOperationCode("save-1");
|
operation.setOperationStatus("PREPARED");
|
operation.setDianziJiluId("record-1");
|
operation.setXiudingId("revision-1");
|
operation.setJiluWendang("revision-1.docx");
|
operation.setExpectedRecordVersion(4);
|
operation.setSessionDocKey("doc-key-1");
|
operation.setSessionVersion(2);
|
operation.setOperatorUserId("user-1");
|
operation.setFTenantId("tenant-1");
|
TestWorkItemSnapshot item = new TestWorkItemSnapshot();
|
item.setTestWorkItemId("item-1");
|
item.setOutcomeCode("SUCCESS");
|
TestWorkItemSnapshotBatch batch = new TestWorkItemSnapshotBatch();
|
batch.setItems(Collections.singletonList(item));
|
ElnTestWorkItemSnapshotProvider taskProvider = new ElnTestWorkItemSnapshotProvider() {
|
@Override
|
public TestWorkItemSnapshotBatch resolve(
|
List<String> testWorkItemIds, List<String> properties) {
|
return batch;
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolveAsActor(
|
String tenantId, String userId,
|
List<String> testWorkItemIds, List<String> properties) {
|
return batch;
|
}
|
};
|
ElnRecordFormulaCalculationService calculations =
|
new ElnRecordFormulaCalculationService() {
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAffected(
|
String tenantId, ElnRecordEntity currentRecord,
|
ElnRecordRevisionEntity currentRevision,
|
List<ElnRecordFieldValueEntity> values,
|
Set<String> changedBindingIds, String operationCode,
|
String operatorUserId) {
|
return Collections.emptyList();
|
}
|
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAll(
|
String tenantId, ElnRecordEntity currentRecord,
|
ElnRecordRevisionEntity currentRevision,
|
List<ElnRecordFieldValueEntity> values,
|
String operationCode, String operatorUserId) {
|
return Collections.emptyList();
|
}
|
};
|
AtomicInteger finishCalls = new AtomicInteger();
|
AtomicReference<List<String>> confirmationArguments = new AtomicReference<>();
|
ElnRecordSourceSyncDocumentConfirmation sourceConfirmation =
|
(tenantId, userId, recordId, revisionId) -> {
|
assertEquals(0, finishCalls.get());
|
confirmationArguments.set(Arrays.asList(
|
tenantId, userId, recordId, revisionId));
|
};
|
ElnRecordSaveOperationMapper operations = mapper(
|
ElnRecordSaveOperationMapper.class, method -> {
|
if ("selectOne".equals(method)) return operation;
|
if ("finishPrepared".equals(method)) {
|
assertNotNull(confirmationArguments.get());
|
finishCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnTemplateBindingConfirmationFacade confirmationFacade =
|
mock(ElnTemplateBindingConfirmationFacade.class);
|
when(confirmationFacade.execute(any(Supplier.class), any(Consumer.class)))
|
.thenAnswer(invocation -> ((Supplier<?>) invocation.getArgument(0)).get());
|
ElnRecordFillAccessServiceImpl service = new ElnRecordFillAccessServiceImpl(
|
mapper(ElnRecordMapper.class, method -> {
|
if ("selectOne".equals(method)) return record;
|
if ("advanceVersion".equals(method)) return 1;
|
return null;
|
}),
|
mapper(ElnRecordRevisionMapper.class, revision),
|
taskProvider,
|
mapper(ElnRecordFieldValueMapper.class,
|
method -> "selectList".equals(method)
|
? Collections.emptyList() : null),
|
mapper(ElnTemplateBindingMapper.class,
|
method -> "selectList".equals(method)
|
? Collections.emptyList() : null),
|
null,
|
operations,
|
mapper(ElnTemplateFormulaBindingMapper.class,
|
method -> "selectList".equals(method)
|
? Collections.emptyList() : null),
|
calculations,
|
(tenantId, fileId) -> Collections.emptyList(),
|
confirmationFacade,
|
null,
|
null,
|
event -> { },
|
new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
},
|
sourceConfirmation);
|
RecordFillSaveCommand command = new RecordFillSaveCommand();
|
command.setTenantId("tenant-1");
|
command.setUserId("user-1");
|
command.setRecordId("record-1");
|
command.setFileId("revision-1.docx");
|
command.setOperationCode("save-1");
|
command.setSessionDocKey("doc-key-1");
|
command.setSessionVersion(2);
|
command.setCallbackStatus(6);
|
|
assertEquals("COMMITTED", service.commit(command).getStatus());
|
assertEquals(Arrays.asList("tenant-1", "user-1", "record-1", "revision-1"),
|
confirmationArguments.get());
|
assertEquals(1, finishCalls.get());
|
}
|
|
@Test
|
public void rejectsMultipleCheckedPrimaryMembersBeforeAnyRecordWrite() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.addPrimary("pass", "合格", "step-result", "test_result", true);
|
fixture.addPrimary("fail", "不合格", "step-result", "test_result", true);
|
|
RecordFillSaveOperation result = fixture.commit();
|
|
assertRejectedCheckboxGroup(result);
|
assertEquals(1, fixture.operationWrites.get());
|
fixture.assertNoRecordWrites();
|
}
|
|
@Test
|
public void rejectsPrimaryGroupBelowConfiguredMinimumBeforeAnyRecordWrite() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.setRule("test_result", 1, 2);
|
fixture.addPrimary("pass", "合格", "step-result", "test_result", false);
|
fixture.addPrimary("fail", "不合格", "step-result", "test_result", false);
|
|
RecordFillSaveOperation result = fixture.commit();
|
|
assertEquals(RecordFillSaveOperation.REJECTED, result.getStatus());
|
assertTrue(result.getReason().contains("test_result"));
|
assertTrue(result.getReason().contains("至少选择 1 项"));
|
assertEquals(1, fixture.operationWrites.get());
|
fixture.assertNoRecordWrites();
|
}
|
|
@Test
|
public void rejectsMultipleCheckedRepeatMembersInTheSameRuntimeRow() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
ElnTemplateBindingEntity pass = fixture.addRepeatBinding("pass", "合格", false);
|
ElnTemplateBindingEntity fail = fixture.addRepeatBinding("fail", "不合格", false);
|
fixture.addRow("row-1", "ACTIVE");
|
fixture.addRuntimeControl(pass, "row-1", true, 2);
|
fixture.addRuntimeControl(fail, "row-1", true, 2);
|
|
RecordFillSaveOperation result = fixture.commit();
|
|
assertRejectedCheckboxGroup(result);
|
assertEquals(1, fixture.operationWrites.get());
|
fixture.assertNoRecordWrites();
|
}
|
|
@Test
|
public void rejectsRepeatRuntimeRowBelowConfiguredMinimumBeforeAnyRecordWrite() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.setRule("test_result", 1, 2);
|
ElnTemplateBindingEntity pass = fixture.addRepeatBinding("pass", "合格", false);
|
ElnTemplateBindingEntity fail = fixture.addRepeatBinding("fail", "不合格", false);
|
fixture.addRow("row-1", "ACTIVE");
|
fixture.addRuntimeControl(pass, "row-1", false, 2);
|
fixture.addRuntimeControl(fail, "row-1", false, 2);
|
|
RecordFillSaveOperation result = fixture.commit();
|
|
assertEquals(RecordFillSaveOperation.REJECTED, result.getStatus());
|
assertTrue(result.getReason().contains("test_result"));
|
assertTrue(result.getReason().contains("至少选择 1 项"));
|
assertEquals(1, fixture.operationWrites.get());
|
fixture.assertNoRecordWrites();
|
}
|
|
@Test
|
public void acceptsLegalAndIndependentlyScopedPrimaryCheckboxSnapshots() {
|
assertPrimarySnapshotCommitted("step-result", "test_result", true,
|
"step-result", "test_result", false);
|
assertPrimarySnapshotCommitted("step-result", "test_result", false,
|
"step-result", "test_result", false);
|
assertPrimarySnapshotCommitted("step-a", "test_result", true,
|
"step-b", "test_result", true);
|
assertPrimarySnapshotCommitted("step-result", null, true,
|
"step-result", null, true);
|
}
|
|
@Test
|
public void acceptsConfiguredPrimaryMaximumAndRejectsTheNextCheckedMember() {
|
CheckboxSaveFixture accepted = new CheckboxSaveFixture();
|
accepted.setRule("test_result", 0, 2);
|
accepted.addPrimary("pass", "合格", "step-result", "test_result", true);
|
accepted.addPrimary("fail", "不合格", "step-result", "test_result", true);
|
accepted.addPrimary("unknown", "无法判定", "step-result", "test_result", false);
|
assertEquals(RecordFillSaveOperation.COMMITTED, accepted.commit().getStatus());
|
|
CheckboxSaveFixture rejected = new CheckboxSaveFixture();
|
rejected.setRule("test_result", 0, 2);
|
rejected.addPrimary("pass", "合格", "step-result", "test_result", true);
|
rejected.addPrimary("fail", "不合格", "step-result", "test_result", true);
|
rejected.addPrimary("unknown", "无法判定", "step-result", "test_result", true);
|
|
RecordFillSaveOperation result = rejected.commit();
|
|
assertEquals(RecordFillSaveOperation.REJECTED, result.getStatus());
|
assertTrue(result.getReason().contains("最多选择 2 项"));
|
rejected.assertNoRecordWrites();
|
}
|
|
@Test
|
public void acceptsMultiplePrimaryMembersWhenConfiguredMaximumIsUnlimited() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.setRule("test_result", 1, null);
|
fixture.addPrimary("pass", "合格", "step-result", "test_result", true);
|
fixture.addPrimary("fail", "不合格", "step-result", "test_result", true);
|
fixture.addPrimary("unknown", "无法判定", "step-result", "test_result", true);
|
|
assertEquals(RecordFillSaveOperation.COMMITTED, fixture.commit().getStatus());
|
}
|
|
@Test
|
public void acceptsIndependentRepeatRowsAndIgnoresPrototypeAndDeletedRows() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
ElnTemplateBindingEntity pass = fixture.addRepeatBinding("pass", "合格", true);
|
ElnTemplateBindingEntity fail = fixture.addRepeatBinding("fail", "不合格", true);
|
fixture.addRow("row-1", "ACTIVE");
|
fixture.addRuntimeControl(pass, "row-1", true, 2);
|
fixture.addRuntimeControl(fail, "row-1", false, 2);
|
ElnRecordRepeatRowEntity deleted = fixture.addRow("row-deleted", "DELETE_PREPARED");
|
deleted.setDeleteRequestedUserId("user-1");
|
fixture.repeatValues.add(repeatValue(deleted, pass, "true"));
|
fixture.repeatValues.add(repeatValue(deleted, fail, "true"));
|
fixture.addRow("row-2", "ACTIVE");
|
fixture.addRuntimeControl(pass, "row-2", false, 3);
|
fixture.addRuntimeControl(fail, "row-2", true, 3);
|
|
assertEquals(RecordFillSaveOperation.COMMITTED, fixture.commit().getStatus());
|
}
|
|
@Test
|
public void appliesConfiguredRepeatMaximumIndependentlyToEachRuntimeRow() {
|
CheckboxSaveFixture accepted = new CheckboxSaveFixture();
|
accepted.setRule("test_result", 1, 2);
|
ElnTemplateBindingEntity acceptedPass = accepted.addRepeatBinding("pass", "合格", false);
|
ElnTemplateBindingEntity acceptedFail = accepted.addRepeatBinding("fail", "不合格", false);
|
ElnTemplateBindingEntity acceptedUnknown = accepted.addRepeatBinding(
|
"unknown", "无法判定", false);
|
accepted.addRow("row-1", "ACTIVE");
|
accepted.addRuntimeControl(acceptedPass, "row-1", true, 2);
|
accepted.addRuntimeControl(acceptedFail, "row-1", true, 2);
|
accepted.addRuntimeControl(acceptedUnknown, "row-1", false, 2);
|
accepted.addRow("row-2", "ACTIVE");
|
accepted.addRuntimeControl(acceptedPass, "row-2", false, 3);
|
accepted.addRuntimeControl(acceptedFail, "row-2", false, 3);
|
accepted.addRuntimeControl(acceptedUnknown, "row-2", true, 3);
|
assertEquals(RecordFillSaveOperation.COMMITTED, accepted.commit().getStatus());
|
|
CheckboxSaveFixture rejected = new CheckboxSaveFixture();
|
rejected.setRule("test_result", 1, 2);
|
ElnTemplateBindingEntity rejectedPass = rejected.addRepeatBinding("pass", "合格", false);
|
ElnTemplateBindingEntity rejectedFail = rejected.addRepeatBinding("fail", "不合格", false);
|
ElnTemplateBindingEntity rejectedUnknown = rejected.addRepeatBinding(
|
"unknown", "无法判定", false);
|
rejected.addRow("row-1", "ACTIVE");
|
rejected.addRuntimeControl(rejectedPass, "row-1", true, 2);
|
rejected.addRuntimeControl(rejectedFail, "row-1", true, 2);
|
rejected.addRuntimeControl(rejectedUnknown, "row-1", true, 2);
|
|
RecordFillSaveOperation result = rejected.commit();
|
|
assertEquals(RecordFillSaveOperation.REJECTED, result.getStatus());
|
assertTrue(result.getReason().contains("最多选择 2 项"));
|
rejected.assertNoRecordWrites();
|
}
|
|
@Test
|
public void normalizesNumberAndDateIntoTypedColumnsAndClearsTheOldType() {
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
value.setZhiRiqi(new Date(0));
|
apply(value, control("12.50"), version("DECIMAL", null));
|
|
assertEquals(new BigDecimal("12.50"), value.getZhiShuzhi());
|
assertEquals("12.50", value.getZhiWenben());
|
assertNull(value.getZhiRiqi());
|
|
apply(value, control("2026-08-23"), version("DATE", null));
|
|
assertNull(value.getZhiShuzhi());
|
assertNotNull(value.getZhiRiqi());
|
assertEquals("2026-08-23", value.getZhiWenben());
|
}
|
|
@Test
|
public void rejectsDamagedCheckboxStateInsteadOfSilentlyCoercingIt() {
|
try {
|
apply(new ElnRecordFieldValueEntity(), control("checked"),
|
version("CHECKBOX", null));
|
fail("damaged checkbox state must be rejected");
|
} catch (DataException expected) {
|
assertEquals("DOCX 勾选字段状态无效", expected.getMessage());
|
}
|
}
|
|
@Test
|
public void mapsDropdownDisplayTextBackToStableBusinessValue() {
|
String config = "{\"options\":[{\"value\":\"PASS\",\"display\":\"合格\"},"
|
+ "{\"value\":\"FAIL\",\"display\":\"不合格\"}]}";
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
|
apply(value, control("合格"), version("DROPDOWN", config));
|
|
assertEquals("PASS", value.getZhiWenben());
|
}
|
|
@Test
|
public void savesUnfilledEditableDropdownAsEmptyDraftValue() {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.addPrimary("conclusion", "检验结论", "step-result", null, false);
|
fixture.fieldVersion = version("SELECT",
|
"{\"placeholder\":\"请选择\",\"options\":["
|
+ "{\"value\":\"pass\",\"display\":\"符合规定\"},"
|
+ "{\"value\":\"fail\",\"display\":\"不符合规定\"}]}");
|
ElnTemplateBindingEntity binding = fixture.bindings.get(0);
|
binding.setKongjianLeixing("DROPDOWN");
|
fixture.primaryValues.get(0).setZhiWenben(null);
|
Map<String, Object> controlConfig = new LinkedHashMap<>();
|
controlConfig.put("pass", "符合规定");
|
controlConfig.put("fail", "不符合规定");
|
fixture.controls.clear();
|
fixture.controls.add(new DocxControlSnapshot(binding.getKongjianBiaoqian(),
|
binding.getKongjianBieming(), null, "DROPDOWN", "PRIMARY",
|
"word/document.xml", "OK", controlConfig, null));
|
|
RecordFillSaveOperation result = fixture.commit();
|
|
assertEquals(fixture.operation.getRejectReason(), RecordFillSaveOperation.COMMITTED,
|
result.getStatus());
|
assertNull(fixture.primaryValues.get(0).getZhiWenben());
|
assertEquals(0, fixture.valueWriteCalls.get());
|
}
|
|
@Test
|
public void rejectsExtraElnControlWithoutAnActiveBinding() {
|
ElnTemplateDocumentEvidence evidence = (tenantId, fileId) ->
|
Collections.singletonList(new DocxControlSnapshot(
|
"eln.field.orphan", "孤立字段", null, "TEXT", "PRIMARY",
|
"word/document.xml", "OK", Collections.emptyMap(), "value"));
|
ElnRecordFillAccessServiceImpl service = service(evidence);
|
|
try {
|
invoke(service, "controls", new Class<?>[]{String.class, String.class, Map.class},
|
"tenant-a", "record.docx", new LinkedHashMap<>());
|
fail("orphan ELN control must be rejected");
|
} catch (DataException expected) {
|
assertEquals("DOCX 存在未绑定的 ELN 字段控件", expected.getMessage());
|
}
|
}
|
|
@Test
|
public void rejectsManualChangesToProviderFields() {
|
ElnFieldVersionEntity version = version("TEXT", null);
|
version.setBangdingPeizhi("{\"read\":{\"source\":\"LIMS_TASK\","
|
+ "\"property\":\"test.basis\"}}");
|
ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity();
|
value.setZhiWenben("database value");
|
|
try {
|
invoke(service((tenantId, fileId) -> Collections.emptyList()),
|
"assertProviderValueUnchanged",
|
new Class<?>[]{ElnRecordFieldValueEntity.class,
|
DocxControlSnapshot.class, ElnFieldVersionEntity.class},
|
value, control("tampered value"), version);
|
fail("provider fields must reject manual changes");
|
} catch (DataException expected) {
|
assertEquals("外部来源字段不允许人工修改,请使用重新同步", expected.getMessage());
|
}
|
}
|
|
@Test
|
public void historicalRevisionIsRejectedBeforeReadingLimsState() {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setJianyanXiangId("item-1");
|
record.setDangqianXiudingId("revision-1");
|
record.setFVersion(4);
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
revision.setDianziJiluId("record-1");
|
revision.setJiluWendang("old.docx");
|
revision.setBizStatus("HISTORICAL");
|
AtomicInteger providerCalls = new AtomicInteger();
|
ElnTestWorkItemSnapshotProvider provider = new ElnTestWorkItemSnapshotProvider() {
|
@Override
|
public TestWorkItemSnapshotBatch resolve(
|
List<String> testWorkItemIds, List<String> properties) {
|
providerCalls.incrementAndGet();
|
return null;
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolveAsActor(
|
String tenantId, String userId,
|
List<String> testWorkItemIds, List<String> properties) {
|
providerCalls.incrementAndGet();
|
return null;
|
}
|
};
|
ElnRecordFillAccessServiceImpl service = new ElnRecordFillAccessServiceImpl(
|
mapper(ElnRecordMapper.class, record),
|
mapper(ElnRecordRevisionMapper.class, revision),
|
provider, null, null, null, null, null, null,
|
null, null,
|
new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
|
assertEquals(false, service.canFill(
|
"tenant-1", "user-1", "record-1", "old.docx"));
|
assertEquals(0, providerCalls.get());
|
}
|
|
@Test
|
public void preparesAndPersistsACompleteRepeatRowFromDocxControls() throws Exception {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity();
|
row.setRowId("row-1");
|
row.setRepeatGroupCode("results");
|
row.setMubanId("template-1");
|
row.setRowOrder(1);
|
row.setOperationCode(String.join("", Collections.nCopies(64, "a")));
|
row.setOperationStatus("PREPARED");
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId("binding-result");
|
binding.setZiduanDingyiId("field-result");
|
binding.setZiduanBanbenId("version-result");
|
binding.setZiduanBianma("result");
|
binding.setKongjianBiaoqian("eln.repeat.results.prototype.result");
|
binding.setKongjianLeixing("TEXT");
|
binding.setKongjianJuese("REPEAT");
|
binding.setRepeatGroupCode("results");
|
binding.setRepeatPrototypeRowId("prototype");
|
ElnFieldVersionEntity version = version("TEXT", null);
|
version.setId("version-result");
|
AtomicReference<ElnRecordRepeatValueEntity> inserted = new AtomicReference<>();
|
AtomicInteger activated = new AtomicInteger();
|
List<AuditEventDTO> audits = new ArrayList<>();
|
ElnRecordRepeatRowMapper rows = mapper(ElnRecordRepeatRowMapper.class, method -> {
|
if ("selectList".equals(method)) return Collections.singletonList(row);
|
if ("updateById".equals(method)) {
|
activated.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordRepeatValueMapper values = mapper(ElnRecordRepeatValueMapper.class, method -> {
|
if ("selectList".equals(method)) return Collections.emptyList();
|
return null;
|
}, argument -> {
|
inserted.set((ElnRecordRepeatValueEntity) argument);
|
return 1;
|
});
|
ElnRecordFillAccessServiceImpl service = new ElnRecordFillAccessServiceImpl(
|
null, null, null, null, null,
|
mapper(ElnFieldVersionMapper.class, version),
|
null, null, null, null, null, rows, values,
|
audits::add,
|
new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
RecordFillSaveCommand command = new RecordFillSaveCommand();
|
command.setTenantId("tenant-1");
|
command.setUserId("user-1");
|
command.setRecordId("record-1");
|
Map<String, ElnTemplateBindingEntity> bindings = new LinkedHashMap<>();
|
bindings.put(binding.getId(), binding);
|
Map<String, DocxControlSnapshot> controls = new LinkedHashMap<>();
|
controls.put(binding.getKongjianBiaoqian(), repeatControl(
|
binding.getKongjianBiaoqian(), "", 1));
|
|
Class<?> currentType = Class.forName(
|
ElnRecordFillAccessServiceImpl.class.getName() + "$Current");
|
Constructor<?> constructor = currentType.getDeclaredConstructor(
|
ElnRecordEntity.class, ElnRecordRevisionEntity.class);
|
constructor.setAccessible(true);
|
Object current = constructor.newInstance(record, revision);
|
Object pending = invoke(service, "prepareRepeatValues",
|
new Class<?>[]{currentType, RecordFillSaveCommand.class, Map.class,
|
Map.class, Set.class},
|
current, command, bindings, controls, Collections.emptySet());
|
invoke(service, "saveRepeatValues", new Class<?>[]{pending.getClass()}, pending);
|
|
assertEquals(null, inserted.get());
|
assertEquals("PREPARED", row.getOperationStatus());
|
assertEquals(0, activated.get());
|
|
controls.put("eln.repeat.results.row-1.result", repeatControl(
|
"eln.repeat.results.row-1.result", "合格", 2));
|
Object prepared = invoke(service, "prepareRepeatValues",
|
new Class<?>[]{currentType, RecordFillSaveCommand.class, Map.class,
|
Map.class, Set.class},
|
current, command, bindings, controls, Collections.emptySet());
|
Field changedBindingIds = prepared.getClass().getDeclaredField("changedBindingIds");
|
changedBindingIds.setAccessible(true);
|
assertTrue(((Set<?>) changedBindingIds.get(prepared)).contains("binding-result"));
|
invoke(service, "saveRepeatValues", new Class<?>[]{prepared.getClass()}, prepared);
|
|
assertNotNull(inserted.get());
|
assertEquals("row-1", inserted.get().getRepeatRowId());
|
assertEquals("合格", inserted.get().getZhiWenben());
|
assertEquals("ACTIVE", row.getOperationStatus());
|
assertEquals(1, activated.get());
|
assertEquals("ELN_RECORD_REPEAT_APPEND_ACTIVATE", audits.get(0).getActionCode());
|
assertEquals(68, audits.get(0).getClientEventId().length());
|
}
|
|
@Test
|
public void softDeletesRegisteredRepeatRowAndValuesAfterTheDocxRowDisappears()
|
throws Exception {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity();
|
row.setId("snapshot-1");
|
row.setRowId("row-1");
|
row.setRepeatGroupCode("results");
|
row.setMubanId("template-1");
|
row.setRowOrder(1);
|
row.setOperationStatus("DELETE_PREPARED");
|
row.setDeleteOperationCode("delete-1");
|
row.setDeleteReason("录入错误");
|
row.setDeleteRequestedUserId("user-1");
|
row.setDeleteRequestedTime(new Date());
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId("binding-result");
|
binding.setZiduanDingyiId("field-result");
|
binding.setZiduanBanbenId("version-result");
|
binding.setZiduanBianma("result");
|
binding.setKongjianBiaoqian("eln.repeat.results.prototype.result");
|
binding.setKongjianLeixing("TEXT");
|
binding.setKongjianJuese("REPEAT");
|
binding.setRepeatGroupCode("results");
|
binding.setRepeatPrototypeRowId("prototype");
|
ElnFieldVersionEntity version = version("TEXT", null);
|
version.setId("version-result");
|
ElnRecordRepeatValueEntity value = new ElnRecordRepeatValueEntity();
|
value.setId("value-1");
|
value.setDianziJiluId("record-1");
|
value.setXiudingId("revision-1");
|
value.setRepeatRowId("row-1");
|
value.setRepeatGroupCode("results");
|
value.setMubanId("template-1");
|
value.setMubanBangdingId("binding-result");
|
value.setZiduanDingyiId("field-result");
|
value.setZiduanBanbenId("version-result");
|
value.setZiduanBianma("result");
|
value.setBangdingJuese("REPEAT");
|
value.setBangdingZhuangtai("ACTIVE");
|
AtomicInteger rowUpdates = new AtomicInteger();
|
AtomicInteger valueUpdates = new AtomicInteger();
|
List<AuditEventDTO> audits = new ArrayList<>();
|
ElnRecordRepeatRowMapper rows = mapper(ElnRecordRepeatRowMapper.class, method -> {
|
if ("selectList".equals(method)) return Collections.singletonList(row);
|
if ("updateById".equals(method)) {
|
rowUpdates.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordRepeatValueMapper values = mapper(ElnRecordRepeatValueMapper.class,
|
method -> {
|
if ("selectList".equals(method)) return Collections.singletonList(value);
|
if ("updateById".equals(method)) {
|
valueUpdates.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordFillAccessServiceImpl service = new ElnRecordFillAccessServiceImpl(
|
null, null, null, null, null,
|
mapper(ElnFieldVersionMapper.class, version),
|
null, null, null, null, null, rows, values,
|
audits::add,
|
new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
RecordFillSaveCommand command = new RecordFillSaveCommand();
|
command.setTenantId("tenant-1");
|
command.setUserId("user-1");
|
command.setRecordId("record-1");
|
Map<String, ElnTemplateBindingEntity> bindings = new LinkedHashMap<>();
|
bindings.put(binding.getId(), binding);
|
Map<String, DocxControlSnapshot> controls = new LinkedHashMap<>();
|
controls.put(binding.getKongjianBiaoqian(), repeatControl(
|
binding.getKongjianBiaoqian(), "", 1));
|
Class<?> currentType = Class.forName(
|
ElnRecordFillAccessServiceImpl.class.getName() + "$Current");
|
Constructor<?> constructor = currentType.getDeclaredConstructor(
|
ElnRecordEntity.class, ElnRecordRevisionEntity.class);
|
constructor.setAccessible(true);
|
Object current = constructor.newInstance(record, revision);
|
|
Object prepared = invoke(service, "prepareRepeatValues",
|
new Class<?>[]{currentType, RecordFillSaveCommand.class, Map.class,
|
Map.class, Set.class},
|
current, command, bindings, controls, Collections.emptySet());
|
invoke(service, "saveRepeatValues", new Class<?>[]{prepared.getClass()}, prepared);
|
|
assertEquals("DELETED", row.getOperationStatus());
|
assertEquals(Integer.valueOf(1), row.getFDeleteMark());
|
assertEquals(Integer.valueOf(1), value.getFDeleteMark());
|
assertEquals(1, rowUpdates.get());
|
assertEquals(1, valueUpdates.get());
|
assertEquals("ELN_RECORD_REPEAT_DELETE", audits.get(0).getActionCode());
|
}
|
|
@Test
|
public void persistsIndependentRuntimeRowsForMultipleRepeatGroups() throws Exception {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setMubanId("template-1");
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
ElnRecordRepeatRowEntity resultRow = repeatRow("row-result", "results");
|
ElnRecordRepeatRowEntity conditionRow = repeatRow("row-condition", "conditions");
|
ElnTemplateBindingEntity resultBinding = repeatBinding(
|
"binding-result", "results", "result");
|
ElnTemplateBindingEntity conditionBinding = repeatBinding(
|
"binding-condition", "conditions", "temperature");
|
ElnFieldVersionEntity version = version("TEXT", null);
|
version.setId("version-repeat");
|
AtomicInteger inserted = new AtomicInteger();
|
AtomicInteger activated = new AtomicInteger();
|
ElnRecordRepeatRowMapper rows = mapper(ElnRecordRepeatRowMapper.class, method -> {
|
if ("selectList".equals(method)) return Arrays.asList(resultRow, conditionRow);
|
if ("updateById".equals(method)) {
|
activated.incrementAndGet();
|
return 1;
|
}
|
return null;
|
});
|
ElnRecordRepeatValueMapper values = mapper(ElnRecordRepeatValueMapper.class,
|
method -> "selectList".equals(method) ? Collections.emptyList() : null,
|
argument -> {
|
inserted.incrementAndGet();
|
return 1;
|
});
|
ElnRecordFillAccessServiceImpl service = new ElnRecordFillAccessServiceImpl(
|
null, null, null, null, null,
|
mapper(ElnFieldVersionMapper.class, version),
|
null, null, null, null, null, rows, values,
|
new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
RecordFillSaveCommand command = new RecordFillSaveCommand();
|
command.setTenantId("tenant-1");
|
command.setUserId("user-1");
|
command.setRecordId("record-1");
|
Map<String, ElnTemplateBindingEntity> bindings = new LinkedHashMap<>();
|
bindings.put(resultBinding.getId(), resultBinding);
|
bindings.put(conditionBinding.getId(), conditionBinding);
|
Map<String, DocxControlSnapshot> controls = new LinkedHashMap<>();
|
controls.put(resultBinding.getKongjianBiaoqian(), repeatControl(
|
resultBinding.getKongjianBiaoqian(), "", 0, 1));
|
controls.put("eln.repeat.results.row-result.result", repeatControl(
|
"eln.repeat.results.row-result.result", "合格", 0, 2));
|
controls.put(conditionBinding.getKongjianBiaoqian(), repeatControl(
|
conditionBinding.getKongjianBiaoqian(), "", 1, 1));
|
controls.put("eln.repeat.conditions.row-condition.temperature", repeatControl(
|
"eln.repeat.conditions.row-condition.temperature", "25", 1, 2));
|
Class<?> currentType = Class.forName(
|
ElnRecordFillAccessServiceImpl.class.getName() + "$Current");
|
Constructor<?> constructor = currentType.getDeclaredConstructor(
|
ElnRecordEntity.class, ElnRecordRevisionEntity.class);
|
constructor.setAccessible(true);
|
Object current = constructor.newInstance(record, revision);
|
|
Object prepared = invoke(service, "prepareRepeatValues",
|
new Class<?>[]{currentType, RecordFillSaveCommand.class, Map.class,
|
Map.class, Set.class},
|
current, command, bindings, controls, Collections.emptySet());
|
invoke(service, "saveRepeatValues", new Class<?>[]{prepared.getClass()}, prepared);
|
|
assertEquals(2, inserted.get());
|
assertEquals(2, activated.get());
|
assertEquals("ACTIVE", resultRow.getOperationStatus());
|
assertEquals("ACTIVE", conditionRow.getOperationStatus());
|
}
|
|
private static ElnRecordRepeatRowEntity repeatRow(String rowId, String groupCode) {
|
ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity();
|
row.setId("snapshot-" + rowId);
|
row.setRowId(rowId);
|
row.setDianziJiluId("record-1");
|
row.setXiudingId("revision-1");
|
row.setMubanId("template-1");
|
row.setRepeatGroupCode(groupCode);
|
row.setRowOrder(1);
|
row.setOperationStatus("PREPARED");
|
return row;
|
}
|
|
@Test
|
public void savesUnchangedConfiguredDecimalWithoutRewritingTheSourceSnapshot() {
|
CheckboxSaveFixture fixture = configuredDecimalFixture("123.456789012345678901");
|
ElnRecordFieldValueEntity value = fixture.primaryValues.get(0);
|
RecordFillSaveOperation saved = fixture.commit();
|
assertEquals(fixture.operation.getRejectReason(), RecordFillSaveOperation.COMMITTED, saved.getStatus());
|
assertEquals(0, fixture.valueWriteCalls.get());
|
assertEquals(null, value.getZhiWenben());
|
assertEquals(new BigDecimal("123.456789012345678901"), value.getZhiShuzhi());
|
assertEquals(1, fixture.sourceConfirmationCalls.get());
|
}
|
|
@Test
|
public void rejectsLastDigitChangesToConfiguredDecimalWithoutConfirmingSourceSync() {
|
CheckboxSaveFixture fixture = configuredDecimalFixture("123.456789012345678902");
|
|
assertEquals(RecordFillSaveOperation.REJECTED, fixture.commit().getStatus());
|
assertEquals("外部来源字段不允许人工修改,请使用重新同步", fixture.operation.getRejectReason());
|
fixture.assertNoRecordWrites();
|
assertEquals(new BigDecimal("123.456789012345678901"), fixture.primaryValues.get(0).getZhiShuzhi());
|
}
|
|
private static CheckboxSaveFixture configuredDecimalFixture(String documentValue) {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.addPrimary("source", "来源", "step-result", null, false);
|
ElnTemplateBindingEntity binding = fixture.bindings.get(0);
|
binding.setKongjianLeixing("TEXT");
|
fixture.fieldVersion = version("DECIMAL", null);
|
fixture.fieldVersion.setBangdingPeizhi("{\"read\":{\"source\":\"LIMS_TASK\","
|
+ "\"property\":\"verification.decimal\",\"propertyRevision\":1}}");
|
ElnRecordFieldValueEntity value = fixture.primaryValues.get(0);
|
value.setZhiWenben(null);
|
value.setZhiShuzhi(new BigDecimal("123.456789012345678901"));
|
value.setZhiLaiyuan("LIMS_TASK");
|
value.setYuanShuxingBanben(1);
|
fixture.controls.clear();
|
fixture.controls.add(new DocxControlSnapshot(binding.getKongjianBiaoqian(),
|
binding.getKongjianBieming(), null, "TEXT", "PRIMARY", "word/document.xml", "OK",
|
Collections.emptyMap(), documentValue));
|
return fixture;
|
}
|
|
@Test
|
public void savesUnchangedConfiguredDatesWithoutRewritingSnapshots() {
|
for (String type : new String[]{"DATE", "DATETIME"}) {
|
String raw = "DATE".equals(type) ? "2026-09-06" : "2026-09-06 09:10:11";
|
CheckboxSaveFixture fixture = configuredDateFixture(type, raw);
|
RecordFillSaveOperation result = fixture.commit();
|
|
assertEquals(type + ": " + fixture.operation.getRejectReason(),
|
RecordFillSaveOperation.COMMITTED, result.getStatus());
|
assertEquals(0, fixture.valueWriteCalls.get());
|
assertEquals(null, fixture.primaryValues.get(0).getZhiWenben());
|
assertEquals(1, fixture.sourceConfirmationCalls.get());
|
}
|
}
|
|
private static CheckboxSaveFixture configuredDateFixture(String type, String raw) {
|
CheckboxSaveFixture fixture = configuredDecimalFixture(raw);
|
fixture.fieldVersion.setShujuLeixing(type);
|
ElnRecordFieldValueEntity value = fixture.primaryValues.get(0);
|
value.setZhiShuzhi(null);
|
value.setZhiRiqi("DATE".equals(type) ? java.sql.Date.valueOf("2026-09-06")
|
: java.sql.Timestamp.valueOf("2026-09-06 09:10:11"));
|
ElnTemplateBindingEntity binding = fixture.bindings.get(0);
|
binding.setKongjianLeixing("DATE");
|
fixture.controls.clear();
|
fixture.controls.add(new DocxControlSnapshot(binding.getKongjianBiaoqian(),
|
binding.getKongjianBieming(), null, "DATE", "PRIMARY", "word/document.xml", "OK",
|
Collections.singletonMap("dateFormat", "DATE".equals(type) ? "yyyy-MM-dd" : "yyyy-MM-dd HH:mm:ss"), raw));
|
return fixture;
|
}
|
|
@Test
|
public void rejectsConfiguredDatePrecisionLossAndTrailingInput() {
|
String instant = java.sql.Timestamp.valueOf("2026-09-06 09:10:11").toInstant().toString();
|
for (String raw : new String[]{"2026-09-06 09:10:11.1", "2026-09-06 09:10:11 trailing",
|
instant.replace("Z", ".000001Z")}) {
|
CheckboxSaveFixture fixture = configuredDateFixture("DATETIME", raw);
|
assertEquals(raw, RecordFillSaveOperation.REJECTED, fixture.commit().getStatus());
|
fixture.assertNoRecordWrites();
|
}
|
}
|
|
@Test
|
public void savesConfiguredCalendarDateFromIsoDocumentTime() {
|
String raw = java.sql.Timestamp.valueOf("2026-09-06 00:00:00").toInstant().toString();
|
CheckboxSaveFixture fixture = configuredDateFixture("DATE", raw);
|
assertEquals(fixture.operation.getRejectReason(), RecordFillSaveOperation.COMMITTED,
|
fixture.commit().getStatus());
|
assertEquals(0, fixture.valueWriteCalls.get());
|
}
|
|
@Test
|
public void savesEmptyConfiguredSelectAndCheckboxWithoutInventingValues() {
|
for (String type : new String[]{"SELECT", "CHECKBOX"}) {
|
CheckboxSaveFixture fixture = configuredEmptyFixture(type,
|
"CHECKBOX".equals(type) ? "false" : null);
|
RecordFillSaveOperation result = fixture.commit();
|
assertEquals(type + ": " + fixture.operation.getRejectReason(),
|
RecordFillSaveOperation.COMMITTED, result.getStatus());
|
assertEquals(null, fixture.primaryValues.get(0).getZhiWenben());
|
assertEquals(0, fixture.valueWriteCalls.get());
|
}
|
}
|
|
@Test
|
public void rejectsInventedValuesForEmptyConfiguredSelectAndCheckbox() {
|
for (Object[] changed : new Object[][]{{"SELECT", "合格"}, {"CHECKBOX", "true"}}) {
|
CheckboxSaveFixture fixture = configuredEmptyFixture((String) changed[0], (String) changed[1]);
|
assertEquals(changed[0].toString(), RecordFillSaveOperation.REJECTED, fixture.commit().getStatus());
|
assertEquals("外部来源字段不允许人工修改,请使用重新同步", fixture.operation.getRejectReason());
|
fixture.assertNoRecordWrites();
|
}
|
}
|
|
private static CheckboxSaveFixture configuredEmptyFixture(String type, String raw) {
|
CheckboxSaveFixture fixture = configuredDecimalFixture(raw);
|
fixture.fieldVersion.setShujuLeixing(type);
|
fixture.primaryValues.get(0).setZhiShuzhi(null);
|
fixture.primaryValues.get(0).setYuanQuzhiZhuangtai("CLEARED");
|
String controlType = "SELECT".equals(type) ? "DROPDOWN" : type;
|
Map<String, Object> config = new LinkedHashMap<>();
|
if ("SELECT".equals(type)) {
|
fixture.fieldVersion.setKongjianPeizhi("{\"options\":[{\"value\":\"pass\",\"display\":\"合格\"},"
|
+ "{\"value\":\"fail\",\"display\":\"不合格\"}]}");
|
config.put("pass", "合格");
|
config.put("fail", "不合格");
|
}
|
ElnTemplateBindingEntity binding = fixture.bindings.get(0);
|
binding.setKongjianLeixing(controlType);
|
fixture.controls.clear();
|
fixture.controls.add(new DocxControlSnapshot(binding.getKongjianBiaoqian(), binding.getKongjianBieming(),
|
null, controlType, "PRIMARY", "word/document.xml", "OK", config, raw));
|
return fixture;
|
}
|
|
private static final class CheckboxSaveFixture {
|
private ElnFieldVersionEntity fieldVersion = version("CHECKBOX", null);
|
private final ElnRecordEntity record = editableRecord();
|
private final ElnRecordRevisionEntity revision = editableRevision();
|
private final ElnRecordSaveOperationEntity operation = preparedOperation();
|
private final List<ElnTemplateBindingEntity> bindings = new ArrayList<>();
|
private final List<ElnRecordFieldValueEntity> primaryValues = new ArrayList<>();
|
private final List<DocxControlSnapshot> controls = new ArrayList<>();
|
private final List<ElnRecordRepeatRowEntity> rows = new ArrayList<>();
|
private final List<ElnRecordRepeatValueEntity> repeatValues = new ArrayList<>();
|
private final List<ElnTemplateVos.CheckboxGroup> checkboxRules = new ArrayList<>();
|
private final AtomicInteger recordCasCalls = new AtomicInteger();
|
private final AtomicInteger valueWriteCalls = new AtomicInteger();
|
private final AtomicInteger repeatWriteCalls = new AtomicInteger();
|
private final AtomicInteger formulaCalls = new AtomicInteger();
|
private final AtomicInteger sourceConfirmationCalls = new AtomicInteger();
|
private final AtomicInteger operationWrites = new AtomicInteger();
|
|
private void addPrimary(String code, String alias, String stepId,
|
String checkboxGroupCode, boolean checked) {
|
ElnTemplateBindingEntity binding = primaryCheckboxBinding(
|
"binding-" + code, code, alias, stepId, checkboxGroupCode);
|
bindings.add(binding);
|
primaryValues.add(primaryValue(binding, "false"));
|
controls.add(checkboxControl(binding, checked));
|
}
|
|
private void setRule(String groupCode, int minSelected, Integer maxSelected) {
|
ElnTemplateVos.CheckboxGroup rule = new ElnTemplateVos.CheckboxGroup();
|
rule.setGroupCode(groupCode);
|
rule.setMinSelected(minSelected);
|
rule.setMaxSelected(maxSelected);
|
checkboxRules.add(rule);
|
}
|
|
private ElnTemplateBindingEntity addRepeatBinding(
|
String code, String alias, boolean prototypeChecked) {
|
ElnTemplateBindingEntity binding = repeatCheckboxBinding(
|
"binding-" + code, "results", code, alias, "test_result");
|
bindings.add(binding);
|
controls.add(repeatCheckboxControl(binding, "prototype", prototypeChecked, 1));
|
return binding;
|
}
|
|
private ElnRecordRepeatRowEntity addRow(String rowId, String status) {
|
ElnRecordRepeatRowEntity row = repeatRow(rowId, "results");
|
row.setOperationStatus(status);
|
rows.add(row);
|
return row;
|
}
|
|
private void addRuntimeControl(ElnTemplateBindingEntity binding,
|
String rowId, boolean checked, int rowIndex) {
|
controls.add(repeatCheckboxControl(binding, rowId, checked, rowIndex));
|
}
|
|
private void assertNoRecordWrites() {
|
assertEquals(0, recordCasCalls.get());
|
assertEquals(0, valueWriteCalls.get());
|
assertEquals(0, repeatWriteCalls.get());
|
assertEquals(0, formulaCalls.get());
|
assertEquals(0, sourceConfirmationCalls.get());
|
}
|
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
private RecordFillSaveOperation commit() {
|
ElnTemplateBindingConfirmationFacade confirmationFacade =
|
mock(ElnTemplateBindingConfirmationFacade.class);
|
when(confirmationFacade.execute(any(Supplier.class), any(Consumer.class)))
|
.thenAnswer(invocation -> {
|
try {
|
return ((Supplier<?>) invocation.getArgument(0)).get();
|
} catch (RuntimeException failure) {
|
((Consumer) invocation.getArgument(1)).accept(failure);
|
operation.setOperationStatus(RecordFillSaveOperation.REJECTED);
|
operation.setRejectReason(failure.getMessage());
|
throw failure;
|
}
|
});
|
ElnRecordFormulaCalculationService calculations =
|
new ElnRecordFormulaCalculationService() {
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAffected(
|
String tenantId, ElnRecordEntity currentRecord,
|
ElnRecordRevisionEntity currentRevision,
|
List<ElnRecordFieldValueEntity> values,
|
Set<String> changedBindingIds, String operationCode,
|
String operatorUserId) {
|
formulaCalls.incrementAndGet();
|
return Collections.emptyList();
|
}
|
|
@Override
|
public List<ElnRecordFieldValueEntity> recalculateAll(
|
String tenantId, ElnRecordEntity currentRecord,
|
ElnRecordRevisionEntity currentRevision,
|
List<ElnRecordFieldValueEntity> values,
|
String operationCode, String operatorUserId) {
|
return Collections.emptyList();
|
}
|
};
|
ElnRecordFillAccessServiceImpl service = new ElnRecordFillAccessServiceImpl(
|
mapper(ElnRecordMapper.class, method -> {
|
if ("selectOne".equals(method)) return record;
|
if ("advanceVersion".equals(method)) {
|
recordCasCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
}),
|
mapper(ElnRecordRevisionMapper.class, revision),
|
successfulTaskProvider(),
|
mapper(ElnRecordFieldValueMapper.class, method -> {
|
if ("selectList".equals(method)) return primaryValues;
|
if ("updateById".equals(method)) {
|
valueWriteCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
}),
|
mapper(ElnTemplateBindingMapper.class, method ->
|
"selectList".equals(method) ? bindings : null),
|
mapper(ElnFieldVersionMapper.class, fieldVersion),
|
mapper(ElnRecordSaveOperationMapper.class, method -> {
|
if ("selectOne".equals(method)) return operation;
|
if ("finishPrepared".equals(method)) {
|
operationWrites.incrementAndGet();
|
return 1;
|
}
|
return null;
|
}),
|
mapper(ElnTemplateFormulaBindingMapper.class, method ->
|
"selectList".equals(method) ? Collections.emptyList() : null),
|
calculations,
|
(tenantId, fileId) -> controls,
|
confirmationFacade,
|
mapper(ElnRecordRepeatRowMapper.class, method -> {
|
if ("selectList".equals(method)) return rows;
|
if ("updateById".equals(method)) {
|
repeatWriteCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
}),
|
mapper(ElnRecordRepeatValueMapper.class,
|
method -> {
|
if ("selectList".equals(method)) return repeatValues;
|
if ("updateById".equals(method)) {
|
repeatWriteCalls.incrementAndGet();
|
return 1;
|
}
|
return null;
|
},
|
argument -> {
|
repeatWriteCalls.incrementAndGet();
|
return 1;
|
}),
|
event -> { },
|
noOpTenantRoute(),
|
(tenantId, userId, recordId, revisionId) ->
|
sourceConfirmationCalls.incrementAndGet(),
|
mapper(ElnTemplateCheckboxGroupService.class, method ->
|
"list".equals(method) ? checkboxRules : null));
|
return service.commit(saveCommand());
|
}
|
}
|
|
private static ElnRecordEntity editableRecord() {
|
ElnRecordEntity record = new ElnRecordEntity();
|
record.setId("record-1");
|
record.setJianyanXiangId("item-1");
|
record.setMubanId("template-1");
|
record.setDangqianXiudingId("revision-1");
|
record.setFVersion(4);
|
return record;
|
}
|
|
private static void assertPrimarySnapshotCommitted(
|
String firstStep, String firstGroup, boolean firstChecked,
|
String secondStep, String secondGroup, boolean secondChecked) {
|
CheckboxSaveFixture fixture = new CheckboxSaveFixture();
|
fixture.addPrimary("pass", "合格", firstStep, firstGroup, firstChecked);
|
fixture.addPrimary("fail", "不合格", secondStep, secondGroup, secondChecked);
|
assertEquals(RecordFillSaveOperation.COMMITTED, fixture.commit().getStatus());
|
}
|
|
private static void assertRejectedCheckboxGroup(RecordFillSaveOperation result) {
|
assertEquals(RecordFillSaveOperation.REJECTED, result.getStatus());
|
assertTrue(result.getReason().contains("test_result"));
|
assertTrue(result.getReason().contains("合格(pass)"));
|
assertTrue(result.getReason().contains("不合格(fail)"));
|
}
|
|
private static ElnRecordRevisionEntity editableRevision() {
|
ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity();
|
revision.setId("revision-1");
|
revision.setDianziJiluId("record-1");
|
revision.setJiluWendang("revision-1.docx");
|
revision.setBizStatus("DRAFT");
|
return revision;
|
}
|
|
private static ElnRecordSaveOperationEntity preparedOperation() {
|
ElnRecordSaveOperationEntity operation = new ElnRecordSaveOperationEntity();
|
operation.setOperationCode("save-1");
|
operation.setOperationStatus(RecordFillSaveOperation.PREPARED);
|
operation.setDianziJiluId("record-1");
|
operation.setXiudingId("revision-1");
|
operation.setJiluWendang("revision-1.docx");
|
operation.setExpectedRecordVersion(4);
|
operation.setSessionDocKey("doc-key-1");
|
operation.setSessionVersion(2);
|
operation.setOperatorUserId("user-1");
|
operation.setFTenantId("tenant-1");
|
return operation;
|
}
|
|
private static RecordFillSaveCommand saveCommand() {
|
RecordFillSaveCommand command = new RecordFillSaveCommand();
|
command.setTenantId("tenant-1");
|
command.setUserId("user-1");
|
command.setRecordId("record-1");
|
command.setFileId("revision-1.docx");
|
command.setOperationCode("save-1");
|
command.setSessionDocKey("doc-key-1");
|
command.setSessionVersion(2);
|
command.setCallbackStatus(6);
|
return command;
|
}
|
|
private static ElnTestWorkItemSnapshotProvider successfulTaskProvider() {
|
TestWorkItemSnapshot item = new TestWorkItemSnapshot();
|
item.setTestWorkItemId("item-1");
|
item.setOutcomeCode("SUCCESS");
|
TestWorkItemSnapshotBatch batch = new TestWorkItemSnapshotBatch();
|
batch.setItems(Collections.singletonList(item));
|
return new ElnTestWorkItemSnapshotProvider() {
|
@Override
|
public TestWorkItemSnapshotBatch resolve(
|
List<String> testWorkItemIds, List<String> properties) {
|
return batch;
|
}
|
|
@Override
|
public TestWorkItemSnapshotBatch resolveAsActor(
|
String tenantId, String userId,
|
List<String> testWorkItemIds, List<String> properties) {
|
return batch;
|
}
|
};
|
}
|
|
private static ElnRecordFillAccessServiceImpl.TenantRoute noOpTenantRoute() {
|
return new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
};
|
}
|
|
private static ElnTemplateBindingEntity primaryCheckboxBinding(
|
String id, String fieldCode, String alias, String stepId, String groupCode) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(id);
|
binding.setZiduanDingyiId("field-" + fieldCode);
|
binding.setZiduanBanbenId("version-checkbox");
|
binding.setZiduanBianma(fieldCode);
|
binding.setKongjianBiaoqian("eln.field." + fieldCode);
|
binding.setKongjianBieming(alias);
|
binding.setKongjianLeixing("CHECKBOX");
|
binding.setKongjianJuese("PRIMARY");
|
binding.setStepId(stepId);
|
binding.setCheckboxGroupCode(groupCode);
|
return binding;
|
}
|
|
private static ElnRecordFieldValueEntity primaryValue(
|
ElnTemplateBindingEntity binding, String value) {
|
ElnRecordFieldValueEntity entity = new ElnRecordFieldValueEntity();
|
entity.setId("value-" + binding.getZiduanBianma());
|
entity.setMubanBangdingId(binding.getId());
|
entity.setZiduanBianma(binding.getZiduanBianma());
|
entity.setZiduanBanbenId(binding.getZiduanBanbenId());
|
entity.setZhiWenben(value);
|
entity.setZhiLaiyuan("MANUAL");
|
return entity;
|
}
|
|
private static DocxControlSnapshot checkboxControl(
|
ElnTemplateBindingEntity binding, boolean checked) {
|
return new DocxControlSnapshot(binding.getKongjianBiaoqian(),
|
binding.getKongjianBieming(), null, "CHECKBOX", "PRIMARY",
|
"word/document.xml", "OK", Collections.emptyMap(),
|
Boolean.toString(checked));
|
}
|
|
private static ElnTemplateBindingEntity repeatBinding(
|
String bindingId, String groupCode, String fieldCode) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(bindingId);
|
binding.setZiduanDingyiId("field-" + fieldCode);
|
binding.setZiduanBanbenId("version-repeat");
|
binding.setZiduanBianma(fieldCode);
|
binding.setKongjianBiaoqian("eln.repeat." + groupCode
|
+ ".prototype." + fieldCode);
|
binding.setKongjianLeixing("TEXT");
|
binding.setKongjianJuese("REPEAT");
|
binding.setRepeatGroupCode(groupCode);
|
binding.setRepeatPrototypeRowId("prototype");
|
return binding;
|
}
|
|
private static ElnTemplateBindingEntity repeatCheckboxBinding(
|
String bindingId, String repeatGroupCode, String fieldCode,
|
String alias, String checkboxGroupCode) {
|
ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity();
|
binding.setId(bindingId);
|
binding.setZiduanDingyiId("field-" + fieldCode);
|
binding.setZiduanBanbenId("version-checkbox");
|
binding.setZiduanBianma(fieldCode);
|
binding.setKongjianBiaoqian("eln.repeat." + repeatGroupCode
|
+ ".prototype." + fieldCode);
|
binding.setKongjianBieming(alias);
|
binding.setKongjianLeixing("CHECKBOX");
|
binding.setKongjianJuese("REPEAT");
|
binding.setRepeatGroupCode(repeatGroupCode);
|
binding.setRepeatPrototypeRowId("prototype");
|
binding.setCheckboxGroupCode(checkboxGroupCode);
|
return binding;
|
}
|
|
private static ElnRecordRepeatValueEntity repeatValue(
|
ElnRecordRepeatRowEntity row, ElnTemplateBindingEntity binding, String value) {
|
ElnRecordRepeatValueEntity entity = new ElnRecordRepeatValueEntity();
|
entity.setId("value-" + row.getRowId() + "-" + binding.getZiduanBianma());
|
entity.setDianziJiluId("record-1");
|
entity.setXiudingId("revision-1");
|
entity.setMubanId("template-1");
|
entity.setRepeatRowId(row.getRowId());
|
entity.setRepeatGroupCode(row.getRepeatGroupCode());
|
entity.setMubanBangdingId(binding.getId());
|
entity.setZiduanDingyiId(binding.getZiduanDingyiId());
|
entity.setZiduanBanbenId(binding.getZiduanBanbenId());
|
entity.setZiduanBianma(binding.getZiduanBianma());
|
entity.setBangdingJuese("REPEAT");
|
entity.setBangdingZhuangtai("ACTIVE");
|
entity.setZhiWenben(value);
|
entity.setFDeleteMark(0);
|
return entity;
|
}
|
|
private static DocxControlSnapshot repeatCheckboxControl(
|
ElnTemplateBindingEntity binding, String rowId, boolean checked, int rowIndex) {
|
String tag = "eln.repeat." + binding.getRepeatGroupCode() + "." + rowId
|
+ "." + binding.getZiduanBianma();
|
return new DocxControlSnapshot(tag, binding.getKongjianBieming(), null,
|
"CHECKBOX", "REPEAT", "word/document.xml", "OK",
|
Collections.emptyMap(), Boolean.toString(checked),
|
0, rowIndex, false, false);
|
}
|
|
private void apply(ElnRecordFieldValueEntity value, DocxControlSnapshot control,
|
ElnFieldVersionEntity version) {
|
invoke(service((tenantId, fileId) -> Collections.emptyList()), "applyNormalizedValue",
|
new Class<?>[]{ElnRecordFieldValueEntity.class, DocxControlSnapshot.class,
|
ElnFieldVersionEntity.class}, value, control, version);
|
}
|
|
private static DocxControlSnapshot control(String value) {
|
return new DocxControlSnapshot("eln.field.result", "结果", null, "TEXT", "PRIMARY",
|
"word/document.xml", "OK", Collections.emptyMap(), value);
|
}
|
|
private static DocxControlSnapshot repeatControl(String tag, String value,
|
int rowIndex) {
|
return repeatControl(tag, value, 0, rowIndex);
|
}
|
|
private static DocxControlSnapshot repeatControl(String tag, String value,
|
int tableIndex, int rowIndex) {
|
return new DocxControlSnapshot(tag, "结果", null, "TEXT", "REPEAT",
|
"word/document.xml", "OK", Collections.emptyMap(), value,
|
tableIndex, rowIndex, false, false);
|
}
|
|
private static ElnFieldVersionEntity version(String type, String config) {
|
ElnFieldVersionEntity version = new ElnFieldVersionEntity();
|
version.setShujuLeixing(type);
|
version.setKongjianPeizhi(config);
|
return version;
|
}
|
|
private static ElnRecordFillAccessServiceImpl service(ElnTemplateDocumentEvidence evidence) {
|
return new ElnRecordFillAccessServiceImpl(
|
null, null, null, null, null, null, null, null, null,
|
evidence, null,
|
new ElnRecordFillAccessServiceImpl.TenantRoute() {
|
@Override public void switchTo(String tenantId) { }
|
@Override public void clear() { }
|
});
|
}
|
|
@SuppressWarnings("unchecked")
|
private static <T> T mapper(Class<T> type, Object selected) {
|
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];
|
}
|
if ("selectOne".equals(method.getName())) return selected;
|
if (method.getReturnType().isPrimitive()) return 0;
|
return null;
|
});
|
}
|
|
@SuppressWarnings("unchecked")
|
private static <T> T mapper(Class<T> type, MapperMethod handler) {
|
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 = handler.invoke(method.getName());
|
if (result != null) return result;
|
if (method.getReturnType().isPrimitive()) return 0;
|
return null;
|
});
|
}
|
|
@SuppressWarnings("unchecked")
|
private static <T> T mapper(Class<T> type, MapperMethod handler,
|
MapperArgument insertHandler) {
|
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];
|
}
|
if ("insert".equals(method.getName())) return insertHandler.invoke(args[0]);
|
Object result = handler.invoke(method.getName());
|
if (result != null) return result;
|
if (method.getReturnType().isPrimitive()) return 0;
|
return null;
|
});
|
}
|
|
@FunctionalInterface
|
private interface MapperMethod { Object invoke(String method); }
|
|
@FunctionalInterface
|
private interface MapperArgument { Object invoke(Object argument); }
|
|
private static Object invoke(Object target, String name, Class<?>[] parameterTypes,
|
Object... arguments) {
|
try {
|
Method method = target.getClass().getDeclaredMethod(name, parameterTypes);
|
method.setAccessible(true);
|
return method.invoke(target, arguments);
|
} catch (InvocationTargetException e) {
|
if (e.getCause() instanceof RuntimeException) {
|
throw (RuntimeException) e.getCause();
|
}
|
throw new RuntimeException(e.getCause());
|
} catch (ReflectiveOperationException e) {
|
throw new RuntimeException(e);
|
}
|
}
|
}
|