Skip to content

Commit

Permalink
Form calc date 109 (#689)
Browse files Browse the repository at this point in the history
* feat: eval-calc-formula

---------

Co-authored-by: devezhao <[email protected]>
  • Loading branch information
getrebuild and devezhao authored Dec 6, 2023
1 parent 3f7feaf commit 2690f07
Show file tree
Hide file tree
Showing 8 changed files with 303 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,19 @@ public class EasyFieldConfigProps {
* 日期格式
*/
public static final String DATE_FORMAT = "dateFormat";
/**
* 表单公式
*/
public static final String DATE_CALCFORMULA = NUMBER_CALCFORMULA;

/**
* 日期格式
*/
public static final String DATETIME_FORMAT = "datetimeFormat";
/**
* 表单公式
*/
public static final String DATETIME_CALCFORMULA = NUMBER_CALCFORMULA;

/**
* 时间格式
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Options;
import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.function.system.AssertFunction;
import com.googlecode.aviator.runtime.type.AviatorFunction;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -20,7 +21,7 @@
import java.util.Map;

/**
* // https://www.yuque.com/boyan-avfmj/aviatorscript
* https://www.yuque.com/boyan-avfmj/aviatorscript
*
* @author devezhao
* @since 2021/4/12
Expand All @@ -43,6 +44,9 @@ public class AviatorUtils {
} catch (Exception ignored) {
}

AVIATOR.addOpFunction(OperatorType.ADD, new OverOperatorType.DateAdd());
AVIATOR.addOpFunction(OperatorType.SUB, new OverOperatorType.DateSub());

addCustomFunction(new DateDiffFunction());
addCustomFunction(new DateAddFunction());
addCustomFunction(new DateSubFunction());
Expand All @@ -55,13 +59,21 @@ public class AviatorUtils {
}

/**
* 表达式计算
*
* @param expression
* @return
* @see #eval(String, Map, boolean)
*/
public static Object eval(String expression) {
return eval(expression, null, false);
}

/**
* @param expression
* @return
* @see #eval(String, Map, boolean)
*/
public static Object evalQuietly(String expression) {
return eval(expression, null, true);
public static Object eval(String expression, Map<String, Object> env) {
return eval(expression, env, false);
}

/**
Expand All @@ -74,13 +86,13 @@ public static Object evalQuietly(String expression) {
*/
public static Object eval(String expression, Map<String, Object> env, boolean quietly) {
try {
return AVIATOR.execute(expression, env);
return AVIATOR.execute(expression, env == null ? Collections.emptyMap() : env);
} catch (Exception ex) {
if (ex instanceof AssertFunction.AssertFailed) {
throw new AssertFailedException((AssertFunction.AssertFailed) ex);
}

log.error("Bad aviator expression : \n{}\n<< {}", expression, env, ex);
log.error("Bad aviator expression : \n>> {}\n>> {}\n>> {}", expression, env, ex.getLocalizedMessage());
if (!quietly) throw ex;
}
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*!
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
See LICENSE and COMMERCIAL in the project root for license information.
*/

package com.rebuild.core.service.trigger.aviator;

import cn.devezhao.commons.CalendarUtils;
import com.googlecode.aviator.lexer.token.OperatorType;
import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.type.AviatorLong;
import com.googlecode.aviator.runtime.type.AviatorObject;

import java.util.Date;
import java.util.Map;

/**
* 操作符重载
*
* @author RB
* @since 2023/12/6
*/
public class OverOperatorType {

private OverOperatorType() {}

/**
* 日期加
*/
static class DateAdd extends AbstractFunction {
@Override
public String getName() {
return OperatorType.ADD.getToken();
}

@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);

if ($argv1 instanceof Date && $argv2 instanceof Number) {
return opDate((Date) $argv1, ((Number) $argv2).intValue());
} else if ($argv2 instanceof Date && $argv1 instanceof Number) {
return opDate((Date) $argv2, ((Number) $argv1).intValue());
} else {
return arg1.add(arg2, env); // Use default
}
}
}

/**
* 日期减
*/
static class DateSub extends AbstractFunction {
@Override
public String getName() {
return OperatorType.SUB.getToken();
}

@Override
public AviatorObject call(Map<String, Object> env, AviatorObject arg1, AviatorObject arg2) {
Object $argv1 = arg1.getValue(env);
Object $argv2 = arg2.getValue(env);

if ($argv1 instanceof Date && $argv2 instanceof Number) {
return opDate((Date) $argv1, -((Number) $argv2).intValue());
} else if ($argv2 instanceof Date && $argv1 instanceof Number) {
return opDate((Date) $argv2, -((Number) $argv1).intValue());
} else if ($argv1 instanceof Date && $argv2 instanceof Date) {
int diff = CalendarUtils.getDayLeft((Date) $argv1, (Date) $argv2);
return AviatorLong.valueOf(diff);
} else {
return arg1.add(arg2, env); // Use default
}
}
}

static AviatorDate opDate(Date date, int num) {
Date d = CalendarUtils.addDay(date, num);
return new AviatorDate(d);
}
}
90 changes: 90 additions & 0 deletions src/main/java/com/rebuild/web/general/ModelExtrasController.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,52 @@
package com.rebuild.web.general;

import cn.devezhao.bizz.privileges.impl.BizzPermission;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONAware;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.AutoFillinManager;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyDecimal;
import com.rebuild.core.metadata.easymeta.EasyEntity;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.service.general.RepeatedRecordsException;
import com.rebuild.core.service.general.transform.RecordTransfomer;
import com.rebuild.core.service.trigger.aviator.AviatorUtils;
import com.rebuild.core.support.general.ContentWithFieldVars;
import com.rebuild.core.support.i18n.I18nUtils;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.BaseController;
import com.rebuild.web.EntityParam;
import com.rebuild.web.IdParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* 表单/视图 功能扩展
Expand Down Expand Up @@ -195,4 +209,80 @@ public JSON checkCreates(HttpServletRequest request) {
}
return allowed;
}

@PostMapping("eval-calc-formula")
public RespBody evalCalcFormula(@EntityParam Entity entity, HttpServletRequest request) {
String targetField = getParameterNotNull(request, "field");
if (!entity.containsField(targetField)) return RespBody.error();

JSONObject post = (JSONObject) ServletUtils.getRequestJson(request);
Map<String, Object> varsInFormula = post.getInnerMap();
for (Object value : varsInFormula.values()) {
if (value == null || StringUtils.isBlank(value.toString())) {
return RespBody.ok();
}
}

EasyField easyField = EasyMetaFactory.valueOf(entity.getField(targetField));
String formula = easyField.getExtraAttr(EasyFieldConfigProps.DATE_CALCFORMULA);

boolean canCalc = true;
Set<String> fieldVars = ContentWithFieldVars.matchsVars(formula);
for (String field : fieldVars) {
if (!entity.containsField(field)) {
canCalc = false;
break;
}

Object fieldValue = varsInFormula.get(field);
if (fieldValue == null) {
canCalc = false;
break;
}

String fieldValue2 = fieldValue.toString();
DisplayType dt = EasyMetaFactory.valueOf(entity.getField(field)).getDisplayType();
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
fieldValue = CalendarUtils.parse(fieldValue2, CalendarUtils.UTC_DATETIME_FORMAT.substring(0, fieldValue2.length()));
} else if (dt == DisplayType.NUMBER || dt == DisplayType.DECIMAL) {
fieldValue = EasyDecimal.clearFlaged(fieldValue2);
if (StringUtils.isNotBlank((String) fieldValue)) {
if (dt == DisplayType.NUMBER) fieldValue = ObjectUtils.toLong(fieldValue);
else fieldValue = ObjectUtils.toDouble(fieldValue);
} else {
fieldValue = null;
}
}

if (fieldValue == null) {
canCalc = false;
break;
}
varsInFormula.put(field, fieldValue);
}
if (!canCalc) return RespBody.ok();

formula = formula
.replace("{", "").replace("}", "")
.replace("×", "*").replace("÷", "/");

Object evalVal = AviatorUtils.eval(formula, varsInFormula, true);
if (evalVal == null) return RespBody.ok();

DisplayType dt = easyField.getDisplayType();
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
if (evalVal instanceof Date) {
evalVal = easyField.wrapValue(evalVal);
return RespBody.ok(evalVal);
}
} else if (dt == DisplayType.NUMBER || dt == DisplayType.DECIMAL) {
if (evalVal instanceof Number) {
evalVal = easyField.wrapValue(evalVal);
return RespBody.ok(evalVal);
}
}

