From 2491297ad0fc4716dae4a724c010ea4b58389d12 Mon Sep 17 00:00:00 2001 From: RB <42044143+getrebuild@users.noreply.github.com> Date: Fri, 8 Apr 2022 17:59:04 +0800 Subject: [PATCH] Parent cascading field (#451) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: calcFormula * trigger forceUpdate * 发起人/审批人 支持部门引用字段 * single-queue * better code --- @rbv | 2 +- pom.xml | 2 +- src/main/java/com/rebuild/api/ApiGateway.java | 43 ++++---- .../general/AutoFillinManager.java | 2 +- .../configuration/general/FormsBuilder.java | 2 +- .../core/metadata/EntityRecordCreator.java | 6 +- .../PrivilegesGuardContextHolder.java | 4 +- .../core/service/approval/FlowNode.java | 20 +++- .../service/general/GeneralEntityService.java | 13 ++- .../trigger/impl/FieldAggregation.java | 32 +++--- .../service/trigger/impl/FieldWriteback.java | 23 ++-- .../support/general/FieldValueHelper.java | 2 +- .../core/support/task/TaskExecutors.java | 60 +++++++--- .../com/rebuild/utils/codec/RbDateCodec.java | 2 +- .../admin/metadata/MetaFieldController.java | 34 ++++-- .../approval/ApprovalAdminController.java | 31 +++--- .../robot/approval/ApprovalController.java | 14 ++- .../java/com/rebuild/web/user/UserAvatar.java | 7 ++ .../rebuild/web/user/signup/LoginAction.java | 17 +-- .../resources/web/assets/css/meta-edit.css | 5 +- src/main/resources/web/assets/css/rb-page.css | 11 +- .../web/assets/js/metadata/entity-switch.js | 11 +- .../web/assets/js/rb-forms.append.js | 1 + src/main/resources/web/assets/js/rb-forms.js | 104 ++++++++++-------- .../js/trigger/trigger.FIELDAGGREGATION.js | 33 ++++-- .../js/trigger/trigger.FIELDWRITEBACK.js | 13 ++- .../core/support/task/TaskExecutorsTest.java | 31 ++++++ 27 files changed, 338 insertions(+), 187 deletions(-) create mode 100644 src/test/java/com/rebuild/core/support/task/TaskExecutorsTest.java diff --git a/@rbv b/@rbv index f62d1b05e..e6951a361 160000 --- a/@rbv +++ b/@rbv @@ -1 +1 @@ -Subproject commit f62d1b05ef27ccf62d1a213f4ddf03184e6deb66 +Subproject commit e6951a36179a8e1087a7888f948c8b3eede860b4 diff --git a/pom.xml b/pom.xml index 0e3649b32..e54cc32f4 100644 --- a/pom.xml +++ b/pom.xml @@ -273,7 +273,7 @@ com.github.devezhao persist4j - ft-time-SNAPSHOT + 1.5.8 com.alibaba diff --git a/src/main/java/com/rebuild/api/ApiGateway.java b/src/main/java/com/rebuild/api/ApiGateway.java index 89715e815..449b506e3 100644 --- a/src/main/java/com/rebuild/api/ApiGateway.java +++ b/src/main/java/com/rebuild/api/ApiGateway.java @@ -10,7 +10,6 @@ package com.rebuild.api; 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; @@ -21,6 +20,7 @@ import com.rebuild.core.configuration.RebuildApiManager; import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.privileges.UserService; import com.rebuild.core.service.DataSpecificationException; +import com.rebuild.core.support.task.TaskExecutors; import com.rebuild.utils.CommonsUtils; import com.rebuild.utils.RateLimiters; import es.moki.ratelimitj.core.limiter.request.RequestRateLimiter; @@ -246,28 +246,27 @@ public class ApiGateway extends Controller implements Initialization { * @param result */ protected void logRequestAsync(Date requestTime, String remoteIp, String requestId, String apiName, ApiContext context, JSON result) { - ThreadPool.exec(() -> { - Record record = EntityHelper.forNew(EntityHelper.RebuildApiRequest, UserService.SYSTEM_USER); - record.setString("requestUrl", apiName); - record.setString("remoteIp", remoteIp); - record.setString("responseBody", requestId + ":" + (result == null ? "{}" : CommonsUtils.maxstr(result.toJSONString(), 10000))); - record.setDate("requestTime", requestTime); - record.setDate("responseTime", CalendarUtils.now()); + Record record = EntityHelper.forNew(EntityHelper.RebuildApiRequest, UserService.SYSTEM_USER); + record.setString("requestUrl", apiName); + record.setString("remoteIp", remoteIp); + record.setString("responseBody", requestId + ":" + (result == null ? "{}" : CommonsUtils.maxstr(result.toJSONString(), 10000))); + record.setDate("requestTime", requestTime); + record.setDate("responseTime", CalendarUtils.now()); - if (context != null) { - record.setString("appId", context.getAppId()); - if (context.getPostData() != null) { - record.setString("requestBody", - CommonsUtils.maxstr(context.getPostData().toJSONString(), 10000)); - } - if (!context.getParameterMap().isEmpty()) { - record.setString("requestUrl", - CommonsUtils.maxstr(apiName + "?" + context.getParameterMap(), 300)); - } - } else { - record.setString("appId", "0"); + if (context != null) { + record.setString("appId", context.getAppId()); + if (context.getPostData() != null) { + record.setString("requestBody", + CommonsUtils.maxstr(context.getPostData().toJSONString(), 10000)); } - Application.getCommonsService().create(record, false); - }); + if (!context.getParameterMap().isEmpty()) { + record.setString("requestUrl", + CommonsUtils.maxstr(apiName + "?" + context.getParameterMap(), 300)); + } + } else { + record.setString("appId", "0"); + } + + TaskExecutors.queue(() -> Application.getCommonsService().create(record, false)); } } diff --git a/src/main/java/com/rebuild/core/configuration/general/AutoFillinManager.java b/src/main/java/com/rebuild/core/configuration/general/AutoFillinManager.java index 5f60f7e9b..c39c64f8f 100644 --- a/src/main/java/com/rebuild/core/configuration/general/AutoFillinManager.java +++ b/src/main/java/com/rebuild/core/configuration/general/AutoFillinManager.java @@ -127,7 +127,7 @@ public class AutoFillinManager implements ConfigManager { } // NOTE 忽略空值 - if (value == null || NullValue.is(value) || StringUtils.isBlank(value.toString())) { + if (NullValue.isNull(value) || StringUtils.isBlank(value.toString())) { continue; } diff --git a/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java b/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java index f781954ee..36d4fc5d7 100644 --- a/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java +++ b/src/main/java/com/rebuild/core/configuration/general/FormsBuilder.java @@ -484,7 +484,7 @@ public class FormsBuilder extends FormsManager { } // 处理日期格式 - if (field.getDisplayType() == DisplayType.REFERENCE && value != null && ((ID) value).getLabelRaw() != null) { + if (field.getDisplayType() == DisplayType.REFERENCE && value instanceof ID && ((ID) value).getLabelRaw() != null) { Field nameField = field.getRawMeta().getReferenceEntity().getNameField(); if (nameField.getType() == FieldType.DATE || nameField.getType() == FieldType.TIMESTAMP) { Object newLabel = EasyMetaFactory.valueOf(nameField).wrapValue(((ID) value).getLabelRaw()); diff --git a/src/main/java/com/rebuild/core/metadata/EntityRecordCreator.java b/src/main/java/com/rebuild/core/metadata/EntityRecordCreator.java index b57e289d1..6ac3a1e4f 100644 --- a/src/main/java/com/rebuild/core/metadata/EntityRecordCreator.java +++ b/src/main/java/com/rebuild/core/metadata/EntityRecordCreator.java @@ -99,10 +99,9 @@ public class EntityRecordCreator extends JsonRecordCreator { } Object hasVal = record.getObjectValue(field.getName()); - boolean isNull = hasVal == null || NullValue.is(hasVal); boolean canNull = field.isNullable() || autoReadonlyFields.contains(field.getName()); - if (isNull) { + if (NullValue.isNull(hasVal)) { if (!canNull) { notNulls.add(easyField.getLabel()); } @@ -127,11 +126,10 @@ public class EntityRecordCreator extends JsonRecordCreator { if (MetadataHelper.isCommonsField(field)) continue; Object hasVal = record.getObjectValue(field.getName()); - boolean isNull = hasVal == null || NullValue.is(hasVal); boolean canNull = field.isNullable() || autoReadonlyFields.contains(field.getName()); EasyField easyField = EasyMetaFactory.valueOf(field); - if (isNull) { + if (NullValue.isNull(hasVal)) { if (!canNull) { notNulls.add(easyField.getLabel()); } diff --git a/src/main/java/com/rebuild/core/privileges/PrivilegesGuardContextHolder.java b/src/main/java/com/rebuild/core/privileges/PrivilegesGuardContextHolder.java index b916342af..08ee6e9e9 100644 --- a/src/main/java/com/rebuild/core/privileges/PrivilegesGuardContextHolder.java +++ b/src/main/java/com/rebuild/core/privileges/PrivilegesGuardContextHolder.java @@ -44,9 +44,7 @@ public class PrivilegesGuardContextHolder { */ public static ID getSkipGuardOnce() { ID recordId = SKIP_GUARD.get(); - if (recordId != null) { - SKIP_GUARD.remove(); - } + if (recordId != null) SKIP_GUARD.remove(); return recordId; } } diff --git a/src/main/java/com/rebuild/core/service/approval/FlowNode.java b/src/main/java/com/rebuild/core/service/approval/FlowNode.java index a36284b20..8beacf130 100644 --- a/src/main/java/com/rebuild/core/service/approval/FlowNode.java +++ b/src/main/java/com/rebuild/core/service/approval/FlowNode.java @@ -178,16 +178,22 @@ public class FlowNode { if (whichUser != null) { Field userField = ApprovalHelper.checkVirtualField(def); if (userField != null) { - Object[] refUser; + Object[] ud; + // 部门中的用户(如上级) if (userField.getOwnEntity().getEntityCode() == EntityHelper.Department) { - Department refDept = Application.getUserStore().getUser(whichUser).getOwningDept(); - refUser = Application.getQueryFactory().uniqueNoFilter( - (ID) refDept.getIdentity(), userField.getName()); + Department d = Application.getUserStore().getUser(whichUser).getOwningDept(); + ud = Application.getQueryFactory().uniqueNoFilter((ID) d.getIdentity(), userField.getName()); } else { - refUser = Application.getQueryFactory().uniqueNoFilter(whichUser, userField.getName()); + ud = Application.getQueryFactory().uniqueNoFilter(whichUser, userField.getName()); } - if (refUser != null && refUser[0] != null) users.add((ID) refUser[0]); + if (ud != null && ud[0] != null) { + if (userField.getReferenceEntity().getEntityCode() == EntityHelper.Department) { + defsList.add(ud[0].toString()); + } else { + users.add((ID) ud[0]); + } + } } } @@ -197,6 +203,8 @@ public class FlowNode { } users.addAll(UserHelper.parseUsers(defsList, record)); + users.removeIf(id -> !UserHelper.isActive(id)); + return users; } diff --git a/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java b/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java index 7ec7cdd9f..49f91a670 100644 --- a/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java +++ b/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java @@ -470,15 +470,15 @@ public class GeneralEntityService extends ObservableService implements EntitySer return false; } - boolean rejected = false; + boolean unallow = false; if (action == BizzPermission.DELETE) { - rejected = currentState == ApprovalState.APPROVED || currentState == ApprovalState.PROCESSING; + unallow = currentState == ApprovalState.APPROVED || currentState == ApprovalState.PROCESSING; } else if (action == BizzPermission.UPDATE) { - rejected = (currentState == ApprovalState.APPROVED && changeState != ApprovalState.CANCELED) /* 管理员撤销 */ + unallow = (currentState == ApprovalState.APPROVED && changeState != ApprovalState.CANCELED) /* 管理员撤销 */ || (currentState == ApprovalState.PROCESSING && !GeneralEntityServiceContextHolder.isAllowForceUpdateOnce() /* 审批时修改 */); } - if (rejected) { + if (unallow) { if (RobotTriggerObserver.getTriggerSource() != null) { recordType = Language.L("关联记录"); } @@ -489,6 +489,11 @@ public class GeneralEntityService extends ObservableService implements EntitySer } } } + + if (action == BizzPermission.CREATE || action == BizzPermission.UPDATE) { + // TODO 父级级联字段强校验,兼容问题??? + } + return true; } diff --git a/src/main/java/com/rebuild/core/service/trigger/impl/FieldAggregation.java b/src/main/java/com/rebuild/core/service/trigger/impl/FieldAggregation.java index fd1667b45..660d0633c 100644 --- a/src/main/java/com/rebuild/core/service/trigger/impl/FieldAggregation.java +++ b/src/main/java/com/rebuild/core/service/trigger/impl/FieldAggregation.java @@ -7,7 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information. package com.rebuild.core.service.trigger.impl; -import cn.devezhao.bizz.privileges.impl.BizzPermission; +import cn.devezhao.commons.CalendarUtils; import cn.devezhao.commons.ObjectUtils; import cn.devezhao.persist4j.Entity; import cn.devezhao.persist4j.Record; @@ -23,6 +23,7 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.privileges.PrivilegesGuardContextHolder; import com.rebuild.core.privileges.UserService; import com.rebuild.core.service.ServiceSpec; +import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.OperatingContext; import com.rebuild.core.service.query.AdvFilterParser; import com.rebuild.core.service.trigger.ActionContext; @@ -54,8 +55,6 @@ public class FieldAggregation implements TriggerAction { final protected ActionContext context; - // 允许无权限更新 - final protected boolean allowNoPermissionUpdate; // 最大触发链深度 final protected int maxTriggerDepth; // 此触发器可能产生连锁反应 @@ -76,17 +75,15 @@ public class FieldAggregation implements TriggerAction { * @param context */ public FieldAggregation(ActionContext context) { - this(context, Boolean.TRUE, 9); + this(context, 9); } /** * @param context - * @param allowNoPermissionUpdate * @param maxTriggerDepth */ - protected FieldAggregation(ActionContext context, boolean allowNoPermissionUpdate, int maxTriggerDepth) { + protected FieldAggregation(ActionContext context, int maxTriggerDepth) { this.context = context; - this.allowNoPermissionUpdate = allowNoPermissionUpdate; this.maxTriggerDepth = maxTriggerDepth; } @@ -133,13 +130,6 @@ public class FieldAggregation implements TriggerAction { return; } - // 如果当前用户对目标记录无修改权限 - if (!allowNoPermissionUpdate - && !Application.getPrivilegesManager().allow(operatingContext.getOperator(), targetRecordId, BizzPermission.UPDATE)) { - log.warn("No permission to update record of target : {}", targetRecordId); - return; - } - // 聚合数据过滤 JSONObject dataFilter = ((JSONObject) context.getActionContent()).getJSONObject("dataFilter"); String dataFilterSql = null; @@ -149,6 +139,7 @@ public class FieldAggregation implements TriggerAction { // 构建目标记录数据 Record targetRecord = EntityHelper.forUpdate(targetRecordId, UserService.SYSTEM_USER, false); + JSONArray items = ((JSONObject) context.getActionContent()).getJSONArray("items"); for (Object o : items) { JSONObject item = (JSONObject) o; @@ -178,9 +169,14 @@ public class FieldAggregation implements TriggerAction { } // 有需要才执行 - if (targetRecord.getAvailableFields().size() > 1) { - if (allowNoPermissionUpdate) { - PrivilegesGuardContextHolder.setSkipGuard(targetRecordId); + if (!targetRecord.isEmpty()) { + final boolean forceUpdate = ((JSONObject) context.getActionContent()).getBooleanValue("forceUpdate"); + + // 跳过权限 + PrivilegesGuardContextHolder.setSkipGuard(targetRecordId); + // 强制更新 (v2.9) + if (forceUpdate) { + GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId); } // 会关联触发下一触发器(如有) @@ -191,10 +187,12 @@ public class FieldAggregation implements TriggerAction { ? Application.getEntityService(targetEntity.getEntityCode()) : Application.getService(targetEntity.getEntityCode()); + targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now()); try { useService.update(targetRecord); } finally { PrivilegesGuardContextHolder.getSkipGuardOnce(); + GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); } } } diff --git a/src/main/java/com/rebuild/core/service/trigger/impl/FieldWriteback.java b/src/main/java/com/rebuild/core/service/trigger/impl/FieldWriteback.java index c8f0a59f7..a1bc004c2 100644 --- a/src/main/java/com/rebuild/core/service/trigger/impl/FieldWriteback.java +++ b/src/main/java/com/rebuild/core/service/trigger/impl/FieldWriteback.java @@ -81,15 +81,17 @@ public class FieldWriteback extends FieldAggregation { this.prepare(operatingContext); if (targetRecordIds.isEmpty()) return; - if (targetRecordData.getAvailableFields().isEmpty()) { + if (targetRecordData.isEmpty()) { log.info("No data of target record available : {}", targetRecordId); return; } - final ServiceSpec targetService = MetadataHelper.isBusinessEntity(targetEntity) + final ServiceSpec useService = MetadataHelper.isBusinessEntity(targetEntity) ? Application.getEntityService(targetEntity.getEntityCode()) : Application.getService(targetEntity.getEntityCode()); + final boolean forceUpdate = ((JSONObject) context.getActionContent()).getBooleanValue("forceUpdate"); + boolean tschainAdded = false; for (ID targetRecordId : targetRecordIds) { if (operatingContext.getAction() == BizzPermission.DELETE @@ -98,13 +100,11 @@ public class FieldWriteback extends FieldAggregation { continue; } - if (allowNoPermissionUpdate) { - PrivilegesGuardContextHolder.setSkipGuard(targetRecordId); - } - // 如果当前用户对目标记录无修改权限 - else if (!Application.getPrivilegesManager().allow(operatingContext.getOperator(), targetRecordId, BizzPermission.UPDATE)) { - log.warn("No permission to update record of target : {}", targetRecordId); - continue; + // 跳过权限 + PrivilegesGuardContextHolder.setSkipGuard(targetRecordId); + // 强制更新 (v2.9) + if (forceUpdate) { + GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId); } // 会关联触发下一触发器 @@ -116,12 +116,15 @@ public class FieldWriteback extends FieldAggregation { Record targetRecord = targetRecordData.clone(); targetRecord.setID(targetEntity.getPrimaryField().getName(), targetRecordId); + targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now()); GeneralEntityServiceContextHolder.setRepeatedCheckMode(GeneralEntityServiceContextHolder.RCM_CHECK_MAIN); + try { - targetService.createOrUpdate(targetRecord); + useService.createOrUpdate(targetRecord); } finally { PrivilegesGuardContextHolder.getSkipGuardOnce(); + GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce(); } } diff --git a/src/main/java/com/rebuild/core/support/general/FieldValueHelper.java b/src/main/java/com/rebuild/core/support/general/FieldValueHelper.java index 5425e8133..342b85d92 100644 --- a/src/main/java/com/rebuild/core/support/general/FieldValueHelper.java +++ b/src/main/java/com/rebuild/core/support/general/FieldValueHelper.java @@ -298,7 +298,7 @@ public class FieldValueHelper { * @return */ public static boolean hasLength(Object o) { - if (o == null || NullValue.is(o)) return false; + if (NullValue.isNull(o)) return false; if (o.getClass().isArray()) return ((Object[]) o).length > 0; else return o.toString().length() > 0; } diff --git a/src/main/java/com/rebuild/core/support/task/TaskExecutors.java b/src/main/java/com/rebuild/core/support/task/TaskExecutors.java index deff6cf97..c67f4b872 100644 --- a/src/main/java/com/rebuild/core/support/task/TaskExecutors.java +++ b/src/main/java/com/rebuild/core/support/task/TaskExecutors.java @@ -18,6 +18,7 @@ import org.springframework.stereotype.Component; import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.concurrent.*; /** @@ -34,12 +35,17 @@ public class TaskExecutors extends DistributedJobLock { private static final int MAX_TASKS_NUMBER = Integer.max(Runtime.getRuntime().availableProcessors() / 2, 2); - private static final ExecutorService EXECS = new ThreadPoolExecutor( + private static final ExecutorService EXEC = new ThreadPoolExecutor( MAX_TASKS_NUMBER, MAX_TASKS_NUMBER, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(MAX_TASKS_NUMBER * 6)); private static final Map> TASKS = new ConcurrentHashMap<>(); + // 队列执行 + private static final ExecutorService SINGLE_QUEUE = new ThreadPoolExecutor( + 1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>()); + /** * 异步执行(提交给任务调度) * @@ -50,7 +56,7 @@ public class TaskExecutors extends DistributedJobLock { public static String submit(HeavyTask task, ID execUser) { String taskid = task.getClass().getSimpleName() + "-" + CodecUtils.randomCode(20); task.setUser(execUser); - EXECS.execute(task); + EXEC.execute(task); TASKS.put(taskid, task); return taskid; } @@ -79,6 +85,8 @@ public class TaskExecutors extends DistributedJobLock { } /** + * 获取任务 + * * @param taskid * @return */ @@ -95,13 +103,27 @@ public class TaskExecutors extends DistributedJobLock { task.run(); } + /** + * 排队执行(单线程) + * + * @param command + */ + public static void queue(Runnable command) { + SINGLE_QUEUE.execute(command); + } + /** * 停止任务执行器 */ public static void shutdown() { - List runs = EXECS.shutdownNow(); - if (!runs.isEmpty()) { - log.warn("{} task(s) were interrupted", runs.size()); + List t = EXEC.shutdownNow(); + if (!t.isEmpty()) { + log.warn("{} task(s) were interrupted", t.size()); + } + + List c = SINGLE_QUEUE.shutdownNow(); + if (!c.isEmpty()) { + log.warn("{} command(s) were interrupted", c.size()); } } @@ -109,21 +131,27 @@ public class TaskExecutors extends DistributedJobLock { @Scheduled(fixedRate = 300000, initialDelay = 300000) public void executeJob() { - if (TASKS.isEmpty() || !tryLock()) return; + if (!tryLock()) return; - log.info("{} task(s) in the queue", TASKS.size()); + if (!TASKS.isEmpty()) { + log.info("{} task(s) in the queue", TASKS.size()); + for (Map.Entry> e : TASKS.entrySet()) { + HeavyTask task = e.getValue(); + if (task.getCompletedTime() == null || !task.isCompleted()) { + continue; + } - for (Map.Entry> e : TASKS.entrySet()) { - HeavyTask task = e.getValue(); - if (task.getCompletedTime() == null || !task.isCompleted()) { - continue; + long leftTime = (System.currentTimeMillis() - task.getCompletedTime().getTime()) / 1000; + if (leftTime > 60 * 120) { + TASKS.remove(e.getKey()); + log.info("HeavyTask self-destroying : " + e.getKey()); + } } + } - long leftTime = (System.currentTimeMillis() - task.getCompletedTime().getTime()) / 1000; - if (leftTime > 60 * 120) { - TASKS.remove(e.getKey()); - log.info("HeavyTask self-destroying : " + e.getKey()); - } + Queue queue = ((ThreadPoolExecutor) SINGLE_QUEUE).getQueue(); + if (!queue.isEmpty()) { + log.info("{} command(s) in the single-queue", queue.size()); } } } diff --git a/src/main/java/com/rebuild/utils/codec/RbDateCodec.java b/src/main/java/com/rebuild/utils/codec/RbDateCodec.java index 0f7b8530e..f2331fe04 100644 --- a/src/main/java/com/rebuild/utils/codec/RbDateCodec.java +++ b/src/main/java/com/rebuild/utils/codec/RbDateCodec.java @@ -32,7 +32,7 @@ public class RbDateCodec extends DateCodec { public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features) throws IOException { SerializeWriter out = serializer.out; - if (object == null || NullValue.is(object)) { + if (NullValue.isNull(object)) { out.writeNull(); return; } diff --git a/src/main/java/com/rebuild/web/admin/metadata/MetaFieldController.java b/src/main/java/com/rebuild/web/admin/metadata/MetaFieldController.java index effb4bb75..4451018ef 100644 --- a/src/main/java/com/rebuild/web/admin/metadata/MetaFieldController.java +++ b/src/main/java/com/rebuild/web/admin/metadata/MetaFieldController.java @@ -198,26 +198,44 @@ public class MetaFieldController extends BaseController { Field refField = currentEntity.getField(getParameterNotNull(request, "field")); Entity referenceEntity = refField.getReferenceEntity(); - // 找到共同的引用字段 - Field[] currentEntityFields = MetadataSorter.sortFields(currentEntity, DisplayType.REFERENCE); + List list = getCoReferenceFields(currentEntity, referenceEntity, false); +// // TODO 开放明细实体关联主实体父级级联 +// if (currentEntity.getMainEntity() != null) { +// list.addAll(getCoReferenceFields(currentEntity.getMainEntity(), referenceEntity, true)); +// } + + return RespBody.ok(list); + } + + // 获取共同引用字段 + private List getCoReferenceFields(Entity entity, Entity referenceEntity, boolean fromDetail) { + Field[] entityFields = MetadataSorter.sortFields(entity, DisplayType.REFERENCE); Field[] referenceEntityFields = MetadataSorter.sortFields(referenceEntity, DisplayType.REFERENCE); - List together = new ArrayList<>(); - for (Field foo : currentEntityFields) { + List co = new ArrayList<>(); + for (Field foo : entityFields) { if (MetadataHelper.isCommonsField(foo)) continue; Entity fooEntity = foo.getReferenceEntity(); for (Field bar : referenceEntityFields) { + if (MetadataHelper.isCommonsField(bar)) continue; + if (fooEntity.equals(bar.getReferenceEntity())) { // 当前实体字段$$$$引用实体字段 String name = foo.getName() + MetadataHelper.SPLITER + bar.getName(); - String label = String.format("%s (%s)", EasyMetaFactory.getLabel(foo), EasyMetaFactory.getLabel(bar)); - together.add(JSONUtils.toJSONObject( + String label = String.format("%s (%s)", + EasyMetaFactory.getLabel(foo), EasyMetaFactory.getLabel(bar)); + + if (fromDetail) { + label = EasyMetaFactory.getLabel(entity) + "." + label; + name = entity.getName() + "." + name; + } + + co.add(JSONUtils.toJSONObject( new String[] { "name", "label" }, new String[] { name, label } )); } } } - - return RespBody.ok(together); + return co; } } diff --git a/src/main/java/com/rebuild/web/robot/approval/ApprovalAdminController.java b/src/main/java/com/rebuild/web/robot/approval/ApprovalAdminController.java index 092a561d4..6acbed6c7 100644 --- a/src/main/java/com/rebuild/web/robot/approval/ApprovalAdminController.java +++ b/src/main/java/com/rebuild/web/robot/approval/ApprovalAdminController.java @@ -35,8 +35,7 @@ import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; /** * @author devezhao zhaofang123@gmail.com @@ -126,31 +125,35 @@ public class ApprovalAdminController extends BaseController { Field[] deptRefFields = MetadataSorter.sortFields( MetadataHelper.getEntity(EntityHelper.Department), DisplayType.REFERENCE); + Set filterNames = new HashSet<>(); + Collections.addAll(filterNames, EntityHelper.ApprovalLastUser, "deptId"); + // 发起人 for (Field field : userRefFields) { - if (isRefUserField(field)) { + if (isRefUserOrDeptField(field, filterNames, true)) { fields.add(new String[] { ApprovalHelper.APPROVAL_SUBMITOR + field.getName(), textSubmitor + EasyMetaFactory.getLabel(field)} ); } } for (Field field : deptRefFields) { - if (isRefUserField(field)) { + if (isRefUserOrDeptField(field, filterNames, true)) { fields.add(new String[] { ApprovalHelper.APPROVAL_SUBMITOR + "deptId." + field.getName(), textSubmitor + textDept + EasyMetaFactory.getLabel(field)} ); } } + // (上一)审批人 for (Field field : userRefFields) { - if (isRefUserField(field)) { + if (isRefUserOrDeptField(field, filterNames, true)) { fields.add(new String[] { ApprovalHelper.APPROVAL_APPROVER + field.getName(), textApprover + EasyMetaFactory.getLabel(field)} ); } } for (Field field : deptRefFields) { - if (isRefUserField(field)) { + if (isRefUserOrDeptField(field, filterNames, true)) { fields.add(new String[] { ApprovalHelper.APPROVAL_APPROVER + "deptId." + field.getName(), textApprover + textDept + EasyMetaFactory.getLabel(field)} ); @@ -160,8 +163,7 @@ public class ApprovalAdminController extends BaseController { // 本实体字段 Field[] refFields = MetadataSorter.sortFields(entity, DisplayType.REFERENCE); for (Field field : refFields) { - int refEntity = field.getReferenceEntity().getEntityCode(); - if (refEntity == EntityHelper.User || refEntity == EntityHelper.Department) { + if (isRefUserOrDeptField(field, filterNames, false)) { fields.add(new String[] { field.getName(), EasyMetaFactory.getLabel(field)} ); } } @@ -170,9 +172,12 @@ public class ApprovalAdminController extends BaseController { new String[] { "id", "text" }, fields.toArray(new String[0][])); } - private boolean isRefUserField(Field field) { - return field.getReferenceEntity().getEntityCode() == EntityHelper.User - && !MetadataHelper.isCommonsField(field); + private boolean isRefUserOrDeptField(Field field, Set filterNames, boolean excludeCommon) { + if (excludeCommon && MetadataHelper.isCommonsField(field)) return false; + if (filterNames.contains(field.getName())) return false; + + int ec = field.getReferenceEntity().getEntityCode(); + return ec == EntityHelper.User || ec == EntityHelper.Department; } @PostMapping("approval/user-fields-show") @@ -201,8 +206,8 @@ public class ApprovalAdminController extends BaseController { if (userField.getOwnEntity().getEntityCode() == EntityHelper.Department) { fieldText = textDept + fieldText; } - fieldText = (idOrField.startsWith(ApprovalHelper.APPROVAL_SUBMITOR) ? textSubmitor : textApprover) - + fieldText; + fieldText = (idOrField.startsWith(ApprovalHelper.APPROVAL_SUBMITOR) + ? textSubmitor : textApprover) + fieldText; shows.add(new String[] { idOrField, fieldText }); } diff --git a/src/main/java/com/rebuild/web/robot/approval/ApprovalController.java b/src/main/java/com/rebuild/web/robot/approval/ApprovalController.java index 52f79ec66..80995dd23 100644 --- a/src/main/java/com/rebuild/web/robot/approval/ApprovalController.java +++ b/src/main/java/com/rebuild/web/robot/approval/ApprovalController.java @@ -85,11 +85,9 @@ public class ApprovalController extends BaseController { } // 审批中提交人可撤回 - if (stateVal == ApprovalState.PROCESSING.getState()) { - ID submitter = ApprovalHelper.getSubmitter(recordId); - if (user.equals(submitter)) { - data.put("canCancel", true); - } + if (stateVal == ApprovalState.PROCESSING.getState() + && user.equals(ApprovalHelper.getSubmitter(recordId))) { + data.put("canCancel", true); } } @@ -123,6 +121,12 @@ public class ApprovalController extends BaseController { data.put("signMode", nextNodes.getSignMode()); data.put("useGroup", nextNodes.getGroupId()); +// // 审批中提交人可撤回 +// if (ApprovalHelper.getApprovalState(recordId) == ApprovalState.PROCESSING +// && user.equals(ApprovalHelper.getSubmitter(recordId))) { +// data.put("canCancel", true); +// } + // 可修改字段 JSONArray editableFields = approvalProcessor.getCurrentNode().getEditableFields(); if (editableFields != null && !editableFields.isEmpty()) { diff --git a/src/main/java/com/rebuild/web/user/UserAvatar.java b/src/main/java/com/rebuild/web/user/UserAvatar.java index 77ce6c4d0..e2598c582 100644 --- a/src/main/java/com/rebuild/web/user/UserAvatar.java +++ b/src/main/java/com/rebuild/web/user/UserAvatar.java @@ -88,6 +88,13 @@ public class UserAvatar extends BaseController { ServletUtils.addCacheHead(response, 30); String avatarUrl = realUser.getAvatarUrl(); + + // 外部地址 + if (avatarUrl != null && (avatarUrl.startsWith("http://") || avatarUrl.startsWith("https://"))) { + response.sendRedirect(avatarUrl); + return; + } + avatarUrl = QiniuCloud.encodeUrl(avatarUrl); if (avatarUrl != null) { int w = getIntParameter(request, "w", 100); diff --git a/src/main/java/com/rebuild/web/user/signup/LoginAction.java b/src/main/java/com/rebuild/web/user/signup/LoginAction.java index 76f715402..296cc4852 100644 --- a/src/main/java/com/rebuild/web/user/signup/LoginAction.java +++ b/src/main/java/com/rebuild/web/user/signup/LoginAction.java @@ -10,7 +10,6 @@ package com.rebuild.web.user.signup; import cn.devezhao.commons.CalendarUtils; import cn.devezhao.commons.CodecUtils; import cn.devezhao.commons.ObjectUtils; -import cn.devezhao.commons.ThreadPool; import cn.devezhao.commons.web.ServletUtils; import cn.devezhao.commons.web.WebUtils; import cn.devezhao.persist4j.Record; @@ -20,6 +19,7 @@ import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.privileges.UserService; import com.rebuild.core.support.KVStorage; import com.rebuild.core.support.License; +import com.rebuild.core.support.task.TaskExecutors; import com.rebuild.utils.AES; import com.rebuild.web.BaseController; import eu.bitwalker.useragentutils.DeviceType; @@ -41,7 +41,7 @@ public class LoginAction extends BaseController { public static final String CK_AUTOLOGIN = "rb.alt"; public static final String SK_USER_THEME = "currentUseTheme"; - + protected static final String SK_NEED_VCODE = "needLoginVCode"; protected static final String SK_START_TOUR = "needStartTour"; @@ -64,7 +64,7 @@ public class LoginAction extends BaseController { ServletUtils.removeCookie(request, response, CK_AUTOLOGIN); } - ThreadPool.exec(() -> createLoginLog(request, user)); + createLoginLog(request, user); ServletUtils.setSessionAttribute(request, WebUtils.CURRENT_USER, user); ServletUtils.setSessionAttribute(request, SK_USER_THEME, KVStorage.getCustomValue("THEME." + user)); @@ -114,14 +114,17 @@ public class LoginAction extends BaseController { String ipAddr = StringUtils.defaultString(ServletUtils.getRemoteAddr(request), "127.0.0.1"); - Record record = EntityHelper.forNew(EntityHelper.LoginLog, UserService.SYSTEM_USER); + final Record record = EntityHelper.forNew(EntityHelper.LoginLog, UserService.SYSTEM_USER); record.setID("user", user); record.setString("ipAddr", ipAddr); record.setString("userAgent", uaClear); record.setDate("loginTime", CalendarUtils.now()); - Application.getCommonsService().create(record); - License.siteApiNoCache( - String.format("api/authority/user/echo?user=%s&ip=%s&ua=%s", user, ipAddr, CodecUtils.urlEncode(ua))); + TaskExecutors.queue(() -> { + Application.getCommonsService().create(record); + + License.siteApiNoCache( + String.format("api/authority/user/echo?user=%s&ip=%s&ua=%s", user, ipAddr, CodecUtils.urlEncode(ua))); + }); } } diff --git a/src/main/resources/web/assets/css/meta-edit.css b/src/main/resources/web/assets/css/meta-edit.css index 4f6885f07..bb50ef223 100644 --- a/src/main/resources/web/assets/css/meta-edit.css +++ b/src/main/resources/web/assets/css/meta-edit.css @@ -223,12 +223,9 @@ a#entityIcon:hover { } .common-patt .badge { - margin: 0; - margin-right: 3px; - margin-top: 6px; + margin: 6px 3px 0 0; font-weight: normal; border: 0 none; - background-color: #eee; background-color: rgb(245, 247, 249); } diff --git a/src/main/resources/web/assets/css/rb-page.css b/src/main/resources/web/assets/css/rb-page.css index a59e950b6..d71eaaf7e 100644 --- a/src/main/resources/web/assets/css/rb-page.css +++ b/src/main/resources/web/assets/css/rb-page.css @@ -196,7 +196,7 @@ button[disabled] { .badge.text-id { font-weight: normal; color: #777; - font-size: 12px; + font-size: 12px !important; text-transform: uppercase; line-height: 1.2 !important; } @@ -4716,3 +4716,12 @@ pre.unstyle { flex: 0 0 66.666667%; max-width: 66.666667%; } + +.dropdown-menu.entity-switch { + min-width: 220px; +} + +.dropdown-menu.entity-switch .dropdown-item.current, +.dropdown-menu.entity-switch .dropdown-item.current .icon { + color: #4285f4; +} diff --git a/src/main/resources/web/assets/js/metadata/entity-switch.js b/src/main/resources/web/assets/js/metadata/entity-switch.js index 3333153fe..a35f1cbda 100644 --- a/src/main/resources/web/assets/js/metadata/entity-switch.js +++ b/src/main/resources/web/assets/js/metadata/entity-switch.js @@ -14,11 +14,14 @@ $(document).ready(() => { href = href.split('/field/')[0] + '/fields' } - const $ul = $('').appendTo('.aside-header') - $ul.perfectScrollbar() + const $ul = $('').appendTo('.aside-header') function _render(item) { - $(` ${item.entityLabel}`).appendTo($ul) + const $item = $(` ${item.entityLabel}`) + if (entity === item.entityName) { + $item.addClass('current') + } + $item.appendTo($ul) } $.get('/admin/entity/entity-list?detail=true&bizz=true', (res) => { @@ -28,6 +31,8 @@ $(document).ready(() => { $(res.data).each((idx, item) => { if (item.builtin === false) _render(item) }) + + $ul.perfectScrollbar() }) $('').appendTo('.aside-header .title') diff --git a/src/main/resources/web/assets/js/rb-forms.append.js b/src/main/resources/web/assets/js/rb-forms.append.js index 9dc731c1e..0a92b7af5 100644 --- a/src/main/resources/web/assets/js/rb-forms.append.js +++ b/src/main/resources/web/assets/js/rb-forms.append.js @@ -193,6 +193,7 @@ class ReferenceSearcher extends RbModal { destroy() { this.setState({ destroy: true }) + window.referenceSearch__dlg = null } } diff --git a/src/main/resources/web/assets/js/rb-forms.js b/src/main/resources/web/assets/js/rb-forms.js index 4cf69328c..5b34f2edc 100644 --- a/src/main/resources/web/assets/js/rb-forms.js +++ b/src/main/resources/web/assets/js/rb-forms.js @@ -313,11 +313,12 @@ class RbForm extends React.Component { } componentDidMount() { + // 新纪录初始值 if (this.isNew) { this.props.children.map((child) => { const val = child.props.value if (val && child.props.readonly !== true) { - // 复合型值 {id:xxx, text:xxx} + // {id:xxx, text:xxx} this.setFieldValue(child.props.field, typeof val === 'object' ? val.id : val) } }) @@ -340,16 +341,16 @@ class RbForm extends React.Component { // 设置字段值 setFieldValue(field, value, error) { this.__FormData[field] = { value: value, error: error } - if (!error && this._onFieldValueChange_calls) this._onFieldValueChange_calls.forEach((c) => c({ name: field, value: value })) + if (!error) this._onFieldValueChangeCall(field, value) // eslint-disable-next-line no-console if (rb.env === 'dev') console.log('FV1 ... ' + JSON.stringify(this.__FormData)) } // 避免无意义更新 - setFieldUnchanged(field) { + setFieldUnchanged(field, originValue) { delete this.__FormData[field] - if (this._onFieldValueChange_calls) this._onFieldValueChange_calls.forEach((c) => c({ name: field })) + this._onFieldValueChangeCall(field, originValue) // eslint-disable-next-line no-console if (rb.env === 'dev') console.log('FV2 ... ' + JSON.stringify(this.__FormData)) @@ -361,6 +362,11 @@ class RbForm extends React.Component { c.push(call) this._onFieldValueChange_calls = c } + _onFieldValueChangeCall(field, value) { + if (this._onFieldValueChange_calls) { + this._onFieldValueChange_calls.forEach((c) => c({ name: field, value: value })) + } + } // 保存并添加明细 static __NEXT_ADDDETAIL = 102 @@ -529,8 +535,8 @@ class RbFormElement extends React.Component { title={this.state.hasError} type="text" value={value || ''} - onChange={(e) => this.handleChange(e)} - onBlur={this.props.readonly ? null : () => this.checkValue()} + onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)} + // onBlur={this.props.readonly ? null : () => this.checkValue()} readOnly={this.props.readonly} maxLength={this.props.maxLength || 200} /> @@ -574,7 +580,7 @@ class RbFormElement extends React.Component { if (this.isValueUnchanged() && !this.props.$$$parent.isNew) { if (err) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg) - else this.props.$$$parent.setFieldUnchanged(this.props.field) + else this.props.$$$parent.setFieldUnchanged(this.props.field, this.state.value) } else { this.setState({ hasError: err }) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg) @@ -761,8 +767,8 @@ class RbFormNumber extends RbFormText { title={this.state.hasError} type="text" value={this._removeComma(value) || ''} - onChange={(e) => this.handleChange(e)} - onBlur={this.props.readonly ? null : () => this.checkValue()} + onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)} + // onBlur={this.props.readonly ? null : () => this.checkValue()} readOnly={this.props.readonly} maxLength="29" /> @@ -775,51 +781,56 @@ class RbFormNumber extends RbFormText { // 表单计算(视图下无效) if (this.props.calcFormula && !this.props.onView) { const calcFormula = this.props.calcFormula.replace(new RegExp('×', 'ig'), '*').replace(new RegExp('÷', 'ig'), '/') - const watchFields = calcFormula.match(/\{([a-z0-9]+)\}/gi) || [] + const fixed = this.props.decimalFormat ? (this.props.decimalFormat.split('.')[1] || '').length : 0 - this.calcFormula__values = {} - // 初始值 + // 等待字段初始化完毕 setTimeout(() => { + const calcFormulaValues = {} + const watchFields = calcFormula.match(/\{([a-z0-9]+)\}/gi) || [] + watchFields.forEach((item) => { const name = item.substr(1, item.length - 2) const fieldComp = this.props.$$$parent.refs[`fieldcomp-${name}`] if (fieldComp && fieldComp.state.value) { - this.calcFormula__values[name] = this._removeComma(fieldComp.state.value) + calcFormulaValues[name] = this._removeComma(fieldComp.state.value) } }) - }, 400) - // 小数位 - const fixed = this.props.decimalFormat ? (this.props.decimalFormat.split('.')[1] || '').length : 0 + // 表单计算 + this.props.$$$parent.onFieldValueChange((s) => { + if (!watchFields.includes(`{${s.name}}`)) { + if (rb.env === 'dev') console.log('onFieldValueChange :', s) + return false + } else if (rb.env === 'dev') { + console.log('onFieldValueChange for calcFormula :', s) + } - // 表单计算 - this.props.$$$parent.onFieldValueChange((s) => { - if (!watchFields.includes(`{${s.name}}`)) return + if (s.value) { + calcFormulaValues[s.name] = this._removeComma(s.value) + } else { + delete calcFormulaValues[s.name] + } - if (s.value) { - this.calcFormula__values[s.name] = this._removeComma(s.value) - } else { - delete this.calcFormula__values[s.name] - } + let formula = calcFormula + for (let key in calcFormulaValues) { + formula = formula.replace(new RegExp(`{${key}}`, 'ig'), calcFormulaValues[key] || 0) + } - let formula = calcFormula - for (let key in this.calcFormula__values) { - formula = formula.replace(new RegExp(`{${key}}`, 'ig'), this.calcFormula__values[key] || 0) - } + if (formula.includes('{')) { + this.setValue(null) + return false + } - if (formula.includes('{')) { - this.setValue(null) - return - } - - try { - let calcv = null - eval(`calcv = ${formula}`) - if (!isNaN(calcv)) this.setValue(calcv.toFixed(fixed)) - } catch (err) { - if (rb.env === 'dev') console.log(err) - } - }) + try { + let calcv = null + eval(`calcv = ${formula}`) + if (!isNaN(calcv)) this.setValue(calcv.toFixed(fixed)) + } catch (err) { + if (rb.env === 'dev') console.log(err) + } + return true + }) + }, 200) } } @@ -859,8 +870,8 @@ class RbFormTextarea extends RbFormElement { className={`form-control form-control-sm row3x ${this.state.hasError ? 'is-invalid' : ''} ${this.props.useMdedit && this.props.readonly ? 'cm-readonly' : ''}`} title={this.state.hasError} value={this.state.value || ''} - onChange={(e) => this.handleChange(e)} - onBlur={this.props.readonly ? null : () => this.checkValue()} + onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)} + // onBlur={this.props.readonly ? null : () => this.checkValue()} readOnly={this.props.readonly} maxLength="6000" /> @@ -973,8 +984,8 @@ class RbFormDateTime extends RbFormElement { title={this.state.hasError} type="text" value={this.state.value || ''} - onChange={(e) => this.handleChange(e)} - onBlur={this.props.readonly ? null : () => this.checkValue()} + onChange={(e) => this.handleChange(e, this.props.readonly ? false : true)} + // onBlur={this.props.readonly ? null : () => this.checkValue()} maxLength="20" /> this.handleClear()} /> @@ -1405,7 +1416,8 @@ class RbFormReference extends RbFormElement { componentWillUnmount() { super.componentWillUnmount() - if (this._ReferenceSearcher) { + + if (this._ReferenceSearcher && !this._hasCascadingField) { this._ReferenceSearcher.destroy() this._ReferenceSearcher = null } diff --git a/src/main/resources/web/assets/js/trigger/trigger.FIELDAGGREGATION.js b/src/main/resources/web/assets/js/trigger/trigger.FIELDAGGREGATION.js index e0879b4c7..ab67c10cd 100644 --- a/src/main/resources/web/assets/js/trigger/trigger.FIELDAGGREGATION.js +++ b/src/main/resources/web/assets/js/trigger/trigger.FIELDAGGREGATION.js @@ -32,7 +32,7 @@ class ContentFieldAggregation extends ActionContentSpec { {this.state.hadApproval && (
- {$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作)')} + {$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作),建议启用“允许强制更新”')}
)} @@ -120,6 +120,16 @@ class ContentFieldAggregation extends ActionContentSpec { + +
- -
- -
- this.dataAdvFilter()}> - {this.state.dataFilterItems ? `${$L('已设置条件')} (${this.state.dataFilterItems})` : $L('点击设置')} - -
{$L('仅会聚合符合过滤条件的数据')}
+
+ +
@@ -169,6 +178,7 @@ class ContentFieldAggregation extends ActionContentSpec { if (content) { $(this._$readonlyFields).attr('checked', content.readonlyFields === true) + $(this._$forceUpdate).attr('checked', content.forceUpdate === true) this.saveAdvFilter(content.dataFilter) } } @@ -320,6 +330,7 @@ class ContentFieldAggregation extends ActionContentSpec { targetEntity: $(this._$targetEntity).val(), items: this.state.items || [], readonlyFields: $(this._$readonlyFields).prop('checked'), + forceUpdate: $(this._$forceUpdate).prop('checked'), dataFilter: this._advFilter__data, } diff --git a/src/main/resources/web/assets/js/trigger/trigger.FIELDWRITEBACK.js b/src/main/resources/web/assets/js/trigger/trigger.FIELDWRITEBACK.js index 913c1dad3..a1b86e4e4 100644 --- a/src/main/resources/web/assets/js/trigger/trigger.FIELDWRITEBACK.js +++ b/src/main/resources/web/assets/js/trigger/trigger.FIELDWRITEBACK.js @@ -45,7 +45,7 @@ class ContentFieldWriteback extends ActionContentSpec { {this.state.hadApproval && (
- {$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作)')} + {$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作),建议启用“允许强制更新”')}
)} @@ -155,6 +155,15 @@ class ContentFieldWriteback extends ActionContentSpec {
+
+ +
@@ -183,6 +192,7 @@ class ContentFieldWriteback extends ActionContentSpec { if (content) { $(this._$readonlyFields).attr('checked', content.readonlyFields === true) + $(this._$forceUpdate).attr('checked', content.forceUpdate === true) } } @@ -290,6 +300,7 @@ class ContentFieldWriteback extends ActionContentSpec { targetEntity: $(this._$targetEntity).val(), items: this.state.items, readonlyFields: $(this._$readonlyFields).prop('checked'), + forceUpdate: $(this._$forceUpdate).prop('checked'), } if (!content.targetEntity) { RbHighbar.create($L('请选择目标实体')) diff --git a/src/test/java/com/rebuild/core/support/task/TaskExecutorsTest.java b/src/test/java/com/rebuild/core/support/task/TaskExecutorsTest.java new file mode 100644 index 000000000..f7472cb68 --- /dev/null +++ b/src/test/java/com/rebuild/core/support/task/TaskExecutorsTest.java @@ -0,0 +1,31 @@ +/* +Copyright (c) REBUILD and/or its owners. All rights reserved. + +rebuild is dual-licensed under commercial and open source licenses (GPLv3). +See LICENSE and COMMERCIAL in the project root for license information. +*/ + +package com.rebuild.core.support.task; + +import cn.devezhao.commons.ThreadPool; +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author devezhao + * @since 04/08/2022 + */ +class TaskExecutorsTest { + + @Test + void queue() { + AtomicInteger idx = new AtomicInteger(0); + for (int i = 0; i < 10; i++) { + TaskExecutors.queue(() -> { + ThreadPool.waitFor(500); + System.out.println("command " + idx.incrementAndGet()); + }); + } + } +} \ No newline at end of file