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 testWorkItemIds, List properties) { return batch; } @Override public TestWorkItemSnapshotBatch resolveAsActor( String tenantId, String userId, List testWorkItemIds, List properties) { return batch; } }; ElnRecordFormulaCalculationService calculations = new ElnRecordFormulaCalculationService() { @Override public List recalculateAffected( String tenantId, ElnRecordEntity currentRecord, ElnRecordRevisionEntity currentRevision, List values, Set changedBindingIds, String operationCode, String operatorUserId) { return Collections.emptyList(); } @Override public List recalculateAll( String tenantId, ElnRecordEntity currentRecord, ElnRecordRevisionEntity currentRevision, List values, String operationCode, String operatorUserId) { return Collections.emptyList(); } }; AtomicInteger finishCalls = new AtomicInteger(); AtomicReference> 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 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 testWorkItemIds, List properties) { providerCalls.incrementAndGet(); return null; } @Override public TestWorkItemSnapshotBatch resolveAsActor( String tenantId, String userId, List testWorkItemIds, List 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 inserted = new AtomicReference<>(); AtomicInteger activated = new AtomicInteger(); List 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 bindings = new LinkedHashMap<>(); bindings.put(binding.getId(), binding); Map 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 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 bindings = new LinkedHashMap<>(); bindings.put(binding.getId(), binding); Map 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 bindings = new LinkedHashMap<>(); bindings.put(resultBinding.getId(), resultBinding); bindings.put(conditionBinding.getId(), conditionBinding); Map 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 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 bindings = new ArrayList<>(); private final List primaryValues = new ArrayList<>(); private final List controls = new ArrayList<>(); private final List rows = new ArrayList<>(); private final List repeatValues = new ArrayList<>(); private final List 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 recalculateAffected( String tenantId, ElnRecordEntity currentRecord, ElnRecordRevisionEntity currentRevision, List values, Set changedBindingIds, String operationCode, String operatorUserId) { formulaCalls.incrementAndGet(); return Collections.emptyList(); } @Override public List recalculateAll( String tenantId, ElnRecordEntity currentRecord, ElnRecordRevisionEntity currentRevision, List 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 testWorkItemIds, List properties) { return batch; } @Override public TestWorkItemSnapshotBatch resolveAsActor( String tenantId, String userId, List testWorkItemIds, List 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 mapper(Class 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 mapper(Class 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 mapper(Class 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); } } }