Fix 4.1.4 (#946)

* open fillbackMode

* select2 _top

* 4.1.4

* fix 数字字段兼容性

* Improve error message extraction in RespBody

* be: options of field

* Update transform-design.html

* _AdminDownload

* fix:Gitee#ICSJD5

* fix: /app/bizuser/group-members
This commit is contained in:
REBUILD 企业管理系统 2025-08-16 16:26:07 +08:00 committed by GitHub
parent 318c46c8b5
commit 240026b9a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 216 additions and 94 deletions

View file

@ -10,7 +10,7 @@
</parent>
<groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId>
<version>4.1.3</version>
<version>4.1.4</version>
<name>rebuild</name>
<description>Building your business-systems freely!</description>
<url>https://getrebuild.com/</url>
@ -292,7 +292,7 @@
<dependency>
<groupId>com.github.devezhao</groupId>
<artifactId>persist4j</artifactId>
<version>1.8.3</version>
<version>1.8.5</version>
<exclusions>
<exclusion>
<groupId>com.alibaba</groupId>

View file

@ -10,10 +10,11 @@ package com.rebuild.api;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.CommonsUtils;
import com.rebuild.utils.JSONUtils;
import com.rebuild.utils.JSONable;
import lombok.Data;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.StringUtils;
/**
* 统一请求返回消息体
@ -80,8 +81,8 @@ public class RespBody implements JSONable {
* @return
*/
public static RespBody error(Throwable errorObject) {
String msg = ExceptionUtils.getRootCauseMessage(errorObject);
return error(msg);
String msg = CommonsUtils.getRootMessage(errorObject);
return error(StringUtils.defaultIfBlank(msg, "UNKNOW ERROR"));
}
/**

View file

@ -76,11 +76,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Version
*/
public static final String VER = "4.1.3";
public static final String VER = "4.1.4";
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 4010307;
public static final int BUILD = 4010409;
static {
// Driver for DB

View file

@ -48,12 +48,10 @@ public class MultiSelectManager extends PickListManager {
* @return
*/
public String[] getLabels(long maskValue, Field field) {
if (maskValue <= 0) {
return new String[0];
}
if (maskValue <= 0) return new String[0];
List<String> labels = new ArrayList<>();
ConfigBean[] entries = getPickListRaw(field, false);
ConfigBean[] entries = getPickListRaw(field, true);
for (ConfigBean e : entries) {
long m = e.get("mask", Long.class);
if ((maskValue & m) != 0) {

View file

@ -31,6 +31,7 @@ import com.rebuild.core.service.query.FilterRecordChecker;
import com.rebuild.core.support.SetUser;
import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NamedThreadLocal;
import java.util.ArrayList;
import java.util.Collection;
@ -51,6 +52,9 @@ import java.util.Objects;
@Slf4j
public class RecordTransfomer extends SetUser {
// 防止自动转换死循环
private static final ThreadLocal<ID> FILLBACK2_ONCE414 = new NamedThreadLocal<>("FallbackMode=2 Trigger Once");
final protected Entity targetEntity;
final protected JSONObject transConfig;
final protected boolean skipGuard;
@ -103,6 +107,8 @@ public class RecordTransfomer extends SetUser {
* @see #checkFilter(ID)
*/
public ID transform(ID sourceRecordId, ID specMainId) {
FILLBACK2_ONCE414.remove();
// 检查配置
Entity sourceEntity = MetadataHelper.getEntity(sourceRecordId.getEntityCode());
Entity sourceDetailEntity = null;
@ -212,10 +218,11 @@ public class RecordTransfomer extends SetUser {
Record updateSource = EntityHelper.forUpdate(sourceRecordId, UserService.SYSTEM_USER, false);
updateSource.setID(fillbackField, newId);
// 此配置未开放
// 4.1.3 配置开放
int fillbackMode = transConfig.getIntValue("fillbackMode");
if (fillbackMode == 2 && !EntityHelper.isUnsavedId(newId)) {
if (fillbackMode == 2 && !EntityHelper.isUnsavedId(newId) && FILLBACK2_ONCE414.get() == null) {
GeneralEntityServiceContextHolder.setAllowForceUpdate(updateSource.getPrimary());
FILLBACK2_ONCE414.set(newId);
try {
Application.getEntityService(sourceEntity.getEntityCode()).update(updateSource);
} finally {
@ -225,7 +232,6 @@ public class RecordTransfomer extends SetUser {
// 无传播更新
Application.getCommonsService().update(updateSource, false);
}
return true;
}

View file

@ -21,6 +21,8 @@ import com.rebuild.core.support.i18n.Language;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @author devezhao
* @since 2020/7/27
@ -42,9 +44,18 @@ public class ProjectCommentService extends BaseTaskService {
final ID user = getCurrentUser();
checkModifications(user, record.getID("taskId"));
String content = record.getString("content");
// @FULL_NAME 转成 @ID
Map<String, ID> map = FeedsHelper.findMentionsMap(content);
for (Map.Entry<String, ID> e : map.entrySet()) {
content = content.replace("@" + e.getKey(), "@" + e.getValue());
}
record.setString("content", content);
record = super.create(record);
checkAtUserAndNotification(record, record.getString("content"));
if (StringUtils.isNotBlank(content) && !map.isEmpty()) {
checkAtUserAndNotification(record, content, map.values().toArray(new ID[0]));
}
return record;
}
@ -68,14 +79,11 @@ public class ProjectCommentService extends BaseTaskService {
* @param content
* @return
*/
private int checkAtUserAndNotification(Record record, String content) {
if (StringUtils.isBlank(content)) return 0;
final String msgContent = Language.L("@%s 在任务中提到了你", record.getEditor()) + " \n> " + content;
private int checkAtUserAndNotification(Record record, String content, ID[] atUsers) {
String msgContent = Language.L("@%s 在任务中提到了你", record.getEditor()) + " \n> " + content;
ID related = record.getID("taskId");
if (related == null) related = (ID) QueryHelper.queryFieldValue(record.getPrimary(), "taskId");
ID[] atUsers = FeedsHelper.findMentions(content);
int send = 0;
for (ID to : atUsers) {
// 是否已经发送过

View file

@ -37,6 +37,7 @@ public class CommandArgs {
public static final String _UseDbFullText = "_UseDbFullText";
public static final String _StartEntityTypeCode = "_StartEntityTypeCode";
public static final String _InitScriptEngine = "_InitScriptEngine";
public static final String _AdminDownload = "_AdminDownload";
/**
* 内部消息同步发送短信

View file

@ -225,7 +225,7 @@ public class DataListWrapper {
// @see MultiSelectManager#getLabels
List<Object> colorLabels = new ArrayList<>();
ConfigBean[] entries = MultiSelectManager.instance.getPickListRaw(field, false);
ConfigBean[] entries = MultiSelectManager.instance.getPickListRaw(field, true);
for (ConfigBean e : entries) {
long m = e.get("mask", Long.class);
if (((long) originValue & m) != 0) {

View file

@ -27,6 +27,8 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
@ -233,7 +235,7 @@ public class CommonsUtils {
}
/**
* 打印调用栈for DEBUG
* 打印调用栈 `dev only`
*/
public static void printStackTrace() {
if (Application.devMode() || log.isDebugEnabled()) {
@ -491,4 +493,18 @@ public class CommonsUtils {
throw new RebuildException(e);
}
}
/**
* @param ex
* @return
*/
public static String getRootMessage(Throwable ex) {
String msg = null;
Throwable th = ExceptionUtils.getRootCause(ex);
if (th != null) {
msg = th.getMessage();
if (StringUtils.isBlank(msg)) msg = ClassUtils.getShortClassName(th, "");
}
return msg == null ? "" : msg;
}
}

View file

@ -16,10 +16,13 @@ import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.support.CommandArgs;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.SysbaseHeartbeat;
import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.RbAssert;
import com.rebuild.web.BaseController;
import com.rebuild.web.commons.FileDownloader;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
@ -30,7 +33,13 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.Optional;
import java.util.stream.Stream;
/**
* @author devezhao
@ -106,4 +115,43 @@ public class AdminVerfiyController extends BaseController {
String res = new AdminCli4(command).exec();
return RespBody.ok(res);
}
// -- FILES
@RequestMapping("/admin/admin-download")
public void adminDownloadFile(HttpServletRequest request, HttpServletResponse response) throws IOException {
if (!CommandArgs.getBoolean(CommandArgs._AdminDownload)) {
response.sendError(404);
return;
}
final String type = getParameterNotNull(request, "type");
// 日志
if ("log".equalsIgnoreCase(type)) {
File logFile = SysbaseHeartbeat.getLogbackFile();
FileDownloader.setDownloadHeaders(response, logFile.getName(), false);
FileDownloader.writeLocalFile(logFile, response);
return;
}
// 数据库
if ("database".equalsIgnoreCase(type)) {
File path = RebuildConfiguration.getFileOfData("");
path = new File(path, "_backups");
String file = getParameter(request, "file");
File dbFile = null;
if (StringUtils.isBlank(file)) {
try (Stream<Path> s = Files.list(path.toPath())) {
Optional<Path> max = s.filter(Files::isRegularFile)
.max(Comparator.comparingLong(p -> p.toFile().lastModified()));
if (max.isPresent()) dbFile = max.get().toFile();
}
} else {
dbFile = new File(path, file);
}
if (dbFile != null) FileDownloader.setDownloadHeaders(response, dbFile.getName(), false);
FileDownloader.writeLocalFile(dbFile, response);
}
}
}

View file

@ -7,16 +7,29 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.admin.bizz;
import cn.devezhao.bizz.security.member.MemberGroup;
import cn.devezhao.bizz.security.member.Role;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.privileges.bizz.CombinedRole;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.core.support.integration.SMSender;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.EntityController;
import org.springframework.stereotype.Controller;
import com.rebuild.web.IdParam;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
/**
* Bizz entity URL-Rewrite
@ -24,7 +37,8 @@ import javax.servlet.http.HttpServletRequest;
* @author devezhao
* @since 11/01/2018
*/
@Controller
@Slf4j
@RestController
@RequestMapping("/app/")
public class BizzPageView extends EntityController {
@ -80,4 +94,62 @@ public class BizzPageView extends EntityController {
public String teamList() {
return "redirect:/admin/bizuser/teams";
}
// -- GROUP
@GetMapping("/bizuser/group-members")
public JSON getMembers(@IdParam ID groupId) {
JSONArray res = new JSONArray();
if (groupId.getEntityCode() == EntityHelper.Role) {
for (User user : Application.getUserStore().getAllUsers()) {
if (user.getId().equals(UserService.SYSTEM_USER)) continue;
Role role = user.getOwningRole();
if (role == null) continue;
if (role.getIdentity().equals(groupId)) {
res.add(new Object[] {
user.getId(),
user.getFullName(),
user.getOwningDept() != null ? user.getOwningDept().getName() : null,
user.isActive(),
});
} else if (role instanceof CombinedRole) {
if (((CombinedRole) role).getRoleAppends().contains(groupId)) {
res.add(new Object[] {
user.getId(),
user.getFullName(),
user.getOwningDept() != null ? user.getOwningDept().getName() : null,
user.isActive(),
true
});
}
}
}
return res;
}
MemberGroup group;
if (groupId.getEntityCode() == EntityHelper.Department) {
group = Application.getUserStore().getDepartment(groupId);
} else if (groupId.getEntityCode() == EntityHelper.Team) {
group = Application.getUserStore().getTeam(groupId);
} else {
log.warn("No group defined : {}", groupId);
return JSONUtils.EMPTY_ARRAY;
}
for (Principal p : group.getMembers()) {
User user = (User) p;
if (user.getId().equals(UserService.SYSTEM_USER)) continue;
res.add(new Object[] {
user.getId(),
user.getFullName(),
user.getOwningDept() != null ? user.getOwningDept().getName() : null,
user.isActive()
});
}
return res;
}
}

View file

@ -7,8 +7,6 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.web.admin.bizz;
import cn.devezhao.bizz.security.member.MemberGroup;
import cn.devezhao.bizz.security.member.Role;
import cn.devezhao.commons.web.ServletUtils;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON;
@ -16,13 +14,8 @@ import com.alibaba.fastjson.JSONArray;
import com.rebuild.api.RespBody;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.DataListManager;
import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.privileges.TeamService;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.UserService;
import com.rebuild.core.privileges.bizz.CombinedRole;
import com.rebuild.core.privileges.bizz.User;
import com.rebuild.utils.JSONUtils;
import com.rebuild.web.EntityController;
import com.rebuild.web.IdParam;
import lombok.extern.slf4j.Slf4j;
@ -33,7 +26,6 @@ import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.util.Collections;
import java.util.Set;
@ -72,60 +64,4 @@ public class TeamController extends EntityController {
Application.getBean(TeamService.class).deleteMembers(teamId, Collections.singletonList(userId));
return RespBody.ok();
}
@GetMapping("group-members")
public JSON getMembers(@IdParam ID groupId) {
JSONArray res = new JSONArray();
if (groupId.getEntityCode() == EntityHelper.Role) {
for (User user : Application.getUserStore().getAllUsers()) {
if (user.getId().equals(UserService.SYSTEM_USER)) continue;
Role role = user.getOwningRole();
if (role == null) continue;
if (role.getIdentity().equals(groupId)) {
res.add(new Object[] {
user.getId(),
user.getFullName(),
user.getOwningDept() != null ? user.getOwningDept().getName() : null,
user.isActive(),
});
} else if (role instanceof CombinedRole) {
if (((CombinedRole) role).getRoleAppends().contains(groupId)) {
res.add(new Object[] {
user.getId(),
user.getFullName(),
user.getOwningDept() != null ? user.getOwningDept().getName() : null,
user.isActive(),
true
});
}
}
}
return res;
}
MemberGroup group;
if (groupId.getEntityCode() == EntityHelper.Department) {
group = Application.getUserStore().getDepartment(groupId);
} else if (groupId.getEntityCode() == EntityHelper.Team) {
group = Application.getUserStore().getTeam(groupId);
} else {
log.warn("No group defined : {}", groupId);
return JSONUtils.EMPTY_ARRAY;
}
for (Principal p : group.getMembers()) {
User user = (User) p;
if (user.getId().equals(UserService.SYSTEM_USER)) continue;
res.add(new Object[] {
user.getId(),
user.getFullName(),
user.getOwningDept() != null ? user.getOwningDept().getName() : null,
user.isActive()
});
}
return res;
}
}

View file

@ -306,6 +306,7 @@ public class FileDownloader extends BaseController {
* @param response
* @return
* @throws IOException
* @see #setDownloadHeaders(HttpServletResponse, String, boolean)
*/
public static boolean writeLocalFile(File file, HttpServletResponse response) throws IOException {
if (file == null || !file.exists()) {

View file

@ -36,6 +36,7 @@ import com.rebuild.web.EntityController;
import com.rebuild.web.EntityParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -97,8 +98,8 @@ public class ReferenceSearchController extends EntityController {
}
}
}
String protocolFilter = fp.parseRef(referenceField.getName() + "." + entity.getName(), cascadingValue);
String protocolFilter = fp.parseRef(referenceField.getName() + "." + entity.getName(), cascadingValue);
String q = StringUtils.trim(getParameter(request, "q"));
// 强制搜索 H5
@ -109,6 +110,12 @@ public class ReferenceSearchController extends EntityController {
if (StringUtils.isBlank(q) && !forceSearchs) {
ID[] used = RecentlyUsedHelper.gets(
user, searchEntity.getName(), getParameter(request, "type"), protocolFilter);
// v4.1.4 当前记录值置顶显示
ID _top = getIdParameter(request, "_top");
if (_top != null && !ArrayUtils.contains(used, _top)) {
_top.setLabel(FieldValueHelper.getLabelNotry(_top));
used = ArrayUtils.insert(0, used, _top);
}
if (used.length == 0) {
if (!forceResults) return JSONUtils.EMPTY_ARRAY;

View file

@ -13,6 +13,7 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.feeds.FeedsHelper;
import com.rebuild.core.service.project.ProjectHelper;
import com.rebuild.core.support.i18n.I18nUtils;
import com.rebuild.utils.JSONUtils;
@ -50,6 +51,7 @@ public class TaskCommentController extends BaseController {
o[3] = I18nUtils.formatDate((Date) o[3]);
o[4] = new Object[]{o[4], UserHelper.getName((ID) o[4])};
o[5] = ProjectHelper.isManageable((ID) o[0], user);
o[1] = FeedsHelper.formatContent((String) o[1]); // fix:Gitee#ICSJD5
JSONObject item = JSONUtils.toJSONObject(
new String[]{"id", "content", "attachments", "createdOn", "createdBy", "isManageable"},

View file

@ -55,6 +55,12 @@
<select class="form-control form-control-sm" id="fillbackField"></select>
</div>
<p class="form-text">[[${bundle.L('可将转换后的记录 ID 回填至源记录中')}]]</p>
<div class="mt-2 bosskey-show">
<label class="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input class="custom-control-input" type="checkbox" id="fillbackMode" value="2" />
<span class="custom-control-label">[[${bundle.L('回填后触发系统规则')}]] (LAB)</span>
</label>
</div>
</div>
</div>
<th:block th:if="${targetIsDetail}">

View file

@ -123,6 +123,11 @@ $(document).ready(() => {
.val(null)
.trigger('change')
if (config.fillbackMode === 2) {
$('#fillbackMode')[0].checked = true
$('#fillbackMode').parents('.bosskey-show').removeClass('bosskey-show')
}
let _ImportsFilterMapping
$('#importsMode').on('click', function () {
if ($val(this)) {
@ -198,6 +203,7 @@ $(document).ready(() => {
fieldsMappingDetail: fmd36,
fieldsMappingDetails: fmdList37,
fillbackField: $('#fillbackField').val(),
fillbackMode: $val('#fillbackMode') ? 2 : 0,
useFilter: _AdvFilter_data,
importsMode: $val('#importsMode'),
importsFilter: importsFilter || null,

View file

@ -87,7 +87,7 @@ class MemberList extends React.Component {
componentDidMount = () => this.loadMembers()
loadMembers() {
$.get(`/admin/bizuser/group-members?id=${this.props.id}`, (res) => {
$.get(`/app/bizuser/group-members?id=${this.props.id}`, (res) => {
const data = res.data || []
this.setState({ members: data })

View file

@ -452,7 +452,7 @@ class MemberList extends React.Component {
componentDidMount = () => this.loadMembers()
loadMembers() {
$.get(`/admin/bizuser/group-members?id=${this.props.id}`, (res) => {
$.get(`/app/bizuser/group-members?id=${this.props.id}`, (res) => {
const data = res.data || []
this.setState({ members: data })

View file

@ -134,7 +134,7 @@ class MemberList extends React.Component {
componentDidMount = () => this.loadMembers()
loadMembers() {
$.get(`/admin/bizuser/group-members?id=${this.props.id}`, (res) => {
$.get(`/app/bizuser/group-members?id=${this.props.id}`, (res) => {
const data = res.data || []
this.setState({ members: data })

View file

@ -2083,7 +2083,10 @@ class RbFormPickList extends RbFormElement {
options.push({ id: props.value, text: '[DELETED]' })
}
}
this._options = options
this._options = options.filter((item) => {
return item.hide !== true || (props.value && item.id === props.value)
})
this._isShowRadio39 = props.showStyle === '10'
this._htmlid = `${props.field}-${$random()}-`
}
@ -2299,6 +2302,12 @@ class RbFormReference extends RbFormElement {
const cascadingValue = this._getCascadingFieldValue()
if (cascadingValue) query.cascadingValue = cascadingValue
// 4.1.3
let val = this.state.value
if (typeof val === 'object') val = val.id
if (val) query._top = val
console.log('Reference query:', query)
return query
},
placeholder: this._placeholderw,
@ -2904,7 +2913,12 @@ class RbFormMultiSelect extends RbFormElement {
super(props)
this._htmlid = `${props.field}-${$random()}_`
this._isShowSelect41 = props.showStyle === '10'
this._options = props.options || []
this._options = (props.options || []).filter((item) => {
if (props.value && props.value.id) {
if ((props.value.id & item.mask) !== 0) return true
}
return item.hide !== true
})
}
renderElement() {