Better v3.0 (#506)

* N2NReferenceSupport

* better tips

* fix: triggers

* fix: AutoApproval trigger

* entity import

* Update @rbv

* fix: #504

* print size #505
This commit is contained in:
RB 2022-08-08 17:28:49 +08:00 committed by GitHub
parent 68c9cc505f
commit ead5219ece
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 442 additions and 277 deletions

View file

@ -17,9 +17,9 @@ git:
submodules: false submodules: false
before_script: before_script:
- mysql -e "CREATE DATABASE rebuild20 COLLATE utf8mb4_general_ci;" - mysql -e "CREATE DATABASE rebuild30 COLLATE utf8mb4_general_ci;"
- mysql -e "CREATE USER 'rebuild'@'127.0.0.1' IDENTIFIED BY 'rebuild'; GRANT ALL PRIVILEGES ON rebuild20.* TO 'rebuild'@'127.0.0.1'; FLUSH PRIVILEGES;" - mysql -e "CREATE USER 'rebuild'@'127.0.0.1' IDENTIFIED BY 'rebuild'; GRANT ALL PRIVILEGES ON rebuild30.* TO 'rebuild'@'127.0.0.1'; FLUSH PRIVILEGES;"
- mysql -D rebuild20 < src/main/resources/scripts/db-init.sql - mysql -D rebuild30 < src/main/resources/scripts/db-init.sql
# codecov # codecov
after_success: after_success:

2
@rbv

@ -1 +1 @@
Subproject commit 263dc325c7903bb3115ba30a9032cf182ee7e4f9 Subproject commit 5c05ac9fdc812ca7e218732dbaf553605d0ffb5a

View file

@ -10,7 +10,7 @@
</parent> </parent>
<groupId>com.rebuild</groupId> <groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId> <artifactId>rebuild</artifactId>
<version>2.10.0-dev</version> <version>3.0.0-dev</version>
<name>rebuild</name> <name>rebuild</name>
<description>Building your business-systems freely!</description> <description>Building your business-systems freely!</description>
<!-- UNCOMMENT USE TOMCAT --> <!-- UNCOMMENT USE TOMCAT -->

View file

@ -65,11 +65,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/** /**
* Rebuild Version * Rebuild Version
*/ */
public static final String VER = "2.10.0-dev"; public static final String VER = "3.0.0-dev";
/** /**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2} * Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{1}
*/ */
public static final int BUILD = 210000; public static final int BUILD = 300000;
static { static {
// Driver for DB // Driver for DB

View file

@ -107,7 +107,7 @@ public class BootEnvironmentPostProcessor implements EnvironmentPostProcessor, I
// `application-bean.xml` 占位符必填 // `application-bean.xml` 占位符必填
if (dbUrl == null) { if (dbUrl == null) {
dbUrl = "jdbc:mysql://127.0.0.1:3306/rebuild20?characterEncoding=UTF8"; dbUrl = "jdbc:mysql://127.0.0.1:3306/rebuild30?characterEncoding=UTF8";
confPs.put("db.url", dbUrl); confPs.put("db.url", dbUrl);
} }
if (env.getProperty("db.user") == null) confPs.put("db.user", "rebuild"); if (env.getProperty("db.user") == null) confPs.put("db.user", "rebuild");

View file

@ -29,22 +29,20 @@ import java.util.Comparator;
import java.util.List; import java.util.List;
/** /**
* 分类数据 * 列表字段分类数据
* *
* @author ZHAO * @author ZHAO
* @since 07/23/2022 * @since 07/23/2022
*/ */
public class DataListClass { public class DataListCategory {
/** /**
*
*
* @param entity * @param entity
* @param user * @param user
* @return * @return
*/ */
public static JSON datas(Entity entity, ID user) { public static JSON datas(Entity entity, ID user) {
final Field classField = getFieldOfClass(entity); final Field classField = getFieldOfCategory(entity);
if (classField == null) return null; if (classField == null) return null;
final String ckey = String.format("DLC1.%s.%s", entity.getName(), classField.getName()); final String ckey = String.format("DLC1.%s.%s", entity.getName(), classField.getName());
@ -107,9 +105,9 @@ public class DataListClass {
* @param entity * @param entity
* @return * @return
*/ */
public static Field getFieldOfClass(Entity entity) { public static Field getFieldOfCategory(Entity entity) {
String classField = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCLASS); String categoryField = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY);
if (StringUtils.isBlank(classField) || !entity.containsField(classField)) return null; if (StringUtils.isBlank(categoryField) || !entity.containsField(categoryField)) return null;
return entity.getField(classField); return entity.getField(categoryField);
} }
} }

View file

@ -11,6 +11,7 @@ import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.PersistManagerFactory; import cn.devezhao.persist4j.PersistManagerFactory;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@ -18,8 +19,6 @@ import com.rebuild.core.Application;
import com.rebuild.core.UserContextHolder; import com.rebuild.core.UserContextHolder;
import com.rebuild.core.configuration.BaseConfigurationService; import com.rebuild.core.configuration.BaseConfigurationService;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.privileges.AdminGuard; import com.rebuild.core.privileges.AdminGuard;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -89,7 +88,7 @@ public class PickListService extends BaseConfigurationService implements AdminGu
// MultiSelect 专用 // MultiSelect 专用
long nextMaskValue = 0; long nextMaskValue = 0;
if (EasyMetaFactory.getDisplayType(field) == DisplayType.MULTISELECT) { if (field.getType() == FieldType.LONG) {
Object[] max = Application.createQueryNoFilter( Object[] max = Application.createQueryNoFilter(
"select max(maskValue) from PickList where belongEntity = ? and belongField = ?") "select max(maskValue) from PickList where belongEntity = ? and belongField = ?")
.setParameter(1, field.getOwnEntity().getName()) .setParameter(1, field.getOwnEntity().getName())

View file

@ -47,7 +47,7 @@ public class CopyEntity extends Entity2Schema {
// 导出 // 导出
JSONObject schemadata = (JSONObject) new MetaSchemaGenerator(sourceEntity).generate(); JSONObject schemadata = (JSONObject) new MetaSchemaGenerator(sourceEntity, false).generate();
String uniqueEntityName = clearConfig(schemadata, entityName); String uniqueEntityName = clearConfig(schemadata, entityName);
JSONObject detailSchema = schemadata.getJSONObject("detail"); JSONObject detailSchema = schemadata.getJSONObject("detail");
@ -74,10 +74,6 @@ public class CopyEntity extends Entity2Schema {
schema.remove(MetaSchemaGenerator.CFG_TRIGGERS); schema.remove(MetaSchemaGenerator.CFG_TRIGGERS);
schema.remove(MetaSchemaGenerator.CFG_FILTERS); schema.remove(MetaSchemaGenerator.CFG_FILTERS);
// 以下保留
// schema.remove(MetaSchemaGenerator.CFG_FILLINS);
// schema.remove(MetaSchemaGenerator.CFG_LAYOUTS);
String uniqueEntityName = toPinyinName(entityName); String uniqueEntityName = toPinyinName(entityName);
for (int i = 0; i < 5; i++) { for (int i = 0; i < 5; i++) {
if (MetadataHelper.containsEntity(uniqueEntityName)) { if (MetadataHelper.containsEntity(uniqueEntityName)) {

View file

@ -37,7 +37,7 @@ public class EasyEntityConfigProps {
/** /**
* 列表分类 * 列表分类
*/ */
public static final String ADV_LIST_SHOWCLASS = "advListShowClass"; public static final String ADV_LIST_SHOWCATEGORY = "advListShowCategory";
/** /**
* 列表查询面板 * 列表查询面板
*/ */

View file

@ -17,6 +17,7 @@ import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.UserContextHolder; import com.rebuild.core.UserContextHolder;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.service.CommonsService; import com.rebuild.core.service.CommonsService;
@ -26,7 +27,6 @@ import com.rebuild.core.support.i18n.Language;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation; import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -111,8 +111,9 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
// 跳过 // 跳过
ID skipGuardId; ID skipGuardId;
if ((skipGuardId = PrivilegesGuardContextHolder.getSkipGuardOnce()) != null) { if ((skipGuardId = PrivilegesGuardContextHolder.getSkipGuardOnce()) != null) {
log.info("Allow no permission({}) passed once : {}", if (!EntityHelper.isUnsavedId(skipGuardId)) {
action.getName(), ObjectUtils.defaultIfNull(recordId, skipGuardId)); log.info("Allow no permission({}) passed once : {}", action.getName(), skipGuardId);
}
return; return;
} }

View file

@ -38,6 +38,9 @@ import java.io.IOException;
*/ */
public class MetaSchemaGenerator { public class MetaSchemaGenerator {
// 保持 ID
public static final String KEEP_ID = "_id";
public static final String CFG_FILLINS = "fillins"; public static final String CFG_FILLINS = "fillins";
public static final String CFG_LAYOUTS = "layouts"; public static final String CFG_LAYOUTS = "layouts";
public static final String CFG_FILTERS = "filters"; public static final String CFG_FILTERS = "filters";
@ -46,12 +49,14 @@ public class MetaSchemaGenerator {
public static final String CFG_TRANSFORMS = "transforms"; public static final String CFG_TRANSFORMS = "transforms";
final private Entity mainEntity; final private Entity mainEntity;
final private boolean keepId;
/** /**
* @param entity * @param entity
*/ */
public MetaSchemaGenerator(Entity entity) { public MetaSchemaGenerator(Entity entity, boolean keepId) {
this.mainEntity = entity; this.mainEntity = entity;
this.keepId = keepId;
} }
/** /**
@ -109,12 +114,12 @@ public class MetaSchemaGenerator {
schemaEntity.put(CFG_FILTERS, performFilters(entity)); schemaEntity.put(CFG_FILTERS, performFilters(entity));
// 触发器 // 触发器
schemaEntity.put(CFG_TRIGGERS, performTriggers(entity)); schemaEntity.put(CFG_TRIGGERS, performTriggers(entity));
// 记录转换
schemaEntity.put(CFG_TRANSFORMS, performTransforms(entity));
if (!detail) { if (!detail) {
// 审批流程 // 审批流程
schemaEntity.put(CFG_APPROVALS, performApprovals(entity)); schemaEntity.put(CFG_APPROVALS, performApprovals(entity));
// 字段转换
schemaEntity.put(CFG_TRANSFORMS, performTransforms(entity));
} }
// TODO 报表模板? // TODO 报表模板?
@ -163,7 +168,8 @@ public class MetaSchemaGenerator {
JSONArray items = new JSONArray(); JSONArray items = new JSONArray();
for (ConfigBean e : entries) { for (ConfigBean e : entries) {
items.add(new Object[] { items.add(new Object[] {
e.getString("text"), e.getBoolean("default"), e.getLong("mask"), e.getString("color") e.getString("text"), e.getBoolean("default"), e.getLong("mask"),
e.getString("color"), this.keepId ? e.getID("id") : null
}); });
} }
return items; return items;
@ -267,7 +273,7 @@ public class MetaSchemaGenerator {
private JSON performTransforms(Entity entity) { private JSON performTransforms(Entity entity) {
Object[][] array = Application.createQueryNoFilter( Object[][] array = Application.createQueryNoFilter(
"select targetEntity,name,config from TransformConfig where belongEntity = ? and isDisabled = 'F'") "select targetEntity,name,config,configId from TransformConfig where belongEntity = ? and isDisabled = 'F'")
.setParameter(1, entity.getName()) .setParameter(1, entity.getName())
.array(); .array();
@ -276,9 +282,11 @@ public class MetaSchemaGenerator {
JSON mappingConfig = parseJSON(o[2]); JSON mappingConfig = parseJSON(o[2]);
if (mappingConfig == null) continue; if (mappingConfig == null) continue;
JSON config = JSONUtils.toJSONObject( JSONObject config = JSONUtils.toJSONObject(
new String[] { "targetEntity", "name", "config" }, new String[] { "targetEntity", "name", "config" },
new Object[] { o[0], o[1], mappingConfig }); new Object[] { o[0], o[1], mappingConfig });
if (this.keepId) config.put(KEEP_ID, o[3]);
transforms.add(config); transforms.add(config);
} }
return transforms; return transforms;

View file

@ -11,6 +11,7 @@ import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.engine.PersistManagerImpl;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
@ -29,8 +30,6 @@ import com.rebuild.core.metadata.impl.Field2Schema;
import com.rebuild.core.metadata.impl.MetadataModificationException; import com.rebuild.core.metadata.impl.MetadataModificationException;
import com.rebuild.core.privileges.UserService; import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.approval.RobotApprovalConfigService; import com.rebuild.core.service.approval.RobotApprovalConfigService;
import com.rebuild.core.service.trigger.ActionFactory;
import com.rebuild.core.service.trigger.ActionType;
import com.rebuild.core.service.trigger.RobotTriggerConfigService; import com.rebuild.core.service.trigger.RobotTriggerConfigService;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.task.HeavyTask; import com.rebuild.core.support.task.HeavyTask;
@ -42,6 +41,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.rebuild.core.rbstore.MetaSchemaGenerator.KEEP_ID;
/** /**
* 元数据模型导入 * 元数据模型导入
* *
@ -147,14 +148,32 @@ public class MetaschemaImporter extends HeavyTask<String> {
try { try {
for (Map.Entry<Field, JSONObject> e : picklistHolders.entrySet()) { for (Map.Entry<Field, JSONObject> e : picklistHolders.entrySet()) {
Field field = e.getKey(); Field field = e.getKey();
Application.getBean(PickListService.class).updateBatch( JSONObject config = e.getValue();
MetadataHelper.getField(field.getOwnEntity().getName(), field.getName()), e.getValue());
JSONArray options = config.getJSONArray("show");
for (Object o : options) {
String keepId = ((JSONObject) o).getString(KEEP_ID);
if (ID.isId(keepId)) {
Record c = EntityHelper.forNew(EntityHelper.PickList, UserService.SYSTEM_USER, true);
c.setString("belongEntity", field.getOwnEntity().getName());
c.setString("belongField", field.getName());
c.setString("text", "temp");
((PersistManagerImpl) Application.getPersistManagerFactory().createPersistManager())
.saveInternal(c, ID.valueOf(keepId));
((JSONObject) o).put("id", keepId);
} }
} finally {
if (sessionUser == null) UserContextHolder.clear();
} }
Application.getBean(PickListService.class).updateBatch(field, config);
}
} catch (Exception ex) {
log.warn("Importing PickList error : {}", ex.getLocalizedMessage());
}
setCompleted(100); setCompleted(100);
if (sessionUser == null) UserContextHolder.clear();
return entityName; return entityName;
} }
@ -325,10 +344,10 @@ public class MetaschemaImporter extends HeavyTask<String> {
if (item.size() > 2) { if (item.size() > 2) {
option.put("mask", item.getLongValue(2)); option.put("mask", item.getLongValue(2));
} }
// v2.10: Color
if (item.size() > 3) { // v2.10: Color, Id
option.put("color", item.getString(3)); if (item.size() > 3) option.put("color", item.getString(3));
} if (item.size() > 4) option.put(KEEP_ID, item.getString(4));
shown.add(option); shown.add(option);
} }
@ -372,20 +391,6 @@ public class MetaschemaImporter extends HeavyTask<String> {
config.put("metadata", JSONUtils.toJSONObject("entity", configEntity.getName())); config.put("metadata", JSONUtils.toJSONObject("entity", configEntity.getName()));
config.put("belongEntity", entity); config.put("belongEntity", entity);
String actionType = config.getString("actionType");
boolean available = false;
for (ActionType type : ActionFactory.getAvailableActions()) {
if (type.name().equalsIgnoreCase(actionType)) {
available = true;
break;
}
}
if (!available) {
log.warn("Trigger `{}` unavailable", actionType);
return;
}
Record record = new EntityRecordCreator(configEntity, config, getUser()) Record record = new EntityRecordCreator(configEntity, config, getUser())
.create(); .create();
Application.getBean(RobotTriggerConfigService.class).create(record); Application.getBean(RobotTriggerConfigService.class).create(record);
@ -408,6 +413,15 @@ public class MetaschemaImporter extends HeavyTask<String> {
Record record = new EntityRecordCreator(configEntity, config, getUser()) Record record = new EntityRecordCreator(configEntity, config, getUser())
.create(); .create();
String keepId = config.getString(KEEP_ID);
if (ID.isId(keepId)) {
((PersistManagerImpl) Application.getPersistManagerFactory().createPersistManager())
.saveInternal(record, ID.valueOf(keepId));
record = EntityHelper.forUpdate(record.getPrimary(), UserService.SYSTEM_USER, false);
Application.getBean(TransformConfigService.class).update(record);
} else {
Application.getBean(TransformConfigService.class).create(record); Application.getBean(TransformConfigService.class).create(record);
} }
} }
}

View file

@ -48,7 +48,7 @@ public class RBStore {
*/ */
public static JSON fetchMetaschema(String fileUri) { public static JSON fetchMetaschema(String fileUri) {
return fetchRemoteJson("metaschemas/" + return fetchRemoteJson("metaschemas/" +
StringUtils.defaultIfBlank(fileUri, "index-3.0.json")); StringUtils.defaultIfBlank(fileUri, "index-4.0.json"));
} }
/** /**

View file

@ -235,10 +235,10 @@ public abstract class ChartData extends SetUser implements ChartSpec {
sorts.add(dim.getSqlName() + " " + fs.toString().toLowerCase()); sorts.add(dim.getSqlName() + " " + fs.toString().toLowerCase());
} }
} }
// NOTE 优先维度排序 // // NOTE 优先维度排序
if (!sorts.isEmpty()) { // if (!sorts.isEmpty()) {
return String.join(", ", sorts); // return String.join(", ", sorts);
} // }
for (Numerical num : getNumericals()) { for (Numerical num : getNumericals()) {
FormatSort fs = num.getFormatSort(); FormatSort fs = num.getFormatSort();

View file

@ -35,7 +35,6 @@ import com.rebuild.core.service.notification.NotificationObserver;
import com.rebuild.core.service.query.QueryHelper; import com.rebuild.core.service.query.QueryHelper;
import com.rebuild.core.service.trigger.*; import com.rebuild.core.service.trigger.*;
import com.rebuild.core.service.trigger.impl.AutoApproval; import com.rebuild.core.service.trigger.impl.AutoApproval;
import com.rebuild.core.service.trigger.impl.GroupAggregation;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.core.support.task.TaskExecutors; import com.rebuild.core.support.task.TaskExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -51,7 +50,7 @@ import java.util.*;
* <br>- 会带有系统设置规则的执行 * <br>- 会带有系统设置规则的执行
* <br>- 会开启一个事务详见 <tt>application-bean.xml</tt> 配置 * <br>- 会开启一个事务详见 <tt>application-bean.xml</tt> 配置
* *
* 如有需要其他实体可根据自身业务继承并复写 * <p>如有需要其他实体可根据自身业务继承并复写</p>
* *
* FIXME 删除主记录时会关联删除明细记录持久层实现但明细记录不会触发业务规则 * FIXME 删除主记录时会关联删除明细记录持久层实现但明细记录不会触发业务规则
* *
@ -99,13 +98,14 @@ public class GeneralEntityService extends ObservableService implements EntitySer
// 含明细 // 含明细
final boolean hasDetails = details != null && !details.isEmpty(); final boolean hasDetails = details != null && !details.isEmpty();
boolean hasAutoApprovalForDetails = false; boolean lazyAutoApprovalForDetails = false;
if (hasDetails) { if (hasDetails) {
Entity de = record.getEntity().getDetailEntity(); TriggerAction[] hasTriggers = getSpecTriggers(
TriggerAction[] hasTriggers = de == null ? null record.getEntity().getDetailEntity(), null, TriggerWhen.APPROVED);
: RobotTriggerManager.instance.getActions(de, TriggerWhen.APPROVED); lazyAutoApprovalForDetails = hasTriggers.length > 0;
hasAutoApprovalForDetails = hasTriggers != null && hasTriggers.length > 0;
AutoApproval.setLazyAutoApproval(); // 自动审批延迟执行因为明细尚未保存好
if (lazyAutoApprovalForDetails) AutoApproval.setLazyAutoApproval();
} }
try { try {
@ -149,7 +149,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
return record; return record;
} finally { } finally {
if (hasAutoApprovalForDetails) { if (lazyAutoApprovalForDetails) {
AutoApproval.executeLazyAutoApproval(); AutoApproval.executeLazyAutoApproval();
} }
} }
@ -173,18 +173,9 @@ public class GeneralEntityService extends ObservableService implements EntitySer
record = super.update(record); record = super.update(record);
// 主记录修改时传导给明细若有以便触发分组聚合触发器 // 主记录修改时传导给明细若有以便触发分组聚合触发器
Entity de = record.getEntity().getDetailEntity(); TriggerAction[] hasTriggers = getSpecTriggers(record.getEntity().getDetailEntity(),
if (de != null) { ActionType.GROUPAGGREGATION, TriggerWhen.UPDATE);
TriggerAction[] hasTriggers = RobotTriggerManager.instance.getActions(de, TriggerWhen.UPDATE); if (hasTriggers.length > 0) {
boolean hasGroupAggregation = false;
for (TriggerAction ta : hasTriggers) {
if (ta instanceof GroupAggregation) {
hasGroupAggregation = true;
break;
}
}
if (hasGroupAggregation) {
RobotTriggerManual triggerManual = new RobotTriggerManual(); RobotTriggerManual triggerManual = new RobotTriggerManual();
ID opUser = UserService.SYSTEM_USER; ID opUser = UserService.SYSTEM_USER;
@ -194,7 +185,6 @@ public class GeneralEntityService extends ObservableService implements EntitySer
OperatingContext.create(opUser, BizzPermission.UPDATE, dUpdate, dUpdate)); OperatingContext.create(opUser, BizzPermission.UPDATE, dUpdate, dUpdate));
} }
} }
}
return record; return record;
} }
@ -723,16 +713,21 @@ public class GeneralEntityService extends ObservableService implements EntitySer
// 传导给明细若有 // 传导给明细若有
// FIXME 此时明细可能尚未做好变更例如新建自动审批 // FIXME 此时明细可能尚未做好变更例如新建自动审批
Entity de = approvalRecord.getEntity().getDetailEntity(); TriggerAction[] hasTriggers = getSpecTriggers(approvalRecord.getEntity().getDetailEntity(), null,
TriggerAction[] hasTriggers = de == null ? null : RobotTriggerManager.instance.getActions(de,
state == ApprovalState.APPROVED ? TriggerWhen.APPROVED : TriggerWhen.REVOKED); state == ApprovalState.APPROVED ? TriggerWhen.APPROVED : TriggerWhen.REVOKED);
if (hasTriggers != null && hasTriggers.length > 0) { if (hasTriggers.length > 0) {
for (ID did : QueryHelper.detailIdsNoFilter(recordId, 0)) { for (ID did : QueryHelper.detailIdsNoFilter(recordId, 0)) {
Record dAfter = EntityHelper.forUpdate(did, approvalUser, false); Record dAfter = EntityHelper.forUpdate(did, approvalUser, false);
if (state == ApprovalState.REVOKED) {
triggerManual.onRevoked(
OperatingContext.create(approvalUser, BizzPermission.UPDATE, null, dAfter));
} else {
triggerManual.onApproved( triggerManual.onApproved(
OperatingContext.create(approvalUser, BizzPermission.UPDATE, null, dAfter)); OperatingContext.create(approvalUser, BizzPermission.UPDATE, null, dAfter));
} }
} }
}
Record before = approvalRecord.clone(); Record before = approvalRecord.clone();
if (state == ApprovalState.REVOKED) { if (state == ApprovalState.REVOKED) {
@ -749,4 +744,18 @@ public class GeneralEntityService extends ObservableService implements EntitySer
new RevisionHistoryObserver().onApprovalManual( new RevisionHistoryObserver().onApprovalManual(
OperatingContext.create(approvalUser, InternalPermission.APPROVAL, before, approvalRecord)); OperatingContext.create(approvalUser, InternalPermission.APPROVAL, before, approvalRecord));
} }
// 获取指定的触发器
private TriggerAction[] getSpecTriggers(Entity entity, ActionType specType, TriggerWhen... when) {
if (entity == null || when.length == 0) return new TriggerAction[0];
TriggerAction[] triggers = RobotTriggerManager.instance.getActions(entity, when);
if (triggers.length == 0 || specType == null) return triggers;
List<TriggerAction> specTriggers = new ArrayList<>();
for (TriggerAction t : triggers) {
if (t.getType() == specType) specTriggers.add(t);
}
return specTriggers.toArray(new TriggerAction[0]);
}
} }

View file

@ -23,13 +23,12 @@ import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyField; import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder; import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.service.TransactionManual; import com.rebuild.core.service.general.GeneralEntityService;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
import com.rebuild.core.service.query.FilterRecordChecker; import com.rebuild.core.service.query.FilterRecordChecker;
import com.rebuild.core.support.SetUser; import com.rebuild.core.support.SetUser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.springframework.transaction.TransactionStatus;
import java.util.*; import java.util.*;
@ -49,6 +48,9 @@ public class RecordTransfomer extends SetUser {
final private JSONObject transConfig; final private JSONObject transConfig;
final private boolean skipGuard; final private boolean skipGuard;
// 所有新建的记录
private List<ID> newIds = new ArrayList<>();
/** /**
* @param trnasid * @param trnasid
*/ */
@ -70,6 +72,13 @@ public class RecordTransfomer extends SetUser {
this.skipGuard = skipGuard; this.skipGuard = skipGuard;
} }
/**
* @return
*/
public List<ID> getNewIds() {
return newIds;
}
/** /**
* @param sourceRecordId * @param sourceRecordId
* @return * @return
@ -95,61 +104,83 @@ public class RecordTransfomer extends SetUser {
* @see #checkFilter(ID) * @see #checkFilter(ID)
*/ */
public ID transform(ID sourceRecordId, ID mainId) { public ID transform(ID sourceRecordId, ID mainId) {
// 手动事务因为可能要转换多条记录 // 检查配置
TransactionStatus tx = TransactionManual.newTransaction(); Entity sourceEntity = MetadataHelper.getEntity(sourceRecordId.getEntityCode());
Entity sourceDetailEntity = null;
try {
// 主记录
Map<String, Object> map = null;
if (mainId != null) {
Field targetDtf = MetadataHelper.getDetailToMainField(targetEntity);
map = Collections.singletonMap(targetDtf.getName(), mainId);
}
//
JSONObject fieldsMapping = transConfig.getJSONObject("fieldsMapping"); JSONObject fieldsMapping = transConfig.getJSONObject("fieldsMapping");
if (fieldsMapping == null || fieldsMapping.isEmpty()) { if (fieldsMapping == null || fieldsMapping.isEmpty()) {
throw new ConfigurationException("Invalid config of transform : " + transConfig); throw new ConfigurationException("Invalid config of transform : " + transConfig);
} }
final Entity sourceEntity = MetadataHelper.getEntity(sourceRecordId.getEntityCode()); // 明细
final ID newId = saveRecord(sourceEntity, targetEntity, fieldsMapping, sourceRecordId, map);
if (newId == null) {
throw new ConfigurationException("Cannot transform record of main : " + transConfig);
}
// 明细记录如有
JSONObject fieldsMappingDetail = transConfig.getJSONObject("fieldsMappingDetail"); JSONObject fieldsMappingDetail = transConfig.getJSONObject("fieldsMappingDetail");
Object[][] sourceDetails = null;
if (fieldsMappingDetail != null && !fieldsMappingDetail.isEmpty()) { if (fieldsMappingDetail != null && !fieldsMappingDetail.isEmpty()) {
Entity sourceDetailEntity = sourceEntity.getDetailEntity(); sourceDetailEntity = sourceEntity.getDetailEntity();
Field sourceDtf = MetadataHelper.getDetailToMainField(sourceDetailEntity); Field sourceRefField;
// v2.10 1 > 2+明细
if (sourceDetailEntity == null) {
sourceDetailEntity = sourceEntity;
sourceRefField = sourceDetailEntity.getPrimaryField();
} else {
sourceRefField = MetadataHelper.getDetailToMainField(sourceDetailEntity);
}
String sql = String.format( String sql = String.format(
"select %s from %s where %s = '%s'", "select %s from %s where %s = '%s'",
sourceDetailEntity.getPrimaryField().getName(), sourceDetailEntity.getName(), sourceDtf.getName(), sourceRecordId); sourceDetailEntity.getPrimaryField().getName(), sourceDetailEntity.getName(), sourceRefField.getName(), sourceRecordId);
Object[][] details = Application.createQueryNoFilter(sql).array(); sourceDetails = Application.createQueryNoFilter(sql).array();
}
Map<String, Object> dvMap = null;
if (mainId != null) {
Field targetDtf = MetadataHelper.getDetailToMainField(targetEntity);
dvMap = Collections.singletonMap(targetDtf.getName(), mainId);
}
Record main = transformRecord(sourceEntity, targetEntity, fieldsMapping, sourceRecordId, dvMap);
ID newId;
// 有多条+明细
if (sourceDetails != null && sourceDetails.length > 0) {
Entity targetDetailEntity = targetEntity.getDetailEntity(); Entity targetDetailEntity = targetEntity.getDetailEntity();
if (details.length > 0) { List<Record> detailsList = new ArrayList<>();
Field targetDtf = MetadataHelper.getDetailToMainField(targetDetailEntity); for (Object[] d : sourceDetails) {
map = Collections.singletonMap(targetDtf.getName(), newId); detailsList.add(transformRecord(sourceDetailEntity, targetDetailEntity, fieldsMappingDetail, (ID) d[0], null));
} }
for (Object[] o : details) { newId = saveRecord(main, detailsList);
saveRecord(sourceDetailEntity, targetDetailEntity, fieldsMappingDetail, (ID) o[0], map); } else {
} newId = saveRecord(main, null);
} }
// 回填 // 回填
fillback(sourceRecordId, newId); fillback(sourceRecordId, newId);
TransactionManual.commit(tx);
return newId; return newId;
}
} catch (Exception ex) { private ID saveRecord(Record record, List<Record> detailsList) {
TransactionManual.rollback(tx); if (this.skipGuard) {
throw ex; PrivilegesGuardContextHolder.setSkipGuard(EntityHelper.UNSAVED_ID);
}
if (detailsList != null && !detailsList.isEmpty()) {
record.setObjectValue(GeneralEntityService.HAS_DETAILS, detailsList);
GeneralEntityServiceContextHolder.setRepeatedCheckMode(GeneralEntityServiceContextHolder.RCM_CHECK_DETAILS);
} else {
GeneralEntityServiceContextHolder.setRepeatedCheckMode(GeneralEntityServiceContextHolder.RCM_CHECK_ALL);
}
try {
record = Application.getEntityService(targetEntity.getEntityCode()).createOrUpdate(record);
return record.getPrimary();
} finally {
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
if (this.skipGuard) PrivilegesGuardContextHolder.getSkipGuardOnce();
} }
} }
@ -193,24 +224,19 @@ public class RecordTransfomer extends SetUser {
return true; return true;
} }
private ID saveRecord(
Entity sourceEntity, Entity targetEntity, JSONObject fieldsMapping,
ID sourceRecordId, Map<String, Object> defaultValue) {
return (ID) transformRecord(sourceEntity, targetEntity, fieldsMapping, sourceRecordId, defaultValue, true);
}
/** /**
* 转换
*
* @param sourceEntity * @param sourceEntity
* @param targetEntity * @param targetEntity
* @param fieldsMapping * @param fieldsMapping
* @param sourceRecordId * @param sourceRecordId
* @param defaultValue * @param defaultValue
* @param save * @return
* @return Returns ID or Record
*/ */
protected Object transformRecord( protected Record transformRecord(
Entity sourceEntity, Entity targetEntity, JSONObject fieldsMapping, Entity sourceEntity, Entity targetEntity, JSONObject fieldsMapping,
ID sourceRecordId, Map<String, Object> defaultValue, boolean save) { ID sourceRecordId, Map<String, Object> defaultValue) {
Record target = EntityHelper.forNew(targetEntity.getEntityCode(), getUser()); Record target = EntityHelper.forNew(targetEntity.getEntityCode(), getUser());
@ -222,7 +248,7 @@ public class RecordTransfomer extends SetUser {
List<String> validFields = checkAndWarnFields(sourceEntity, fieldsMapping.values()); List<String> validFields = checkAndWarnFields(sourceEntity, fieldsMapping.values());
if (validFields.isEmpty()) { if (validFields.isEmpty()) {
log.warn("No fields for transform"); log.warn("No fields for transform : {}", fieldsMapping);
return null; return null;
} }
@ -255,20 +281,7 @@ public class RecordTransfomer extends SetUser {
} }
} }
if (!save) return target; return target;
if (this.skipGuard) {
PrivilegesGuardContextHolder.setSkipGuard(EntityHelper.UNSAVED_ID);
}
GeneralEntityServiceContextHolder.setRepeatedCheckMode(GeneralEntityServiceContextHolder.RCM_CHECK_MAIN);
try {
target = Application.getEntityService(targetEntity.getEntityCode()).createOrUpdate(target);
return target.getPrimary();
} finally {
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
if (this.skipGuard) PrivilegesGuardContextHolder.getSkipGuardOnce();
}
} }
private List<String> checkAndWarnFields(Entity entity, Collection<?> fields) { private List<String> checkAndWarnFields(Entity entity, Collection<?> fields) {

View file

@ -17,8 +17,8 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.configuration.ConfigBean; import com.rebuild.core.configuration.ConfigBean;
import com.rebuild.core.configuration.ConfigurationException; import com.rebuild.core.configuration.ConfigurationException;
import com.rebuild.core.configuration.general.FormsBuilderContextHolder;
import com.rebuild.core.configuration.general.FormsBuilder; import com.rebuild.core.configuration.general.FormsBuilder;
import com.rebuild.core.configuration.general.FormsBuilderContextHolder;
import com.rebuild.core.configuration.general.TransformManager; import com.rebuild.core.configuration.general.TransformManager;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
@ -88,8 +88,8 @@ public class TransformerPreview {
FormsBuilderContextHolder.setMainIdOfDetail(fakeMainid); FormsBuilderContextHolder.setMainIdOfDetail(fakeMainid);
try { try {
for (ID did : ids) { for (ID did : ids) {
Record targetRecord = (Record) transfomer.transformRecord( Record targetRecord = transfomer.transformRecord(
sourceEntity, targetEntity, fieldsMapping, did, null, false); sourceEntity, targetEntity, fieldsMapping, did, null);
fillLabelOfReference(targetRecord); fillLabelOfReference(targetRecord);
JSON model = UseFormsBuilder.instance.buildNewForm(targetEntity, targetRecord, user); JSON model = UseFormsBuilder.instance.buildNewForm(targetEntity, targetRecord, user);
@ -113,8 +113,8 @@ public class TransformerPreview {
throw new ConfigurationException("Invalid config of transform : " + transConfig); throw new ConfigurationException("Invalid config of transform : " + transConfig);
} }
Record targetRecord = (Record) transfomer.transformRecord( Record targetRecord = transfomer.transformRecord(
sourceEntity, targetEntity, fieldsMapping, sourceId, null, false); sourceEntity, targetEntity, fieldsMapping, sourceId, null);
fillLabelOfReference(targetRecord); fillLabelOfReference(targetRecord);
// 转为明细 // 转为明细

View file

@ -78,8 +78,9 @@ public class ActionFactory {
} }
@Override @Override
public void execute(OperatingContext operatingContext) throws TriggerException { public Object execute(OperatingContext operatingContext) throws TriggerException {
log.warn("@rbv not attached"); log.warn("@rbv not attached");
return "@rbv";
} }
} }
} }

View file

@ -15,6 +15,7 @@ import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.service.general.OperatingContext; import com.rebuild.core.service.general.OperatingContext;
import com.rebuild.core.service.general.OperatingObserver; import com.rebuild.core.service.general.OperatingObserver;
import com.rebuild.core.service.general.RepeatedRecordsException; import com.rebuild.core.service.general.RepeatedRecordsException;
import com.rebuild.core.service.trigger.impl.FieldAggregation;
import com.rebuild.core.support.CommonsLog; import com.rebuild.core.support.CommonsLog;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -113,7 +114,7 @@ public class RobotTriggerObserver extends OperatingObserver {
TriggerAction[] beExecuted = when == TriggerWhen.DELETE TriggerAction[] beExecuted = when == TriggerWhen.DELETE
? DELETE_BEFORE_HOLD.get(primaryId) ? DELETE_BEFORE_HOLD.get(primaryId)
: RobotTriggerManager.instance.getActions(getEffectedId(context), when); : RobotTriggerManager.instance.getActions(getRealRecordId(context), when);
if (beExecuted == null || beExecuted.length == 0) { if (beExecuted == null || beExecuted.length == 0) {
return; return;
} }
@ -125,6 +126,10 @@ public class RobotTriggerObserver extends OperatingObserver {
if (originTriggerSource) { if (originTriggerSource) {
TRIGGER_SOURCE.set(new TriggerSource(context, when)); TRIGGER_SOURCE.set(new TriggerSource(context, when));
// 强制清理一次正常不会出现此情况
Object o = FieldAggregation.cleanTriggerChain();
if (o != null) log.warn("Force clean last trigger-chain : {}", o);
} else { } else {
// 是否自己触发自己避免无限执行 // 是否自己触发自己避免无限执行
boolean isOriginRecord = primaryId.equals(triggerSource.getOriginRecord()); boolean isOriginRecord = primaryId.equals(triggerSource.getOriginRecord());
@ -144,24 +149,35 @@ public class RobotTriggerObserver extends OperatingObserver {
int depth = triggerSource == null ? 1 : triggerSource.getSourceDepth(); int depth = triggerSource == null ? 1 : triggerSource.getSourceDepth();
try { try {
for (TriggerAction action : beExecuted) { for (TriggerAction action : beExecuted) {
log.info("Trigger.{} [ {} ] executing on record ({}) : {}", depth, action.getType(), when.name(), primaryId); String w = String.format("Trigger.%d [ %s ] executed on record (%s) : %s",
depth, action.getType(), when.name(), primaryId);
System.out.println("[dev] " + w.replace("executed", "executing"));
try { try {
action.execute(context); Object ret = action.execute(context);
CommonsLog.createLog(TYPE_TRIGGER, context.getOperator(), action.getActionContext().getConfigId()); log.info(w + " > " + (ret == null ? "N" : ret));
CommonsLog.createLog(TYPE_TRIGGER,
context.getOperator(), action.getActionContext().getConfigId(), String.valueOf(ret));
} catch (Throwable ex) { } catch (Throwable ex) {
log.info(w);
// DataValidate 直接抛出 // DataValidate 直接抛出
if (ex instanceof DataValidateException) throw ex; if (ex instanceof DataValidateException) throw ex;
log.error("Trigger execution failed : {} << {}", action, context, ex); log.error("Trigger execution failed : {} << {}", action, context, ex);
CommonsLog.createLog(TYPE_TRIGGER, context.getOperator(), action.getActionContext().getConfigId(), ex); CommonsLog.createLog(TYPE_TRIGGER,
context.getOperator(), action.getActionContext().getConfigId(), ex);
// FIXME 触发器执行失败是否抛出 // FIXME 触发器执行失败是否抛出
if (ex instanceof MissingMetaExcetion if (ex instanceof MissingMetaExcetion
|| ex instanceof ExpressionRuntimeException || ex instanceof ExpressionRuntimeException
|| ex instanceof RepeatedRecordsException) { || ex instanceof RepeatedRecordsException) {
throw new TriggerException(Language.L("触发器执行失败 : %s", ex.getLocalizedMessage())); String errMsg = ex.getLocalizedMessage();
if (ex instanceof RepeatedRecordsException) errMsg = Language.L("存在重复记录");
throw new TriggerException(Language.L("触发器执行失败 : %s", errMsg));
} else if (ex instanceof TriggerException) { } else if (ex instanceof TriggerException) {
throw (TriggerException) ex; throw (TriggerException) ex;
} else { } else {
@ -169,16 +185,16 @@ public class RobotTriggerObserver extends OperatingObserver {
} }
} finally { } finally {
if (originTriggerSource) {
action.clean(); action.clean();
} }
} }
}
} finally { } finally {
if (originTriggerSource) { if (originTriggerSource) {
log.info("Clear trigger-source : {}", getTriggerSource()); log.info("Clear trigger-source : {}", getTriggerSource());
TRIGGER_SOURCE.remove(); TRIGGER_SOURCE.remove();
FieldAggregation.cleanTriggerChain();
} }
} }
} }
@ -189,12 +205,12 @@ public class RobotTriggerObserver extends OperatingObserver {
* *
* @return * @return
*/ */
private ID getEffectedId(OperatingContext context) { private ID getRealRecordId(OperatingContext context) {
ID effectId = context.getAnyRecord().getPrimary(); ID recordId = context.getAnyRecord().getPrimary();
if (effectId.getEntityCode() == EntityHelper.ShareAccess) { if (recordId.getEntityCode() == EntityHelper.ShareAccess) {
effectId = context.getAnyRecord().getID("recordId"); recordId = context.getAnyRecord().getID("recordId");
} }
return effectId; return recordId;
} }
// -- // --

View file

@ -30,7 +30,7 @@ public abstract class TriggerAction {
abstract public ActionType getType(); abstract public ActionType getType();
abstract public void execute(OperatingContext operatingContext) throws TriggerException; abstract public Object execute(OperatingContext operatingContext) throws TriggerException;
/** /**
* 如果是删除动作会先调用此方法可在此方法中保持一些数据状态以便删除后还可继续使用 * 如果是删除动作会先调用此方法可在此方法中保持一些数据状态以便删除后还可继续使用
@ -59,6 +59,6 @@ public abstract class TriggerAction {
@Override @Override
public String toString() { public String toString() {
return super.toString() + "#" + actionContext.getConfigId(); return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode()) + "#" + actionContext.getConfigId();
} }
} }

View file

@ -8,7 +8,9 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.trigger; package com.rebuild.core.service.trigger;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application;
import com.rebuild.core.service.general.OperatingContext; import com.rebuild.core.service.general.OperatingContext;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -17,6 +19,7 @@ import java.util.List;
* @author RB * @author RB
* @since 2022/07/04 * @since 2022/07/04
*/ */
@Slf4j
public class TriggerSource { public class TriggerSource {
private final List<Object[]> sources = new ArrayList<>(); private final List<Object[]> sources = new ArrayList<>();
@ -24,6 +27,7 @@ public class TriggerSource {
protected TriggerSource(OperatingContext origin, TriggerWhen originAction) { protected TriggerSource(OperatingContext origin, TriggerWhen originAction) {
addNext(origin, originAction); addNext(origin, originAction);
if (Application.devMode()) log.warn("[dev] New trigger-source : {}", this);
} }
public void addNext(OperatingContext next, TriggerWhen nextAction) { public void addNext(OperatingContext next, TriggerWhen nextAction) {

View file

@ -51,20 +51,19 @@ public class AutoApproval extends TriggerAction {
} }
@Override @Override
public void execute(OperatingContext operatingContext) throws TriggerException { public Object execute(OperatingContext operatingContext) throws TriggerException {
this.operatingContext = operatingContext; this.operatingContext = operatingContext;
List<AutoApproval> lazyed; List<AutoApproval> lazyed;
if ((lazyed = isLazyAutoApproval(false)) != null) { if ((lazyed = isLazyAutoApproval(false)) != null) {
lazyed.add(this); lazyed.add(this);
log.info("Lazy AutoApproval : {}", lazyed); log.info("Lazy AutoApproval : {}", lazyed);
return; return "lazy";
} }
ID recordId = operatingContext.getAnyRecord().getPrimary(); ID recordId = operatingContext.getAnyRecord().getPrimary();
String useApproval = ((JSONObject) actionContext.getActionContent()).getString("useApproval"); String useApproval = ((JSONObject) actionContext.getActionContent()).getString("useApproval");
// ID approver = operatingContext.getOperator();
ID approver = UserService.SYSTEM_USER; ID approver = UserService.SYSTEM_USER;
ID approvalId = ID.isId(useApproval) ? ID.valueOf(useApproval) : null; ID approvalId = ID.isId(useApproval) ? ID.valueOf(useApproval) : null;
@ -77,6 +76,7 @@ public class AutoApproval extends TriggerAction {
} else { } else {
Application.getBean(ApprovalStepService.class).txAutoApproved(recordId, approver, approvalId); Application.getBean(ApprovalStepService.class).txAutoApproved(recordId, approver, approvalId);
} }
return "approval:" + recordId;
} }
@Override @Override

View file

@ -49,14 +49,15 @@ public class AutoAssign extends TriggerAction {
} }
@Override @Override
public void execute(OperatingContext operatingContext) throws TriggerException { public Object execute(OperatingContext operatingContext) throws TriggerException {
final JSONObject content = (JSONObject) actionContext.getActionContent(); final JSONObject content = (JSONObject) actionContext.getActionContent();
final ID recordId = operatingContext.getAnyRecord().getPrimary(); final ID recordId = operatingContext.getAnyRecord().getPrimary();
JSONArray assignTo = content.getJSONArray("assignTo"); JSONArray assignTo = content.getJSONArray("assignTo");
Set<ID> toUsers = UserHelper.parseUsers(assignTo, recordId, true); Set<ID> toUsers = UserHelper.parseUsers(assignTo, recordId, true);
if (toUsers.isEmpty()) { if (toUsers.isEmpty()) {
return; log.warn("No andy users found : {}", assignTo);
return null;
} }
ID toUser = null; ID toUser = null;
@ -113,5 +114,6 @@ public class AutoAssign extends TriggerAction {
PrivilegesGuardContextHolder.getSkipGuardOnce(); PrivilegesGuardContextHolder.getSkipGuardOnce();
GeneralEntityServiceContextHolder.isFromTrigger(true); GeneralEntityServiceContextHolder.isFromTrigger(true);
} }
return "assign:" + toUser;
} }
} }

View file

@ -42,14 +42,15 @@ public class AutoShare extends AutoAssign {
} }
@Override @Override
public void execute(OperatingContext operatingContext) throws TriggerException { public Object execute(OperatingContext operatingContext) throws TriggerException {
final JSONObject content = (JSONObject) actionContext.getActionContent(); final JSONObject content = (JSONObject) actionContext.getActionContent();
final ID recordId = operatingContext.getAnyRecord().getPrimary(); final ID recordId = operatingContext.getAnyRecord().getPrimary();
JSONArray shareTo = content.getJSONArray("shareTo"); JSONArray shareTo = content.getJSONArray("shareTo");
Set<ID> toUsers = UserHelper.parseUsers(shareTo, recordId, true); Set<ID> toUsers = UserHelper.parseUsers(shareTo, recordId, true);
if (toUsers.isEmpty()) { if (toUsers.isEmpty()) {
return; log.warn("No andy users found : {}", shareTo);
return null;
} }
String hasCascades = ((JSONObject) actionContext.getActionContent()).getString("cascades"); String hasCascades = ((JSONObject) actionContext.getActionContent()).getString("cascades");
@ -75,5 +76,6 @@ public class AutoShare extends AutoAssign {
GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
} }
} }
return "share:" + toUsers;
} }
} }

View file

@ -93,13 +93,17 @@ public class FieldAggregation extends TriggerAction {
List<String> tschain = TRIGGER_CHAIN.get(); List<String> tschain = TRIGGER_CHAIN.get();
if (tschain == null) { if (tschain == null) {
tschain = new ArrayList<>(); tschain = new ArrayList<>();
if (Application.devMode()) log.warn("[dev] New trigger-chain : {}", this);
} else { } else {
log.info("Occured trigger-chain : {} > {} (current)", StringUtils.join(tschain, " > "), chainName); String w = String.format("Occured trigger-chain : %s > %s (current)", StringUtils.join(tschain, " > "), chainName);
// 在整个触发链上只触发一次避免循环调用 // 在整个触发链上只触发1次避免循环调用
// FIXME 20220804 某些场景是否允许2次而非1次???
if (tschain.contains(chainName)) { if (tschain.contains(chainName)) {
if (Application.devMode()) log.warn("[dev] Record triggered only once on trigger-chain : {}", chainName); log.warn(w + "! TRIGGER ONCE ONLY : {}", chainName);
return null; return null;
} else {
log.info(w);
} }
} }
@ -111,15 +115,15 @@ public class FieldAggregation extends TriggerAction {
} }
@Override @Override
public void execute(OperatingContext operatingContext) throws TriggerException { public Object execute(OperatingContext operatingContext) throws TriggerException {
final String chainName = actionContext.getConfigId() + ":" + operatingContext.getAction().getName(); final String chainName = actionContext.getConfigId() + ":" + operatingContext.getAction().getName();
final List<String> tschain = checkTriggerChain(chainName); final List<String> tschain = checkTriggerChain(chainName);
if (tschain == null) return; if (tschain == null) return "trigger-once";
this.prepare(operatingContext); this.prepare(operatingContext);
if (targetRecordId == null) { if (targetRecordId == null) {
log.warn("No target record found"); log.warn("No target record found");
return; return null;
} }
// 聚合数据过滤 // 聚合数据过滤
@ -187,7 +191,10 @@ public class FieldAggregation extends TriggerAction {
PrivilegesGuardContextHolder.getSkipGuardOnce(); PrivilegesGuardContextHolder.getSkipGuardOnce();
GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
} }
return "target:" + targetRecord.getPrimary();
} }
return "target-empty";
} }
@Override @Override
@ -223,8 +230,12 @@ public class FieldAggregation extends TriggerAction {
this.followSourceWhere = String.format("%s = '%s'", followSourceField, targetRecordId); this.followSourceWhere = String.format("%s = '%s'", followSourceField, targetRecordId);
} }
@Override /**
public void clean() { * @return
*/
public static Object cleanTriggerChain() {
Object o = TRIGGER_CHAIN.get();
TRIGGER_CHAIN.remove(); TRIGGER_CHAIN.remove();
return o;
} }
} }

View file

@ -80,17 +80,17 @@ public class FieldWriteback extends FieldAggregation {
} }
@Override @Override
public void execute(OperatingContext operatingContext) throws TriggerException { public Object execute(OperatingContext operatingContext) throws TriggerException {
final String chainName = actionContext.getConfigId() + ":" + operatingContext.getAction().getName(); final String chainName = actionContext.getConfigId() + ":" + operatingContext.getAction().getName();
final List<String> tschain = checkTriggerChain(chainName); final List<String> tschain = checkTriggerChain(chainName);
if (tschain == null) return; if (tschain == null) return "trigger-once";
this.prepare(operatingContext); this.prepare(operatingContext);
if (targetRecordIds.isEmpty()) return; if (targetRecordIds.isEmpty()) return null;
if (targetRecordData.isEmpty()) { if (targetRecordData.isEmpty()) {
log.info("No data of target record available : {}", targetRecordId); log.info("No data of target record available : {}", targetRecordId);
return; return "target-empty";
} }
final ServiceSpec useService = MetadataHelper.isBusinessEntity(targetEntity) final ServiceSpec useService = MetadataHelper.isBusinessEntity(targetEntity)
@ -100,6 +100,7 @@ public class FieldWriteback extends FieldAggregation {
final boolean forceUpdate = ((JSONObject) actionContext.getActionContent()).getBooleanValue("forceUpdate"); final boolean forceUpdate = ((JSONObject) actionContext.getActionContent()).getBooleanValue("forceUpdate");
boolean tschainAdded = false; boolean tschainAdded = false;
List<ID> affected = new ArrayList<>();
for (ID targetRecordId : targetRecordIds) { for (ID targetRecordId : targetRecordIds) {
if (operatingContext.getAction() == BizzPermission.DELETE if (operatingContext.getAction() == BizzPermission.DELETE
&& targetRecordId.equals(operatingContext.getAnyRecord().getPrimary())) { && targetRecordId.equals(operatingContext.getAnyRecord().getPrimary())) {
@ -129,12 +130,14 @@ public class FieldWriteback extends FieldAggregation {
try { try {
useService.createOrUpdate(targetRecord); useService.createOrUpdate(targetRecord);
affected.add(targetRecord.getPrimary());
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); PrivilegesGuardContextHolder.getSkipGuardOnce();
GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce(); GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
} }
} }
return "target:" + affected;
} }
@Override @Override

View file

@ -25,10 +25,10 @@ import java.util.List;
/** /**
* 分组聚合目标数据刷新 * 分组聚合目标数据刷新
* 场景举例 * 场景举例
* 1. 新建产品A + 仓库A分组组合A+A * 1.1 新建产品A + 仓库A分组组合A+A
* 2. 之后修改了仓库A > B组合A+B此时原组合A+A纪录不会更新 * 1.2 修改仓库A > B组合A+B此时原组合A+A纪录不会更新
* 3. 这里需要强制更新相关原纪录 * 2. 这里需要强制更新相关原纪录
* 4. NOTE 如果组合值均为空则无法匹配任何目标记录此时需要全量刷新通过任一字段必填解决 * 3. NOTE 如果组合值均为空则无法匹配任何目标记录此时需要全量刷新通过任一字段必填解决
* *
* @author RB * @author RB
* @since 2022/7/8 * @since 2022/7/8

View file

@ -56,7 +56,7 @@ public class SendNotification extends TriggerAction {
} }
@Override @Override
public void execute(OperatingContext operatingContext) { public Object execute(OperatingContext operatingContext) {
ThreadPool.exec(() -> { ThreadPool.exec(() -> {
try { try {
// FIXME 等待事物完成 // FIXME 等待事物完成
@ -67,6 +67,7 @@ public class SendNotification extends TriggerAction {
log.error(null, ex); log.error(null, ex);
} }
}); });
return "async";
} }
private void executeAsync(OperatingContext operatingContext) { private void executeAsync(OperatingContext operatingContext) {

View file

@ -14,6 +14,9 @@ import com.rebuild.core.Application;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.Set;
/** /**
* 多引用字段支持 * 多引用字段支持
@ -78,4 +81,25 @@ public class N2NReferenceSupport {
Field lastField = father.getField(paths[paths.length - 1]); Field lastField = father.getField(paths[paths.length - 1]);
return items(lastField, fatherRecordId); return items(lastField, fatherRecordId);
} }
/**
* 获取指定字段的全部引用项不含排除
*
* @param field
* @param excluded 排除指定记录
* @return
*/
public static Set<ID> itemsUsed(Field field, ID excluded) {
String sql = "select referenceId from NreferenceItem where belongEntity = ? and belongField = ?";
if (excluded != null) sql += String.format(" and recordId <> '%s'", excluded);
Object[][] array = Application.createQueryNoFilter(sql)
.setParameter(1, field.getOwnEntity().getName())
.setParameter(2, field.getName())
.array();
Set<ID> set = new HashSet<>();
for (Object[] o : array) set.add((ID) o[0]);
return set;
}
} }

View file

@ -15,7 +15,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.configuration.ConfigBean; import com.rebuild.core.configuration.ConfigBean;
import com.rebuild.core.configuration.general.AdvFilterManager; import com.rebuild.core.configuration.general.AdvFilterManager;
import com.rebuild.core.configuration.general.DataListClass; import com.rebuild.core.configuration.general.DataListCategory;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType; import com.rebuild.core.metadata.easymeta.DisplayType;
@ -46,7 +46,7 @@ public class ProtocolFilterParser {
final private String protocolExpr; final private String protocolExpr;
/** /**
* @param protocolExpr via:xxx:[field] ref:xxx:[id] class:entity:value * @param protocolExpr via:xxx:[field] ref:xxx:[id] category:entity:value
*/ */
public ProtocolFilterParser(String protocolExpr) { public ProtocolFilterParser(String protocolExpr) {
this.protocolExpr = protocolExpr; this.protocolExpr = protocolExpr;
@ -64,8 +64,8 @@ public class ProtocolFilterParser {
case "ref": { case "ref": {
return parseRef(ps[1], ps.length > 2 ? ps[2] : null); return parseRef(ps[1], ps.length > 2 ? ps[2] : null);
} }
case "class": { case "category": {
return parseClass(ps[1], ps[2]); return parseCategory(ps[1], ps[2]);
} }
default: { default: {
log.warn("Unknown protocol expr : {}", protocolExpr); log.warn("Unknown protocol expr : {}", protocolExpr);
@ -160,9 +160,9 @@ public class ProtocolFilterParser {
* @return * @return
* @see AdvFilterParser#parseItem(JSONObject, JSONObject) * @see AdvFilterParser#parseItem(JSONObject, JSONObject)
*/ */
public String parseClass(String entity, String value) { public String parseCategory(String entity, String value) {
Entity rootEntity = MetadataHelper.getEntity(entity); Entity rootEntity = MetadataHelper.getEntity(entity);
Field classField = DataListClass.getFieldOfClass(rootEntity); Field classField = DataListCategory.getFieldOfCategory(rootEntity);
if (classField == null) return "(9=9)"; if (classField == null) return "(9=9)";
DisplayType dt = EasyMetaFactory.getDisplayType(classField); DisplayType dt = EasyMetaFactory.getDisplayType(classField);

View file

@ -231,7 +231,7 @@ public class MetaEntityController extends BaseController {
File dest = RebuildConfiguration.getFileOfTemp("schema-" + entity.getName() + ".json"); File dest = RebuildConfiguration.getFileOfTemp("schema-" + entity.getName() + ".json");
if (dest.exists()) FileUtils.deleteQuietly(dest); if (dest.exists()) FileUtils.deleteQuietly(dest);
new MetaSchemaGenerator(entity).generate(dest); new MetaSchemaGenerator(entity, true).generate(dest);
if (ServletUtils.isAjaxRequest(request)) { if (ServletUtils.isAjaxRequest(request)) {
writeSuccess(response, JSONUtils.toJSONObject("file", dest.getName())); writeSuccess(response, JSONUtils.toJSONObject("file", dest.getName()));

View file

@ -15,7 +15,7 @@ import com.rebuild.api.RespBody;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.configuration.ConfigBean; import com.rebuild.core.configuration.ConfigBean;
import com.rebuild.core.configuration.general.BaseLayoutManager; import com.rebuild.core.configuration.general.BaseLayoutManager;
import com.rebuild.core.configuration.general.DataListClass; import com.rebuild.core.configuration.general.DataListCategory;
import com.rebuild.core.configuration.general.DataListManager; import com.rebuild.core.configuration.general.DataListManager;
import com.rebuild.core.configuration.general.LayoutConfigService; import com.rebuild.core.configuration.general.LayoutConfigService;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
@ -66,9 +66,9 @@ public class WidgetController extends BaseController implements ShareTo {
return RespBody.ok(config == null ? null : config.toJSON()); return RespBody.ok(config == null ? null : config.toJSON());
} }
@GetMapping("widget-class-data") @GetMapping("widget-category-data")
public RespBody getClassData(@PathVariable String entity) { public RespBody getCategoryData(@PathVariable String entity) {
JSON data = DataListClass.datas(MetadataHelper.getEntity(entity), null); JSON data = DataListCategory.datas(MetadataHelper.getEntity(entity), null);
return RespBody.ok(data); return RespBody.ok(data);
} }
} }

View file

@ -83,13 +83,13 @@ public class GeneralListController extends EntityController {
// 扩展配置 // 扩展配置
String advListHideFilters = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS); String advListHideFilters = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS);
String advListHideCharts = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_CHARTS); String advListHideCharts = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_HIDE_CHARTS);
String advListShowClass = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCLASS); String advListShowCategory = easyEntity.getExtraAttr(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY);
mv.getModel().put(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS, advListHideFilters); mv.getModel().put(EasyEntityConfigProps.ADV_LIST_HIDE_FILTERS, advListHideFilters);
mv.getModel().put(EasyEntityConfigProps.ADV_LIST_HIDE_CHARTS, advListHideCharts); mv.getModel().put(EasyEntityConfigProps.ADV_LIST_HIDE_CHARTS, advListHideCharts);
mv.getModel().put(EasyEntityConfigProps.ADV_LIST_SHOWCLASS, StringUtils.isNotBlank(advListShowClass)); mv.getModel().put(EasyEntityConfigProps.ADV_LIST_SHOWCATEGORY, StringUtils.isNotBlank(advListShowCategory));
mv.getModel().put("hideAside", mv.getModel().put("hideAside",
BooleanUtils.toBoolean(advListHideFilters) && BooleanUtils.toBoolean(advListHideCharts) && StringUtils.isBlank(advListShowClass)); BooleanUtils.toBoolean(advListHideFilters) && BooleanUtils.toBoolean(advListHideCharts) && StringUtils.isBlank(advListShowCategory));
// 查询面板 // 查询面板

View file

@ -77,9 +77,13 @@ public class TransformConfigController extends BaseController {
mv.getModelMap().put("sourceEntity", buildEntity(sourceEntity, true)); mv.getModelMap().put("sourceEntity", buildEntity(sourceEntity, true));
mv.getModelMap().put("targetEntity", buildEntity(targetEntity, false)); mv.getModelMap().put("targetEntity", buildEntity(targetEntity, false));
// 明细两个实体均有明细才返回 // v2.10 目标为主实体
if (sourceEntity.getDetailEntity() != null && targetEntity.getDetailEntity() != null) { if (targetEntity.getDetailEntity() != null) {
if (sourceEntity.getDetailEntity() != null) {
mv.getModelMap().put("sourceDetailEntity", buildEntity(sourceEntity.getDetailEntity(), true)); mv.getModelMap().put("sourceDetailEntity", buildEntity(sourceEntity.getDetailEntity(), true));
} else {
mv.getModelMap().put("sourceDetailEntity", buildEntity(sourceEntity, true));
}
mv.getModelMap().put("targetDetailEntity", buildEntity(targetEntity.getDetailEntity(), false)); mv.getModelMap().put("targetDetailEntity", buildEntity(targetEntity.getDetailEntity(), false));
} }

View file

@ -107,7 +107,7 @@ public class TriggerAdminController extends BaseController {
public Object[][] triggerList(HttpServletRequest request) { public Object[][] triggerList(HttpServletRequest request) {
String belongEntity = getParameter(request, "entity"); String belongEntity = getParameter(request, "entity");
String q = getParameter(request, "q"); String q = getParameter(request, "q");
String sql = "select configId,belongEntity,belongEntity,name,isDisabled,modifiedOn,when,actionType,configId from RobotTriggerConfig" + String sql = "select configId,belongEntity,belongEntity,name,isDisabled,modifiedOn,when,actionType,configId,priority from RobotTriggerConfig" +
" where (1=1) and (2=2)" + " where (1=1) and (2=2)" +
" order by modifiedOn desc, name"; " order by modifiedOn desc, name";

View file

@ -1,5 +1,5 @@
# !!! `db.*` ONLY FOR DEV MODE !!! # !!! `db.*` ONLY FOR DEV MODE !!!
db.url: jdbc:mysql://127.0.0.1:3306/rebuild20?characterEncoding=UTF8&useUnicode=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B08:00 db.url: jdbc:mysql://127.0.0.1:3306/rebuild30?characterEncoding=UTF8&useUnicode=true&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B08:00
db.user: rebuild db.user: rebuild
db.passwd: rebuild db.passwd: rebuild
# Use built-in ehcache if redis not defined or unavailable # Use built-in ehcache if redis not defined or unavailable

View file

@ -2264,5 +2264,16 @@
"请选择驳回方式":"请选择驳回方式", "请选择驳回方式":"请选择驳回方式",
"系统用户":"系统用户", "系统用户":"系统用户",
"下载模板":"下载模板", "下载模板":"下载模板",
"退回至":"退回至" "退回至":"退回至",
"提交模式":"提交模式",
"启用提交模式必须选择审批流程":"启用提交模式必须选择审批流程",
"显示侧栏“分类”":"显示侧栏“分类”",
"显示侧栏“常用查询”":"显示侧栏“常用查询”",
"仅提交不做自动审批。选择的审批流程至少配置一个审批人,否则会提交失败":"仅提交不做自动审批。选择的审批流程至少配置一个审批人,否则会提交失败",
"需要先添加 [记录转换](../transforms) 才能在此处选择":"需要先添加 [记录转换](../transforms) 才能在此处选择",
"打开新建页面而非直接转换":"打开新建页面而非直接转换",
"显示侧栏“图表”":"显示侧栏“图表”",
"选择的审批流程至少配置一个审批人":"选择的审批流程至少配置一个审批人",
"需要先添加 [审批流程](../approvals) 才能在此处选择":"需要先添加 [审批流程](../approvals) 才能在此处选择",
"Category":"分类"
} }

View file

@ -7,12 +7,12 @@
-- #1 database/user -- #1 database/user
-- 首次使用请移除以下注释以创建数据库和用户 -- 首次使用请移除以下注释以创建数据库和用户
/* /*
CREATE DATABASE rebuild20 COLLATE utf8mb4_general_ci; CREATE DATABASE rebuild30 COLLATE utf8mb4_general_ci;
CREATE USER 'rebuild'@'127.0.0.1' IDENTIFIED BY 'rebuild'; CREATE USER 'rebuild'@'127.0.0.1' IDENTIFIED BY 'rebuild';
GRANT ALL PRIVILEGES ON rebuild20.* TO 'rebuild'@'127.0.0.1'; GRANT ALL PRIVILEGES ON rebuild30.* TO 'rebuild'@'127.0.0.1';
GRANT RELOAD ON *.* TO 'rebuild'@'127.0.0.1'; GRANT RELOAD ON *.* TO 'rebuild'@'127.0.0.1';
FLUSH PRIVILEGES; FLUSH PRIVILEGES;
USE rebuild20; USE rebuild30;
*/ */
-- #2 schemas -- #2 schemas

View file

@ -191,6 +191,6 @@
<script th:src="@{/assets/js/metadata/field-valueset.js}" type="text/babel"></script> <script th:src="@{/assets/js/metadata/field-valueset.js}" type="text/babel"></script>
<script th:src="@{/assets/js/metadata/field-formula.js}" type="text/babel"></script> <script th:src="@{/assets/js/metadata/field-formula.js}" type="text/babel"></script>
<script th:src="@{/assets/js/trigger/trigger-design.js}" type="text/babel"></script> <script th:src="@{/assets/js/trigger/trigger-design.js}" type="text/babel"></script>
<script th:src="|${baseUrl}/assets/js/trigger/trigger.${actionType}.js?v=2.3.0416|" type="text/babel"></script> <script th:src="|${baseUrl}/assets/js/trigger/trigger.${actionType}.js?v=2.10|" type="text/babel"></script>
</body> </body>
</html> </html>

View file

@ -49,6 +49,7 @@
<th>[[${bundle.L('源实体')}]]</th> <th>[[${bundle.L('源实体')}]]</th>
<th>[[${bundle.L('触发类型')}]]</th> <th>[[${bundle.L('触发类型')}]]</th>
<th class="no-sort">[[${bundle.L('触发动作')}]]</th> <th class="no-sort">[[${bundle.L('触发动作')}]]</th>
<th width="80" class="int-sort">[[${bundle.L('优先级')}]]</th>
<th width="80" class="no-sort">[[${bundle.L('启用')}]]</th> <th width="80" class="no-sort">[[${bundle.L('启用')}]]</th>
<th width="120" class="no-sort">[[${bundle.L('修改时间')}]]</th> <th width="120" class="no-sort">[[${bundle.L('修改时间')}]]</th>
<th width="120" class="no-sort"></th> <th width="120" class="no-sort"></th>

View file

@ -3377,6 +3377,11 @@ form {
overflow: hidden; overflow: hidden;
} }
.page-aside.widgets .nav-tabs > li.nav-item a.nav-link {
padding-left: 16px;
padding-right: 16px;
}
.page-aside.widgets .tab-container { .page-aside.widgets .tab-container {
margin-top: 2px; margin-top: 2px;
min-width: 279px; min-width: 279px;

View file

@ -76,7 +76,16 @@ class ConfigList extends React.Component {
}) })
// 简单排序 // 简单排序
if ($.tablesort) $('.tablesort').tablesort() if ($.tablesort) {
$('.tablesort').tablesort()
// 数字排序
$('table th.int-sort').each(function () {
$(this).data('sortBy', (th, td) => {
return ~~$(td).text()
})
})
}
} }
// 加载数据 // 加载数据

View file

@ -161,7 +161,7 @@ class DatabaseConf extends React.Component {
<div className="form-group row"> <div className="form-group row">
<div className="col-sm-3 col-form-label text-sm-right">{$L('数据库名称')}</div> <div className="col-sm-3 col-form-label text-sm-right">{$L('数据库名称')}</div>
<div className="col-sm-7"> <div className="col-sm-7">
<input type="text" className="form-control form-control-sm" name="dbName" value={this.state.dbName || ''} onChange={this.handleValue} placeholder="rebuild20" /> <input type="text" className="form-control form-control-sm" name="dbName" value={this.state.dbName || ''} onChange={this.handleValue} placeholder="rebuild30" />
<div className="form-text">{$L('如数据库不存在系统将自动创建')}</div> <div className="form-text">{$L('如数据库不存在系统将自动创建')}</div>
</div> </div>
</div> </div>
@ -228,7 +228,7 @@ class DatabaseConf extends React.Component {
dbType: 'mysql', dbType: 'mysql',
dbHost: this.state.dbHost || '127.0.0.1', dbHost: this.state.dbHost || '127.0.0.1',
dbPort: this.state.dbPort || 3306, dbPort: this.state.dbPort || 3306,
dbName: this.state.dbName || 'rebuild20', dbName: this.state.dbName || 'rebuild30',
dbUser: this.state.dbUser || 'rebuild', dbUser: this.state.dbUser || 'rebuild',
dbPassword: this.state.dbPassword || 'rebuild', dbPassword: this.state.dbPassword || 'rebuild',
} }

View file

@ -122,16 +122,16 @@ class DlgMode1Option extends RbFormHandler {
<label className="col-sm-3 col-form-label text-sm-right">{$L('显示侧栏分类')}</label> <label className="col-sm-3 col-form-label text-sm-right">{$L('显示侧栏分类')}</label>
<div className="col-sm-7"> <div className="col-sm-7">
<div className="switch-button switch-button-xs"> <div className="switch-button switch-button-xs">
<input type="checkbox" id="advListShowClass" defaultChecked={wpc.extConfig && wpc.extConfig.advListShowClass} /> <input type="checkbox" id="advListShowCategory" defaultChecked={wpc.extConfig && wpc.extConfig.advListShowCategory} />
<span> <span>
<label htmlFor="advListShowClass" /> <label htmlFor="advListShowCategory" />
</span> </span>
</div> </div>
<div className="clearfix"></div> <div className="clearfix"></div>
<div className={`J_advListShowClass mt-2 ${this.state.advListShowClass ? '' : 'hide'}`}> <div className={`J_advListShowCategory mt-2 ${this.state.advListShowCategory ? '' : 'hide'}`}>
<select className="form-control form-control-sm"> <select className="form-control form-control-sm">
{this.state.advListShowClassFields && {this.state.advListShowCategoryFields &&
this.state.advListShowClassFields.map((item) => { this.state.advListShowCategoryFields.map((item) => {
return ( return (
<option key={item.name} value={item.name}> <option key={item.name} value={item.name}>
{item.label} {item.label}
@ -182,33 +182,33 @@ class DlgMode1Option extends RbFormHandler {
componentDidMount() { componentDidMount() {
const that = this const that = this
let $class2 let $class2
$('#advListShowClass').on('change', function () { $('#advListShowCategory').on('change', function () {
if ($val(this)) { if ($val(this)) {
that.setState({ advListShowClass: true }) that.setState({ advListShowCategory: true })
} else { } else {
that.setState({ advListShowClass: null }) that.setState({ advListShowCategory: null })
} }
if (!$class2) { if (!$class2) {
$class2 = $('.J_advListShowClass select') $class2 = $('.J_advListShowCategory select')
$.get(`/commons/metadata/fields?entity=${wpc.entityName}`, (res) => { $.get(`/commons/metadata/fields?entity=${wpc.entityName}`, (res) => {
const _data = [] const _data = []
res.data.forEach((item) => { res.data.forEach((item) => {
if (CLASS_TYPES.includes(item.type)) _data.push(item) if (CLASS_TYPES.includes(item.type)) _data.push(item)
}) })
that.setState({ advListShowClassFields: _data }, () => { that.setState({ advListShowCategoryFields: _data }, () => {
$class2 $class2
.select2({ placeholder: $L('选择分类字段') }) .select2({ placeholder: $L('选择分类字段') })
.val((wpc.extConfig && wpc.extConfig.advListShowClass) || null) .val((wpc.extConfig && wpc.extConfig.advListShowCategory) || null)
.trigger('change') .trigger('change')
}) })
}) })
} }
}) })
if (wpc.extConfig && wpc.extConfig.advListShowClass) { if (wpc.extConfig && wpc.extConfig.advListShowCategory) {
$('#advListShowClass').trigger('change') $('#advListShowCategory').trigger('change')
} }
} }
@ -216,7 +216,7 @@ class DlgMode1Option extends RbFormHandler {
const o = { const o = {
advListHideFilters: !$val('#advListHideFilters'), advListHideFilters: !$val('#advListHideFilters'),
advListHideCharts: !$val('#advListHideCharts'), advListHideCharts: !$val('#advListHideCharts'),
advListShowClass: this.state.advListShowClass ? $val('.J_advListShowClass select') : null, advListShowCategory: this.state.advListShowCategory ? $val('.J_advListShowCategory select') : null,
advListFilterPane: $val('#advListFilterPane'), advListFilterPane: $val('#advListFilterPane'),
} }

View file

@ -9,6 +9,14 @@ See LICENSE and COMMERCIAL in the project root for license information.
const wpc = window.__PageConfig const wpc = window.__PageConfig
$(document).ready(() => { $(document).ready(() => {
renderRbcomp(<PreviewTable data={wpc.content} />, 'preview-table') renderRbcomp(<PreviewTable data={wpc.content} />, 'preview-table')
const $size = $('.preview-tools select').on('change', function () {
const s = $(this).val()
$('.preview-content').css('font-size', (13 * ~~s) / 10)
$storage.set('PRINTSIZE', s)
})
const s = $storage.get('PRINTSIZE')
if (s) $size.val(s).trigger('change')
}) })
class PreviewTable extends React.Component { class PreviewTable extends React.Component {

View file

@ -986,7 +986,7 @@ UserPopup.create = function (el) {
} }
// ~~ HTML 内容 // ~~ HTML 内容
const WrapHtml = (htmlContent) => <div dangerouslySetInnerHTML={{ __html: htmlContent }} /> const WrapHtml = (htmlContent) => <span dangerouslySetInnerHTML={{ __html: htmlContent }} />
// ~~ short React.Fragment // ~~ short React.Fragment
const RF = ({ children }) => <React.Fragment>{children}</React.Fragment> const RF = ({ children }) => <React.Fragment>{children}</React.Fragment>

View file

@ -1230,7 +1230,7 @@ const ClassWidget = {
}, },
loadClass() { loadClass() {
$.get(`/app/${wpc.entity[0]}/widget-class-data`, (res) => { $.get(`/app/${wpc.entity[0]}/widget-category-data`, (res) => {
this._classLoaded = true this._classLoaded = true
res.data && res.data &&
@ -1247,7 +1247,7 @@ const ClassWidget = {
const v = $(this).data('id') const v = $(this).data('id')
if (v === '$ALL$') wpc.protocolFilter = null if (v === '$ALL$') wpc.protocolFilter = null
else wpc.protocolFilter = `class:${wpc.entity[0]}:${v}` else wpc.protocolFilter = `category:${wpc.entity[0]}:${v}`
RbListPage.reload() RbListPage.reload()
}) })

View file

@ -59,6 +59,9 @@ class TriggerList extends ConfigList {
<td>{item[2] || item[1]}</td> <td>{item[2] || item[1]}</td>
<td>{item[7]}</td> <td>{item[7]}</td>
<td>{item[6] > 0 ? $L(' %s ', formatWhen(item[6])) : <span className="text-warning">({$L('无触发动作')})</span>}</td> <td>{item[6] > 0 ? $L(' %s ', formatWhen(item[6])) : <span className="text-warning">({$L('无触发动作')})</span>}</td>
<td>
<span className="badge badge-light font-weight-light">{item[9]}</span>
</td>
<td>{ShowEnable(item[4])}</td> <td>{ShowEnable(item[4])}</td>
<td> <td>
<DateShow date={item[5]} /> <DateShow date={item[5]} />

View file

@ -16,7 +16,7 @@
<div class="tab-container"> <div class="tab-container">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item" th:if="${advListHideFilters != 'true'}"><a class="nav-link" href="#asideFilters" data-toggle="tab">[[${bundle.L('常用查询')}]]</a></li> <li class="nav-item" th:if="${advListHideFilters != 'true'}"><a class="nav-link" href="#asideFilters" data-toggle="tab">[[${bundle.L('常用查询')}]]</a></li>
<li class="nav-item" th:if="${advListShowClass}"><a class="nav-link J_load-class" href="#asideClass" data-toggle="tab">[[${bundle.L('分类')}]]</a></li> <li class="nav-item" th:if="${advListShowCategory}"><a class="nav-link J_load-class" href="#asideClass" data-toggle="tab">[[${bundle.L('Category')}]]</a></li>
<li class="nav-item" th:if="${advListHideCharts != 'true'}"><a class="nav-link J_load-charts" href="#asideWidgets" data-toggle="tab">[[${bundle.L('图表')}]]</a></li> <li class="nav-item" th:if="${advListHideCharts != 'true'}"><a class="nav-link J_load-charts" href="#asideWidgets" data-toggle="tab">[[${bundle.L('图表')}]]</a></li>
</ul> </ul>
<div class="tab-content rb-scroller"> <div class="tab-content rb-scroller">

View file

@ -12,6 +12,14 @@
padding: 10px 15px 5px; padding: 10px 15px 5px;
text-align: right; text-align: right;
} }
.preview-tools select.form-control-sm {
width: 100px;
display: inline-block;
height: 36px !important;
min-height: 36px;
margin-right: 6px;
transform: translateY(-1.5px);
}
.preview-content { .preview-content {
margin: 0 20px 20px; margin: 0 20px 20px;
} }
@ -22,9 +30,9 @@
} }
.table th { .table th {
background-color: #eee !important; background-color: #eee !important;
width: auto; width: 13.33%;
min-width: 110px; min-width: 120px;
max-width: 180px; max-width: 240px;
} }
.table th.divider { .table th.divider {
width: 100%; width: 100%;
@ -55,6 +63,20 @@
</head> </head>
<body> <body>
<div class="preview-tools d-print-none"> <div class="preview-tools d-print-none">
<select class="form-control form-control-sm">
<option value="10">[[${bundle.L('默认大小')}]]</option>
<option value="10">100%</option>
<option value="11">110%</option>
<option value="12">120%</option>
<option value="13">130%</option>
<option value="14">140%</option>
<option value="15">150%</option>
<option value="16">160%</option>
<option value="17">170%</option>
<option value="18">180%</option>
<option value="19">190%</option>
<option value="20">200%</option>
</select>
<button class="btn btn-space btn-primary" onclick="window.print()"><i class="icon zmdi zmdi-print"></i> [[${bundle.L('打印')}]]</button> <button class="btn btn-space btn-primary" onclick="window.print()"><i class="icon zmdi zmdi-print"></i> [[${bundle.L('打印')}]]</button>
<button class="btn btn-space btn-secondary" onclick="window.close()">[[${bundle.L('关闭')}]]</button> <button class="btn btn-space btn-secondary" onclick="window.close()">[[${bundle.L('关闭')}]]</button>
</div> </div>

View file

@ -16,7 +16,7 @@
<div class="tab-container"> <div class="tab-container">
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li class="nav-item" th:if="${advListHideFilters != 'true'}"><a class="nav-link" href="#asideFilters" data-toggle="tab">[[${bundle.L('常用查询')}]]</a></li> <li class="nav-item" th:if="${advListHideFilters != 'true'}"><a class="nav-link" href="#asideFilters" data-toggle="tab">[[${bundle.L('常用查询')}]]</a></li>
<li class="nav-item" th:if="${advListShowClass}"><a class="nav-link J_load-class" href="#asideClass" data-toggle="tab">[[${bundle.L('分类')}]]</a></li> <li class="nav-item" th:if="${advListShowCategory}"><a class="nav-link J_load-class" href="#asideClass" data-toggle="tab">[[${bundle.L('Category')}]]</a></li>
<li class="nav-item" th:if="${advListHideCharts != 'true'}"><a class="nav-link J_load-charts" href="#asideWidgets" data-toggle="tab">[[${bundle.L('图表')}]]</a></li> <li class="nav-item" th:if="${advListHideCharts != 'true'}"><a class="nav-link J_load-charts" href="#asideWidgets" data-toggle="tab">[[${bundle.L('图表')}]]</a></li>
</ul> </ul>
<div class="tab-content rb-scroller"> <div class="tab-content rb-scroller">

View file

@ -23,7 +23,7 @@ public class MetaSchemaGeneratorTest extends TestSupport {
public void testGenerate() { public void testGenerate() {
if (MetadataHelper.containsEntity(Account)) { if (MetadataHelper.containsEntity(Account)) {
Entity test = MetadataHelper.getEntity(Account); Entity test = MetadataHelper.getEntity(Account);
MetaSchemaGenerator generator = new MetaSchemaGenerator(test); MetaSchemaGenerator generator = new MetaSchemaGenerator(test, true);
JSON schema = generator.generate(); JSON schema = generator.generate();
System.out.println(JSON.toJSONString(schema, true)); System.out.println(JSON.toJSONString(schema, true));
} }
@ -33,7 +33,7 @@ public class MetaSchemaGeneratorTest extends TestSupport {
public void testGenerateHaveDetail() { public void testGenerateHaveDetail() {
if (MetadataHelper.containsEntity(SalesOrder)) { if (MetadataHelper.containsEntity(SalesOrder)) {
Entity test = MetadataHelper.getEntity(SalesOrder); Entity test = MetadataHelper.getEntity(SalesOrder);
MetaSchemaGenerator generator = new MetaSchemaGenerator(test); MetaSchemaGenerator generator = new MetaSchemaGenerator(test, true);
JSON schema = generator.generate(); JSON schema = generator.generate();
System.out.println(JSON.toJSONString(schema, true)); System.out.println(JSON.toJSONString(schema, true));
} }