Fix 3.7 beta5 (#775)

* be AutoGenReport

* feat: ValueConvertFunc

* feat: convertPictureWithSize

* fix: #IA8DC8

* feat: #IA8DC8
This commit is contained in:
REBUILD 企业管理系统 2024-06-27 12:40:31 +08:00 committed by GitHub
parent bfc1e6c566
commit 229f543ac9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 306 additions and 98 deletions

2
@rbv

@ -1 +1 @@
Subproject commit c2f072f6ed0ee3c576c81d96e51ef8b147b29be7
Subproject commit d6da52b4833a867f059c276b1927a8a8b551232b

View file

@ -21,7 +21,7 @@
1. [新增] 限时审批
2. [新增] 新建任务(触发器)
3. [新增] 地图图表
3. [新增] 地图等多个图表
4. [新增] 自动明细记录导入(记录转换)
5. [新增] 数据列表之卡片模式
6. [新增] 多个 FrontJS 函数

View file

@ -10,7 +10,7 @@
</parent>
<groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId>
<version>3.7.0-beta4</version>
<version>3.7.0</version>
<name>rebuild</name>
<description>Building your business-systems freely!</description>
<url>https://getrebuild.com/</url>

View file

@ -75,11 +75,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/**
* Rebuild Version
*/
public static final String VER = "3.7.0-beta4";
public static final String VER = "3.7.0";
/**
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/
public static final int BUILD = 3070004;
public static final int BUILD = 3070005;
static {
// Driver for DB

View file

@ -217,7 +217,7 @@ public class DataImporter extends HeavyTask<Integer> {
}
}
log.info("Checking repeated : " + wheres);
log.info("Checking repeated : {}", wheres);
if (wheres.isEmpty()) return null;
Entity entity = data.getEntity();

View file

@ -15,9 +15,6 @@ import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID;
import cn.devezhao.persist4j.record.FieldValueException;
import cn.devezhao.persist4j.record.RecordVisitor;
import cn.hutool.core.date.DateException;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson.JSON;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.general.ClassificationManager;
@ -273,20 +270,7 @@ public class RecordCheckout {
protected Date checkoutDateValue(Cell cell) {
Date date = cell.asDate();
if (date != null) return date;
String date2str = cell.asString();
try {
DateTime dt = DateUtil.parse(date2str);
if (dt != null) date = dt.toJdkDate();
} catch (DateException ignored) {
}
// 2017/11/19 11:07
if (date == null && date2str.contains("/")) {
date = cell.asDate(new String[]{"yyyy/M/d H:m:s", "yyyy/M/d H:m", "yyyy/M/d"});
}
return date;
return CommonsUtils.parseDate(cell.asString());
}
protected LocalTime checkoutTimeValue(Cell cell) {

View file

@ -32,6 +32,7 @@ import com.rebuild.core.metadata.easymeta.MultiValue;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.approval.ApprovalState;
import com.rebuild.core.service.query.QueryHelper;
import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.general.BarCodeSupport;
@ -341,25 +342,18 @@ public class EasyExcelGenerator extends SetUser {
}
}
for (final String fieldName : varsMap.values()) {
for (Map.Entry<String, String> e : varsMap.entrySet()) {
final String fieldName = e.getValue();
if (fieldName == null) continue;
EasyField easyField = EasyMetaFactory.valueOf(MetadataHelper.getLastJoinField(entity, fieldName));
DisplayType dt = easyField.getDisplayType();
// 替换成变量名
String varName = fieldName;
for (Map.Entry<String, String> e : varsMap.entrySet()) {
if (fieldName.equalsIgnoreCase(e.getValue())) {
varName = e.getKey();
break;
}
}
String varName = e.getKey();
if (varName.startsWith(NROW_PREFIX)) {
varName = varName.substring(1);
}
EasyField easyField = EasyMetaFactory.valueOf(MetadataHelper.getLastJoinField(entity, fieldName));
DisplayType dt = easyField.getDisplayType();
// FIXME v3.2 图片仅支持导出第一张
if (!dt.isExportable() && dt != DisplayType.IMAGE) {
data.put(varName, unsupportFieldTip);
@ -386,9 +380,7 @@ public class EasyExcelGenerator extends SetUser {
String format = easyField.getExtraAttr(EasyFieldConfigProps.DECIMAL_FORMAT);
int scale = StringUtils.isBlank(format) ? 2 : format.split("\\.")[1].length();
// Keep Type
// fieldValue = ObjectUtils.round(((BigDecimal) fieldValue).doubleValue(), scale);
fieldValue = ((BigDecimal) fieldValue).setScale(scale, RoundingMode.HALF_UP);
} else {
fieldValue = FieldValueHelper.wrapFieldValue(fieldValue, easyField, Boolean.TRUE);
}
@ -420,6 +412,9 @@ public class EasyExcelGenerator extends SetUser {
}
}
}
// v3.7.0
fieldValue = ValueConvertFunc.convert(easyField, fieldValue, varName);
}
data.put(varName, fieldValue);
@ -490,22 +485,27 @@ public class EasyExcelGenerator extends SetUser {
return phName.length() > PH__KEEP.length()
? phName.substring(PH__KEEP.length() + 1) : "";
}
// 列表序号
if (phName.equals(PH__NUMBER)) {
return phNumber;
switch (phName) {
case PH__NUMBER:
return phNumber;
case PH__CURRENTUSER:
return UserHelper.getName(getUser());
case PH__CURRENTBIZUNIT:
return Objects.requireNonNull(UserHelper.getDepartment(getUser())).getName();
case PH__CURRENTDATE:
return CalendarUtils.getUTCDateFormat().format(CalendarUtils.now());
case PH__CURRENTDATETIME:
return CalendarUtils.getUTCDateTimeFormat().format(CalendarUtils.now());
}
if (phName.equals(PH__CURRENTUSER)) {
return UserHelper.getName(getUser());
}
if (phName.equals(PH__CURRENTBIZUNIT)) {
return Objects.requireNonNull(UserHelper.getDepartment(getUser())).getName();
}
if (phName.equals(PH__CURRENTDATE)) {
return CalendarUtils.getUTCDateFormat().format(CalendarUtils.now());
}
if (phName.equals(PH__CURRENTDATETIME)) {
return CalendarUtils.getUTCDateTimeFormat().format(CalendarUtils.now());
// v3.7
if (phName.startsWith(PH__CURRENTUSER)) {
String useField = phName.substring(PH__CURRENTUSER.length() + 1);
Object useValue = QueryHelper.queryFieldValue(getUser(), useField);
return useValue == null ? null : useValue.toString();
}
return null;
}

View file

@ -9,12 +9,12 @@ package com.rebuild.core.service.datareport;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.esotericsoftware.minlog.Log;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import java.util.List;
@ -24,6 +24,7 @@ import java.util.List;
* @author devezhao
* @since 2021/4/3
*/
@Slf4j
public class FixsMergeStrategy extends AbstractMergeStrategy {
@Override
@ -32,30 +33,67 @@ public class FixsMergeStrategy extends AbstractMergeStrategy {
return;
}
// int rowIndex = cell.getRowIndex();
// int colIndex = cell.getColumnIndex();
// sheet = cell.getSheet();
// Row prevRow = sheet.getRow(rowIndex - 1);
// if (prevRow == null) return;
// Cell prevCell = prevRow.getCell(colIndex);
// List<CellRangeAddress> craList = sheet.getMergedRegions();
// CellStyle cs = cell.getCellStyle();
// cell.setCellStyle(cs);
//
// for (CellRangeAddress cellRangeAddress : craList) {
// if (cellRangeAddress.containsRow(prevCell.getRowIndex()) && cellRangeAddress.containsColumn(prevCell.getColumnIndex())) {
// int lastColIndex = cellRangeAddress.getLastColumn();
// int firstColIndex = cellRangeAddress.getFirstColumn();
// CellRangeAddress cra = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), firstColIndex, lastColIndex);
// sheet.addMergedRegion(cra);
//
// // 复制线框样式
// RegionUtil.setBorderBottom(cs.getBorderBottom(), cra, sheet);
// RegionUtil.setBorderLeft(cs.getBorderLeft(), cra, sheet);
// RegionUtil.setBorderRight(cs.getBorderRight(), cra, sheet);
// RegionUtil.setBorderTop(cs.getBorderTop(), cra, sheet);
//
// break;
// }
// }
try {
merge37(sheet, cell, head, relativeRowIndex);
} catch (Exception ex) {
log.warn("Cannot merge cell", ex);
}
}
// v3.7
// THANKS https://github.com/alibaba/easyexcel/issues/2963#issuecomment-1432827475
private void merge37(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
int rowIndex = cell.getRowIndex();
int colIndex = cell.getColumnIndex();
sheet = cell.getSheet();
Row prevRow = sheet.getRow(rowIndex - 1);
if (prevRow == null) return;
Cell prevCell = prevRow.getCell(colIndex);
List<CellRangeAddress> craList = sheet.getMergedRegions();
CellStyle cs = cell.getCellStyle();
cell.setCellStyle(cs);
Sheet thisSheet = cell.getSheet();
Row preRow = thisSheet.getRow(rowIndex - 1);
Row thisRow = thisSheet.getRow(rowIndex);
Cell preCell = preRow.getCell(colIndex);
Cell tmpCell;
List<CellRangeAddress> list = thisSheet.getMergedRegions();
for (CellRangeAddress cellRangeAddress : craList) {
if (cellRangeAddress.containsRow(prevCell.getRowIndex()) && cellRangeAddress.containsColumn(prevCell.getColumnIndex())) {
for (int i = 0; i < list.size(); i++) {
CellRangeAddress cellRangeAddress = list.get(i);
if (cellRangeAddress.containsRow(preCell.getRowIndex()) && cellRangeAddress.containsColumn(preCell.getColumnIndex())) {
int lastColIndex = cellRangeAddress.getLastColumn();
int firstColIndex = cellRangeAddress.getFirstColumn();
CellRangeAddress cra = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), firstColIndex, lastColIndex);
sheet.addMergedRegion(cra);
// 复制线框样式
RegionUtil.setBorderBottom(cs.getBorderBottom(), cra, sheet);
RegionUtil.setBorderLeft(cs.getBorderLeft(), cra, sheet);
RegionUtil.setBorderRight(cs.getBorderRight(), cra, sheet);
RegionUtil.setBorderTop(cs.getBorderTop(), cra, sheet);
break;
thisSheet.addMergedRegion(cra);
for (int j = firstColIndex; j <= lastColIndex; j++) {
tmpCell = thisRow.getCell(j);
if (tmpCell == null) {
tmpCell = thisRow.createCell(j);
}
tmpCell.setCellStyle(preRow.getCell(j).getCellStyle());
}
return;
}
}
}

View file

@ -37,16 +37,15 @@ public class TemplateExtractor33 extends TemplateExtractor {
// 明细字段
public static final String DETAIL_PREFIX = NROW_PREFIX + "detail";
// $
// $ 前缀
public static final String NROW_PREFIX2 = "$";
public static final String DETAIL_PREFIX2 = NROW_PREFIX2 + "detail";
public static final String APPROVAL_PREFIX2 = NROW_PREFIX2 + "approval";
// 排序
private static final String SORT_ASC = ":asc";
private static final String SORT_DESC = ":desc";
private Map<String, String> sortFields = new HashMap<>();
private Map<String, String> sortFields = new HashMap<>();
private Set<String> inShapeVars = new HashSet<>();
/**
@ -66,16 +65,18 @@ public class TemplateExtractor33 extends TemplateExtractor {
Map<String, String> map = new HashMap<>();
for (final String varName : vars) {
String thatName = ValueConvertFunc.splitName(varName);
// 列表型字段
if (varName.startsWith(NROW_PREFIX) || varName.startsWith(NROW_PREFIX2)) {
final String listField = varName.substring(1).replace("$", ".");
if (thatName.startsWith(NROW_PREFIX) || thatName.startsWith(NROW_PREFIX2)) {
final String listField = thatName.substring(1).replace("$", ".");
// 占位
if (isPlaceholder(listField)) {
map.put(varName, null);
}
// 审批流程
else if (varName.startsWith(APPROVAL_PREFIX) || varName.startsWith(APPROVAL_PREFIX2)) {
else if (thatName.startsWith(APPROVAL_PREFIX) || thatName.startsWith(APPROVAL_PREFIX2)) {
String stepNodeField = listField.substring(APPROVAL_PREFIX.length());
if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) {
map.put(varName, stepNodeField);
@ -84,7 +85,7 @@ public class TemplateExtractor33 extends TemplateExtractor {
}
}
// 明细实体
else if (varName.startsWith(DETAIL_PREFIX) || varName.startsWith(DETAIL_PREFIX2)) {
else if (thatName.startsWith(DETAIL_PREFIX) || thatName.startsWith(DETAIL_PREFIX2)) {
String detailField = listField.substring(DETAIL_PREFIX.length());
detailField = getFieldNameWithSort(DETAIL_PREFIX, detailField);
@ -117,10 +118,10 @@ public class TemplateExtractor33 extends TemplateExtractor {
}
}
} else if (MetadataHelper.getLastJoinField(entity, varName) != null) {
map.put(varName, varName);
} else if (MetadataHelper.getLastJoinField(entity, thatName) != null) {
map.put(varName, thatName);
} else {
map.put(varName, transformRealField(entity, varName));
map.put(varName, transformRealField(entity, thatName));
}
}
return map;

View file

@ -0,0 +1,141 @@
/*!
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.service.datareport;
import cn.devezhao.commons.CalendarUtils;
import cn.hutool.core.convert.Convert;
import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyDecimal;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.utils.CommonsUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.Date;
/**
* @author devezhao
* @since 2024/6/25
*/
@Slf4j
public class ValueConvertFunc {
// # 函数
protected static final String FUNC_SPLITER = "#";
/**
* @param field
* @param value
* @param varName
* @return
*/
public static Object convert(EasyField field, Object value, String varName) {
String thatFunc = splitFunc(varName);
if (thatFunc == null) return value;
final DisplayType type = field.getDisplayType();
if (type == DisplayType.NUMBER || type == DisplayType.DECIMAL) {
if ("CHINESEYUAN".equals(thatFunc)) {
return Convert.digitToChinese((Number) value);
}
if ("THOUSANDS".equals(thatFunc)) {
String format = "##,##0";
if (type == DisplayType.DECIMAL) {
int scale = ((EasyDecimal) field).getScale();
if (scale > 0) {
format += "." + StringUtils.leftPad("", scale, "0");
}
}
return new DecimalFormat(format).format(value);
}
} else if (type == DisplayType.DATE || type == DisplayType.DATETIME) {
if ("CHINESEDATE".equals(thatFunc)) {
Date d = CommonsUtils.parseDate(value.toString());
if (d == null) return value;
int len = field.wrapValue(CalendarUtils.now()).toString().length();
if (len <= 10) len += 1; // yyyy-MM-dd
else len += 2;
String format = CalendarUtils.CN_DATETIME_FORMAT.substring(0, len);
return CalendarUtils.getDateFormat(format).format(d);
}
}
return value;
}
/**
* 转换图片
*
* @param value
* @param varName
* @return
*/
public static PictureRenderData convertPictureWithSize(byte[] value, String varName) {
Pictures.PictureBuilder builder = Pictures.ofBytes(value);
String thatFunc = splitFunc(varName);
if (thatFunc == null) return builder.create();
if (thatFunc.startsWith("SIZE") && thatFunc.length() > 4) {
String[] wh = thatFunc.substring(4).split("\\*");
int width = NumberUtils.toInt(wh[0]);
int height = -1;
// 指定宽度高度自适应
if (wh.length == 1) {
try (InputStream is = new ByteArrayInputStream(value)) {
BufferedImage bi = ImageIO.read(is);
int originWidth = bi.getWidth();
int originHeight = bi.getHeight();
if (originWidth > 0 && originHeight > 0) {
double scale = width * 1.0 / originWidth;
height = (int) (originHeight * scale);
}
} catch (IOException e) {
log.error(null, e);
}
} else {
height = NumberUtils.toInt(wh[1]);
}
if (height < 0) height = width;
builder = Pictures.ofBytes(value).size(width, height);
}
return builder.create();
}
/**
* @param varName
* @return
*/
public static String splitName(String varName) {
return varName.split("#")[0].trim();
}
/**
* @param varName
* @return
*/
public static String splitFunc(String varName) {
return varName.contains(FUNC_SPLITER) ? varName.split("#")[1].trim() : null;
}
}

View file

@ -11,6 +11,7 @@ import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONAware;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.utils.JSONUtils;
import lombok.Getter;
import java.util.Collection;
@ -20,6 +21,7 @@ import java.util.Collection;
* @author RB
* @since 2022/09/26
*/
@Getter
public class TriggerResult implements JSONAware {
// 状态 1=成功, 2=警告, 3=错误
@ -30,6 +32,7 @@ public class TriggerResult implements JSONAware {
final private Collection<ID> affected;
private TriggerSource chain;
private boolean breakNext;
protected TriggerResult(int level, String message, Collection<ID> affected) {
this.level = level;
@ -37,28 +40,25 @@ public class TriggerResult implements JSONAware {
this.affected = affected;
}
protected void setChain(TriggerSource chain) {
public void setChain(TriggerSource chain) {
this.chain = chain;
}
public void setBreakNext(boolean breakNext) {
this.breakNext = breakNext;
}
public boolean hasAffected() {
return affected != null && !affected.isEmpty();
}
public int getLevel() {
return level;
}
public String getMessage() {
return message;
}
@Override
public String toJSONString() {
JSONObject res = JSONUtils.toJSONObject("level", level);
if (message != null) res.put("message", message);
if (affected != null) res.put("affected", affected);
if (chain != null) res.put("chain", chain.toString());
if (breakNext) res.put("break", true);
return res.toJSONString();
}
@ -67,6 +67,8 @@ public class TriggerResult implements JSONAware {
return toJSONString();
}
// --
/**
* @param affected
* @return

View file

@ -110,8 +110,8 @@ public class SMSender {
* @param attach
* @param useTemplate
* @param specAccount
* @return returns null if failed or SENDID
* @throws ConfigurationException If mail-account unset
* @return
* @throws ConfigurationException
*/
public static String sendMail(String to, String subject, String content, File[] attach, boolean useTemplate, String[] specAccount) throws ConfigurationException {
if (specAccount == null || specAccount.length < 6
@ -121,7 +121,7 @@ public class SMSender {
}
if (Application.devMode()) {
log.info("[dev] FAKE SEND EMAIL : {}, {}, {}", to, subject, content);
log.info("[dev] FAKE SEND EMAIL. T:{}, S:{}, M:{}, F:{}", to, subject, content, attach);
return null;
}
@ -326,7 +326,7 @@ public class SMSender {
}
if (Application.devMode()) {
log.warn("[dev] FAKE SEND SMS : {}, {}", to, content);
log.warn("[dev] FAKE SEND SMS. T:{}, M:{}", to, content);
return null;
}

View file

@ -7,9 +7,13 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.utils;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.commons.ReflectUtils;
import cn.devezhao.persist4j.engine.NullValue;
import cn.hutool.core.date.DateException;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.rebuild.core.Application;
import com.rebuild.core.BootApplication;
import com.rebuild.core.RebuildException;
@ -30,6 +34,7 @@ import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Pattern;
@ -334,4 +339,36 @@ public class CommonsUtils {
throw new SecurityException("Attack path detected : " + escapeHtml(filepath));
}
}
/**
* 日期转换
*
* @param source
* @return
*/
public static Date parseDate(String source) {
if ("yyyy".length() == source.length()) {
return CalendarUtils.parse(source, "yyyy");
}
if ("yyyy-MM".length() == source.length()) {
return CalendarUtils.parse(source, "yyyy-MM");
}
try {
DateTime dt = DateUtil.parse(source);
if (dt != null) return dt.toJdkDate();
} catch (DateException ignored) {
}
// 2017/11/19 11:07
if (source.contains("/")) {
String[] fs = new String[]{"yyyy/M/d H:m:s", "yyyy/M/d H:m", "yyyy/M/d"};
for (String format : fs) {
Date d = CalendarUtils.parse(source, format);
if (d != null) return d;
}
}
return null;
}
}

View file

@ -219,8 +219,13 @@ class ChartIndex extends BaseChart {
const N1 = this._num(data.index.data)
const N2 = this._num(data.index.data2)
clazz2 = N1 >= N2 ? 'ge' : 'le'
rate2 = (((N1 - N2) * 1.0) / N2) * 100
rate2 = `${Math.abs(rate2).toFixed(2)}%`
// eslint-disable-next-line eqeqeq
if (N2 == 0) {
rate2 = '100.00%'
} else {
rate2 = (((N1 - N2) * 1.0) / N2) * 100
rate2 = `${Math.abs(rate2).toFixed(2)}%`
}
}
const chartdata = (

View file

@ -310,7 +310,7 @@ class BaiduMap extends React.Component {
const map = new _BMapGL.Map(that._mapid)
map.addControl(new _BMapGL.ZoomControl())
map.addControl(new _BMapGL.ScaleControl())
// map.addControl(new _BMapGL.LocationControl())
map.addControl(new _BMapGL.LocationControl())
// 滚动缩放
if (that.props.disableScrollWheelZoom !== true) map.enableScrollWheelZoom()