mirror of
https://github.com/getrebuild/rebuild.git
synced 2025-03-13 15:44:26 +08:00
Parent cascading field (#451)
* fix: calcFormula * trigger forceUpdate * 发起人/审批人 支持部门引用字段 * single-queue * better code
This commit is contained in:
parent
588baa9861
commit
2491297ad0
27 changed files with 338 additions and 187 deletions
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit f62d1b05ef27ccf62d1a213f4ddf03184e6deb66
|
||||
Subproject commit e6951a36179a8e1087a7888f948c8b3eede860b4
|
2
pom.xml
2
pom.xml
|
@ -273,7 +273,7 @@
|
|||
<dependency>
|
||||
<groupId>com.github.devezhao</groupId>
|
||||
<artifactId>persist4j</artifactId>
|
||||
<version>ft-time-SNAPSHOT</version>
|
||||
<version>1.5.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<String, HeavyTask<?>> 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<Runnable> runs = EXECS.shutdownNow();
|
||||
if (!runs.isEmpty()) {
|
||||
log.warn("{} task(s) were interrupted", runs.size());
|
||||
List<Runnable> t = EXEC.shutdownNow();
|
||||
if (!t.isEmpty()) {
|
||||
log.warn("{} task(s) were interrupted", t.size());
|
||||
}
|
||||
|
||||
List<Runnable> 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<String, HeavyTask<?>> e : TASKS.entrySet()) {
|
||||
HeavyTask<?> task = e.getValue();
|
||||
if (task.getCompletedTime() == null || !task.isCompleted()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Map.Entry<String, HeavyTask<?>> 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<Runnable> queue = ((ThreadPoolExecutor) SINGLE_QUEUE).getQueue();
|
||||
if (!queue.isEmpty()) {
|
||||
log.info("{} command(s) in the single-queue", queue.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<JSONObject> list = getCoReferenceFields(currentEntity, referenceEntity, false);
|
||||
// // TODO 开放明细实体关联主实体父级级联
|
||||
// if (currentEntity.getMainEntity() != null) {
|
||||
// list.addAll(getCoReferenceFields(currentEntity.getMainEntity(), referenceEntity, true));
|
||||
// }
|
||||
|
||||
return RespBody.ok(list);
|
||||
}
|
||||
|
||||
// 获取共同引用字段
|
||||
private List<JSONObject> getCoReferenceFields(Entity entity, Entity referenceEntity, boolean fromDetail) {
|
||||
Field[] entityFields = MetadataSorter.sortFields(entity, DisplayType.REFERENCE);
|
||||
Field[] referenceEntityFields = MetadataSorter.sortFields(referenceEntity, DisplayType.REFERENCE);
|
||||
|
||||
List<JSONObject> together = new ArrayList<>();
|
||||
for (Field foo : currentEntityFields) {
|
||||
List<JSONObject> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> 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<String> 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 });
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -14,11 +14,14 @@ $(document).ready(() => {
|
|||
href = href.split('/field/')[0] + '/fields'
|
||||
}
|
||||
|
||||
const $ul = $('<ul class="dropdown-menu auto-scroller"></ul>').appendTo('.aside-header')
|
||||
$ul.perfectScrollbar()
|
||||
const $ul = $('<ul class="dropdown-menu auto-scroller entity-switch"></ul>').appendTo('.aside-header')
|
||||
|
||||
function _render(item) {
|
||||
$(`<a class="dropdown-item" href="${href.replace(`/${entity}/`, `/${item.entityName}/`)}"><i class="icon zmdi zmdi-${item.icon}"></i> ${item.entityLabel}</a>`).appendTo($ul)
|
||||
const $item = $(`<a class="dropdown-item" href="${href.replace(`/${entity}/`, `/${item.entityName}/`)}"><i class="icon zmdi zmdi-${item.icon}"></i> ${item.entityLabel}</a>`)
|
||||
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()
|
||||
})
|
||||
|
||||
$('<i class="icon zmdi zmdi-caret-down ml-1 text-muted"></i>').appendTo('.aside-header .title')
|
||||
|
|
|
@ -193,6 +193,7 @@ class ReferenceSearcher extends RbModal {
|
|||
|
||||
destroy() {
|
||||
this.setState({ destroy: true })
|
||||
window.referenceSearch__dlg = null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
<span className={'zmdi zmdi-close clean ' + (this.state.value ? '' : 'hide')} onClick={() => 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
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class ContentFieldAggregation extends ActionContentSpec {
|
|||
{this.state.hadApproval && (
|
||||
<div className="form-text text-danger">
|
||||
<i className="zmdi zmdi-alert-triangle fs-16 down-1 mr-1" />
|
||||
{$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作)')}
|
||||
{$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作),建议启用“允许强制更新”')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -120,6 +120,16 @@ class ContentFieldAggregation extends ActionContentSpec {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group row">
|
||||
<label className="col-md-12 col-lg-3 col-form-label text-lg-right">{$L('聚合数据条件')}</label>
|
||||
<div className="col-md-12 col-lg-9">
|
||||
<a className="btn btn-sm btn-link pl-0 text-left down-2" onClick={() => this.dataAdvFilter()}>
|
||||
{this.state.dataFilterItems ? `${$L('已设置条件')} (${this.state.dataFilterItems})` : $L('点击设置')}
|
||||
</a>
|
||||
<div className="form-text mt-0">{$L('仅会聚合符合过滤条件的数据')}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group row pb-0">
|
||||
<label className="col-md-12 col-lg-3 col-form-label" />
|
||||
<div className="col-md-12 col-lg-9">
|
||||
|
@ -130,16 +140,15 @@ class ContentFieldAggregation extends ActionContentSpec {
|
|||
<i className="zmdi zmdi-help zicon down-1" data-toggle="tooltip" title={$L('本选项仅针对表单有效')} />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group row">
|
||||
<label className="col-md-12 col-lg-3 col-form-label text-lg-right">{$L('聚合数据条件')}</label>
|
||||
<div className="col-md-12 col-lg-9">
|
||||
<a className="btn btn-sm btn-link pl-0 text-left down-2" onClick={() => this.dataAdvFilter()}>
|
||||
{this.state.dataFilterItems ? `${$L('已设置条件')} (${this.state.dataFilterItems})` : $L('点击设置')}
|
||||
</a>
|
||||
<div className="form-text mt-0">{$L('仅会聚合符合过滤条件的数据')}</div>
|
||||
<div className="mt-2">
|
||||
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
|
||||
<input className="custom-control-input" type="checkbox" ref={(c) => (this._$forceUpdate = c)} />
|
||||
<span className="custom-control-label">
|
||||
{$L('允许强制更新')}
|
||||
<i className="zmdi zmdi-help zicon down-1" data-toggle="tooltip" title={$L('强制更新只读记录')} />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ class ContentFieldWriteback extends ActionContentSpec {
|
|||
{this.state.hadApproval && (
|
||||
<div className="form-text text-danger">
|
||||
<i className="zmdi zmdi-alert-triangle fs-16 down-1 mr-1" />
|
||||
{$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作)')}
|
||||
{$L('目标实体已启用审批流程,可能影响源实体操作 (触发动作),建议启用“允许强制更新”')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -155,6 +155,15 @@ class ContentFieldWriteback extends ActionContentSpec {
|
|||
<i className="zmdi zmdi-help zicon down-1" data-toggle="tooltip" title={$L('本选项仅针对表单有效')} />
|
||||
</span>
|
||||
</label>
|
||||
<div className="mt-2">
|
||||
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
|
||||
<input className="custom-control-input" type="checkbox" ref={(c) => (this._$forceUpdate = c)} />
|
||||
<span className="custom-control-label">
|
||||
{$L('允许强制更新')}
|
||||
<i className="zmdi zmdi-help zicon down-1" data-toggle="tooltip" title={$L('强制更新只读记录')} />
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -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('请选择目标实体'))
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
Copyright (c) REBUILD <https://getrebuild.com/> and/or its owners. All rights reserved.
|
||||
|
||||
rebuild is dual-licensed under commercial and open source licenses (GPLv3).
|
||||
See LICENSE and COMMERCIAL in the project root for license information.
|
||||
*/
|
||||
|
||||
package com.rebuild.core.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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue