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<String, Object> input = (Map<String, Object>) 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<String, Object> input = (Map<String, Object>) 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<ElnRecordFieldValueEntity> primaryValues = new ArrayList<>();
|
private final List<ElnRecordRepeatRowEntity> rows = new ArrayList<>();
|
private final List<ElnRecordRepeatValueEntity> repeatValues = new ArrayList<>();
|
private final List<ElnFormulaCalculationLogEntity> 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> T mapper(Class<T> 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);
|
}
|
}
|