diff --git a/@rbv b/@rbv index e83ed9efb..0ace77d3b 160000 --- a/@rbv +++ b/@rbv @@ -1 +1 @@ -Subproject commit e83ed9efb79ef2bffacbbe8c2288aecc7bcd2042 +Subproject commit 0ace77d3b95da6f61691bd2610230c4bd979011d diff --git a/pom.xml b/pom.xml index b4a139fd8..7019e6f30 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.rebuild rebuild - 3.4.3 + 3.4.4 rebuild Building your business-systems freely! @@ -38,19 +38,17 @@ - + + - --> com.github.eirslett frontend-maven-plugin diff --git a/src/main/java/com/rebuild/core/Application.java b/src/main/java/com/rebuild/core/Application.java index 5dcf8432d..620265639 100644 --- a/src/main/java/com/rebuild/core/Application.java +++ b/src/main/java/com/rebuild/core/Application.java @@ -73,11 +73,11 @@ public class Application implements ApplicationListener /** * Rebuild Version */ - public static final String VER = "3.4.3"; + public static final String VER = "3.4.4"; /** * Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2} */ - public static final int BUILD = 3040308; + public static final int BUILD = 3040409; static { // Driver for DB diff --git a/src/main/java/com/rebuild/core/service/BaseService.java b/src/main/java/com/rebuild/core/service/BaseService.java index a2149dd4a..e6568f883 100644 --- a/src/main/java/com/rebuild/core/service/BaseService.java +++ b/src/main/java/com/rebuild/core/service/BaseService.java @@ -149,7 +149,8 @@ public class BaseService extends InternalPersistService { for (Field n2nField : n2nFields) { ID[] newRefs; if (isNew) { - newRefs = record.getIDArray(n2nField.getName()); + Object maybeNull = record.getObjectValue(n2nField.getName()); + newRefs = NullValue.is(maybeNull) ? ID.EMPTY_ID_ARRAY : (ID[]) maybeNull; if (newRefs == null || newRefs.length == 0) continue; } else { if (record.hasValue(n2nField.getName())) { diff --git a/src/main/java/com/rebuild/core/service/approval/FlowParser.java b/src/main/java/com/rebuild/core/service/approval/FlowParser.java index e97f392c0..593af78c2 100644 --- a/src/main/java/com/rebuild/core/service/approval/FlowParser.java +++ b/src/main/java/com/rebuild/core/service/approval/FlowParser.java @@ -14,7 +14,14 @@ import com.rebuild.core.support.i18n.Language; import com.rebuild.utils.JSONUtils; import org.apache.commons.lang.StringUtils; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; /** * 流程解析 @@ -102,11 +109,14 @@ public class FlowParser { return Collections.emptyList(); } - // 非条件分支,只会有一个节点 + // 非条件分支只会有一个节点 if (!FlowNode.TYPE_BRANCH.equals(next.get(0).getType())) { return next; } + // fix:Gitee#I8326Z 移除非条件节点 + next.removeIf(flowNode -> !flowNode.getType().equals(FlowNode.TYPE_BRANCH)); + // 条件节点优先级排序 next.sort((o1, o2) -> { int p1 = ((FlowBranch) o1).getPriority(); diff --git a/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java b/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java index 340fdd4d2..b2871ab79 100644 --- a/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java +++ b/src/main/java/com/rebuild/core/service/query/AdvFilterParser.java @@ -335,26 +335,32 @@ public class AdvFilterParser extends SetUser { int x = NumberUtils.toInt(value); value = formatDate(addDay(x), 0); } else if (ParseHelper.EVW.equalsIgnoreCase(op) || ParseHelper.EVM.equalsIgnoreCase(op)) { - Calendar c = CalendarUtils.getInstance(); + final Calendar today = CalendarUtils.getInstance(); + int x = NumberUtils.toInt(value); if (ParseHelper.EVW.equalsIgnoreCase(op)) { - boolean isSunday = c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY; - if (isSunday) c.add(Calendar.DAY_OF_WEEK, -1); + boolean isSunday = today.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY; + if (isSunday) today.add(Calendar.DAY_OF_WEEK, -1); if (x < 1) x = 1; if (x > 7) x = 7; x += 1; if (x <= 7) { - c.set(Calendar.DAY_OF_WEEK, x); + today.set(Calendar.DAY_OF_WEEK, x); } else { - c.set(Calendar.DAY_OF_WEEK, 7); - c.add(Calendar.DAY_OF_WEEK, 1); + today.set(Calendar.DAY_OF_WEEK, 7); + today.add(Calendar.DAY_OF_WEEK, 1); } } else { if (x < 1) x = 1; if (x > 31) x = 31; - c.set(Calendar.DAY_OF_MONTH, x); + + // v3.4.4 每月最后一天 + int maxDayOfMonth = today.getActualMaximum(Calendar.DAY_OF_MONTH); + if (x > maxDayOfMonth) x = maxDayOfMonth; + + today.set(Calendar.DAY_OF_MONTH, x); } - value = formatDate(c.getTime(), 0); + value = formatDate(today.getTime(), 0); } else if (ParseHelper.YTA.equalsIgnoreCase(op)) { value = formatDate(addDay(-1), 0); diff --git a/src/main/java/com/rebuild/core/service/trigger/impl/AutoHoldTriggerAction.java b/src/main/java/com/rebuild/core/service/trigger/impl/AutoHoldTriggerAction.java new file mode 100644 index 000000000..52f1cd454 --- /dev/null +++ b/src/main/java/com/rebuild/core/service/trigger/impl/AutoHoldTriggerAction.java @@ -0,0 +1,160 @@ +/*! +Copyright (c) Ruifang Tech and/or its owners. All rights reserved. +*/ + +package com.rebuild.core.service.trigger.impl; + +import cn.devezhao.persist4j.Entity; +import cn.devezhao.persist4j.Field; +import cn.devezhao.persist4j.dialect.FieldType; +import cn.devezhao.persist4j.engine.ID; +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import com.rebuild.core.Application; +import com.rebuild.core.metadata.EntityHelper; +import com.rebuild.core.metadata.MetadataHelper; +import com.rebuild.core.privileges.bizz.InternalPermission; +import com.rebuild.core.service.approval.ApprovalHelper; +import com.rebuild.core.service.approval.ApprovalState; +import com.rebuild.core.service.general.OperatingContext; +import com.rebuild.core.service.trigger.ActionContext; +import com.rebuild.core.service.trigger.TriggerAction; +import com.rebuild.core.service.trigger.TriggerException; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * @author devezhao + * @since 2023/9/27 + */ +@Slf4j +public abstract class AutoHoldTriggerAction extends TriggerAction { + + private Set willRecords; + + protected AutoHoldTriggerAction(ActionContext actionContext) { + super(actionContext); + } + + // 删除前保持 + @Override + protected void prepare(OperatingContext operatingContext) throws TriggerException { + if (operatingContext.getAction() == InternalPermission.DELETE_BEFORE) { + willRecords = getRelatedRecords( + actionContext, operatingContext.getAnyRecord().getPrimary()); + } + } + + /** + * 获取待处理记录 + * + * @param operatingContext + * @return + */ + protected Set getWillRecords(OperatingContext operatingContext) { + if (willRecords == null) { + willRecords = getRelatedRecords( + actionContext, operatingContext.getAnyRecord().getPrimary()); + } + return willRecords; + } + + /** + * 获取相关记录 + * + * @param actionContext + * @param sourceRecordId + * @return + */ + protected static Set getRelatedRecords(ActionContext actionContext, ID sourceRecordId) { + // 共享的需要使用记录 ID + if (sourceRecordId.getEntityCode() == EntityHelper.ShareAccess) { + Object[] o = Application.getQueryFactory().uniqueNoFilter(sourceRecordId, "recordId"); + if (o == null) return Collections.emptySet(); + sourceRecordId = (ID) o[0]; + } + + final JSONObject actionContent = (JSONObject) actionContext.getActionContent(); + + Entity sourceEntity = actionContext.getSourceEntity(); + JSONArray fs = actionContent.getJSONArray("fields"); + + List fieldsSelf = new ArrayList<>(); + List fieldsRefto = new ArrayList<>(); + boolean hasSelf = false; + + for (Object o : fs) { + String field = (String) o; + if (field.contains(".")) { + String[] fieldAndEntity = field.split("\\."); + if (MetadataHelper.containsField(fieldAndEntity[1], fieldAndEntity[0])) { + fieldsRefto.add(MetadataHelper.getField(fieldAndEntity[1], fieldAndEntity[0])); + } + + } else { + if (SOURCE_SELF.equals(field)) { + fieldsSelf.add(sourceEntity.getPrimaryField().getName()); + hasSelf = true; + } if (sourceEntity.containsField(field)) { + fieldsSelf.add(field); + } + } + } + + final Set relateds = new HashSet<>(); + + if (!fieldsSelf.isEmpty()) { + fieldsSelf.add(sourceEntity.getPrimaryField().getName()); + Object[] o = Application.getQueryFactory().uniqueNoFilter(sourceRecordId, fieldsSelf.toArray(new String[0])); + if (o != null) { + for (Object id : o) { + if (id != null) { + if (id instanceof ID[]) Collections.addAll(relateds, (ID[]) id); + else relateds.add((ID) id); + } + } + } + } + + for (Field refto : fieldsRefto) { + Entity ownEntity = refto.getOwnEntity(); + String sql = String.format("select %s from %s where ", + ownEntity.getPrimaryField().getName(), ownEntity.getName()); + + // N2N + if (refto.getType() == FieldType.REFERENCE_LIST) { + sql += String.format( + "exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')", + ownEntity.getPrimaryField().getName(), refto.getName(), sourceRecordId); + } else { + sql += String.format("%s = '%s'", refto.getName(), sourceRecordId); + } + + Object[][] array = Application.createQueryNoFilter(sql).array(); + for (Object[] o : array) relateds.add((ID) o[0]); + } + + // 不含自己 + if (!hasSelf) relateds.remove(sourceRecordId); + + return relateds; + } + + /** + * 获取审核状态 + * + * @param recordId + * @return + * @see ApprovalHelper#getApprovalState(ID) + */ + protected static ApprovalState getApprovalState(ID recordId) { + Entity entity = MetadataHelper.getEntity(recordId.getEntityCode()); + if (MetadataHelper.hasApprovalField(entity)) return ApprovalHelper.getApprovalState(recordId); + else return null; + } +} diff --git a/src/main/java/com/rebuild/core/service/trigger/impl/AutoShare.java b/src/main/java/com/rebuild/core/service/trigger/impl/AutoShare.java index 96cc5dadf..f0fb97955 100644 --- a/src/main/java/com/rebuild/core/service/trigger/impl/AutoShare.java +++ b/src/main/java/com/rebuild/core/service/trigger/impl/AutoShare.java @@ -8,10 +8,12 @@ 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.persist4j.Entity; import cn.devezhao.persist4j.engine.ID; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.rebuild.core.Application; +import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.privileges.PrivilegesGuardContextHolder; import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.service.general.EntityService; @@ -19,6 +21,7 @@ import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.OperatingContext; import com.rebuild.core.service.trigger.ActionContext; import com.rebuild.core.service.trigger.ActionType; +import com.rebuild.core.service.trigger.TriggerAction; import com.rebuild.core.service.trigger.TriggerException; import com.rebuild.core.service.trigger.TriggerResult; import lombok.extern.slf4j.Slf4j; @@ -33,7 +36,7 @@ import java.util.Set; * @since 2019/8/23 */ @Slf4j -public class AutoShare extends AutoAssign { +public class AutoShare extends TriggerAction { public AutoShare(ActionContext context) { super(context); @@ -44,6 +47,12 @@ public class AutoShare extends AutoAssign { return ActionType.AUTOSHARE; } + @Override + public boolean isUsableSourceEntity(int entityCode) { + Entity e = MetadataHelper.getEntity(entityCode); + return MetadataHelper.hasPrivilegesField(e) || e.getMainEntity() != null; + } + @Override public Object execute(OperatingContext operatingContext) throws TriggerException { final JSONObject content = (JSONObject) actionContext.getActionContent(); diff --git a/src/main/resources/web/assets/js/rb-forms.js b/src/main/resources/web/assets/js/rb-forms.js index fa54e7d0a..eea6dac12 100644 --- a/src/main/resources/web/assets/js/rb-forms.js +++ b/src/main/resources/web/assets/js/rb-forms.js @@ -2683,7 +2683,24 @@ class RbFormTag extends RbFormElement { } } - // isValueUnchanged() {} + setValue(val) { + if (typeof val === 'object') val = val.join('$$$$') + super.setValue(val) + + // fix: v3.4.4 + if ($empty(val)) { + this.__select2.val(null).trigger('change') + } else { + const names = [] + val.split('$$$$').forEach((name) => { + if (!names.includes(name)) { + const o = new Option(name, name, true, true) + this.__select2.append(o) + names.push(name) + } + }) + } + } } // 不支持/未开放的字段 diff --git a/src/test/resources/frontjs-sdk-demo.js b/src/test/resources/frontjs-sdk-demo.js new file mode 100644 index 000000000..2f49b63ae --- /dev/null +++ b/src/test/resources/frontjs-sdk-demo.js @@ -0,0 +1,97 @@ +// FrontJS 演示 +// 建议将上方匹配路径设置为 `/` 以便观察效果 + +const demoEntityName = 'Account' // TODO 修改为你要测试的实体 +const demoFieldName = 'AccountName' // TODO 修改为你要测试的实体字段 + +let _List, _Form, _View + +FrontJS.ready(() => { + _List = FrontJS.DataList + _Form = FrontJS.Form + _View = FrontJS.View + + demoAddButton() + + demoForList() + + demoForForm() +}) + +// 添加按钮 +// 注意在 onOpen 中调用 +function demoAddButton() { + _List.onOpen(() => { + _List.addButtonGroup({ + text: 'FrontJS!', + items: [ + { + text: '获取第一选中', + onClick: () => { + alert(_List.getSelectedId()) + }, + }, + { + text: '获取全部选中', + onClick: () => { + alert(_List.getSelectedIds()) + }, + }, + ], + }) + }) + + _Form.onOpen(() => { + _Form.addButton({ + text: 'FrontJS!', + onClick: () => { + alert(_Form.getCurrentId()) + }, + }) + }) + + _View.onOpen(() => { + _Form.addButton({ + text: 'FrontJS!', + onClick: () => { + alert(_Form.getCurrentId()) + }, + }) + }) +} + +// 列表操作 +function demoForList() { + // 指定字段加粗加红显示 + const fieldKey = `${demoEntityName}.${demoFieldName}` + _List.regCellRender(fieldKey, function (v) { + return {JSON.stringify(v)} + }) + + // 轻量级表单 + _List.onOpen(() => { + _List.addButton({ + text: 'LiteForm', + onClick: () => { + const id = _List.getSelectedId() + if (!id) { + alert('请选择一条记录') + return + } + + FrontJS.openLiteForm(id, [{ field: demoFieldName, tip: '提示', readonly: true, nullable: true }]) + }, + }) + }) +} + +// 表单操作 +function demoForForm() { + // 监听指定字段值变化 + const lookFieldKey = `${demoEntityName}.${demoFieldName}` + _Form.onFieldValueChange(function (fieldKey, fieldValue, recordId) { + if (lookFieldKey === fieldKey) { + RbHighbar.create(`记录: ${recordId || ''} 的新值 : ${fieldValue}`) + } + }) +}