diff --git a/.codebeatignore b/.codebeatignore
deleted file mode 100644
index 5fae8584f..000000000
--- a/.codebeatignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/src/test/**
-/src/main/webapp/assets/lib/**
diff --git a/.codecov.yml b/.codecov.yml
index 168510d95..377a9ed46 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -16,3 +16,4 @@ ignore:
- "src/test/.*"
- "src/main/webapp/.*"
- "src/main/java/com/rebuild/web/.*"
+ - ".*/.*Exception.java"
diff --git a/.eslintrc.json b/.eslintrc.json
index 14079c468..d547f67f4 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,3 +1,4 @@
+// Use ESLint in locally : `npm install eslint babel-eslint eslint-plugin-react --save-dev`
{
"env": {
"browser": true,
@@ -21,6 +22,7 @@
},
"settings": {
"react": {
+ "pragma": "React",
"version": "16.10.2"
}
},
@@ -93,6 +95,7 @@
},
"rules": {
"strict": 0,
+ "no-redeclare": 0,
"indent": [2, 2],
"linebreak-style": [0, "unix"],
"quotes": [2, "single"],
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..ae5554d84
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @getrebuild @devezhao
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index f96389c70..bc61075a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,5 @@ rebuild.iml
.DS_Store
+node_modules
+package-lock.json
diff --git a/.setup/db-init.sql b/.setup/db-init.sql
deleted file mode 100644
index 2edf6ada6..000000000
--- a/.setup/db-init.sql
+++ /dev/null
@@ -1 +0,0 @@
--- The file has been moved to `src/main/resources/scripts/db-init.sql`
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index f7140c092..69af1139b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -14,7 +14,9 @@
"editor.fontSize": 12,
"editor.tabSize": 2,
"editor.formatOnSave": true,
- "eslint.autoFixOnSave": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": true
+ },
"eslint.options": {
"configFile": "./.eslintrc.json"
},
@@ -26,6 +28,4 @@
"prettier.eslintIntegration": true,
"workbench.editor.enablePreview": false,
"java.configuration.updateBuildConfiguration": "disabled"
-}
-// node and eslint(-g)
-// Plugins: Beautify and ESLint
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 802810b37..dcc0d2d97 100644
--- a/README.md
+++ b/README.md
@@ -2,8 +2,8 @@
[![codecov](https://codecov.io/gh/getrebuild/rebuild/branch/master/graph/badge.svg)](https://codecov.io/gh/getrebuild/rebuild)
[![Build Status](https://travis-ci.org/getrebuild/rebuild.svg?branch=master)](https://travis-ci.org/getrebuild/rebuild)
[![Crowdin](https://badges.crowdin.net/rebuild/localized.svg)](https://crowdin.com/project/rebuild)
-[![License GPLv3](https://img.shields.io/github/license/getrebuild/rebuild.svg)](https://raw.githubusercontent.com/getrebuild/rebuild/master/LICENSE)
-[![License COMMERCIAL](https://img.shields.io/badge/license-COMMERCIAL-orange.svg)](https://raw.githubusercontent.com/getrebuild/rebuild/master/COMMERCIAL)
+[![License GPLv3](https://img.shields.io/github/license/getrebuild/rebuild.svg)](https://getrebuild.com/license/LICENSE.txt)
+[![License COMMERCIAL](https://img.shields.io/badge/license-COMMERCIAL-orange.svg)](https://getrebuild.com/license/COMMERCIAL.txt)
## 快速开始
@@ -29,6 +29,6 @@ REBUILD is a true production-grade project that fully considers security, robust
## 注意 NOTICE
-REBUILD 使用 [开源 GPL-3.0](https://raw.githubusercontent.com/getrebuild/rebuild/master/LICENSE) 和 [商用](https://raw.githubusercontent.com/getrebuild/rebuild/master/COMMERCIAL) 双重授权许可,您应当认真阅读许可内容。使用 REBUILD 即表示您完全同意许可内容/条款。感谢支持!
+REBUILD 使用 [开源 GPL-3.0](https://getrebuild.com/license/LICENSE.txt) 和 [商用](https://getrebuild.com/license/COMMERCIAL.txt) 双重授权许可,您应当认真阅读许可内容。使用 REBUILD 即表示您完全同意许可内容/条款。感谢支持!
-REBUILD uses the [open source GPL-3.0](https://raw.githubusercontent.com/getrebuild/rebuild/master/LICENSE) and [commercial](https://raw.githubusercontent.com/getrebuild/rebuild/master/COMMERCIAL) dual license agreements, and you should read the contents of the agreement carefully. By using REBUILD, you fully agree to the Licensed Content/Terms. Thanks for the support!
\ No newline at end of file
+REBUILD uses the [open source GPL-3.0](https://getrebuild.com/license/LICENSE.txt) and [commercial](https://getrebuild.com/license/COMMERCIAL.txt) dual license agreements, and you should read the contents of the agreement carefully. By using REBUILD, you fully agree to the Licensed Content/Terms. Thanks for the support!
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 3069f8620..55d0145bb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.rebuild
rebuild
war
- 1.7.2
+ 1.8.0
rebuild
Building your business-systems free!
https://getrebuild.com/
@@ -127,7 +127,7 @@
com.github.devezhao
persist4j
- 1.3.4
+ 1.3.5
junit
@@ -290,6 +290,12 @@
com.alibaba
easyexcel
2.1.3
+
+
+ ehcache
+ org.ehcache
+
+
org.jxls
@@ -316,5 +322,10 @@
h2
1.4.200
+
+ com.googlecode.aviator
+ aviator
+ 4.2.9
+
diff --git a/src/main/java/com/rebuild/api/ApiGateway.java b/src/main/java/com/rebuild/api/ApiGateway.java
index 4c5ec3edc..4d22ffa1d 100644
--- a/src/main/java/com/rebuild/api/ApiGateway.java
+++ b/src/main/java/com/rebuild/api/ApiGateway.java
@@ -21,6 +21,7 @@
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.EncryptUtils;
import cn.devezhao.commons.ObjectUtils;
+import cn.devezhao.commons.ThreadPool;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
@@ -32,6 +33,7 @@
import com.rebuild.server.service.DataSpecificationException;
import com.rebuild.server.service.bizz.UserService;
import com.rebuild.utils.AppUtils;
+import com.rebuild.utils.CommonsUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -81,6 +83,7 @@ public void api(@PathVariable String apiName,
JSON ok = formatSuccess(data);
ServletUtils.writeJson(response, ok.toJSONString());
logRequestAsync(reuqestTime, remoteIp, apiName, context, ok);
+
return;
} catch (ApiInvokeException ex) {
@@ -201,17 +204,19 @@ protected void logRequestAsync(Date requestTime, String remoteIp, String apiName
return;
}
- Record record = EntityHelper.forNew(EntityHelper.RebuildApiRequest, UserService.SYSTEM_USER);
- record.setString("appId", context.getAppId());
- record.setString("remoteIp", remoteIp);
- record.setString("requestUrl", apiName + " " + context.getParameterMap());
- if (context.getPostData() != null) {
- record.setString("requestBody", context.getPostData().toJSONString());
- }
- record.setString("responseBody", result.toJSONString());
- record.setDate("requestTime", requestTime);
- record.setDate("responseTime", CalendarUtils.now());
- Application.getCommonService().create(record);
+ ThreadPool.exec(() -> {
+ Record record = EntityHelper.forNew(EntityHelper.RebuildApiRequest, UserService.SYSTEM_USER);
+ record.setString("appId", context.getAppId());
+ record.setString("remoteIp", remoteIp);
+ record.setString("requestUrl", CommonsUtils.maxstr(apiName + "?" + context.getParameterMap(),300));
+ if (context.getPostData() != null) {
+ record.setString("requestBody", CommonsUtils.maxstr(context.getPostData().toJSONString(), 10000));
+ }
+ record.setString("responseBody", CommonsUtils.maxstr(result.toJSONString(), 10000));
+ record.setDate("requestTime", requestTime);
+ record.setDate("responseTime", CalendarUtils.now());
+ Application.getCommonService().create(record, false);
+ });
}
// -- 注册 API
diff --git a/src/main/java/com/rebuild/api/LoginToken.java b/src/main/java/com/rebuild/api/LoginToken.java
index fff49651e..a475e5a06 100644
--- a/src/main/java/com/rebuild/api/LoginToken.java
+++ b/src/main/java/com/rebuild/api/LoginToken.java
@@ -92,7 +92,7 @@ public static ID verifyToken(String loginToken) {
* @return
*/
public static String checkUser(String user, String password) {
- if (!Application.getUserStore().exists(user)) {
+ if (!Application.getUserStore().existsUser(user)) {
return Languages.lang("InputWrong", "UsernameOrPassword");
}
diff --git a/src/main/java/com/rebuild/server/Application.java b/src/main/java/com/rebuild/server/Application.java
index 94ef596f4..1b44d4fe2 100644
--- a/src/main/java/com/rebuild/server/Application.java
+++ b/src/main/java/com/rebuild/server/Application.java
@@ -71,7 +71,7 @@ public final class Application {
/** Rebuild Version
*/
- public static final String VER = "1.7.2";
+ public static final String VER = "1.8.0";
/** Logging for Global
*/
diff --git a/src/main/java/com/rebuild/server/ServerListener.java b/src/main/java/com/rebuild/server/ServerListener.java
index cf332a0ba..976637ebe 100644
--- a/src/main/java/com/rebuild/server/ServerListener.java
+++ b/src/main/java/com/rebuild/server/ServerListener.java
@@ -21,7 +21,7 @@
import cn.devezhao.commons.CalendarUtils;
import com.rebuild.server.helper.ConfigurableItem;
import com.rebuild.server.helper.SysConfiguration;
-import com.rebuild.server.helper.setup.InstallAfter;
+import com.rebuild.server.helper.setup.InstallState;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -38,7 +38,7 @@
* @author devezhao
* @since 10/13/2018
*/
-public class ServerListener extends ContextCleanupListener implements InstallAfter {
+public class ServerListener extends ContextCleanupListener implements InstallState {
private static final Log LOG = LogFactory.getLog(ServerListener.class);
diff --git a/src/main/java/com/rebuild/server/ServerStatus.java b/src/main/java/com/rebuild/server/ServerStatus.java
index 794e1e4ef..8b8c56714 100644
--- a/src/main/java/com/rebuild/server/ServerStatus.java
+++ b/src/main/java/com/rebuild/server/ServerStatus.java
@@ -127,7 +127,7 @@ protected static Status checkCreateFile() {
if (!test.exists()) {
return Status.error(name, "Couldn't create file in temp Directory");
} else {
- test.delete();
+ FileUtils.deleteQuietly(test);
}
} catch (Exception ex) {
diff --git a/src/main/java/com/rebuild/server/business/approval/ApprovalProcessor.java b/src/main/java/com/rebuild/server/business/approval/ApprovalProcessor.java
index abcf21fe3..973f96ad9 100644
--- a/src/main/java/com/rebuild/server/business/approval/ApprovalProcessor.java
+++ b/src/main/java/com/rebuild/server/business/approval/ApprovalProcessor.java
@@ -124,7 +124,25 @@ public boolean submit(JSONObject selectNextUsers) throws ApprovalException {
* @throws ApprovalException
*/
public void approve(ID approver, ApprovalState state, String remark, JSONObject selectNextUsers) throws ApprovalException {
- Integer currentState = (Integer) Application.getQueryFactory().unique(this.record, EntityHelper.ApprovalState)[0];
+ approve(approver, state, remark, selectNextUsers, null);
+ }
+
+ /**
+ * 审批
+ *
+ * @param approver
+ * @param state
+ * @param remark
+ * @param selectNextUsers
+ * @param addedData
+ * @throws ApprovalException
+ */
+ public void approve(ID approver, ApprovalState state, String remark, JSONObject selectNextUsers, Record addedData) throws ApprovalException {
+ Object[] o = Application.getQueryFactory().unique(this.record, EntityHelper.ApprovalState);
+ if (o == null) {
+ throw new NoRecordFoundException("审批记录不存在或你无权查看");
+ }
+ Integer currentState = (Integer) o[0];
if (currentState != ApprovalState.PROCESSING.getState()) {
throw new ApprovalException("当前记录已经" + (currentState == ApprovalState.APPROVED.getState() ? "审批完成" : "驳回审批"));
}
@@ -152,28 +170,28 @@ public void approve(ID approver, ApprovalState state, String remark, JSONObject
Set ccs = nextNodes.getCcUsers(this.getUser(), this.record, selectNextUsers);
Set nextApprovers = null;
String nextNode = null;
- if (!nextNodes.isLastStep()) {
+
+ if (state == ApprovalState.APPROVED && !nextNodes.isLastStep()) {
nextApprovers = nextNodes.getApproveUsers(this.getUser(), this.record, selectNextUsers);
if (nextApprovers.isEmpty()) {
throw new ApprovalException("无下一步审批人可用,请联系管理员配置");
}
-
+
FlowNode nextApprovalNode = nextNodes.getApprovalNode();
nextNode = nextApprovalNode != null ? nextApprovalNode.getNodeId() : null;
}
-
+
FlowNode currentNode = getFlowParser().getNode((String) stepApprover[2]);
Application.getBean(ApprovalStepService.class)
- .txApprove(approvedStep, currentNode.getSignMode(), ccs, nextApprovers, nextNode);
+ .txApprove(approvedStep, currentNode.getSignMode(), ccs, nextApprovers, nextNode, addedData);
}
/**
* 撤销
*
- * @param remark
* @throws ApprovalException
*/
- public void cancel(String remark) throws ApprovalException {
+ public void cancel() throws ApprovalException {
Object[] state = Application.getQueryFactory().unique(this.record, EntityHelper.ApprovalState, EntityHelper.ApprovalId);
Integer currentState = (Integer) state[0];
if ((Integer) state[0] != ApprovalState.PROCESSING.getState()) {
@@ -183,6 +201,13 @@ public void cancel(String remark) throws ApprovalException {
Application.getBean(ApprovalStepService.class).txCancel(this.record, (ID) state[1], getCurrentNodeId());
}
+ /**
+ * @return
+ */
+ public FlowNode getCurrentNode() {
+ return getFlowParser().getNode(getCurrentNodeId());
+ }
+
/**
* @return
* @see #getNextNode(String)
diff --git a/src/main/java/com/rebuild/server/business/approval/FlowNode.java b/src/main/java/com/rebuild/server/business/approval/FlowNode.java
index 7785ef92f..6e7fd8294 100644
--- a/src/main/java/com/rebuild/server/business/approval/FlowNode.java
+++ b/src/main/java/com/rebuild/server/business/approval/FlowNode.java
@@ -136,7 +136,7 @@ public Set getSpecUsers(ID operator, ID record) {
if (userDefs == null || userDefs.isEmpty()) {
return Collections.emptySet();
}
-
+
String userType = userDefs.getString(0);
if (USER_SELF.equalsIgnoreCase(userType)) {
Set users = new HashSet<>();
@@ -171,7 +171,27 @@ public int hashCode() {
@Override
public boolean equals(Object obj) {
- return obj.hashCode() == this.hashCode();
+ if (obj == null) return false;
+ return obj instanceof FlowNode && obj.hashCode() == this.hashCode();
+ }
+
+ /**
+ * 节点可编辑字段
+ *
+ * @return
+ */
+ public JSONArray getEditableFields() {
+ JSONArray editableFields = dataMap == null ? null : dataMap.getJSONArray("editableFields");
+ if (editableFields == null) {
+ return null;
+ }
+
+ editableFields = (JSONArray) JSONUtils.clone(editableFields);
+ for (Object o : editableFields) {
+ JSONObject field = (JSONObject) o;
+ field.put("nullable", !((Boolean) field.remove("notNull")));
+ }
+ return editableFields;
}
// --
diff --git a/src/main/java/com/rebuild/server/business/approval/FlowNodeGroup.java b/src/main/java/com/rebuild/server/business/approval/FlowNodeGroup.java
index 234cf1002..0d9122e5f 100644
--- a/src/main/java/com/rebuild/server/business/approval/FlowNodeGroup.java
+++ b/src/main/java/com/rebuild/server/business/approval/FlowNodeGroup.java
@@ -21,25 +21,31 @@
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.service.bizz.UserHelper;
+import org.springframework.util.Assert;
import java.util.HashSet;
import java.util.Set;
/**
+ * 1个审批节点+N个抄送节点
+ *
* @author devezhao zhaofang123@gmail.com
* @since 2019/07/11
+ * @see FlowNode
*/
public class FlowNodeGroup {
private Set nodes = new HashSet<>();
protected FlowNodeGroup() {
+ super();
}
/**
* @param node
*/
public void addNode(FlowNode node) {
+ Assert.isNull(getApprovalNode(), "Cannot add multiple approved nodes");
nodes.add(node);
}
@@ -59,12 +65,8 @@ public boolean allowSelfSelectingCc() {
* @return
*/
public boolean allowSelfSelectingApprover() {
- for (FlowNode node : nodes) {
- if (node.getType().equals(FlowNode.TYPE_APPROVER) && node.allowSelfSelecting()) {
- return true;
- }
- }
- return false;
+ FlowNode node = getApprovalNode();
+ return node != null && node.allowSelfSelecting();
}
/**
@@ -95,12 +97,12 @@ public Set getCcUsers(ID operator, ID recordId, JSONObject selectUsers) {
*/
public Set getApproveUsers(ID operator, ID recordId, JSONObject selectUsers) {
Set users = new HashSet<>();
- for (FlowNode node : nodes) {
- if (FlowNode.TYPE_APPROVER.equals(node.getType())) {
- users.addAll(node.getSpecUsers(operator, recordId));
- }
+
+ FlowNode node = getApprovalNode();
+ if (node != null) {
+ users.addAll(node.getSpecUsers(operator, recordId));
}
-
+
if (selectUsers != null) {
users.addAll(UserHelper.parseUsers(selectUsers.getJSONArray("selectApprovers"), recordId));
}
@@ -114,12 +116,7 @@ public Set getApproveUsers(ID operator, ID recordId, JSONObject selectUsers)
*/
public boolean isLastStep() {
// TODO 对审批最后一步加强判断
- for (FlowNode node : nodes) {
- if (node.getType().equals(FlowNode.TYPE_APPROVER)) {
- return false;
- }
- }
- return true;
+ return getApprovalNode() == null;
}
/**
@@ -130,6 +127,8 @@ public boolean isValid() {
}
/**
+ * 获取审批节点
+ *
* @return
*/
public FlowNode getApprovalNode() {
diff --git a/src/main/java/com/rebuild/server/business/approval/FormBuilder.java b/src/main/java/com/rebuild/server/business/approval/FormBuilder.java
new file mode 100644
index 000000000..07255796d
--- /dev/null
+++ b/src/main/java/com/rebuild/server/business/approval/FormBuilder.java
@@ -0,0 +1,45 @@
+/*
+rebuild - Building your business-systems freely.
+Copyright (C) 2020 devezhao
+
+rebuild is dual-licensed under commercial and open source licenses (GPLv3).
+For more information, please see
+*/
+
+package com.rebuild.server.business.approval;
+
+import cn.devezhao.persist4j.Record;
+import cn.devezhao.persist4j.engine.ID;
+import com.alibaba.fastjson.JSONArray;
+import com.rebuild.server.configuration.portals.FormsBuilder;
+
+/**
+ * 审批可修改字段表单
+ *
+ * @author devezhao
+ * @since 2020/2/5
+ */
+public class FormBuilder {
+
+ final private ID record;
+ final private ID user;
+
+ /**
+ * @param record
+ * @param user
+ */
+ public FormBuilder(ID record, ID user) {
+ this.record = record;
+ this.user = user;
+ }
+
+ /**
+ * @param elements
+ * @return
+ */
+ public JSONArray build(JSONArray elements) {
+ Record data = FormsBuilder.instance.findRecord(record, user, elements);
+ FormsBuilder.instance.buildModelElements(elements, data.getEntity(), data, user);
+ return elements;
+ }
+}
diff --git a/src/main/java/com/rebuild/server/business/charts/TreeBuilder.java b/src/main/java/com/rebuild/server/business/charts/TreeBuilder.java
index b561c6bce..433747f15 100644
--- a/src/main/java/com/rebuild/server/business/charts/TreeBuilder.java
+++ b/src/main/java/com/rebuild/server/business/charts/TreeBuilder.java
@@ -66,6 +66,10 @@ public JSON toJSON() {
for (Object[] o : rows) {
double value = (double) o[lastIndex];
+ // 排除0,因为0在树图中本就不显示
+ if (value <= 0d) {
+ continue;
+ }
String name = (String) o[0];
Item L1 = thereAll.get(name);
@@ -85,7 +89,7 @@ public JSON toJSON() {
}
}
- Item L3 = null;
+ Item L3;
if (lastIndex > 2) {
name = name + NAME_SPEA + o[2];
L3 = thereAll.get(name);
@@ -102,7 +106,8 @@ public JSON toJSON() {
}
return treeJson;
}
-
+
+ // 单项
private class Item {
private Item parent;
private List children = new ArrayList<>();
@@ -123,9 +128,8 @@ private Item(String name, double value, Item parent) {
protected String getName() {
return name;
-// String[] names = name.split(NAME_SPEA);
-// return names[names.length - 1];
}
+
protected double getValue() {
if (this.children.isEmpty()) {
return value;
diff --git a/src/main/java/com/rebuild/server/business/charts/builtin/ApprovalList.java b/src/main/java/com/rebuild/server/business/charts/builtin/ApprovalList.java
index 938e75c60..d55f84a68 100644
--- a/src/main/java/com/rebuild/server/business/charts/builtin/ApprovalList.java
+++ b/src/main/java/com/rebuild/server/business/charts/builtin/ApprovalList.java
@@ -20,6 +20,7 @@
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.momentjava.Moment;
+import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
@@ -30,6 +31,7 @@
import com.rebuild.server.configuration.portals.FieldValueWrapper;
import com.rebuild.server.helper.cache.NoRecordFoundException;
import com.rebuild.server.metadata.MetadataHelper;
+import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.server.service.bizz.UserHelper;
import com.rebuild.utils.JSONUtils;
@@ -73,7 +75,8 @@ public JSONObject getChartConfig() {
@Override
public JSON build() {
final int viewState = ObjectUtils.toInt(getExtraParams().get("state"), ApprovalState.DRAFT.getState());
- final String baseWhere = "where isCanceled = 'F' and isWaiting = 'F' and approver = ? and approvalId <> '' and approvalId is not null and ";
+ final String baseWhere = "where isCanceled = 'F' and isWaiting = 'F' and approver = ?" +
+ " and approvalId <> '' and recordId <> '' and ";
Object[][] array = Application.createQueryNoFilter(
"select createdBy,modifiedOn,recordId,approvalId from RobotApprovalStep " +
@@ -101,6 +104,7 @@ public JSON build() {
continue;
}
+ Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
ID s = ApprovalHelper.getSubmitter(recordId, (ID) o[3]);
rearray.add(new Object[] {
s,
@@ -109,7 +113,8 @@ public JSON build() {
o[2],
label,
o[3],
- MetadataHelper.getEntityLabel(recordId)
+ EasyMeta.getLabel(entity),
+ entity.getName()
});
}
diff --git a/src/main/java/com/rebuild/server/business/dataimport/DataFileParser.java b/src/main/java/com/rebuild/server/business/dataimport/DataFileParser.java
index 54116297c..6f029741e 100644
--- a/src/main/java/com/rebuild/server/business/dataimport/DataFileParser.java
+++ b/src/main/java/com/rebuild/server/business/dataimport/DataFileParser.java
@@ -41,8 +41,7 @@ public class DataFileParser {
* @param sourceFile
*/
public DataFileParser(File sourceFile) {
- this.sourceFile = sourceFile;
- this.encoding = "GBK";
+ this(sourceFile, "utf-8");
}
/**
diff --git a/src/main/java/com/rebuild/server/business/datareport/ReportGenerator.java b/src/main/java/com/rebuild/server/business/datareport/ReportGenerator.java
index 8f0ba2a5a..c4b390e7c 100644
--- a/src/main/java/com/rebuild/server/business/datareport/ReportGenerator.java
+++ b/src/main/java/com/rebuild/server/business/datareport/ReportGenerator.java
@@ -64,7 +64,8 @@ public class ReportGenerator extends SetUser {
* @param record
*/
public ReportGenerator(ID reportId, ID record) {
- this(DataReportManager.instance.getTemplateFile(MetadataHelper.getEntity(record.getEntityCode()), reportId), record);
+ this(DataReportManager.instance.getTemplateFile(MetadataHelper.getEntity(record.getEntityCode()),
+ reportId), record);
}
/**
diff --git a/src/main/java/com/rebuild/server/business/datareport/TemplateExtractor.java b/src/main/java/com/rebuild/server/business/datareport/TemplateExtractor.java
index 7eed90316..d3d0aa924 100644
--- a/src/main/java/com/rebuild/server/business/datareport/TemplateExtractor.java
+++ b/src/main/java/com/rebuild/server/business/datareport/TemplateExtractor.java
@@ -63,10 +63,10 @@ public TemplateExtractor(File template) {
public Set extractVars(boolean matchsAny) {
List rows = CommonsUtils.readExcel(this.template);
- String regex = "\\$\\{[0-9a-zA-Z\\.]+\\}";
+ String regex = "\\$\\{[0-9a-zA-Z_.]+}";
// 能够匹配中文
if (matchsAny) {
- regex = "\\$\\{.+\\}";
+ regex = "\\$\\{.+}";
}
// jxls 不支持中文变量
@@ -131,7 +131,7 @@ protected String getRealField(Entity entity, String fieldPath) {
String[] paths = fieldPath.split("\\.");
List realPaths = new ArrayList<>();
- Field lastField = null;
+ Field lastField;
Entity father = entity;
for (String field : paths) {
if (father == null) {
diff --git a/src/main/java/com/rebuild/server/business/feeds/FeedsHelper.java b/src/main/java/com/rebuild/server/business/feeds/FeedsHelper.java
index b02d9189f..44cf8a419 100644
--- a/src/main/java/com/rebuild/server/business/feeds/FeedsHelper.java
+++ b/src/main/java/com/rebuild/server/business/feeds/FeedsHelper.java
@@ -19,17 +19,22 @@
package com.rebuild.server.business.feeds;
import cn.devezhao.bizz.security.member.Team;
+import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.server.Application;
import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.service.bizz.UserHelper;
+import com.rebuild.server.service.notification.MessageBuilder;
+import com.rebuild.utils.AppUtils;
import org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* @author devezhao
@@ -163,4 +168,37 @@ public static boolean checkReadable(ID feedsOrComment, ID user) {
}
return false;
}
+
+ /**
+ * URL 提取
+ */
+ public static final Pattern URL_PATTERN = Pattern.compile("((www|https?:\\/\\/)[-a-zA-Z0-9+&@#/%?=~_|!:,.;]{5,300})");
+
+ /**
+ * 格式化动态内容
+ *
+ * @param content
+ * @return
+ */
+ public static String formatContent(String content) {
+ Matcher urlMatcher = URL_PATTERN.matcher(content);
+ while (urlMatcher.find()) {
+ String url = urlMatcher.group();
+ String safeUrl = AppUtils.getContextPath() + "/commons/url-safe?url=" + CodecUtils.urlEncode(url);
+ content = content.replace(url,
+ String.format("%s", safeUrl, url));
+ }
+
+ Matcher atMatcher = MessageBuilder.AT_PATTERN.matcher(content);
+ while (atMatcher.find()) {
+ String at = atMatcher.group();
+ ID user = ID.valueOf(at.substring(1));
+ if (user.getEntityCode() == EntityHelper.User && Application.getUserStore().existsUser(user)) {
+ String fullName = Application.getUserStore().getUser(user).getFullName();
+ content = content.replace(at, String.format("@%s", user, fullName));
+ }
+ }
+
+ return content;
+ }
}
diff --git a/src/main/java/com/rebuild/server/business/feeds/FeedsType.java b/src/main/java/com/rebuild/server/business/feeds/FeedsType.java
index d2141a4d0..54b8da6b3 100644
--- a/src/main/java/com/rebuild/server/business/feeds/FeedsType.java
+++ b/src/main/java/com/rebuild/server/business/feeds/FeedsType.java
@@ -28,6 +28,7 @@ public enum FeedsType {
ACTIVITY(1, "动态"),
FOLLOWUP(2, "跟进"),
+ ANNOUNCEMENT(3, "公告"),
;
diff --git a/src/main/java/com/rebuild/server/business/recyclebin/RecycleBinCleanerJob.java b/src/main/java/com/rebuild/server/business/recyclebin/RecycleBinCleanerJob.java
index c4a5d3d4e..ec825d9fb 100644
--- a/src/main/java/com/rebuild/server/business/recyclebin/RecycleBinCleanerJob.java
+++ b/src/main/java/com/rebuild/server/business/recyclebin/RecycleBinCleanerJob.java
@@ -46,8 +46,9 @@ public class RecycleBinCleanerJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
- int keepingDays = SysConfiguration.getInt(ConfigurableItem.RecycleBinKeepingDays);
+ final int keepingDays = getKeepingDays();
LOG.info("RecycleBinCleanerJob running ... " + keepingDays);
+ // Keep forever
if (keepingDays > 9999) {
return;
}
@@ -61,5 +62,16 @@ protected void executeInternal(JobExecutionContext context) throws JobExecutionE
CalendarUtils.getUTCDateFormat().format(before));
int del = Application.getSQLExecutor().execute(delSql, 120);
LOG.warn("RecycleBin cleaned : " + del);
+
+ // TODO 相关引用也在此时一并删除,因为记录已经彻底删除了
+ }
+
+ /**
+ * 回收站保留天数。小于等于 0 表示未开启回收站,大于等于 9999 表示永远保留
+ *
+ * @return
+ */
+ public static int getKeepingDays() {
+ return SysConfiguration.getInt(ConfigurableItem.RecycleBinKeepingDays);
}
}
diff --git a/src/main/java/com/rebuild/server/business/recyclebin/RecycleRestore.java b/src/main/java/com/rebuild/server/business/recyclebin/RecycleRestore.java
index 06606d198..e50480ae0 100644
--- a/src/main/java/com/rebuild/server/business/recyclebin/RecycleRestore.java
+++ b/src/main/java/com/rebuild/server/business/recyclebin/RecycleRestore.java
@@ -27,7 +27,9 @@
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
import com.rebuild.server.RebuildException;
+import com.rebuild.server.metadata.EntityHelper;
import com.rebuild.server.metadata.MetadataHelper;
+import com.rebuild.server.service.OperatingContext;
import com.rebuild.server.service.TransactionManual;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -68,7 +70,7 @@ public int restore() {
/**
* 恢复数据
*
- * @param cascade 级联恢复
+ * @param cascade 恢复关联删除的数据
* @return
*/
public int restore(boolean cascade) {
@@ -84,9 +86,9 @@ public int restore(boolean cascade) {
final List recycleIds = new ArrayList<>();
- final List willRestores = new ArrayList<>(toRecord(JSON.parseObject((String) main[0]), (ID) main[1]));
+ final List willRestores = new ArrayList<>(conver2Record(JSON.parseObject((String) main[0]), (ID) main[1]));
if (willRestores.isEmpty()) {
- throw new RebuildException("记录的所属实体不存在");
+ throw new RebuildException("记录实体已经不存在");
}
recycleIds.add((ID) main[2]);
@@ -96,7 +98,7 @@ public int restore(boolean cascade) {
.setParameter(1, main[1])
.array();
for (Object[] o : array) {
- List records = toRecord(JSON.parseObject((String) o[0]), (ID) o[1]);
+ List records = conver2Record(JSON.parseObject((String) o[0]), (ID) o[1]);
if (!records.isEmpty()) {
willRestores.addAll(records);
recycleIds.add((ID) o[2]);
@@ -114,13 +116,12 @@ public int restore(boolean cascade) {
String primaryName = r.getEntity().getPrimaryField().getName();
ID primaryId = (ID) r.removeValue(primaryName);
PM.saveInternal(r, primaryId);
+ restoreAttachment(PM, primaryId);
- // 非明细才计数
- if (r.getEntity().getMasterEntity() == null) {
- restored++;
- }
+ restored++;
}
+ // 从回收站删除
PM.delete(recycleIds.toArray(new ID[0]));
TransactionManual.commit(status);
@@ -133,11 +134,13 @@ public int restore(boolean cascade) {
}
/**
+ * 转换成 Record 对象,返回多条是可能存在明细
+ *
* @param content
* @param recordId
* @return
*/
- private List toRecord(JSONObject content, ID recordId) {
+ private List conver2Record(JSONObject content, ID recordId) {
if (!MetadataHelper.containsEntity(recordId.getEntityCode())) {
return Collections.emptyList();
}
@@ -162,4 +165,21 @@ private List toRecord(JSONObject content, ID recordId) {
}
return records;
}
+
+ /**
+ * @param PM
+ * @param recordId
+ * @see com.rebuild.server.service.base.AttachmentAwareObserver#onDelete(OperatingContext)
+ */
+ private void restoreAttachment(PersistManagerImpl PM, ID recordId) {
+ Object[][] array = Application.createQueryNoFilter(
+ "select attachmentId from Attachment where relatedRecord = ?")
+ .setParameter(1, recordId)
+ .array();
+ for (Object[] o : array) {
+ Record u = EntityHelper.forUpdate((ID) o[0], null, false);
+ u.setBoolean(EntityHelper.IsDeleted, false);
+ PM.update(u);
+ }
+ }
}
diff --git a/src/main/java/com/rebuild/server/business/trigger/ActionType.java b/src/main/java/com/rebuild/server/business/trigger/ActionType.java
index b90fb0624..7b6f7673e 100644
--- a/src/main/java/com/rebuild/server/business/trigger/ActionType.java
+++ b/src/main/java/com/rebuild/server/business/trigger/ActionType.java
@@ -21,6 +21,7 @@
import com.rebuild.server.business.trigger.impl.AutoAssign;
import com.rebuild.server.business.trigger.impl.AutoShare;
import com.rebuild.server.business.trigger.impl.FieldAggregation;
+import com.rebuild.server.business.trigger.impl.FieldWriteback;
import com.rebuild.server.business.trigger.impl.SendNotification;
import org.springframework.cglib.core.ReflectUtils;
@@ -35,8 +36,8 @@
public enum ActionType {
FIELDAGGREGATION("数据聚合", FieldAggregation.class),
- SENDNOTIFICATION("发送通知 (内部消息)", SendNotification.class),
-
+ FIELDWRITEBACK("数据回写", FieldWriteback.class),
+ SENDNOTIFICATION("发送通知", SendNotification.class),
AUTOSHARE("自动共享", AutoShare.class),
AUTOASSIGN("自动分派", AutoAssign.class),
@@ -70,7 +71,7 @@ public Class extends TriggerAction> getActionClazz() {
* @throws NoSuchMethodException
*/
public TriggerAction newInstance(ActionContext context) throws NoSuchMethodException {
- Constructor c = getActionClazz().getConstructor(ActionContext.class);
+ Constructor extends TriggerAction> c = getActionClazz().getConstructor(ActionContext.class);
return (TriggerAction) ReflectUtils.newInstance(c, new Object[] { context });
}
}
diff --git a/src/main/java/com/rebuild/server/business/trigger/RobotTriggerObserver.java b/src/main/java/com/rebuild/server/business/trigger/RobotTriggerObserver.java
index 317ac0297..6e00437cd 100644
--- a/src/main/java/com/rebuild/server/business/trigger/RobotTriggerObserver.java
+++ b/src/main/java/com/rebuild/server/business/trigger/RobotTriggerObserver.java
@@ -78,6 +78,10 @@ protected void execAction(OperatingContext context, TriggerWhen when) {
if (cleanSource) {
setTriggerSource(context);
}
+ // 自己触发自己,避免无限执行
+ else if (getTriggerSource().getAnyRecord().getPrimary().equals(context.getAnyRecord().getPrimary())) {
+ return;
+ }
final ID currentUser = Application.getCurrentUser();
try {
diff --git a/src/main/java/com/rebuild/server/business/trigger/impl/AggregationEvaluator.java b/src/main/java/com/rebuild/server/business/trigger/impl/AggregationEvaluator.java
new file mode 100644
index 000000000..8250a8e9b
--- /dev/null
+++ b/src/main/java/com/rebuild/server/business/trigger/impl/AggregationEvaluator.java
@@ -0,0 +1,162 @@
+/*
+rebuild - Building your business-systems freely.
+Copyright (C) 2018-2020 devezhao
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+*/
+
+package com.rebuild.server.business.trigger.impl;
+
+import cn.devezhao.persist4j.Entity;
+import cn.devezhao.persist4j.engine.ID;
+import com.alibaba.fastjson.JSONObject;
+import com.googlecode.aviator.AviatorEvaluator;
+import com.googlecode.aviator.AviatorEvaluatorInstance;
+import com.googlecode.aviator.Options;
+import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
+import com.rebuild.server.Application;
+import com.rebuild.server.metadata.MetadataHelper;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * 归集计算
+ *
+ * @author devezhao
+ * @since 2020/1/16
+ */
+public class AggregationEvaluator {
+
+ private static final Log LOG = LogFactory.getLog(FieldAggregation.class);
+
+ private static final Pattern FIELD_PATT = Pattern.compile("\\{(.*?)}");
+
+ private static AviatorEvaluatorInstance AVIATOR = AviatorEvaluator.newInstance();
+ static {
+ // 强制使用 BigDecimal/BigInteger 运算
+ AVIATOR.setOption(Options.ALWAYS_PARSE_FLOATING_POINT_NUMBER_INTO_DECIMAL, true);
+ }
+
+ final private Entity sourceEntity;
+ final private JSONObject item;
+ final private String followSourceField;
+ final private String filterSql;
+
+ /**
+ * @param item
+ * @param sourceEntity
+ * @param followSourceField
+ * @param filterSql
+ */
+ protected AggregationEvaluator(JSONObject item, Entity sourceEntity, String followSourceField, String filterSql) {
+ this.sourceEntity = sourceEntity;
+ this.item = item;
+ this.followSourceField = followSourceField;
+ this.filterSql = filterSql;
+ }
+
+ /**
+ * @param triggerRecord
+ * @return
+ */
+ public Object eval(ID triggerRecord) {
+ String calcMode = item.getString("calcMode");
+ if ("FORMULA".equalsIgnoreCase(calcMode)) {
+ return evalFormula(triggerRecord);
+ }
+
+ String sourceField = item.getString("sourceField");
+ if (MetadataHelper.getLastJoinField(sourceEntity, sourceField) == null) {
+ return null;
+ }
+
+ String funcAndField = String.format("%s(%s)", calcMode, sourceField);
+ String sql = String.format("select %s from %s where %s = ?",
+ funcAndField, sourceEntity.getName(), followSourceField);
+ if (filterSql != null) {
+ sql += " and " + filterSql;
+ }
+
+ Object[] o = Application.createQueryNoFilter(sql)
+ .setParameter(1, triggerRecord)
+ .unique();
+ return o == null || o[0] == null ? 0 : o[0];
+ }
+
+ /**
+ * @param triggerRecord
+ * @return
+ */
+ private Object evalFormula(ID triggerRecord) {
+ String formula = item.getString("sourceFormula");
+ Matcher m = FIELD_PATT.matcher(formula);
+
+ final List fields = new ArrayList<>();
+ while (m.find()) {
+ String[] fieldAndFunc = m.group(1).split("\\$\\$\\$\\$");
+ if (MetadataHelper.getLastJoinField(sourceEntity, fieldAndFunc[0]) != null) {
+ fields.add(fieldAndFunc);
+ }
+ }
+ if (fields.isEmpty()) {
+ return null;
+ }
+
+ StringBuilder sql = new StringBuilder("select ");
+ for (String[] field : fields) {
+ if (field.length == 2) {
+ sql.append(String.format("%s(%s)", field[1], field[0]));
+ } else {
+ sql.append(field[0]);
+ }
+ sql.append(',');
+ }
+ sql.deleteCharAt(sql.length() - 1)
+ .append(" from ").append(sourceEntity.getName())
+ .append(" where ").append(followSourceField).append(" = ?");
+ if (filterSql != null) {
+ sql.append(" and ").append(filterSql);
+ }
+
+ Object[] o = Application.createQueryNoFilter(sql.toString())
+ .setParameter(1, triggerRecord)
+ .unique();
+ if (o == null) {
+ return null;
+ }
+
+ String newFormual = formula.toUpperCase()
+ .replace("×", "*")
+ .replace("÷", "/");
+ for (int i = 0; i < fields.size(); i++) {
+ String[] field = fields.get(i);
+ Object v = o[i] == null ? "0" : o[i];
+ String replace = "{" + StringUtils.join(field, "$$$$") + "}";
+ newFormual = newFormual.replace(replace.toUpperCase(), v.toString());
+ }
+
+ try {
+ return AVIATOR.execute(newFormual);
+ } catch (ExpressionSyntaxErrorException ex) {
+ LOG.error("Bad formula : " + formula + " > " + newFormual, ex);
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/com/rebuild/server/business/trigger/impl/CompatibleValueConversion.java b/src/main/java/com/rebuild/server/business/trigger/impl/CompatibleValueConversion.java
new file mode 100644
index 000000000..0c44186e1
--- /dev/null
+++ b/src/main/java/com/rebuild/server/business/trigger/impl/CompatibleValueConversion.java
@@ -0,0 +1,134 @@
+/*
+rebuild - Building your business-systems freely.
+Copyright (C) 2020 devezhao
+
+rebuild is dual-licensed under commercial and open source licenses (GPLv3).
+For more information, please see
+*/
+
+package com.rebuild.server.business.trigger.impl;
+
+import cn.devezhao.commons.CalendarUtils;
+import cn.devezhao.persist4j.Field;
+import cn.devezhao.persist4j.engine.ID;
+import cn.devezhao.persist4j.engine.NullValue;
+import com.rebuild.server.configuration.portals.FieldValueWrapper;
+import com.rebuild.server.configuration.portals.PickListManager;
+import com.rebuild.server.metadata.entity.DisplayType;
+import com.rebuild.server.metadata.entity.EasyMeta;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * 字段值兼容转换
+ *
+ * @author ZHAO
+ * @since 2020/2/8
+ */
+public class CompatibleValueConversion {
+
+ private static final Log LOG = LogFactory.getLog(CompatibleValueConversion.class);
+
+ final private Field source;
+ final private Field target;
+
+ /**
+ * @param sourceField
+ * @param targetField
+ */
+ public CompatibleValueConversion(Field sourceField, Field targetField) {
+ this.source = sourceField;
+ this.target = targetField;
+ }
+
+ /**
+ * @param sourceValue
+ * @return
+ */
+ public Object conversion(Object sourceValue) {
+ return conversion(sourceValue, false);
+ }
+
+ /**
+ * @param sourceValue
+ * @param mixValue
+ * @return
+ */
+ public Object conversion(Object sourceValue, boolean mixValue) {
+ if (sourceValue == null || NullValue.is(sourceValue)) {
+ return null;
+ }
+
+ final EasyMeta sourceField = EasyMeta.valueOf(source);
+ final DisplayType sourceType = sourceField.getDisplayType();
+ final DisplayType targetType = EasyMeta.getDisplayType(target);
+ final boolean is2Text = targetType == DisplayType.TEXT || targetType == DisplayType.NTEXT;
+
+ Object compatibleValue = sourceValue;
+ if (sourceType == DisplayType.ID) {
+ if (is2Text) {
+ compatibleValue = sourceValue.toString().toUpperCase();
+ }
+ }
+ else if (sourceType == DisplayType.REFERENCE) {
+ if (is2Text) {
+ compatibleValue = FieldValueWrapper.getLabelNotry((ID) sourceValue);
+ } else if (mixValue) {
+ String text = FieldValueWrapper.getLabelNotry((ID) sourceValue);
+ compatibleValue = FieldValueWrapper.wrapMixValue((ID) sourceValue, text);
+ }
+ }
+ else if (sourceType == DisplayType.CLASSIFICATION) {
+ if (is2Text) {
+ compatibleValue = FieldValueWrapper.instance.wrapFieldValue(sourceValue, sourceField, true);
+ } else if (mixValue) {
+ compatibleValue = FieldValueWrapper.instance.wrapFieldValue(sourceValue, sourceField, false);
+ }
+ }
+ else if (sourceType == DisplayType.PICKLIST) {
+ String text = FieldValueWrapper.instance.wrapPickList(sourceValue, sourceField);
+ if (is2Text) {
+ compatibleValue = text;
+ } else {
+ // 转换 PickList ID
+ compatibleValue = PickListManager.instance.findItemByLabel(text, target);
+ if (compatibleValue == null) {
+ LOG.warn("Cannot find value of PickList : " + text + " << " + target);
+ }
+ }
+ }
+ else if (sourceType == DisplayType.STATE) {
+ if (is2Text) {
+ compatibleValue = FieldValueWrapper.instance.wrapState(sourceValue, sourceField);
+ }
+ }
+ else if (sourceType == DisplayType.DATETIME && targetType == DisplayType.DATE) {
+ String datetime = FieldValueWrapper.instance.wrapDatetime(sourceValue, sourceField);
+ compatibleValue = datetime.split(" ")[0];
+ if (!(is2Text || mixValue)) {
+ compatibleValue = CalendarUtils.parse((String) compatibleValue);
+ }
+ }
+ else if (sourceType == DisplayType.DATE && targetType == DisplayType.DATETIME) {
+ String date = FieldValueWrapper.instance.wrapDate(sourceValue, sourceField);
+ if (date.length() == 4) { // YYYY
+ compatibleValue = date + "01-01 00:00:00";
+ } else if (date.length() == 7) { // YYYY-MM
+ compatibleValue = date + "-01 00:00:00";
+ } else {
+ compatibleValue = date + " 00:00:00";
+ }
+
+ if (!(is2Text || mixValue)) {
+ compatibleValue = CalendarUtils.parse((String) compatibleValue);
+ }
+ }
+ else if (is2Text) {
+ compatibleValue = FieldValueWrapper.instance.wrapFieldValue(sourceValue, sourceField);
+ }
+ // 整数/浮点数无需转换,因为持久层框架已有兼容处理
+
+ return compatibleValue;
+ }
+
+}
diff --git a/src/main/java/com/rebuild/server/business/trigger/impl/FieldAggregation.java b/src/main/java/com/rebuild/server/business/trigger/impl/FieldAggregation.java
index ae3ad0e2f..08c5cda1d 100644
--- a/src/main/java/com/rebuild/server/business/trigger/impl/FieldAggregation.java
+++ b/src/main/java/com/rebuild/server/business/trigger/impl/FieldAggregation.java
@@ -51,53 +51,69 @@
* @see com.rebuild.server.business.trigger.RobotTriggerObserver
*/
public class FieldAggregation implements TriggerAction {
-
- private static final Log LOG = LogFactory.getLog(FieldAggregation.class);
-
- // 此触发器可能产生连锁反应
- // 如触发器 A 调用 B,而 B 又调用了 C ... 以此类推。此处记录其深度
- private static final ThreadLocal CALL_CHAIN_DEPTH = new ThreadLocal<>();
- // 最大调用深度
- private static final int MAX_DEPTH = 5;
-
- final private ActionContext context;
-
- // 允许无权限更新
- private boolean allowNoPermissionUpdate;
-
- private Entity sourceEntity;
- private Entity targetEntity;
-
- private String followSourceField;
- private ID targetRecordId;
-
- public FieldAggregation(ActionContext context) {
- this(context, Boolean.TRUE);
- }
- public FieldAggregation(ActionContext context, boolean allowNoPermissionUpdate) {
- this.context = context;
- this.allowNoPermissionUpdate = allowNoPermissionUpdate;
+ private static final Log LOG = LogFactory.getLog(FieldAggregation.class);
+
+ /**
+ * 归集到自己
+ */
+ public static final String SOURCE_SELF = "$PRIMARY$";
+
+ final protected ActionContext context;
+ // 允许无权限更新
+ final private boolean allowNoPermissionUpdate;
+ // 最大触发链深度
+ final private int maxTriggerDepth;
+
+ // 此触发器可能产生连锁反应
+ // 如触发器 A 调用 B,而 B 又调用了 C ... 以此类推。此处记录其深度
+ private static final ThreadLocal TRIGGER_CHAIN_DEPTH = new ThreadLocal<>();
+
+ // 源实体
+ protected Entity sourceEntity;
+ // 目标实体
+ protected Entity targetEntity;
+ // 关联字段
+ protected String followSourceField;
+ // 触发记录
+ protected ID targetRecordId;
+
+ /**
+ * @param context
+ */
+ public FieldAggregation(ActionContext context) {
+ this(context, Boolean.TRUE, 5);
}
-
+
+ /**
+ * @param context
+ * @param allowNoPermissionUpdate
+ * @param maxTriggerDepth
+ */
+ protected FieldAggregation(ActionContext context, boolean allowNoPermissionUpdate, int maxTriggerDepth) {
+ this.context = context;
+ this.allowNoPermissionUpdate = allowNoPermissionUpdate;
+ this.maxTriggerDepth = maxTriggerDepth;
+ }
+
@Override
public ActionType getType() {
return ActionType.FIELDAGGREGATION;
}
-
- @Override
- public boolean isUsableSourceEntity(int entityCode) {
- return true;
- }
-
- @Override
+
+ @Override
+ public boolean isUsableSourceEntity(int entityCode) {
+ return true;
+ }
+
+ @Override
public void execute(OperatingContext operatingContext) throws TriggerException {
- Integer depth = CALL_CHAIN_DEPTH.get();
+ Integer depth = TRIGGER_CHAIN_DEPTH.get();
if (depth == null) {
depth = 1;
}
- if (depth > MAX_DEPTH) {
- throw new TriggerException("Too many call-chain with triggers : " + depth);
+ if (depth > maxTriggerDepth) {
+ throw new TriggerException("Too many trigger-chain with triggers : " + depth);
}
this.prepare(operatingContext);
@@ -116,56 +132,54 @@ public void execute(OperatingContext operatingContext) throws TriggerException {
// 聚合数据过滤
JSONObject dataFilter = ((JSONObject) context.getActionContent()).getJSONObject("dataFilter");
- String dataFilterWhere = null;
+ String dataFilterSql = null;
if (dataFilter != null && !dataFilter.isEmpty()) {
- dataFilterWhere = new AdvFilterParser(dataFilter).toSqlWhere();
+ dataFilterSql = new AdvFilterParser(dataFilter).toSqlWhere();
}
- // 更新目标
- Record targetRecord = EntityHelper.forUpdate(targetRecordId, UserService.SYSTEM_USER, false);
-
- JSONArray items = ((JSONObject) context.getActionContent()).getJSONArray("items");
- for (Object o : items) {
- JSONObject item = (JSONObject) o;
- String sourceField = item.getString("sourceField");
- String targetField = item.getString("targetField");
- if (!MetadataHelper.checkAndWarnField(sourceEntity, sourceField)
- || !MetadataHelper.checkAndWarnField(targetEntity, targetField)) {
- continue;
- }
-
- // 直接利用 SQL 函数计算结果
- String calcMode = item.getString("calcMode");
- String calcField = "COUNT".equalsIgnoreCase(calcMode) ? sourceEntity.getPrimaryField().getName() : sourceField;
-
- String sql = String.format("select %s(%s) from %s where %s = ?",
- calcMode, calcField, sourceEntity.getName(), followSourceField);
- if (dataFilterWhere != null) {
- sql += " and " + dataFilterWhere;
- }
+ Record targetRecord = EntityHelper.forUpdate(targetRecordId, UserService.SYSTEM_USER, false);
+ buildTargetRecord(targetRecord, dataFilterSql);
- Object[] result = Application.createQueryNoFilter(sql).setParameter(1, targetRecordId).unique();
- double calcValue = result == null || result[0] == null ? 0d : ObjectUtils.toDouble(result[0]);
-
- DisplayType dt = EasyMeta.getDisplayType(targetEntity.getField(targetField));
- if (dt == DisplayType.NUMBER) {
- targetRecord.setInt(targetField, (int) calcValue);
- } else if (dt == DisplayType.DECIMAL) {
- targetRecord.setDouble(targetField, calcValue);
- }
- }
-
- if (targetRecord.getAvailableFieldIterator().hasNext()) {
+ // 不含 ID
+ if (targetRecord.getAvailableFields().size() > 1) {
if (allowNoPermissionUpdate) {
PrivilegesGuardInterceptor.setNoPermissionPassOnce(targetRecordId);
}
// 会关联触发下一触发器(如有)
- CALL_CHAIN_DEPTH.set(depth + 1);
+ TRIGGER_CHAIN_DEPTH.set(depth + 1);
Application.getEntityService(targetEntity.getEntityCode()).update(targetRecord);
}
}
+ /**
+ * @param record
+ * @param dataFilterSql
+ */
+ protected void buildTargetRecord(Record record, String dataFilterSql) {
+ JSONArray items = ((JSONObject) context.getActionContent()).getJSONArray("items");
+ for (Object o : items) {
+ JSONObject item = (JSONObject) o;
+ String targetField = item.getString("targetField");
+ if (!MetadataHelper.checkAndWarnField(targetEntity, targetField)) {
+ continue;
+ }
+
+ Object evalValue = new AggregationEvaluator(item, sourceEntity, followSourceField, dataFilterSql)
+ .eval(targetRecordId);
+ if (evalValue == null) {
+ continue;
+ }
+
+ DisplayType dt = EasyMeta.getDisplayType(targetEntity.getField(targetField));
+ if (dt == DisplayType.NUMBER) {
+ record.setLong(targetField, ObjectUtils.toLong(evalValue));
+ } else if (dt == DisplayType.DECIMAL) {
+ record.setDouble(targetField, ObjectUtils.toDouble(evalValue));
+ }
+ }
+ }
+
@Override
public void prepare(OperatingContext operatingContext) throws TriggerException {
if (sourceEntity != null) { // 已经初始化
@@ -180,22 +194,29 @@ public void prepare(OperatingContext operatingContext) throws TriggerException {
this.sourceEntity = context.getSourceEntity();
this.targetEntity = MetadataHelper.getEntity(targetFieldEntity[1]);
- this.followSourceField = targetFieldEntity[0];
- if (!sourceEntity.containsField(followSourceField)) {
- return;
- }
- // 找到主记录
- Object[] o = Application.getQueryFactory().uniqueNoFilter(
- context.getSourceRecord(), followSourceField, followSourceField + "." + EntityHelper.OwningUser);
- // o[1] 为空说明记录不存在
- if (o != null && o[0] != null && o[1] != null) {
- this.targetRecordId = (ID) o[0];
+ // 自己
+ if (SOURCE_SELF.equalsIgnoreCase(targetFieldEntity[0])) {
+ this.followSourceField = sourceEntity.getPrimaryField().getName();
+ this.targetRecordId = context.getSourceRecord();
+ } else {
+ this.followSourceField = targetFieldEntity[0];
+ if (!sourceEntity.containsField(followSourceField)) {
+ return;
+ }
+
+ // 找到主记录
+ Object[] o = Application.getQueryFactory().uniqueNoFilter(
+ context.getSourceRecord(), followSourceField, followSourceField + "." + EntityHelper.OwningUser);
+ // o[1] 为空说明记录不存在
+ if (o != null && o[0] != null && o[1] != null) {
+ this.targetRecordId = (ID) o[0];
+ }
}
}
@Override
public void clean() {
- CALL_CHAIN_DEPTH.remove();
+ TRIGGER_CHAIN_DEPTH.remove();
}
}
diff --git a/src/main/java/com/rebuild/server/business/trigger/impl/FieldWriteback.java b/src/main/java/com/rebuild/server/business/trigger/impl/FieldWriteback.java
new file mode 100644
index 000000000..dd7ef9f0c
--- /dev/null
+++ b/src/main/java/com/rebuild/server/business/trigger/impl/FieldWriteback.java
@@ -0,0 +1,90 @@
+/*
+rebuild - Building your business-systems freely.
+Copyright (C) 2020 devezhao
+
+rebuild is dual-licensed under commercial and open source licenses (GPLv3).
+For more information, please see
+*/
+
+package com.rebuild.server.business.trigger.impl;
+
+import cn.devezhao.persist4j.Field;
+import cn.devezhao.persist4j.Record;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.rebuild.server.Application;
+import com.rebuild.server.business.trigger.ActionContext;
+import com.rebuild.server.business.trigger.ActionType;
+import com.rebuild.server.metadata.MetadataHelper;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 数据回填
+ *
+ * @author devezhao
+ * @since 2020/2/7
+ *
+ * @see com.rebuild.server.configuration.AutoFillinManager
+ */
+public class FieldWriteback extends FieldAggregation {
+
+ /**
+ * @param context
+ */
+ public FieldWriteback(ActionContext context) {
+ super(context, Boolean.TRUE, 5);
+ }
+
+ @Override
+ public ActionType getType() {
+ return ActionType.FIELDWRITEBACK;
+ }
+
+ @Override
+ protected void buildTargetRecord(Record record, String dataFilterSql) {
+ JSONArray items = ((JSONObject) context.getActionContent()).getJSONArray("items");
+ Map t2sMap = new HashMap<>();
+ for (Object o : items) {
+ JSONObject item = (JSONObject) o;
+ String targetField = item.getString("targetField");
+ String sourceField = item.getString("sourceField");
+ if (!MetadataHelper.checkAndWarnField(targetEntity, targetField)
+ || MetadataHelper.getLastJoinField(sourceEntity, sourceField) == null) {
+ continue;
+ }
+ t2sMap.put(targetField, sourceField);
+ }
+
+ if (t2sMap.isEmpty()) {
+ return;
+ }
+
+ String sql = String.format("select %s from %s where %s = ?",
+ StringUtils.join(t2sMap.values(), ","), sourceEntity.getName(), followSourceField);
+
+ final Record o = Application.createQueryNoFilter(sql)
+ .setParameter(1, targetRecordId)
+ .record();
+ if (o == null) {
+ return;
+ }
+
+ for (Map.Entry e : t2sMap.entrySet()) {
+ Object value = o.getObjectValue(e.getValue());
+ // NOTE 忽略空值
+ if (value == null) {
+ continue;
+ }
+
+ Field sourceField = MetadataHelper.getLastJoinField(sourceEntity, e.getValue());
+ Field targetField = targetEntity.getField(e.getKey());
+ Object newValue = new CompatibleValueConversion(sourceField, targetField).conversion(value);
+ if (newValue != null) {
+ record.setObjectValue(targetField.getName(), newValue);
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/rebuild/server/business/trigger/impl/SendNotification.java b/src/main/java/com/rebuild/server/business/trigger/impl/SendNotification.java
index f46f8767f..7d16cd949 100644
--- a/src/main/java/com/rebuild/server/business/trigger/impl/SendNotification.java
+++ b/src/main/java/com/rebuild/server/business/trigger/impl/SendNotification.java
@@ -18,6 +18,8 @@
package com.rebuild.server.business.trigger.impl;
+import cn.devezhao.persist4j.Entity;
+import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@@ -26,14 +28,22 @@
import com.rebuild.server.business.trigger.ActionType;
import com.rebuild.server.business.trigger.TriggerAction;
import com.rebuild.server.business.trigger.TriggerException;
+import com.rebuild.server.configuration.portals.FieldValueWrapper;
+import com.rebuild.server.helper.SMSender;
+import com.rebuild.server.metadata.MetadataHelper;
import com.rebuild.server.service.OperatingContext;
import com.rebuild.server.service.bizz.UserHelper;
import com.rebuild.server.service.notification.Message;
import com.rebuild.server.service.notification.MessageBuilder;
+import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* @author devezhao zhaofang123@gmail.com
@@ -41,6 +51,14 @@
*/
public class SendNotification implements TriggerAction {
+ // 内部消息
+ @SuppressWarnings("unused")
+ private static final int TYPE_NOTIFICATION = 1;
+ // 邮件
+ private static final int TYPE_MAIL = 2;
+ // 短信
+ private static final int TYPE_SMS = 3;
+
final private ActionContext context;
public SendNotification(ActionContext context) {
@@ -70,28 +88,84 @@ public void execute(OperatingContext operatingContext) {
if (toUsers.isEmpty()) {
return;
}
-
+
String message = content.getString("content");
message = formatMessage(message, context.getSourceRecord());
+
+ final int type = content.getIntValue("type");
+ final String title = StringUtils.defaultIfBlank(content.getString("title"), "你有一条新通知");
+
for (ID user : toUsers) {
- Message m = MessageBuilder.createMessage(user, message, context.getSourceRecord());
- Application.getNotifications().send(m);
+ if (type == TYPE_MAIL) {
+ if (!SMSender.availableMail()) break;
+
+ String emailAddr = Application.getUserStore().getUser(user).getEmail();
+ if (emailAddr != null) {
+ SMSender.sendMail(emailAddr, title, message);
+ }
+
+ } else if (type == TYPE_SMS) {
+ // TODO 发送短信(暂无手机字段)
+
+ } else {
+ Message m = MessageBuilder.createMessage(user, message, context.getSourceRecord());
+ Application.getNotifications().send(m);
+ }
}
}
@Override
public void prepare(OperatingContext operatingContext) throws TriggerException {
- // Nothings
+ // NOOP
}
+ private static final Pattern PATT_FIELD = Pattern.compile("\\{([0-9a-zA-Z._]+)}");
/**
* @param message
* @param recordId
* @return
*/
- private String formatMessage(String message, ID recordId) {
- // TODO 处理变量
-// return message + " @" + recordId;
+ protected String formatMessage(String message, ID recordId) {
+ Map vars = null;
+ if (recordId != null) {
+ Entity entity = MetadataHelper.getEntity(recordId.getEntityCode());
+ vars = new HashMap<>();
+
+ Matcher m = PATT_FIELD.matcher(message);
+ while (m.find()) {
+ String field = m.group(1);
+ if (MetadataHelper.getLastJoinField(entity, field) == null) {
+ continue;
+ }
+ vars.put(field, null);
+ }
+
+ if (!vars.isEmpty()) {
+ String sql = String.format("select %s from %s where %s = ?",
+ StringUtils.join(vars.keySet(), ","), entity.getName(), entity.getPrimaryField().getName());
+
+ Record o = Application.createQueryNoFilter(sql)
+ .setParameter(1, recordId)
+ .record();
+ if (o != null) {
+ for (String field : vars.keySet()) {
+ Object value = o.getObjectValue(field);
+ value = FieldValueWrapper.instance.wrapFieldValue(
+ value, MetadataHelper.getLastJoinField(entity, field), true);
+ if (value != null) {
+ vars.put(field, value.toString());
+ }
+ }
+ }
+ }
+ }
+
+ if (vars != null) {
+ for (Map.Entry e : vars.entrySet()) {
+ message = message.replaceAll(
+ "\\{" + e.getKey() + "}", StringUtils.defaultIfBlank(e.getValue(), StringUtils.EMPTY));
+ }
+ }
return message;
}
diff --git a/src/main/java/com/rebuild/server/configuration/AutoFillinManager.java b/src/main/java/com/rebuild/server/configuration/AutoFillinManager.java
index 5822336bf..52a8207a4 100644
--- a/src/main/java/com/rebuild/server/configuration/AutoFillinManager.java
+++ b/src/main/java/com/rebuild/server/configuration/AutoFillinManager.java
@@ -22,13 +22,13 @@
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
+import cn.devezhao.persist4j.engine.NullValue;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.server.Application;
-import com.rebuild.server.configuration.portals.FieldValueWrapper;
+import com.rebuild.server.business.trigger.impl.CompatibleValueConversion;
import com.rebuild.server.metadata.MetadataHelper;
-import com.rebuild.server.metadata.entity.DisplayType;
import com.rebuild.server.metadata.entity.EasyMeta;
import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang.StringUtils;
@@ -57,6 +57,11 @@ private AutoFillinManager() { }
* @return
*/
public JSONArray getFillinValue(Field field, ID source) {
+ // @see field-edit.jsp 内建字段无配置
+ if (EasyMeta.valueOf(field).isBuiltin()) {
+ return JSONUtils.EMPTY_ARRAY;
+ }
+
final List config = getConfig(field);
if (config.isEmpty()) {
return JSONUtils.EMPTY_ARRAY;
@@ -101,7 +106,7 @@ public JSONArray getFillinValue(Field field, ID source) {
}
// NOTE 忽略空值
- if (value == null || StringUtils.isBlank(value.toString())) {
+ if (value == null || NullValue.is(value) || StringUtils.isBlank(value.toString())) {
continue;
}
@@ -119,40 +124,10 @@ public JSONArray getFillinValue(Field field, ID source) {
* @param target
* @param value
* @return
+ * @see CompatibleValueConversion
*/
protected Object conversionCompatibleValue(Field source, Field target, Object value) {
- DisplayType sourceType = EasyMeta.getDisplayType(source);
- DisplayType targetType = EasyMeta.getDisplayType(target);
- boolean is2Text = targetType == DisplayType.TEXT || targetType == DisplayType.NTEXT;
- EasyMeta sourceField = EasyMeta.valueOf(source);
-
- Object compatibleValue = null;
- if (sourceType == DisplayType.REFERENCE) {
- String idLabel = FieldValueWrapper.getLabelNotry((ID) value);
- if (is2Text) {
- compatibleValue = idLabel;
- } else {
- compatibleValue = FieldValueWrapper.wrapMixValue((ID) value, idLabel);
- }
- } else if (sourceType == DisplayType.CLASSIFICATION) {
- compatibleValue = FieldValueWrapper.instance.wrapFieldValue(value, sourceField, is2Text);
- } else if (sourceType == DisplayType.PICKLIST || sourceType == DisplayType.STATE) {
- if (is2Text) {
- compatibleValue = FieldValueWrapper.instance.wrapFieldValue(value, sourceField);
- } else {
- compatibleValue = value;
- }
- } else if (sourceType == DisplayType.DATETIME && targetType == DisplayType.DATE) {
- String datetime = FieldValueWrapper.instance.wrapDatetime(value, sourceField);
- compatibleValue = datetime.split(" ")[0];
- } else if (sourceType == DisplayType.DATE && targetType == DisplayType.DATETIME) {
- String date = FieldValueWrapper.instance.wrapDate(value, sourceField);
- compatibleValue = date + " 00:00:00";
- } else {
- compatibleValue = FieldValueWrapper.instance.wrapFieldValue(value, sourceField);
- }
-
- return compatibleValue;
+ return new CompatibleValueConversion(source, target).conversion(value, true);
}
/**
diff --git a/src/main/java/com/rebuild/server/configuration/RebuildApiManager.java b/src/main/java/com/rebuild/server/configuration/RebuildApiManager.java
index 1dd4ce2d8..50a8f2c48 100644
--- a/src/main/java/com/rebuild/server/configuration/RebuildApiManager.java
+++ b/src/main/java/com/rebuild/server/configuration/RebuildApiManager.java
@@ -60,8 +60,8 @@ public ConfigEntry getApp(String appid) {
}
@Override
- public void clean(String cacheKey) {
- final String cKey = "RebuildApiManager-" + cacheKey;
+ public void clean(String appid) {
+ final String cKey = "RebuildApiManager-" + appid;
Application.getCommonCache().evict(cKey);
}
}
diff --git a/src/main/java/com/rebuild/server/configuration/RobotTriggerManager.java b/src/main/java/com/rebuild/server/configuration/RobotTriggerManager.java
index a7e817cc6..30e573762 100644
--- a/src/main/java/com/rebuild/server/configuration/RobotTriggerManager.java
+++ b/src/main/java/com/rebuild/server/configuration/RobotTriggerManager.java
@@ -119,7 +119,7 @@ private boolean allowedWhen(ConfigEntry entry, TriggerWhen... when) {
* @param record
* @return
*/
- public boolean isFiltered(JSONObject whenFilter, ID record) {
+ private boolean isFiltered(JSONObject whenFilter, ID record) {
if (whenFilter == null || whenFilter.isEmpty()) {
return false;
}
@@ -195,8 +195,9 @@ public Set getAutoReadonlyFields(String entity) {
*/
private Map> initAutoReadonlyFields() {
Object[][] array = Application.createQueryNoFilter(
- "select actionContent from RobotTriggerConfig where actionType = ? and isDisabled = 'F'")
+ "select actionContent from RobotTriggerConfig where (actionType = ? or actionType = ?) and isDisabled = 'F'")
.setParameter(1, ActionType.FIELDAGGREGATION.name())
+ .setParameter(2, ActionType.FIELDWRITEBACK.name())
.array();
CaseInsensitiveMap> fieldsMap = new CaseInsensitiveMap<>();
diff --git a/src/main/java/com/rebuild/server/configuration/portals/AdvFilterManager.java b/src/main/java/com/rebuild/server/configuration/portals/AdvFilterManager.java
index 5d9c83992..a3d9f0795 100644
--- a/src/main/java/com/rebuild/server/configuration/portals/AdvFilterManager.java
+++ b/src/main/java/com/rebuild/server/configuration/portals/AdvFilterManager.java
@@ -46,8 +46,8 @@ protected String getConfigEntity() {
}
@Override
- protected String getFieldsForConfig() {
- return super.getFieldsForConfig() + ",filterName";
+ protected String getConfigFields() {
+ return super.getConfigFields() + ",filterName";
}
/**
diff --git a/src/main/java/com/rebuild/server/configuration/portals/BaseLayoutManager.java b/src/main/java/com/rebuild/server/configuration/portals/BaseLayoutManager.java
index 52d4ba68a..72ff4f70b 100644
--- a/src/main/java/com/rebuild/server/configuration/portals/BaseLayoutManager.java
+++ b/src/main/java/com/rebuild/server/configuration/portals/BaseLayoutManager.java
@@ -73,11 +73,21 @@ public ConfigEntry getLayoutOfDatalist(ID user, String entity) {
}
/**
+ * @param user
+ * @return
+ */
+ public ConfigEntry getLayoutOfNav(ID user) {
+ return getLayout(user, null, TYPE_NAV);
+ }
+
+ /**
+ * 列表页 SIDE 图表
+ *
* @param user
* @param entity
* @return
*/
- public ConfigEntry getWidgetOfCharts(ID user, String entity) {
+ public ConfigEntry getWidgetCharts(ID user, String entity) {
ConfigEntry e = getLayout(user, entity, TYPE_WCHARTS);
if (e == null) {
return null;
@@ -90,14 +100,6 @@ public ConfigEntry getWidgetOfCharts(ID user, String entity) {
.set("shareTo", null);
}
- /**
- * @param user
- * @return
- */
- public ConfigEntry getLayoutOfNav(ID user) {
- return getLayout(user, null, TYPE_NAV);
- }
-
/**
* @param user
* @param belongEntity
@@ -132,19 +134,18 @@ public ConfigEntry getLayoutById(ID cfgid) {
}
/**
- * @param cached
+ * @param uses
* @param cfgid
* @return
*/
- private ConfigEntry findEntry(Object[][] cached, ID cfgid) {
- for (Object[] c : cached) {
- if (!c[0].equals(cfgid)) {
- continue;
+ protected ConfigEntry findEntry(Object[][] uses, ID cfgid) {
+ for (Object[] c : uses) {
+ if (c[0].equals(cfgid)) {
+ return new ConfigEntry()
+ .set("id", c[0])
+ .set("shareTo", c[1])
+ .set("config", JSON.parse((String) c[3]));
}
- return new ConfigEntry()
- .set("id", c[0])
- .set("shareTo", c[1])
- .set("config", JSON.parse((String) c[3]));
}
return null;
}
diff --git a/src/main/java/com/rebuild/server/configuration/portals/DashboardManager.java b/src/main/java/com/rebuild/server/configuration/portals/DashboardManager.java
index 80cda0b68..88c107d50 100644
--- a/src/main/java/com/rebuild/server/configuration/portals/DashboardManager.java
+++ b/src/main/java/com/rebuild/server/configuration/portals/DashboardManager.java
@@ -28,6 +28,9 @@
import com.rebuild.server.service.configuration.DashboardConfigService;
import com.rebuild.utils.JSONUtils;
+import java.util.Arrays;
+import java.util.Comparator;
+
/**
* 首页仪表盘
*
@@ -45,8 +48,8 @@ protected String getConfigEntity() {
}
@Override
- protected String getFieldsForConfig() {
- return super.getFieldsForConfig() + ",title";
+ protected String getConfigFields() {
+ return super.getConfigFields() + ",title";
}
/**
@@ -75,7 +78,7 @@ public JSON getDashList(ID user) {
canUses[i][2] = isSelf(user, (ID) canUses[i][2]);
}
- sort(canUses, 4);
+ Arrays.sort(canUses, Comparator.comparing(o -> o[4].toString()));
return (JSON) JSON.toJSON(canUses);
}
diff --git a/src/main/java/com/rebuild/server/configuration/portals/DataListManager.java b/src/main/java/com/rebuild/server/configuration/portals/DataListManager.java
index 1aa6d73f9..0dfc7cc47 100644
--- a/src/main/java/com/rebuild/server/configuration/portals/DataListManager.java
+++ b/src/main/java/com/rebuild/server/configuration/portals/DataListManager.java
@@ -153,4 +153,20 @@ public Map formatField(Field field, Field parent) {
new String[] { "field", "label", "type" },
new Object[] { parentField + easyField.getName(), parentLabel + easyField.getLabel(), easyField.getDisplayType(false) });
}
+
+ /**
+ * 获取可用列显示ID
+ *
+ * @param entity
+ * @param user
+ * @return
+ */
+ public ID[] getUsesDataListId(String entity, ID user) {
+ Object[][] uses = getUsesConfig(user, entity, TYPE_DATALIST);
+ List array = new ArrayList<>();
+ for (Object[] c : uses) {
+ array.add((ID) c[0]);
+ }
+ return array.toArray(new ID[0]);
+ }
}
diff --git a/src/main/java/com/rebuild/server/configuration/portals/FieldValueWrapper.java b/src/main/java/com/rebuild/server/configuration/portals/FieldValueWrapper.java
index fd31c8c0e..e075fb6d7 100644
--- a/src/main/java/com/rebuild/server/configuration/portals/FieldValueWrapper.java
+++ b/src/main/java/com/rebuild/server/configuration/portals/FieldValueWrapper.java
@@ -86,7 +86,7 @@ public Object wrapFieldValue(Object value, EasyMeta field, boolean unpackMix) {
value = wrapFieldValue(value, field);
if (unpackMix && value != null) {
DisplayType dt = field.getDisplayType();
- if (dt == DisplayType.CLASSIFICATION || dt == DisplayType.REFERENCE) {
+ if (value instanceof JSON && (dt == DisplayType.CLASSIFICATION || dt == DisplayType.REFERENCE)) {
return ((JSONObject) value).getString("text");
} else if (dt == DisplayType.FILE || dt == DisplayType.IMAGE) {
return value.toString();
@@ -195,11 +195,14 @@ public String wrapDecimal(Object value, EasyMeta field) {
* @see #wrapMixValue(ID, String)
*/
public JSON wrapReference(Object value, EasyMeta field) {
- String text = ((ID) value).getLabel();
+ Object text = ((ID) value).getLabelRaw();
if (text == null) {
- text = getLabelNotry((ID) value);
+ text = getLabelNotry((ID) value);
+ } else {
+ Field nameField = ((Field) field.getBaseMeta()).getReferenceEntity().getNameField();
+ text = instance.wrapFieldValue(text, nameField, true);
}
- return wrapMixValue((ID) value, text);
+ return wrapMixValue((ID) value, text == null ? null : text.toString());
}
/**
@@ -320,9 +323,27 @@ protected Object wrapSpecialField(Object value, EasyMeta field) {
* @throws NoRecordFoundException If no record found
*/
public static String getLabel(ID id, String defaultValue) throws NoRecordFoundException {
+ if (id == null) {
+ throw new NoRecordFoundException("[id] must not be null");
+ }
+
Entity entity = MetadataHelper.getEntity(id.getEntityCode());
- Field nameField = MetadataHelper.getNameField(entity);
+ if (id.getEntityCode() == EntityHelper.ClassificationData) {
+ String hasValue = ClassificationManager.instance.getFullName(id);
+ if (hasValue == null) {
+ throw new NoRecordFoundException("No ClassificationData found by ID : " + id);
+ }
+ return hasValue;
+ } else if (id.getEntityCode() == EntityHelper.PickList) {
+ String hasValue = PickListManager.instance.getLabel(id);
+ if (hasValue == null) {
+ throw new NoRecordFoundException("No PickList found by ID : " + id);
+ }
+ return hasValue;
+ }
+
+ Field nameField = MetadataHelper.getNameField(entity);
Object[] nameValue = Application.getQueryFactory().uniqueNoFilter(id, nameField.getName());
if (nameValue == null) {
throw new NoRecordFoundException("No record found by ID : " + id);
@@ -354,7 +375,7 @@ public static String getLabel(ID id) throws NoRecordFoundException {
*/
public static String getLabelNotry(ID id) {
try {
- return FieldValueWrapper.getLabel(id);
+ return getLabel(id);
} catch (MetadataException | NoRecordFoundException ex) {
return MISS_REF_PLACE;
}
diff --git a/src/main/java/com/rebuild/server/configuration/portals/FormsBuilder.java b/src/main/java/com/rebuild/server/configuration/portals/FormsBuilder.java
index 461808037..96bf8f65c 100644
--- a/src/main/java/com/rebuild/server/configuration/portals/FormsBuilder.java
+++ b/src/main/java/com/rebuild/server/configuration/portals/FormsBuilder.java
@@ -114,14 +114,12 @@ public JSON buildView(String entity, ID user, ID record) {
* @param viewMode 视图模式
* @return
*/
- protected JSON buildModel(String entity, ID user, ID record, boolean viewMode) {
+ private JSON buildModel(String entity, ID user, ID record, boolean viewMode) {
Assert.notNull(entity, "[entity] not be null");
Assert.notNull(user, "[user] not be null");
final Entity entityMeta = MetadataHelper.getEntity(entity);
- final User currentUser = Application.getUserStore().getUser(user);
- final Date now = CalendarUtils.now();
-
+
// 明细实体
final Entity masterEntity = entityMeta.getMasterEntity();
// 审批流程(状态)
@@ -197,39 +195,124 @@ else if (viewMode) {
}
}
- // 自动只读字段
- final Set autoReadonlyByTriggers = RobotTriggerManager.instance.getAutoReadonlyFields(entity);
+ // 触发器自动只读
+ Set roViaTriggers = RobotTriggerManager.instance.getAutoReadonlyFields(entity);
+ for (Object o : elements) {
+ JSONObject field = (JSONObject) o;
+ if (roViaTriggers.contains(field.getString("field"))) {
+ field.put("readonly", true);
+ }
+ }
+
+ buildModelElements(elements, entityMeta, data, user);
+
+ if (elements.isEmpty()) {
+ return formatModelError("此表单布局尚未配置,请配置后使用");
+ }
+
+ // 主/明细实体处理
+ if (entityMeta.getMasterEntity() != null) {
+ model.set("isSlave", true);
+ } else if (entityMeta.getSlaveEntity() != null) {
+ model.set("isMaster", true);
+ model.set("slaveMeta", EasyMeta.getEntityShow(entityMeta.getSlaveEntity()));
+ }
+ if (data != null && data.hasValue(EntityHelper.ModifiedOn)) {
+ model.set("lastModified", data.getDate(EntityHelper.ModifiedOn).getTime());
+ }
+
+ if (approvalState != null) {
+ model.set("hadApproval", approvalState.getState());
+ }
+
+ model.set("id", null); // Clean form's ID of config
+ return model.toJSON();
+ }
+
+ /**
+ * @param error
+ * @return
+ */
+ private JSONObject formatModelError(String error) {
+ JSONObject cfg = new JSONObject();
+ cfg.put("error", error);
+ return cfg;
+ }
+
+ /**
+ * @param entity
+ * @param recordId
+ * @return
+ *
+ * @see RobotApprovalManager#hadApproval(Entity, ID)
+ */
+ private ApprovalState getHadApproval(Entity entity, ID recordId) {
+ Entity masterEntity = entity.getMasterEntity();
+ if (masterEntity == null) {
+ return RobotApprovalManager.instance.hadApproval(entity, recordId);
+ }
+
+ ID masterRecordId = MASTERID4NEWSLAVE.get();
+ if (masterRecordId == null) {
+ Field stm = MetadataHelper.getSlaveToMasterField(entity);
+ String sql = String.format("select %s from %s where %s = ?",
+ Objects.requireNonNull(stm).getName(), entity.getName(), entity.getPrimaryField().getName());
+ Object[] o = Application.createQueryNoFilter(sql).setParameter(1, recordId).unique();
+ if (o == null) {
+ return null;
+ }
+ masterRecordId = (ID) o[0];
+ }
+ return RobotApprovalManager.instance.hadApproval(masterEntity, masterRecordId);
+ }
+
+ /**
+ * 构建表单元素
+ *
+ * @param elements
+ * @param entity
+ * @param data
+ * @param user
+ */
+ public void buildModelElements(JSONArray elements, Entity entity, Record data, ID user) {
+ final User currentUser = Application.getUserStore().getUser(user);
+ final Date now = CalendarUtils.now();
+
// Check and clean
for (Iterator |