log.warn("Bad eval value `{}` for field : {}", evalVal, easyField.getRawMeta());
return RespBody.ok();
}
}
9 changes: 6 additions & 3 deletions src/main/resources/web/admin/metadata/field-edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
</label>
<label class="custom-control custom-control-sm custom-radio custom-control-inline mb-0 mr-1">
<input class="custom-control-input" type="radio" name="decimalType" value="¥" />
<span class="custom-control-label">[[${bundle.L('货币')}]]</span>
<span class="custom-control-label">[[${bundle.L('符号')}]]</span>
</label>
<span>
<select class="underline-sm J_decimalTypeFlag">
Expand Down Expand Up @@ -359,14 +359,17 @@ <h5>[[${bundle.L('常用')}]]</h5>
</div>
</div>
</div>
<div th:if="${fieldType == 'DECIMAL' or fieldType == 'NUMBER'}" class="form-group row J_for-DECIMAL J_for-NUMBER">
<div
th:if="${fieldType == 'DECIMAL' or fieldType == 'NUMBER' or fieldType == 'DATE' or fieldType == 'DATETIME'}"
class="form-group row J_for-DECIMAL J_for-NUMBER J_for-DATE J_for-DATETIME"
>
<label class="col-md-12 col-xl-3 col-lg-4 col-form-label text-lg-right">[[${bundle.L('表单计算公式')}]]</label>
<div class="col-md-12 col-xl-6 col-lg-8">
<input type="hidden" class="form-control" id="calcFormula" />
<div class="form-control-plaintext formula" id="calcFormula2" th:_title="${bundle.L('无')}">[[${calcFormula ?: calcFormula}]]</div>
<p
class="form-text"
th:utext="${bundle.L('本公式仅做前端计算,如公式中所用字段未布局/未显示,则无法进行计算。你也可以通过 [触发器 (字段更新)](/admin/robot/triggers) 实现更强大的计算规则')}"
th:utext="${bundle.L('如公式中所用字段未布局/未显示,则无法进行计算。本公式适用前端简单计算,你可以通过 [触发器 (字段更新)](/admin/robot/triggers) 实现更强大的计算规则')}"
></p>
</div>
</div>
Expand Down
Loading

0 comments on commit 2690f07

Please sign in to comment.