package jnpf.elnService.impl; import jnpf.elnEntity.formula.ElnFormulaParameterSlotEntity; import jnpf.elnEntity.formula.ElnFormulaVersionEntity; import jnpf.elnEntity.formula.ElnTemplateFormulaBindingEntity; import jnpf.elnEntity.runtime.ElnFormulaCalculationLogEntity; import jnpf.elnEntity.runtime.ElnRecordEntity; import jnpf.elnEntity.runtime.ElnRecordFieldValueEntity; import jnpf.elnEntity.runtime.ElnRecordFormulaCalculationResult; import jnpf.elnEntity.runtime.ElnRecordRepeatRowEntity; import jnpf.elnEntity.runtime.ElnRecordRepeatValueEntity; import jnpf.elnEntity.runtime.ElnRecordRevisionEntity; import jnpf.elnEntity.template.ElnTemplateBindingEntity; import jnpf.elnMapper.ElnFormulaCalculationLogMapper; import jnpf.elnMapper.ElnFormulaParameterSlotMapper; import jnpf.elnMapper.ElnFormulaVersionMapper; import jnpf.elnMapper.ElnRecordRepeatRowMapper; import jnpf.elnMapper.ElnRecordRepeatValueMapper; import jnpf.elnMapper.ElnTemplateBindingMapper; import jnpf.elnMapper.ElnTemplateFormulaBindingMapper; import jnpf.elnService.formula.FormulaDependencyGraph; import jnpf.elnService.formula.impl.FormulaPreviewServiceImpl; import jnpf.elnService.formula.impl.FormulaValidationServiceImpl; import org.junit.Test; import java.lang.reflect.Proxy; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class ElnRecordRepeatFormulaCalculationServiceImplTest { @Test public void calculatesScalarFormulaForEveryActiveRowOnly() { Fixture fixture = new Fixture("DECIMAL", true, "x + 1", "REPEAT"); fixture.addRow("row-1", 1, "ACTIVE", 0, new BigDecimal("2")); fixture.addRow("row-2", 2, "ACTIVE", 0, new BigDecimal("5")); fixture.addRow("row-deleted", 3, "DELETED", 1, new BigDecimal("99")); ElnRecordFormulaCalculationResult result = fixture.service.recalculateAllWithRepeats( "tenant-a", fixture.record, fixture.revision, fixture.primaryValues, "all-repeat", "user-1"); assertEquals(2, result.getRepeatValues().size()); assertEquals(new BigDecimal("3"), fixture.repeatValue("row-1", "result").getZhiShuzhi()); assertEquals(new BigDecimal("6"), fixture.repeatValue("row-2", "result").getZhiShuzhi()); assertEquals(new BigDecimal("100"), fixture.repeatValue("row-deleted", "result").getZhiShuzhi()); assertEquals(2, fixture.logs.size()); assertEquals("row-1", fixture.logs.get(0).getShuruKuaizhao().get("rowId")); assertEquals("row-2", fixture.logs.get(1).getShuruKuaizhao().get("rowId")); } @Test @SuppressWarnings("unchecked") public void excludesOptionalEmptyCellsAndRecordsParticipatingRows() { Fixture fixture = new Fixture("DECIMAL_LIST", false, "SUM(x)", "PRIMARY"); fixture.addRow("row-1", 1, "ACTIVE", 0, new BigDecimal("2")); fixture.addRow("row-empty", 2, "ACTIVE", 0, null); fixture.addRow("row-deleted", 3, "DELETED", 1, new BigDecimal("99")); ElnRecordFormulaCalculationResult result = fixture.service.recalculateAllWithRepeats( "tenant-a", fixture.record, fixture.revision, fixture.primaryValues, "all-aggregate", "user-1"); assertEquals(new BigDecimal("2"), result.getFieldValues().get(0).getZhiShuzhi()); Map input = (Map) fixture.logs.get(0) .getShuruKuaizhao().get("x"); assertEquals(Collections.singletonList(new BigDecimal("2")), input.get("values")); assertEquals(Collections.singletonList("row-1"), input.get("participatingRowIds")); assertEquals(Collections.singletonList("row-empty"), input.get("excludedRowIds")); } @Test @SuppressWarnings("unchecked") public void requiredEmptyAggregateCellProducesMissingInput() { Fixture fixture = new Fixture("DECIMAL_LIST", true, "SUM(x)", "PRIMARY"); fixture.addRow("row-1", 1, "ACTIVE", 0, new BigDecimal("2")); fixture.addRow("row-empty", 2, "ACTIVE", 0, null); ElnRecordFormulaCalculationResult result = fixture.service.recalculateAllWithRepeats( "tenant-a", fixture.record, fixture.revision, fixture.primaryValues, "all-missing", "user-1"); ElnRecordFieldValueEntity value = result.getFieldValues().get(0); assertEquals("MISSING_INPUT", value.getJisuanZhuangtai()); assertNull(value.getZhiShuzhi()); Map input = (Map) fixture.logs.get(0) .getShuruKuaizhao().get("x"); assertEquals(Collections.singletonList("row-empty"), input.get("excludedRowIds")); } @Test public void recordsFormulaSetupFailureForEveryActiveRow() { Fixture fixture = new Fixture("DECIMAL", true, "x + 1", "REPEAT"); fixture.addRow("row-1", 1, "ACTIVE", 0, new BigDecimal("2")); fixture.addRow("row-2", 2, "ACTIVE", 0, new BigDecimal("5")); fixture.version.setBizStatus("DRAFT"); ElnRecordFormulaCalculationResult result = fixture.service.recalculateAllWithRepeats( "tenant-a", fixture.record, fixture.revision, fixture.primaryValues, "invalid-version", "user-1"); assertEquals(2, result.getRepeatValues().size()); assertEquals("ERROR", fixture.repeatValue("row-1", "result").getJisuanZhuangtai()); assertEquals("ERROR", fixture.repeatValue("row-2", "result").getJisuanZhuangtai()); assertEquals(2, fixture.logs.size()); assertEquals("ERROR", fixture.logs.get(0).getJisuanZhuangtai()); assertEquals("ERROR", fixture.logs.get(1).getJisuanZhuangtai()); } private static final class Fixture { private final ElnRecordEntity record = new ElnRecordEntity(); private final ElnRecordRevisionEntity revision = new ElnRecordRevisionEntity(); private final List primaryValues = new ArrayList<>(); private final List rows = new ArrayList<>(); private final List repeatValues = new ArrayList<>(); private final List logs = new ArrayList<>(); private final ElnFormulaVersionEntity version = new ElnFormulaVersionEntity(); private final ElnRecordFormulaCalculationServiceImpl service; private Fixture(String slotType, boolean required, String expression, String resultRole) { record.setId("record-1"); record.setMubanId("template-1"); revision.setId("revision-1"); revision.setDianziJiluId(record.getId()); ElnTemplateBindingEntity input = binding("input", "REPEAT"); ElnTemplateBindingEntity result = binding("result", resultRole); if ("PRIMARY".equals(resultRole)) primaryValues.add(primaryResult()); ElnTemplateFormulaBindingEntity formula = new ElnTemplateFormulaBindingEntity(); formula.setId("formula-1"); formula.setMubanId(record.getMubanId()); formula.setGongshiDingyiId("definition-1"); formula.setGongshiBanbenId("version-1"); formula.setJieguoBangdingId("result"); formula.setBangdingZhuangtai("ACTIVE"); formula.setCanshuYingshe(Collections.singletonMap("x", "input")); version.setId("version-1"); version.setGongshiDingyiId("definition-1"); version.setBiaodashi(expression); version.setJieguoLeixing("DECIMAL"); version.setBizStatus("PUBLISHED"); ElnFormulaParameterSlotEntity slot = new ElnFormulaParameterSlotEntity(); slot.setId("slot-1"); slot.setGongshiBanbenId(version.getId()); slot.setCanshuBianma("x"); slot.setCanshuMingcheng("输入"); slot.setShujuLeixing(slotType); slot.setShifouBitian(required ? 1 : 0); slot.setPaixu(1); service = new ElnRecordFormulaCalculationServiceImpl( mapper(ElnTemplateFormulaBindingMapper.class, (method, args) -> "selectList".equals(method) ? Collections.singletonList(formula) : null), mapper(ElnFormulaVersionMapper.class, (method, args) -> "selectOne".equals(method) ? version : null), mapper(ElnFormulaParameterSlotMapper.class, (method, args) -> "selectList".equals(method) ? Collections.singletonList(slot) : null), mapper(ElnTemplateBindingMapper.class, (method, args) -> "selectList".equals(method) ? Arrays.asList(input, result) : null), mapper(ElnFormulaCalculationLogMapper.class, (method, args) -> { if ("insert".equals(method)) { logs.add((ElnFormulaCalculationLogEntity) args[0]); return 1; } return null; }), mapper(ElnRecordRepeatRowMapper.class, (method, args) -> "selectList".equals(method) ? rows : null), mapper(ElnRecordRepeatValueMapper.class, (method, args) -> "selectList".equals(method) ? repeatValues : null), new FormulaPreviewServiceImpl(new FormulaValidationServiceImpl()), new FormulaDependencyGraph()); } private void addRow(String rowId, int order, String status, int deleteMark, BigDecimal decimal) { ElnRecordRepeatRowEntity row = new ElnRecordRepeatRowEntity(); row.setId("physical-" + rowId); row.setDianziJiluId(record.getId()); row.setXiudingId(revision.getId()); row.setMubanId(record.getMubanId()); row.setRepeatGroupCode("group-a"); row.setRowId(rowId); row.setRowOrder(order); row.setOperationStatus(status); row.setFDeleteMark(deleteMark); rows.add(row); repeatValues.add(repeatValue(rowId, "input", decimal)); repeatValues.add(repeatValue(rowId, "result", new BigDecimal("100"))); } private ElnRecordRepeatValueEntity repeatValue( String rowId, String bindingId, BigDecimal decimal) { ElnRecordRepeatValueEntity value = new ElnRecordRepeatValueEntity(); value.setId("value-" + rowId + "-" + bindingId); value.setDianziJiluId(record.getId()); value.setXiudingId(revision.getId()); value.setMubanId(record.getMubanId()); value.setRepeatRowId(rowId); value.setRepeatGroupCode("group-a"); value.setMubanBangdingId(bindingId); value.setZiduanDingyiId("definition-" + bindingId); value.setZiduanBianma("code-" + bindingId); value.setZiduanBanbenId("field-version-" + bindingId); value.setBangdingJuese("REPEAT"); value.setBangdingZhuangtai("ACTIVE"); value.setZhiShuzhi(decimal); value.setZhiWenben(decimal == null ? null : decimal.toPlainString()); value.setFDeleteMark(0); return value; } private ElnRecordRepeatValueEntity repeatValue(String rowId, String bindingId) { for (ElnRecordRepeatValueEntity value : repeatValues) { if (rowId.equals(value.getRepeatRowId()) && bindingId.equals(value.getMubanBangdingId())) return value; } throw new AssertionError(rowId + ":" + bindingId); } private ElnRecordFieldValueEntity primaryResult() { ElnRecordFieldValueEntity value = new ElnRecordFieldValueEntity(); value.setId("primary-result"); value.setDianziJiluId(record.getId()); value.setXiudingId(revision.getId()); value.setMubanId(record.getMubanId()); value.setMubanBangdingId("result"); value.setZiduanDingyiId("definition-result"); value.setZiduanBianma("code-result"); value.setZiduanBanbenId("field-version-result"); value.setBangdingJuese("PRIMARY"); value.setBangdingZhuangtai("ACTIVE"); value.setZhiShuzhi(new BigDecimal("100")); value.setZhiWenben("100"); return value; } } private static ElnTemplateBindingEntity binding(String id, String role) { ElnTemplateBindingEntity binding = new ElnTemplateBindingEntity(); binding.setId(id); binding.setMubanId("template-1"); binding.setZiduanDingyiId("definition-" + id); binding.setZiduanBianma("code-" + id); binding.setZiduanBanbenId("field-version-" + id); binding.setKongjianJuese(role); binding.setRepeatGroupCode("REPEAT".equals(role) ? "group-a" : null); binding.setBangdingZhuangtai("ACTIVE"); return binding; } @SuppressWarnings("unchecked") private static T mapper(Class type, Invocation invocation) { return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, (target, method, args) -> { if (method.getDeclaringClass() == Object.class) { if ("toString".equals(method.getName())) return type.getSimpleName(); if ("hashCode".equals(method.getName())) return System.identityHashCode(target); if ("equals".equals(method.getName())) return target == args[0]; } Object value = invocation.invoke(method.getName(), args); if (value != null || !method.getReturnType().isPrimitive()) return value; return method.getReturnType() == boolean.class ? false : 0; }); } @FunctionalInterface private interface Invocation { Object invoke(String method, Object[] args); } }