mirror of
https://github.com/getrebuild/rebuild.git
synced 2024-09-20 07:25:54 +08:00
merge-3.8 (#813)
* Update @rbv * feat: trigger when update-fields approve-nodes (#728) * ** NEED TEST ** * Update metadata-conf.xml * 3.7-dev * bump: echarts v5 * COLOR_PALETTES * be: DlgSpecFields into common * feat: spec approval-node trigger * feat: hasUpdateFields for all * tmp: RobotSopConfig * Update @rbv * enh: TriggerByTimerJob 未完成也可重进 (#731) * enh: TriggerByTimerJob 未完成也可重进 * enh: fileName use#698 * feat: UseDbFullText * !!!@EnableAsync --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * feat: chart CNMAP (#732) * style: chart in datalist * feat: CNMAP * feat: Details auto imports 110 (#733) * js: $.trim, $.isArray, click-on * feat: details import auto * enh: $type, select2 * Add lib react18 * bump lib * Update lint.yml --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Update @rbv * Enh auto approval (#735) * rbv: AutoApproval * console: RBAPI ASSISTANT * Update @rbv * print Approval Node * filter: op=HHH * feat: AutoApproval * be: use tags * be: LastLogsViewer.renderLog * Feat list3 card 100 (#737) * style * datalist conf * feat: mode3 * mode23 style * enh: datalist2 * fjs: openModal, getType * Update @rbv * bump: react18, jq (#738) * js: $.trim, $.isArray, click-on * feat: details import auto * enh: $type, select2 * Add lib react18 * enh: js * bump lib * react18 * cnmap style * actions * loadmore style --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * feat: approval step users (#739) * feat: approvalLastTime * feat: approvalStepUsers * img indicator * Update @rbv * be: filter * submail attach --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Notify use sms email (#741) * feat: EmailDistributor, SmsDistributor * feat: ApprovalStepNodeName * isOceanBase --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Update @rbv * Update @rbv * feat: Auto create task 119 (#742) feat: CreateTask feat:SMS/EmailDistributor * be: save cb * entity searchbox * feat: REP * Chart axis 120 (#743) * style: CNMAP * feat: Stack Bar * feat: showHorizontal * be --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Approval expires 114 (#745) * feat: expiresAuto * be: approval copy * be:entity view by code * feat: tasks list (#746) * style: icon of chart * feat: project list tasks * be: setEditableFields keep sort Gitee#I9EGJB * be: executeLazy * Nd trans (#747) * enh: getDisksUsed * be: link entity * feat: ND trans * style * be: filter * icon: zmdi-filter-list * style: list badge 12px * detailImports * style project * be: $multipleUploader * feat: sop (#748) * be * be: trans 1>N * enh: sql ver * styles * ps style * feat: sop * enh: useExecManual for all --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Update forms.html * Report use cond (#749) * style * be: sop * feat: report useFilter * field image _captureType * TSID error --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Feat datasync 94 (#750) * be: ref-search pageSize=20 * feat: date W Gitee#I9I67Z * style * feat: bar3 * be: charts style * feat: DataSyncer spec * Enh extforms (#752) * dock style * enh: trigger edit code * fix: guide * trubo * fix: Add no-rollback-for=RepeatedRecordsException * 通过》已完成 * be: charts * enh: .detail-form-table.fullscreen * fix: Gitee#I9J3UR https://github.com/alibaba/easyexcel/issues/3432 * ExcelClipboardData * Update README.md --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Update @rbv * Feat excel clipboard data (#753) * style * feat: csvdata-rebuild * enh: DataListCategory (Use tree) * be: Nval duplicate * Apidock (#754) * TsetEntity * theme color * be: trigger on update * rm: LazyWaitDetailsFinished * apiman * fix: ApprovalStepNodeName * Enh charts (#755) * enh: axis filter * enh: FunnelChart * feat: SendNotification email attach * AutoGenReport * enh: `_readonly ` for setReadonly * be 3.7 (#756) * be: nodeName * url-safe * md pdf * $cleanNumbern * be: text:关联记录>相关记录 * enh: 级联支持N2N字段 * entity-overview * Dockerfile * fix yj * enh: AutoApproval revoke * Update @rbv --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * feat: List cat ref (#757) * be: DataListCategory * fix: 审批返回上一步有分支节点时错误 * be: 清理备份错误提示 * oshi * Be 3.7 2 (#759) * Update MarkdownUtils.java * be: filter N2N:User * apiman pdf * Be 3.7 3 (#760) * fix * be: webcam * Update field-edit.html * be * Be 3.7 4 (#761) * Update system-cfg.html * ConcatIdFunction * Update FieldWriteback.java * be video * Update media-capturer.js * Update DataImportController.java * lang * be * Update DataImportController.java * Update RebuildWebConfigurer.java * flatpickr * Update rb-base.js * handleChange lazy * fix chart in datalist * Update submail.js * style * Be 3.7 5 (#762) * $hex2rgb * be: checkRefDataFilter * style: feeds * style: file-icon * fix: 记录转换 D>M+D * be install * be: tests * feat: form-formula {NOW} * fix: 日期短格式区间查询 * _readonly37 --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Be 3.7 6 (#764) * form: __LAB_FORMACTION_105, __LAB_FORMACTION_103 * __LAB_MINUTESTEP * fix: 字段更新清空时支持N2N * be: Installer.java * feat: $dropUpload * be: file RbPreview * fix * mde paste * be dropUpload * fix: 不触发 onClientProgress??? * Be 3.7 7 (#765) * fix: num input * style: NTEXT keep empty-line * media-capturer.js * fix: pdf 预览下载文件名不对 * feat: nform * Update KnownExceptionConverter.java * be * Be 3.7 8 (#766) * be NFORM * Update form-design.js * Update charts.js * Update FormsManager.java * Be 3.7 9 (#767) * feat: speclayout * feat: Gitee#I9UJ7N * feat: easyaction * useCode * Be 3.7 10 (#768) * v3.7-hide * lang * be: targetEntityMatchFields * fix: * _StartEntityTypeCode * be * Update README.md * Feat html5 report tinymce (#777) * f-3.8 * tinymce * be: template5 * Update @rbv * Check inst (#778) * bump lib * refactor * feat: stopPropagation * flatpickr * fix: time query * feat: class color * be --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Be easyaction2 (#781) * Update @rbv * open: nform * Update flatpickr.min.js * fix: class color * feat: word 相关项支持 * feat: html5 相关项 * Update report-templates.js * feat: open: __LAB_MATCHFIELDS * feat: class code * feat: ConcatArrayFunction * Update @rbv * be * Update @rbv * fix * Update @rbv * style * Update @rbv * Protable width (#784) * feat: series reset * col-resize * feat: user batch * be: IncreasingVar * be * enh:reports (#785) * feat: report CHECKBOX/CHECKBOX2 * open: _cfParent * enh: 多选字段显示不要边框 * fix:backspace select2 * enh: query maxlength * enh: FeedsSchedule relatedRecord * Update @rbv * File upload cam (#788) * enh: _captureType * bump lib * style: sop * enh: Share2 edit * feat: fp * Update @rbv * Fp details (#789) * enh: CreateFeed feedType * enh: fp for details * enh: stopPropagation=quickMode * enh: html5 recordIdMultiple * be: admin deep=3 * fix: ApprovalFields2Schema complement * Custom lang (#790) * feat: PREV_APPROVER_BACKED * feat; vertical38 * feat: _ProtectedAdmin * List group tab (#791) * feat: advListShowCategory-set * style * kill-session (#792) * be: MultipleSessions feat: kill-session h5 * Be template5 (#793) * fix: CVE * $tagStyle2 * Update rb-forms.append.js * Charts field filter (#796) * be * enh: extform search-filter; HANPINY * be exportReport * Update DataReportManager.java * 变更历史合并显示 * feat: class, ref: code-append * feat: axis filter * be: html5 preview * barcode decode * Update @rbv * Enh easyaction (#798) * enh-easyaction * enh: ValueConvertFunc * Update @rbv * Update FieldPrivileges.java * enh: excel 列表支持值转换(#SIZE不支持) * Update EasyExcelGenerator.java * Update @rbv * Update @rbv * be * rm PrivilegesGuardContextHolder * Update rb-forms.js * be: wrapReturn * 多表单适用新建 --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Update @rbv * be:3.8-1 (#799) * be * HowtoPointcut * style * fix SN * be-3.8-2 (#800) * be:weakMode * feat: calcFormulaBackend * feat: groupFields * be: sop * feat: 修改模版文件 * fix * style * Be 3.8 3 (#801) * be * NODE MIRROR * Update @rbv --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Be 3.8 4 (#802) * fix: 通过字段匹配没有 SERIES 字段 * fix: best form-layout * style * fix: sop * MysqldumpBin * style * Update application-dev.yml * Update @rbv * be-3.8-5 (#803) * Update ValueConvertFunc.java * pt fixedWidth * Update AdvFilterParser.java * YYY, MMM * Update @rbv * be: user-delete cascade * fix * RbvMissingController * ROUND * be * be-3.8-6 (#804) * bugfix * Update ValueConvertFunc.java * DECIMAL_ROUNDING, etc * lang * Update @rbv * Be 3.8 7 (#805) * be * style * fix * fix * Update @rbv * Update ValueConvertFunc.java * Be 3.8 8 (#806) * base64 * be * Update @rbv * fix CVE * lang * Be 3.8 9 (#807) * Update @rbv * codemirror hints * be NForms * feat: _expandLine * fix 单字段保存取消后仍有脏数据 * fix: 代码丢失??? * Be 3.8 10 (#808) * be * enh: 字段匹配支持N级 * enh: 补充自增编号顺序 * feat: FromtJS.Query * Be 3.8 10 (#809) * er * Be 3.8 10 (#810) * er * fix: 位置字段数据格式 * be * Be 3.8 11 (#811) * Update charts.js * be:表单回调 * -bosskey-show * report * fix * Update DataListWrapper.java * open:regRowButton * be * Update @rbv * Be 3.8 12 (#812) * be * be * Update DataExporter.java * fix fjs * be * Update @rbv * beta1 * Update README.md * Update media-capturer.js * Update @rbv * be: quick-filter * Update AdvFilterParser.java * Update rb-datalist.common.js * feat: 触发器执行日 * Update @rbv * style * 允许撤回、撤销审批 * Update MetadataGetting.java * AutoGenReport * Update form-design.js * style:sop --------- Co-authored-by: devezhao <zhaofang123@gmail.com> * Update ServerStatus.java --------- Co-authored-by: devezhao <zhaofang123@gmail.com>
This commit is contained in:
parent
82e51fe46c
commit
0478a8e08e
|
@ -145,5 +145,6 @@ module.exports = {
|
|||
$hex2rgb: true,
|
||||
$isImage: true,
|
||||
$dropUpload: true,
|
||||
$tagStyle2: true,
|
||||
},
|
||||
}
|
||||
|
|
2
@rbv
2
@rbv
|
@ -1 +1 @@
|
|||
Subproject commit fb639d01abceb0dbb85760e475a153e2f69237b5
|
||||
Subproject commit 325da768fde9291914a1132283e62fe8cb83053c
|
16
README.md
16
README.md
|
@ -15,18 +15,18 @@
|
|||
|
||||
> **福利:加入 REBUILD VIP 用户 QQ 交流群 819865721 1013051587 GET 使用技能**
|
||||
|
||||
## V3.7 新特性
|
||||
## V3.8 新特性
|
||||
|
||||
本次更新为你带来众多功能增强与优化。
|
||||
|
||||
1. [新增] 限时审批
|
||||
2. [新增] 新建任务(触发器)
|
||||
3. [新增] 地图等多个图表
|
||||
4. [新增] 自动明细记录导入(记录转换)
|
||||
5. [新增] 数据列表之卡片模式
|
||||
1. [新增] HTML 报表模版
|
||||
2. [新增] 多表单布局
|
||||
3. [新增] 明细支持 Excel 粘贴录入
|
||||
4. [新增] 图片/附件支持摄像头上传模式
|
||||
5. [新增] 手机版数据列表可导出报表
|
||||
6. [新增] 多个 FrontJS 函数
|
||||
7. [优化] 图表支持多轴显示、横向显示、显示背景
|
||||
8. [优化] 手机版全新列表搜索组件
|
||||
7. [新增] 用户支持批量操作
|
||||
8. [优化] 20+ 细节/BUG/安全性更新
|
||||
9. ...
|
||||
|
||||
更多更新详情请参见 [更新日志](https://getrebuild.com/docs/dev/changelog)
|
||||
|
|
38
pom.xml
38
pom.xml
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
<groupId>com.rebuild</groupId>
|
||||
<artifactId>rebuild</artifactId>
|
||||
<version>3.7.7</version>
|
||||
<version>3.8.0-beta1</version>
|
||||
<name>rebuild</name>
|
||||
<description>Building your business-systems freely!</description>
|
||||
<url>https://getrebuild.com/</url>
|
||||
|
@ -86,6 +86,11 @@
|
|||
<configuration>
|
||||
<nodeVersion>v10.22.0</nodeVersion>
|
||||
<workingDirectory>.deploy</workingDirectory>
|
||||
<!-- UNCOMMENT USE NODE MIRROR -->
|
||||
<!--
|
||||
<nodeDownloadRoot>https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/</nodeDownloadRoot>
|
||||
<npmRegistryURL>https://registry.npmmirror.com</npmRegistryURL>
|
||||
-->
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- USE COMMERCIAL MODULES IF EXISTS -->
|
||||
|
@ -187,9 +192,9 @@
|
|||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>alimaven</id>
|
||||
<id>alimaven2</id>
|
||||
<name>public</name>
|
||||
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
|
||||
<url>https://maven.aliyun.com/repository/public</url>
|
||||
</repository>
|
||||
<!-- Unpublished libs can be found on https://github.com/devezhao/ -->
|
||||
<repository>
|
||||
|
@ -298,7 +303,7 @@
|
|||
<dependency>
|
||||
<groupId>com.github.devezhao</groupId>
|
||||
<artifactId>persist4j</artifactId>
|
||||
<version>1.7.10</version>
|
||||
<version>1.7.11</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.alibaba</groupId>
|
||||
|
@ -334,12 +339,12 @@
|
|||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>1.2.22</version>
|
||||
<version>1.2.23</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<version>8.3.0</version>
|
||||
<version>8.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.ehcache</groupId>
|
||||
|
@ -349,12 +354,12 @@
|
|||
<dependency>
|
||||
<groupId>redis.clients</groupId>
|
||||
<artifactId>jedis</artifactId>
|
||||
<version>4.4.7</version>
|
||||
<version>4.4.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.qiniu</groupId>
|
||||
<artifactId>qiniu-java-sdk</artifactId>
|
||||
<version>7.15.0</version>
|
||||
<version>7.15.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.whvcse</groupId>
|
||||
|
@ -364,7 +369,7 @@
|
|||
<dependency>
|
||||
<groupId>com.github.oshi</groupId>
|
||||
<artifactId>oshi-core</artifactId>
|
||||
<version>6.5.0</version>
|
||||
<version>6.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
|
@ -470,7 +475,7 @@
|
|||
<dependency>
|
||||
<groupId>org.redisson</groupId>
|
||||
<artifactId>redisson</artifactId>
|
||||
<version>3.27.2</version>
|
||||
<version>3.33.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
@ -496,7 +501,7 @@
|
|||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-core</artifactId>
|
||||
<version>5.8.26</version>
|
||||
<version>5.8.29</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
@ -516,15 +521,10 @@
|
|||
<artifactId>jansi</artifactId>
|
||||
<version>2.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.zwobble.mammoth</groupId>
|
||||
<artifactId>mammoth</artifactId>
|
||||
<version>1.7.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.getui.push</groupId>
|
||||
<artifactId>restful-sdk</artifactId>
|
||||
<version>1.0.0.17</version>
|
||||
<version>1.0.2.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- fix: CVEs -->
|
||||
|
@ -536,7 +536,7 @@
|
|||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-compress</artifactId>
|
||||
<version>1.26.1</version>
|
||||
<version>1.26.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
|
@ -546,7 +546,7 @@
|
|||
<dependency>
|
||||
<groupId>com.fasterxml.woodstox</groupId>
|
||||
<artifactId>woodstox-core</artifactId>
|
||||
<version>6.6.1</version>
|
||||
<version>6.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
|
|
|
@ -141,25 +141,4 @@ public class AuthTokenManager {
|
|||
|
||||
return ID.valueOf(descs[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 AccessToken 延长有效期
|
||||
*
|
||||
* @param token
|
||||
* @return
|
||||
* @see #verifyToken(String, boolean, boolean)
|
||||
* @deprecated Use {@link #verifyToken(String, boolean, boolean)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static ID refreshAccessToken(String token) {
|
||||
Assert.notNull(token, "[token] cannot be null");
|
||||
String desc = Application.getCommonsCache().get(TOKEN_PREFIX + token);
|
||||
if (desc == null) return null;
|
||||
|
||||
String[] descs = desc.split(",");
|
||||
Assert.isTrue(TYPE_ACCESS_TOKEN.equals(descs[0]), "Cannot refresh none access token");
|
||||
|
||||
Application.getCommonsCache().put(TOKEN_PREFIX + token, desc, ACCESSTOKEN_EXPIRES);
|
||||
return ID.valueOf(descs[1]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -74,11 +74,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
|
|||
/**
|
||||
* Rebuild Version
|
||||
*/
|
||||
public static final String VER = "3.7.7";
|
||||
public static final String VER = "3.8.0-beta1";
|
||||
/**
|
||||
* Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
|
||||
*/
|
||||
public static final int BUILD = 3070713;
|
||||
public static final int BUILD = 3080001;
|
||||
|
||||
static {
|
||||
// Driver for DB
|
||||
|
|
|
@ -127,7 +127,7 @@ public class BootApplication extends SpringBootServletInitializer {
|
|||
try {
|
||||
initTomcatPort();
|
||||
} catch (Exception ex) {
|
||||
log.debug("Cannot to get Tomcat port : " + ex.getLocalizedMessage());
|
||||
log.debug("Cannot to get Tomcat port : {}", ex.getLocalizedMessage());
|
||||
}
|
||||
|
||||
log.info("Initializing SpringBoot context {}...", devMode() ? "(dev) " : "");
|
||||
|
|
|
@ -18,7 +18,7 @@ import org.apache.commons.io.IOUtils;
|
|||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.util.ArrayList;
|
||||
|
@ -114,22 +114,23 @@ public final class ServerStatus {
|
|||
* @return
|
||||
*/
|
||||
static Status checkCreateFile() {
|
||||
String name = "Create File";
|
||||
FileWriter fw = null;
|
||||
String name = "CreateFile";
|
||||
File test = null;
|
||||
RandomAccessFile raf = null;
|
||||
try {
|
||||
File test = new File(FileUtils.getTempDirectory(), "ServerStatus.test");
|
||||
fw = new FileWriter(test);
|
||||
IOUtils.write(CodecUtils.randomCode(1024), fw);
|
||||
test = new File(FileUtils.getTempDirectory(), "ServerStatus.test");
|
||||
raf = new RandomAccessFile(test, "rw");
|
||||
raf.setLength(1024 * 1024 * 5); // 5M
|
||||
|
||||
if (!test.exists()) {
|
||||
return Status.error(name, "Cannot create file in temp-directory");
|
||||
} else {
|
||||
FileUtils.deleteQuietly(test);
|
||||
}
|
||||
|
||||
} catch (Exception ex) {
|
||||
return Status.error(name, ThrowableUtils.getRootCause(ex).getLocalizedMessage());
|
||||
} finally {
|
||||
IOUtils.closeQuietly(fw);
|
||||
if (raf != null) IOUtils.closeQuietly(raf);
|
||||
if (test != null) FileUtils.deleteQuietly(test);
|
||||
}
|
||||
return Status.success(name);
|
||||
}
|
||||
|
@ -184,9 +185,9 @@ public final class ServerStatus {
|
|||
this.error = error;
|
||||
|
||||
if (success) {
|
||||
log.debug("Checking " + this);
|
||||
log.debug("Checking {}", this);
|
||||
} else {
|
||||
log.error("Checking " + this);
|
||||
log.error("Checking {}", this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -516,8 +516,10 @@ public class NavBuilder extends NavManager {
|
|||
for (Object[] nd : topNav) {
|
||||
String url = AppUtils.getContextPath("/app/home?def=" + nd[0]);
|
||||
if (nd[1] != null) url += ":" + nd[1];
|
||||
|
||||
topNavHtml.append(String.format(
|
||||
"<li class=\"nav-item\" data-id=\"%s\"><a class=\"nav-link text-ellipsis\" href=\"%s\">%s</a></li>", nd[0], url, nd[2]));
|
||||
"<li class=\"nav-item\" data-id=\"%s\"><a class=\"nav-link text-ellipsis\" href=\"%s\">%s</a></li>",
|
||||
nd[0], url, CommonsUtils.escapeHtml(nd[2])));
|
||||
}
|
||||
return topNavHtml.toString();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
|||
import com.rebuild.core.metadata.easymeta.EasyN2NReference;
|
||||
import com.rebuild.core.metadata.easymeta.MixValue;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.support.general.CalcFormulaSupport;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -47,7 +48,8 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 表单自动回填
|
||||
* 表单自动回填。
|
||||
* 请注意此功能的优先级:回填实在构建 Record 时发生,因此相对触发器,其优先级较低
|
||||
*
|
||||
* @author devezhao zhaofang123@gmail.com
|
||||
* @since 2019/05/17
|
||||
|
@ -237,6 +239,10 @@ public class AutoFillinManager implements ConfigManager {
|
|||
|
||||
fillin += fillinRecordItem(easyField.getRawMeta(), record.getObjectValue(fieldName), isNew, fillinForce, record);
|
||||
}
|
||||
|
||||
// v3.8 借用贵宝地
|
||||
CalcFormulaSupport.calcFormulaBackend(record);
|
||||
|
||||
return fillin;
|
||||
}
|
||||
|
||||
|
|
|
@ -57,25 +57,43 @@ public class ClassificationManager implements ConfigManager {
|
|||
return item == null ? null : item.FullName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemId
|
||||
* @return
|
||||
*/
|
||||
public String getColor(ID itemId) {
|
||||
Item item = getItem(itemId);
|
||||
return item == null ? null : item.Color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemId
|
||||
* @return
|
||||
*/
|
||||
public String getCode(ID itemId) {
|
||||
Item item = getItem(itemId);
|
||||
return item == null ? null : item.Code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param itemId
|
||||
* @return
|
||||
*/
|
||||
private Item getItem(ID itemId) {
|
||||
final String ckey = "ClassificationITEM31-" + itemId;
|
||||
final String ckey = "ClassificationITEM38-" + itemId;
|
||||
Item ditem = (Item) Application.getCommonsCache().getx(ckey);
|
||||
if (ditem != null) {
|
||||
return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
|
||||
}
|
||||
|
||||
Object[] o = Application.createQueryNoFilter(
|
||||
"select name,fullName,code from ClassificationData where itemId = ?")
|
||||
"select name,fullName,code,color from ClassificationData where itemId = ?")
|
||||
.setParameter(1, itemId)
|
||||
.unique();
|
||||
if (o != null) ditem = new Item((String) o[0], (String) o[1], (String) o[2]);
|
||||
if (o != null) ditem = new Item((String) o[0], (String) o[1], (String) o[2], (String) o[3]);
|
||||
|
||||
// 可能已删除
|
||||
if (ditem == null) ditem = new Item(DELETED_ITEM, null, null);
|
||||
if (ditem == null) ditem = new Item(DELETED_ITEM, null, null, null);
|
||||
|
||||
Application.getCommonsCache().putx(ckey, ditem);
|
||||
return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
|
||||
|
@ -163,7 +181,7 @@ public class ClassificationManager implements ConfigManager {
|
|||
public void clean(Object cid) {
|
||||
ID id2 = (ID) cid;
|
||||
if (id2.getEntityCode() == EntityHelper.ClassificationData) {
|
||||
Application.getCommonsCache().evict("ClassificationITEM31-" + cid);
|
||||
Application.getCommonsCache().evict("ClassificationITEM38-" + cid);
|
||||
} else if (id2.getEntityCode() == EntityHelper.Classification) {
|
||||
Application.getCommonsCache().evict("ClassificationLEVEL-" + cid);
|
||||
}
|
||||
|
@ -172,14 +190,16 @@ public class ClassificationManager implements ConfigManager {
|
|||
// Bean
|
||||
static class Item implements Serializable {
|
||||
private static final long serialVersionUID = -1903227875771376652L;
|
||||
Item(String name, String fullName, String code) {
|
||||
Item(String name, String fullName, String code, String color) {
|
||||
this.Name = name;
|
||||
this.FullName = fullName;
|
||||
this.Code = code;
|
||||
this.Color = color;
|
||||
}
|
||||
|
||||
final String Name;
|
||||
final String FullName;
|
||||
final String Code;
|
||||
final String Color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,12 +77,7 @@ public class DataListCategory {
|
|||
|
||||
// 使用全部
|
||||
if (dt == DisplayType.MULTISELECT || dt == DisplayType.PICKLIST) {
|
||||
ConfigBean[] cbs = MultiSelectManager.instance.getPickListRaw(categoryField, true);
|
||||
for (ConfigBean cb : cbs) {
|
||||
Object id = cb.getID("id");
|
||||
if (dt == DisplayType.MULTISELECT) id = cb.getLong("mask");
|
||||
dataList.add(new Item(id, cb.getString("text")));
|
||||
}
|
||||
dataList = datasOptions(categoryField, dt);
|
||||
|
||||
} else if (dt == DisplayType.CLASSIFICATION) {
|
||||
// 分类
|
||||
|
@ -153,6 +148,24 @@ public class DataListCategory {
|
|||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* for PICKLIST, MULTISELECT
|
||||
*
|
||||
* @param field
|
||||
* @param dt
|
||||
* @return
|
||||
*/
|
||||
protected Collection<Item> datasOptions(Field field, DisplayType dt) {
|
||||
Collection<Item> dataList = new LinkedHashSet<>();
|
||||
ConfigBean[] cbs = MultiSelectManager.instance.getPickListRaw(field, true);
|
||||
for (ConfigBean cb : cbs) {
|
||||
Object id = cb.getID("id");
|
||||
if (dt == DisplayType.MULTISELECT) id = cb.getLong("mask");
|
||||
dataList.add(new Item(id, cb.getString("text")));
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Max. 4L
|
||||
* @param field
|
||||
|
|
|
@ -0,0 +1,321 @@
|
|||
/*!
|
||||
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.configuration.general;
|
||||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.commons.ObjectUtils;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import cn.devezhao.persist4j.metadata.MissingMetaExcetion;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.configuration.general.DataListCategory.Item;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyEntityConfigProps;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据列表分组查询
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 07/27/2024
|
||||
*/
|
||||
@Slf4j
|
||||
public class DataListCategory38 {
|
||||
|
||||
public static final DataListCategory38 instance = new DataListCategory38();
|
||||
|
||||
private DataListCategory38() {}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param parentValues
|
||||
* @return
|
||||
*/
|
||||
public JSON datas(Entity entity, Object[] parentValues) {
|
||||
final String categoryField = getSettings(entity);
|
||||
if (categoryField == null) return null;
|
||||
|
||||
// 查第几个字段
|
||||
int fieldIndex = parentValues == null ? 0 : parentValues.length;
|
||||
// 特殊处理:引用字段父级
|
||||
if (fieldIndex > 0 && categoryField.split(";").length == 1) {
|
||||
String[] ff = categoryField.split(":");
|
||||
Field fieldMeta = entity.getField(ff[0]);
|
||||
if (ff.length == 2
|
||||
&& entity.containsField(ff[0]) && fieldMeta.getReferenceEntity().containsField(ff[1])) {
|
||||
fieldIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
final List<String[]> categoryFields = new ArrayList<>();
|
||||
for (String ff : categoryField.split(";")) {
|
||||
String[] fieldAndFormat = ff.split(":");
|
||||
if (!entity.containsField(fieldAndFormat[0])) {
|
||||
throw new MissingMetaExcetion(entity.getName(), fieldAndFormat[0]);
|
||||
}
|
||||
categoryFields.add(fieldAndFormat);
|
||||
}
|
||||
if (fieldIndex + 1 > categoryFields.size()) return null;
|
||||
|
||||
// 使用配置
|
||||
String[] ff = categoryFields.get(fieldIndex);
|
||||
final String useField = ff[0];
|
||||
String useFormat = ff.length > 1 ? ff[1] : null;
|
||||
final Field useFieldMeta = entity.getField(useField);
|
||||
final DisplayType dt = EasyMetaFactory.getDisplayType(useFieldMeta);
|
||||
|
||||
// 单个字段
|
||||
final boolean isSingleLevel = categoryFields.size() == 1;
|
||||
// 分类,引用(父级)支持树状
|
||||
if (!isSingleLevel && useFormat != null) {
|
||||
if (dt == DisplayType.CLASSIFICATION || dt == DisplayType.REFERENCE) {
|
||||
log.warn("When multiple category fields, the format is disabled : {}", categoryField);
|
||||
useFormat = null;
|
||||
}
|
||||
}
|
||||
|
||||
Collection<Item> dataList;
|
||||
// 是否还有下级需要加载
|
||||
boolean hasChild = false;
|
||||
// 是否排序
|
||||
int sortMode = 0;
|
||||
|
||||
// 单字段适用:选项类的
|
||||
if ((dt == DisplayType.MULTISELECT || dt == DisplayType.PICKLIST)) {
|
||||
dataList = DataListCategory.instance.datasOptions(useFieldMeta, dt);
|
||||
hasChild = categoryFields.size() > fieldIndex + 1;
|
||||
}
|
||||
// 单字段适用:分类字段
|
||||
else if (dt == DisplayType.CLASSIFICATION && isSingleLevel) {
|
||||
dataList = DataListCategory.instance.datasClassification(useFieldMeta, useFormat);
|
||||
// 开放了几级
|
||||
int level = ClassificationManager.instance.getOpenLevel(useFieldMeta);
|
||||
int levelSpec = useFormat == null ? level : ObjectUtils.toInt(useFormat);
|
||||
if (levelSpec < level) level = levelSpec;
|
||||
if (level > 0) hasChild = true;
|
||||
}
|
||||
// 单字段适用:引用字段父级
|
||||
else if (dt == DisplayType.REFERENCE && useFormat != null) {
|
||||
dataList = datasReference(useFieldMeta, useFormat,
|
||||
parentValues == null ? null : parentValues[parentValues.length - 1]);
|
||||
hasChild = true;
|
||||
sortMode = 1;
|
||||
}
|
||||
else {
|
||||
sortMode = 1;
|
||||
String sql;
|
||||
if (dt == DisplayType.N2NREFERENCE) {
|
||||
sql = String.format(
|
||||
"select distinct referenceId from NreferenceItem where belongEntity = '%s' and belongField = '%s'",
|
||||
entity.getName(), useField);
|
||||
// N级
|
||||
if (parentValues != null) {
|
||||
String nestSql = String.format("select %s from %s where %s",
|
||||
entity.getPrimaryField().getName(), entity.getName(),
|
||||
buildParentFilters(entity, categoryFields, parentValues));
|
||||
sql += String.format(" and recordId in ( %s )", nestSql);
|
||||
}
|
||||
|
||||
} else {
|
||||
String wrapField = useField;
|
||||
// 日期格式
|
||||
if (dt == DisplayType.DATETIME || dt == DisplayType.DATE) {
|
||||
// DATE 使用字段设置的
|
||||
if (useFormat == null) {
|
||||
useFormat = EasyMetaFactory.valueOf(useFieldMeta).getExtraAttr(EasyFieldConfigProps.DATE_FORMAT);
|
||||
}
|
||||
if ("yyyy".equalsIgnoreCase(useFormat)) wrapField = String.format("DATE_FORMAT(%s, '%%Y')", useField);
|
||||
else if ("yyyy-MM".equalsIgnoreCase(useFormat)) wrapField = String.format("DATE_FORMAT(%s, '%%Y-%%m')", useField);
|
||||
else wrapField = String.format("DATE_FORMAT(%s, '%%Y-%%m-%%d')", useField);
|
||||
|
||||
sortMode = 2;
|
||||
}
|
||||
|
||||
sql = MessageFormat.format(
|
||||
"select distinct {0} from {1} where {2} is not null",
|
||||
wrapField, entity.getName(), useField);
|
||||
|
||||
if (parentValues != null) {
|
||||
sql += " and " + buildParentFilters(entity, categoryFields, parentValues);
|
||||
}
|
||||
}
|
||||
|
||||
Object[][] array = Application.createQuery(sql).array();
|
||||
|
||||
dataList = new LinkedHashSet<>();
|
||||
for (Object[] o : array) {
|
||||
Object id = o[0];
|
||||
String label;
|
||||
if (id instanceof Date) {
|
||||
String dateFormat = StringUtils.defaultIfBlank(useFormat, CalendarUtils.UTC_DATE_FORMAT);
|
||||
label = CalendarUtils.format(dateFormat, (Date) id);
|
||||
} else if (id instanceof ID) {
|
||||
label = FieldValueHelper.getLabelNotry((ID) id);
|
||||
} else {
|
||||
label = id.toString();
|
||||
}
|
||||
|
||||
dataList.add(new Item(id, label));
|
||||
}
|
||||
|
||||
hasChild = categoryFields.size() > fieldIndex + 1;
|
||||
}
|
||||
|
||||
JSONArray res = new JSONArray();
|
||||
for (Item i : dataList) res.add(i.toJSON(0));
|
||||
// 排序
|
||||
if (sortMode == 2 || sortMode == 1) {
|
||||
final boolean isDesc = sortMode == 2;
|
||||
res.sort((o1, o2) -> {
|
||||
String text1 = ((JSONObject) o1).getString("text");
|
||||
String text2 = ((JSONObject) o2).getString("text");
|
||||
return isDesc ? text2.compareTo(text1) : text1.compareTo(text2);
|
||||
});
|
||||
}
|
||||
|
||||
return JSONUtils.toJSONObject(
|
||||
new String[]{ "hasChild", "data" },
|
||||
new Object[]{ hasChild, res });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @param parentField
|
||||
* @param parentValue
|
||||
* @return
|
||||
*/
|
||||
protected Collection<Item> datasReference(Field field, String parentField, Object parentValue) {
|
||||
Field parentFieldMeta = field.getReferenceEntity().getField(parentField);
|
||||
String sql = MessageFormat.format(
|
||||
"select {0} from {1} where {2}",
|
||||
parentFieldMeta.getOwnEntity().getPrimaryField().getName(),
|
||||
parentFieldMeta.getOwnEntity().getName(),
|
||||
parentFieldMeta.getName());
|
||||
if (parentValue == null) sql += " is null";
|
||||
else sql += " = '" + parentValue + "'";
|
||||
|
||||
Object[][] array = Application.createQuery(sql).array();
|
||||
|
||||
Collection<Item> dataList = new LinkedHashSet<>();
|
||||
for (Object[] o : array) {
|
||||
Object id = o[0];
|
||||
String label = FieldValueHelper.getLabelNotry((ID) id);
|
||||
dataList.add(new Item(id, label));
|
||||
}
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param categoryFields
|
||||
* @param parentValues
|
||||
* @return
|
||||
*/
|
||||
protected String buildParentFilters(Entity entity, List<String[]> categoryFields, Object[] parentValues) {
|
||||
List<String> and = new ArrayList<>();
|
||||
for (int i = 0; i < parentValues.length; i++) {
|
||||
String[] ff = categoryFields.get(i);
|
||||
String fieldName = ff[0];
|
||||
String fieldValue = parentValues[i].toString();
|
||||
Field fieldMeta = entity.getField(fieldName);
|
||||
DisplayType dt = EasyMetaFactory.getDisplayType(fieldMeta);
|
||||
|
||||
// 一级树:引用
|
||||
if (categoryFields.size() == 1 && dt == DisplayType.REFERENCE) {
|
||||
fieldValue = parentValues[parentValues.length - 1].toString();
|
||||
return String.format("%s = '%s'", fieldName, fieldValue);
|
||||
}
|
||||
// 一级树:分类
|
||||
if (categoryFields.size() == 1 && dt == DisplayType.CLASSIFICATION) {
|
||||
fieldValue = parentValues[parentValues.length - 1].toString();
|
||||
int level = ClassificationManager.instance.getOpenLevel(fieldMeta);
|
||||
// 用 or 提高数据兼容性
|
||||
List<String> parentSql = new ArrayList<>();
|
||||
parentSql.add(String.format("%s = '%s'", fieldName, fieldValue));
|
||||
if (level > 0) parentSql.add(String.format("%s.parent = '%s'", fieldName, fieldValue));
|
||||
if (level > 1) parentSql.add(String.format("%s.parent.parent = '%s'", fieldName, fieldValue));
|
||||
if (level > 2) parentSql.add(String.format("%s.parent.parent.parent = '%s'", fieldName, fieldValue));
|
||||
return "( " + StringUtils.join(parentSql, " or ") + " )";
|
||||
}
|
||||
|
||||
if (dt == DisplayType.DATETIME || dt == DisplayType.DATE) {
|
||||
String s = fieldValue + "0000-01-01 00:00:00".substring(fieldValue.length());
|
||||
String e = fieldValue + "0000-12-31 23:59:59".substring(fieldValue.length());
|
||||
if (dt == DisplayType.DATE) {
|
||||
s = s.substring(0, 10);
|
||||
e = e.substring(0, 10);
|
||||
}
|
||||
|
||||
String filter = MessageFormat.format("({0} >= ''{1}'' and {0} <= ''{2}'')", fieldName, s, e);
|
||||
and.add(filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dt == DisplayType.MULTISELECT) {
|
||||
String filter = String.format("%s && %d", fieldName, ObjectUtils.toInt(fieldValue));
|
||||
and.add(filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dt == DisplayType.N2NREFERENCE) {
|
||||
String filter = String.format(
|
||||
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')",
|
||||
entity.getPrimaryField().getName(), fieldMeta.getName(), fieldValue);
|
||||
and.add(filter);
|
||||
continue;
|
||||
}
|
||||
|
||||
String simple = String.format("%s = '%s'", fieldName, fieldValue);
|
||||
and.add(simple);
|
||||
}
|
||||
return "( " + StringUtils.join(and, " and ") + " )";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param parentValues
|
||||
* @return
|
||||
*/
|
||||
public String buildParentFilters(Entity entity, Object[] parentValues) {
|
||||
final String categoryField = getSettings(entity);
|
||||
if (categoryField == null) return null;
|
||||
|
||||
List<String[]> categoryFields = new ArrayList<>();
|
||||
for (String ff : categoryField.split(";")) {
|
||||
categoryFields.add(ff.split(":"));
|
||||
}
|
||||
|
||||
return buildParentFilters(entity, categoryFields, parentValues);
|
||||
}
|
||||
|
||||
private String getSettings(Entity entity) {
|
||||
int listMode = ObjectUtils.toInt(EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADVLIST_MODE), 1);
|
||||
String categoryField;
|
||||
if (listMode == 3) {
|
||||
categoryField = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADVLIST_MODE3_SHOWCATEGORY);
|
||||
} else {
|
||||
categoryField = EasyMetaFactory.valueOf(entity).getExtraAttr(EasyEntityConfigProps.ADVLIST_SHOWCATEGORY);
|
||||
}
|
||||
return StringUtils.isBlank(categoryField) ? null : categoryField;
|
||||
}
|
||||
}
|
|
@ -77,4 +77,11 @@ public class EasyActionManager extends BaseLayoutManager {
|
|||
public ConfigBean getEasyActionRaw(String entity) {
|
||||
return getLayout(UserService.SYSTEM_USER, entity, TYPE_EASYACTION, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean(Object layoutId) {
|
||||
super.clean(layoutId);
|
||||
|
||||
// TODO JS 支持 ES6 > ES5
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.rebuild.core.metadata.easymeta.EasyField;
|
|||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyEntityConfigProps;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.privileges.FieldPrivileges;
|
||||
import com.rebuild.core.privileges.UserFilters;
|
||||
import com.rebuild.core.privileges.bizz.Department;
|
||||
import com.rebuild.core.privileges.bizz.User;
|
||||
|
@ -129,7 +130,7 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
}
|
||||
|
||||
// 明细实体有主实体
|
||||
// 明细实体
|
||||
final Entity hasMainEntity = entityMeta.getMainEntity();
|
||||
// 审批流程(状态)
|
||||
ApprovalState approvalState;
|
||||
|
@ -142,7 +143,7 @@ public class FormsBuilder extends FormsManager {
|
|||
if (recordId == null) {
|
||||
if (hasMainEntity != null) {
|
||||
ID mainid = FormsBuilderContextHolder.getMainIdOfDetail(false);
|
||||
Assert.notNull(mainid, "Call `FormBuilderContextHolder#setMainIdOfDetail` first!");
|
||||
Assert.notNull(mainid, "CALL `FormBuilderContextHolder#setMainIdOfDetail` FIRST!");
|
||||
|
||||
approvalState = EntityHelper.isUnsavedId(mainid) ? null : getHadApproval(hasMainEntity, mainid);
|
||||
if ((approvalState == ApprovalState.PROCESSING || approvalState == ApprovalState.APPROVED)) {
|
||||
|
@ -176,7 +177,7 @@ public class FormsBuilder extends FormsManager {
|
|||
// 编辑
|
||||
else {
|
||||
if (!Application.getPrivilegesManager().allowUpdate(user, recordId)) {
|
||||
return formatModelError(Language.L("你没有修改此记录的权限"));
|
||||
return formatModelError(Language.L("你没有编辑此记录的权限"));
|
||||
}
|
||||
|
||||
approvalState = getHadApproval(entityMeta, recordId);
|
||||
|
@ -205,8 +206,8 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
}
|
||||
|
||||
int applyType = recordId == null ? FormsManager.APPLY_ONNEW : FormsManager.APPLY_ONEDIT;
|
||||
if (viewMode) applyType = FormsManager.APPLY_ONVIEW;
|
||||
int applyType = recordId == null ? FormsManager.APPLY_NEW : FormsManager.APPLY_EDIT;
|
||||
if (viewMode) applyType = FormsManager.APPLY_VIEW;
|
||||
|
||||
ConfigBean model = getFormLayout(entity, recordOrLayoutId, applyType);
|
||||
JSONArray elements = (JSONArray) model.getJSON("elements");
|
||||
|
@ -353,6 +354,10 @@ public class FormsBuilder extends FormsManager {
|
|||
final boolean isNew = recordData == null || recordData.getPrimary() == null
|
||||
|| EntityHelper.isUnsavedId(recordData.getPrimary());
|
||||
|
||||
final FieldPrivileges fp = Application.getPrivilegesManager().getFieldPrivileges();
|
||||
// 在共同编辑时,对于明细应该是编辑而非新建
|
||||
final boolean isProTableLayout = FormsBuilderContextHolder.getMainIdOfDetail(false) != null;
|
||||
|
||||
// Check and clean
|
||||
for (Iterator<Object> iter = elements.iterator(); iter.hasNext(); ) {
|
||||
JSONObject el = (JSONObject) iter.next();
|
||||
|
@ -462,18 +467,15 @@ public class FormsBuilder extends FormsManager {
|
|||
el.put("options", ObjectUtils.defaultIfNull(el.remove("tagList"), JSONUtils.EMPTY_ARRAY));
|
||||
} else if (dt == DisplayType.DATETIME) {
|
||||
String format = StringUtils.defaultIfBlank(
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.DATETIME_FORMAT),
|
||||
easyField.getDisplayType().getDefaultFormat());
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.DATETIME_FORMAT), dt.getDefaultFormat());
|
||||
el.put(EasyFieldConfigProps.DATETIME_FORMAT, format);
|
||||
} else if (dt == DisplayType.DATE) {
|
||||
String format = StringUtils.defaultIfBlank(
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.DATE_FORMAT),
|
||||
easyField.getDisplayType().getDefaultFormat());
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.DATE_FORMAT), dt.getDefaultFormat());
|
||||
el.put(EasyFieldConfigProps.DATE_FORMAT, format);
|
||||
} else if (dt == DisplayType.TIME) {
|
||||
String format = StringUtils.defaultIfBlank(
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.TIME_FORMAT),
|
||||
easyField.getDisplayType().getDefaultFormat());
|
||||
easyField.getExtraAttr(EasyFieldConfigProps.TIME_FORMAT), dt.getDefaultFormat());
|
||||
el.put(EasyFieldConfigProps.TIME_FORMAT, format);
|
||||
} else if (dt == DisplayType.CLASSIFICATION) {
|
||||
el.put("openLevel", ClassificationManager.instance.getOpenLevel(fieldMeta));
|
||||
|
@ -529,7 +531,7 @@ public class FormsBuilder extends FormsManager {
|
|||
if (defaultValue != null) {
|
||||
defaultValue = easyField.wrapValue(defaultValue);
|
||||
// `wrapValue` 会添加格式符号
|
||||
if (easyField.getDisplayType() == DisplayType.DECIMAL) {
|
||||
if (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER) {
|
||||
defaultValue = EasyDecimal.clearFlaged(defaultValue);
|
||||
}
|
||||
el.put("value", defaultValue);
|
||||
|
@ -570,7 +572,7 @@ public class FormsBuilder extends FormsManager {
|
|||
Object value = wrapFieldValue(recordData, easyField, user);
|
||||
if (value != null) {
|
||||
// `wrapValue` 会添加格式符号
|
||||
if (!viewModel && easyField.getDisplayType() == DisplayType.DECIMAL) {
|
||||
if (!viewModel && (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER)) {
|
||||
value = EasyDecimal.clearFlaged(value);
|
||||
}
|
||||
el.put("value", value);
|
||||
|
@ -595,6 +597,14 @@ public class FormsBuilder extends FormsManager {
|
|||
if (decimalType != null && decimalType.contains("%s")) {
|
||||
el.put("decimalType", decimalType.replace("%s", ""));
|
||||
}
|
||||
|
||||
// v3.8
|
||||
if (isNew && !isProTableLayout) {
|
||||
if (!fp.isCreatable(fieldMeta, user)) el.put("readonly", true);
|
||||
} else {
|
||||
if (!fp.isReadble(fieldMeta, user)) iter.remove();
|
||||
else if (!fp.isUpdatable(fieldMeta, user)) el.put("readonly", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -747,8 +757,9 @@ public class FormsBuilder extends FormsManager {
|
|||
}
|
||||
// 其他
|
||||
else if (entity.containsField(field)) {
|
||||
EasyField easyField = EasyMetaFactory.valueOf(entity.getField(field));
|
||||
if (easyField.getDisplayType() == DisplayType.REFERENCE || easyField.getDisplayType() == DisplayType.N2NREFERENCE) {
|
||||
final EasyField easyField = EasyMetaFactory.valueOf(entity.getField(field));
|
||||
final DisplayType dt = easyField.getDisplayType();
|
||||
if (dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE) {
|
||||
// v3.4 如果字段设置了附加过滤条件,从相关项新建时要检查是否符合
|
||||
if (!FieldValueHelper.checkRefDataFilter(easyField, ID.valueOf(value))) {
|
||||
((JSONObject) formModel).put("alertMessage",
|
||||
|
@ -758,7 +769,7 @@ public class FormsBuilder extends FormsManager {
|
|||
|
||||
Object mixValue = inFormFields.contains(field) ? getReferenceMixValue(value) : value;
|
||||
if (mixValue != null) {
|
||||
if (easyField.getDisplayType() == DisplayType.REFERENCE) {
|
||||
if (dt == DisplayType.REFERENCE) {
|
||||
initialValReady.put(field, mixValue);
|
||||
} else {
|
||||
// N2N 是数组
|
||||
|
|
|
@ -35,16 +35,16 @@ public class FormsManager extends BaseLayoutManager {
|
|||
protected FormsManager() {}
|
||||
|
||||
// 表单布局适用于
|
||||
public static int APPLY_ONNEW = 1;
|
||||
public static int APPLY_ONEDIT = 2;
|
||||
public static int APPLY_ONVIEW = 4;
|
||||
public static int APPLY_NEW = 1;
|
||||
public static int APPLY_EDIT = 2;
|
||||
public static int APPLY_VIEW = 4;
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public ConfigBean getNewFormLayout(String entity) {
|
||||
return getFormLayout(entity, null, APPLY_ONNEW);
|
||||
return getFormLayout(entity, null, APPLY_NEW);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,26 +62,21 @@ public class FormsManager extends BaseLayoutManager {
|
|||
|
||||
// 1.指定布局
|
||||
if (recordOrLayoutId != null && recordOrLayoutId.getEntityCode() == EntityHelper.LayoutConfig) {
|
||||
for (Object[] o : alls) {
|
||||
if (recordOrLayoutId.equals(o[0])) {
|
||||
use = findConfigBean(alls, (ID) o[0]);;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
use = findConfigBean(alls, recordOrLayoutId);
|
||||
if (use == null) {
|
||||
log.warn("Spec layout not longer exists : {}", recordOrLayoutId);
|
||||
recordOrLayoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 2.使用布局
|
||||
// 2.查找布局
|
||||
if (use == null) {
|
||||
// 优先使用条件匹配的
|
||||
for (Object[] o : alls) {
|
||||
ConfigBean cb = findConfigBean(alls, (ID) o[0]);
|
||||
ShareToAttr attr = new ShareToAttr(cb);
|
||||
if (recordOrLayoutId == null) {
|
||||
if (attr.isFallback()) {
|
||||
if (attr.isFallback() || attr.isForNew()) {
|
||||
use = cb;
|
||||
break;
|
||||
}
|
||||
|
@ -92,9 +87,14 @@ public class FormsManager extends BaseLayoutManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 默认优先级
|
||||
if (recordOrLayoutId == null) {
|
||||
use = findDefault(alls);
|
||||
}
|
||||
}
|
||||
|
||||
// 3.默认布局(fallback)
|
||||
// 3.默认布局
|
||||
if (use == null && recordOrLayoutId != null) {
|
||||
for (Object[] o : alls) {
|
||||
ConfigBean cb = findConfigBean(alls, (ID) o[0]);
|
||||
|
@ -121,6 +121,16 @@ public class FormsManager extends BaseLayoutManager {
|
|||
.set("elements", JSONUtils.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
// 默认优先级布局
|
||||
private ConfigBean findDefault(Object[][] alls) {
|
||||
for (Object[] o : alls) {
|
||||
ConfigBean cb = findConfigBean(alls, (ID) o[0]);
|
||||
ShareToAttr attr = new ShareToAttr(cb);
|
||||
if (attr.isFallback() && attr.isForNew()) return cb;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// -- ADMIN
|
||||
|
||||
/**
|
||||
|
@ -130,6 +140,14 @@ public class FormsManager extends BaseLayoutManager {
|
|||
*/
|
||||
public ConfigBean getFormLayout(ID formConfigId, String entity) {
|
||||
final Object[][] alls = getAllConfig(entity, TYPE_FORM);
|
||||
|
||||
// 高优先级
|
||||
if (formConfigId == null) {
|
||||
ConfigBean best = findDefault(alls);
|
||||
if (best != null) return best;
|
||||
}
|
||||
|
||||
// 次优先级
|
||||
for (Object[] o : alls) {
|
||||
if (formConfigId == null) {
|
||||
return findConfigBean(alls, (ID) o[0]);
|
||||
|
@ -148,12 +166,26 @@ public class FormsManager extends BaseLayoutManager {
|
|||
* @return
|
||||
*/
|
||||
public List<ConfigBean> getAllFormsAttr(String entity) {
|
||||
return getAllFormsAttr(entity, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param forNew 仅新建布局
|
||||
* @return
|
||||
*/
|
||||
public List<ConfigBean> getAllFormsAttr(String entity, boolean forNew) {
|
||||
final Object[][] alls = getAllConfig(entity, TYPE_FORM);
|
||||
|
||||
List<ConfigBean> flist = new ArrayList<>();
|
||||
for (Object[] o : alls) {
|
||||
ConfigBean cb = findConfigBean(alls, (ID) o[0]).remove("config");
|
||||
flist.add(cb.remove("elements"));
|
||||
cb.remove("elements");
|
||||
if (forNew) {
|
||||
if (new ShareToAttr(cb).isForNew()) flist.add(cb.remove("shareTo"));
|
||||
} else {
|
||||
flist.add(cb);
|
||||
}
|
||||
}
|
||||
|
||||
// A-Z
|
||||
|
@ -194,6 +226,7 @@ public class FormsManager extends BaseLayoutManager {
|
|||
static class ShareToAttr {
|
||||
|
||||
private final JSONObject attrs;
|
||||
private final boolean sysDefault;
|
||||
protected ShareToAttr(ConfigBean cb) {
|
||||
Object s = cb.getObject("shareTo");
|
||||
if (s instanceof JSON) {
|
||||
|
@ -202,11 +235,17 @@ public class FormsManager extends BaseLayoutManager {
|
|||
// shareTo=ALL
|
||||
this.attrs = JSONUtils.toJSONObject("fallback", true);
|
||||
}
|
||||
this.sysDefault = cb.getString("name") == null; // 系统默认的
|
||||
}
|
||||
|
||||
// 默认
|
||||
boolean isFallback() {
|
||||
return this.attrs.getBooleanValue("fallback");
|
||||
return this.sysDefault || this.attrs.getBooleanValue("fallback");
|
||||
}
|
||||
|
||||
// 新建
|
||||
boolean isForNew() {
|
||||
return this.sysDefault || this.attrs.getBooleanValue("fornew");
|
||||
}
|
||||
|
||||
// 符合使用条件
|
||||
|
|
|
@ -11,6 +11,7 @@ import cn.devezhao.persist4j.Entity;
|
|||
import cn.devezhao.persist4j.Record;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.rebuild.core.DefinedException;
|
||||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.service.NoRecordFoundException;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
|
@ -59,6 +60,10 @@ public class LiteFormBuilder {
|
|||
* @return
|
||||
*/
|
||||
public JSONArray build(JSONArray fieldElements) {
|
||||
if (fieldElements == null || fieldElements.isEmpty()) {
|
||||
throw new DefinedException("No field elements");
|
||||
}
|
||||
|
||||
Record recordData = null;
|
||||
if (recordId != null) {
|
||||
recordData = FormsBuilder.instance.findRecord(recordId, user, fieldElements);
|
||||
|
|
|
@ -8,8 +8,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
package com.rebuild.core.metadata.easymeta;
|
||||
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.configuration.general.ClassificationManager;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
|
||||
/**
|
||||
|
@ -26,7 +28,11 @@ public class EasyClassification extends EasyReference {
|
|||
@Override
|
||||
public Object wrapValue(Object value) {
|
||||
JSONObject map = (JSONObject) super.wrapValue(value);
|
||||
if (map != null) map.remove("entity");
|
||||
if (map != null) {
|
||||
map.remove("entity");
|
||||
String color = ClassificationManager.instance.getColor((ID) value);
|
||||
if (color != null) map.put("color", color);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,6 +86,18 @@ public class EasyDecimal extends EasyField {
|
|||
return format.substring(dotIndex).length() - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 进位模式
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public RoundingMode getRoundingMode() {
|
||||
String rounding = getExtraAttr(EasyFieldConfigProps.DECIMAL_ROUNDING);
|
||||
if ("2".equals(rounding)) return RoundingMode.CEILING;
|
||||
else if ("1".equals(rounding)) return RoundingMode.DOWN;
|
||||
return RoundingMode.HALF_UP;
|
||||
}
|
||||
|
||||
// --
|
||||
|
||||
/**
|
||||
|
@ -108,12 +120,13 @@ public class EasyDecimal extends EasyField {
|
|||
*/
|
||||
public static BigDecimal fixedDecimalScale(Object decimalValue, EasyField decimalField) {
|
||||
int scale = ((EasyDecimal) decimalField).getScale();
|
||||
RoundingMode roundingMode = ((EasyDecimal) decimalField).getRoundingMode();
|
||||
|
||||
if (decimalValue instanceof BigDecimal) {
|
||||
return ((BigDecimal) decimalValue).setScale(scale, RoundingMode.HALF_UP);
|
||||
return ((BigDecimal) decimalValue).setScale(scale, roundingMode);
|
||||
} else {
|
||||
double d = ObjectUtils.round(ObjectUtils.toDouble(decimalValue), scale);
|
||||
return BigDecimal.valueOf(d);
|
||||
BigDecimal v = BigDecimal.valueOf(ObjectUtils.toDouble(decimalValue));
|
||||
return v.setScale(scale, roundingMode);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,11 +39,15 @@ public class EasyLocation extends EasyField implements MixValue {
|
|||
@Override
|
||||
public Object wrapValue(Object value) {
|
||||
if (value == null) return null;
|
||||
String[] vals = value.toString().split(MetadataHelper.SPLITER_RE);
|
||||
|
||||
JSONObject mixVal = JSONUtils.toJSONObject("text", vals[0]);
|
||||
if (vals.length >= 2) {
|
||||
String[] lnglat = vals[vals.length - 1].split(",");
|
||||
// be: v3.8
|
||||
final String val2str = value.toString();
|
||||
if (JSONUtils.wellFormat(val2str)) return JSONObject.parse(val2str);
|
||||
|
||||
String[] valSplit = val2str.split(MetadataHelper.SPLITER_RE);
|
||||
JSONObject mixVal = JSONUtils.toJSONObject("text", valSplit[0]);
|
||||
if (valSplit.length >= 2) {
|
||||
String[] lnglat = valSplit[valSplit.length - 1].split(",");
|
||||
mixVal.put("lng", lnglat[0]);
|
||||
mixVal.put("lat", lnglat.length == 2 ? lnglat[1] : null);
|
||||
}
|
||||
|
|
|
@ -53,4 +53,15 @@ public class EasyNumber extends EasyField {
|
|||
getExtraAttr(EasyFieldConfigProps.NUMBER_FORMAT), getDisplayType().getDefaultFormat());
|
||||
return new DecimalFormat(format).format(value);
|
||||
}
|
||||
|
||||
// --
|
||||
|
||||
/**
|
||||
* @param flagedValue
|
||||
* @return
|
||||
* @see EasyDecimal#clearFlaged(Object)
|
||||
*/
|
||||
public static String clearFlaged(Object flagedValue) {
|
||||
return EasyDecimal.clearFlaged(flagedValue);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ public class EasyPickList extends EasyField implements MixValue {
|
|||
|
||||
ID itemId = PickListManager.instance.findItemByLabel(text, targetField.getRawMeta());
|
||||
if (itemId == null) {
|
||||
log.warn("Cannot find value in PickList : " + text + " << " + targetField);
|
||||
log.warn("Cannot find value in PickList : {} << {}", text, targetField);
|
||||
}
|
||||
return itemId;
|
||||
}
|
||||
|
|
|
@ -7,14 +7,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.metadata.easymeta;
|
||||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.record.RecordVisitor;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.TemporalAccessor;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -44,9 +48,15 @@ public class EasyTime extends EasyDateTime {
|
|||
String valueExpr = (String) getRawMeta().getDefaultValue();
|
||||
if (StringUtils.isBlank(valueExpr)) return null;
|
||||
|
||||
if (valueExpr.contains("NOW")) { // {NOW}
|
||||
return LocalTime.now();
|
||||
} else {
|
||||
// 表达式
|
||||
if (valueExpr.contains(VAR_NOW) || valueExpr.contains("NOW")) {
|
||||
Date d = FieldValueHelper.parseDateExpr(valueExpr, null);
|
||||
if (d == null) return null;
|
||||
Calendar c = CalendarUtils.getInstance(d);
|
||||
return LocalTime.of(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE), c.get(Calendar.SECOND));
|
||||
}
|
||||
// 具体的时间值
|
||||
else {
|
||||
return RecordVisitor.tryParseTime(valueExpr);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,16 +77,19 @@ public class EasyEntityConfigProps {
|
|||
public static final String ENABLE_RECORD_MERGER = "enableRecordMerger";
|
||||
|
||||
/**
|
||||
* 列表模式
|
||||
* 详情模式:字段
|
||||
*/
|
||||
public static final String ADVLIST_MODE2_SHOWFIELDS = "mode2ShowFields";
|
||||
/**
|
||||
* 卡片模式:字段
|
||||
*/
|
||||
public static final String ADVLIST_MODE3_SHOWFIELDS = "mode3ShowFields";
|
||||
/**
|
||||
* @see #ADVLIST_HIDE_FILTERS
|
||||
*/
|
||||
public static final String ADVLIST_MODE3_SHOWFILTERS = "mode3ShowFilters";
|
||||
/**
|
||||
* @see #ADVLIST_SHOWCATEGORY TODO
|
||||
* @see #ADVLIST_SHOWCATEGORY
|
||||
*/
|
||||
public static final String ADVLIST_MODE3_SHOWCATEGORY = "mode3ShowCategory";
|
||||
}
|
||||
|
|
|
@ -46,7 +46,10 @@ public class EasyFieldConfigProps {
|
|||
* 表单公式
|
||||
*/
|
||||
public static final String NUMBER_CALCFORMULA = "calcFormula";
|
||||
|
||||
/**
|
||||
* 表单公式
|
||||
*/
|
||||
public static final String NUMBER_CALCFORMULABACKEND = "calcFormulaBackend";
|
||||
/**
|
||||
* 是否允许负数
|
||||
*/
|
||||
|
@ -63,6 +66,10 @@ public class EasyFieldConfigProps {
|
|||
* 表单公式
|
||||
*/
|
||||
public static final String DECIMAL_CALCFORMULA = NUMBER_CALCFORMULA;
|
||||
/**
|
||||
* 进位模式(默认四舍五入)
|
||||
*/
|
||||
public static final String DECIMAL_ROUNDING = "decimalRounding";
|
||||
|
||||
/**
|
||||
* 日期格式
|
||||
|
@ -102,9 +109,13 @@ public class EasyFieldConfigProps {
|
|||
public static final String IMAGE_UPLOADNUMBER = FILE_UPLOADNUMBER;
|
||||
|
||||
/**
|
||||
* 图片获取方式(仅H5)
|
||||
* 图片获取方式(兼容附件)
|
||||
*/
|
||||
public static final String IMAGE_CAPTURE = "imageCapture";
|
||||
/**
|
||||
* 图片获取方式(兼容附件)
|
||||
*/
|
||||
public static final String IMAGE_CAPTURE_DEF = "imageCaptureDef";
|
||||
|
||||
/**
|
||||
* 自动编号规则
|
||||
|
|
|
@ -47,11 +47,10 @@ import java.util.Collections;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.DATETIME_CALCFORMULA;
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.DATETIME_FORMAT;
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.DATE_CALCFORMULA;
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.DATE_FORMAT;
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.NUMBER_CALCFORMULA;
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.NUMBER_CALCFORMULABACKEND;
|
||||
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.NUMBER_NOTNEGATIVE;
|
||||
|
||||
/**
|
||||
|
@ -396,7 +395,7 @@ public class Field2Schema extends SetUser {
|
|||
String identifier = text;
|
||||
|
||||
// 全英文直接返回
|
||||
if (identifier.length() >= 4 && identifier.matches("[a-zA-Z0-9]+")) {
|
||||
if (identifier.length() >= 4 && identifier.matches("[a-zA-Z0-9_]+")) {
|
||||
if (!CharSet.ASCII_ALPHA.contains(identifier.charAt(0)) || BlockList.isBlock(identifier)) {
|
||||
identifier = "rb" + identifier;
|
||||
}
|
||||
|
@ -458,14 +457,15 @@ public class Field2Schema extends SetUser {
|
|||
extraAttrs.remove(DATETIME_FORMAT);
|
||||
Object notNegative = extraAttrs.remove(NUMBER_NOTNEGATIVE);
|
||||
Object calcFormula = extraAttrs.remove(NUMBER_CALCFORMULA);
|
||||
Object calcFormulaBackend = extraAttrs.remove(NUMBER_CALCFORMULABACKEND);
|
||||
|
||||
extraAttrs.clear();
|
||||
if (notNegative != null) extraAttrs.put(NUMBER_NOTNEGATIVE, notNegative);
|
||||
if (calcFormula != null) extraAttrs.put(NUMBER_CALCFORMULA, calcFormula);
|
||||
if (calcFormulaBackend instanceof Boolean && (Boolean) calcFormulaBackend) extraAttrs.put(NUMBER_CALCFORMULABACKEND, true);
|
||||
|
||||
if (!extraAttrs.isEmpty()) {
|
||||
fieldMeta.setString("extConfig", extraAttrs.toJSONString());
|
||||
}
|
||||
if (extraAttrs.isEmpty()) fieldMeta.setNull("extConfig");
|
||||
else fieldMeta.setString("extConfig", extraAttrs.toJSONString());
|
||||
}
|
||||
Application.getCommonsService().update(fieldMeta, false);
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ public class ChangeOwningDeptTask extends HeavyTask<Integer> {
|
|||
|
||||
@Override
|
||||
protected Integer exec() {
|
||||
log.info("Start modifying the `OwningDept` ... " + this.user);
|
||||
log.info("Start modifying the `OwningDept` ... {}:{}", this.user, this.deptNew);
|
||||
this.setTotal(MetadataHelper.getEntities().length);
|
||||
|
||||
final StopWatch sw = new StopWatch("ChangeOwningDeptTask");
|
||||
|
@ -68,7 +68,7 @@ public class ChangeOwningDeptTask extends HeavyTask<Integer> {
|
|||
sw.stop();
|
||||
}
|
||||
|
||||
log.info("Modify the `OwningDept` to complete : {} > {}\n{}", this.user, changed, sw.prettyPrint());
|
||||
log.info("Modify the `OwningDept` to complete : {}:{} : {}\n{}", this.user, deptNew, changed, sw.prettyPrint());
|
||||
return changed;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*!
|
||||
Copyright (c) Ruifang Tech <http://ruifang-tech.com/> and/or its owners. All rights reserved.
|
||||
*/
|
||||
|
||||
package com.rebuild.core.privileges;
|
||||
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 字段权限
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2024/7/23
|
||||
* @see PrivilegesManager
|
||||
*/
|
||||
@ConditionalOnMissingClass("com.rebuild.Rbv")
|
||||
@Component
|
||||
public class FieldPrivileges {
|
||||
|
||||
/**
|
||||
* 可新建?
|
||||
* @param field
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public boolean isCreatable(Field field, ID user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可读?
|
||||
* @param field
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public boolean isReadble(Field field, ID user) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 可修改?
|
||||
* @param field
|
||||
* @param user
|
||||
* @return
|
||||
*/
|
||||
public boolean isUpdatable(Field field, ID user) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/*!
|
||||
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.privileges;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 跳过权限检查
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2020/9/28
|
||||
*/
|
||||
@Slf4j
|
||||
public class PrivilegesGuardContextHolder {
|
||||
|
||||
private static final ThreadLocal<ID> SKIP_GUARD = new NamedThreadLocal<>("Skip some check once");
|
||||
|
||||
/**
|
||||
* 允许无权限操作一次
|
||||
*
|
||||
* @param recordId
|
||||
*/
|
||||
public static void setSkipGuard(ID recordId) {
|
||||
Assert.notNull(recordId, "[recordId] cannot be null");
|
||||
|
||||
ID existsWarn = SKIP_GUARD.get();
|
||||
if (existsWarn != null) {
|
||||
log.warn("Not removed skip record : {}", existsWarn);
|
||||
SKIP_GUARD.remove();
|
||||
}
|
||||
SKIP_GUARD.set(recordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see #setSkipGuard(ID)
|
||||
*/
|
||||
public static ID getSkipGuardOnce() {
|
||||
ID recordId = SKIP_GUARD.get();
|
||||
if (recordId != null) SKIP_GUARD.remove();
|
||||
return recordId;
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import com.rebuild.core.privileges.bizz.InternalPermission;
|
|||
import com.rebuild.core.service.CommonsService;
|
||||
import com.rebuild.core.service.general.BulkContext;
|
||||
import com.rebuild.core.service.general.EntityService;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
|
@ -110,7 +111,7 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
|
|||
|
||||
// 跳过
|
||||
ID skipGuardId;
|
||||
if ((skipGuardId = PrivilegesGuardContextHolder.getSkipGuardOnce()) != null) {
|
||||
if ((skipGuardId = GeneralEntityServiceContextHolder.isSkipGuardOnce()) != null) {
|
||||
log.debug("Allow no permission({}) passed once : {}", action.getName(), skipGuardId);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -554,4 +554,11 @@ public class PrivilegesManager {
|
|||
}
|
||||
throw new BizzException("Unknown Permission : " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public FieldPrivileges getFieldPrivileges() {
|
||||
return Application.getBean(FieldPrivileges.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -311,13 +311,16 @@ public class UserService extends BaseService {
|
|||
super.update(record);
|
||||
}
|
||||
|
||||
if (updateRoleAppends(user, roleAppends)) changed = true;
|
||||
if (roleAppends != null) {
|
||||
if (updateRoleAppends(user, roleAppends)) changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
Application.getUserStore().refreshUser(user);
|
||||
}
|
||||
|
||||
// 改变记录的所属部门
|
||||
// 并发修改可能导致数据紊乱
|
||||
if (deptOld != null) {
|
||||
TaskExecutors.submit(new ChangeOwningDeptTask(user, deptNew), UserContextHolder.getUser());
|
||||
}
|
||||
|
@ -372,15 +375,17 @@ public class UserService extends BaseService {
|
|||
* @return
|
||||
*/
|
||||
protected boolean updateRoleAppends(ID user, ID[] roleAppends) {
|
||||
if (roleAppends == null) return false;
|
||||
|
||||
Object[][] exists = Application.createQueryNoFilter(
|
||||
"select memberId,roleId from RoleMember where userId = ?")
|
||||
.setParameter(1, user)
|
||||
.array();
|
||||
if (exists.length == 0 && (roleAppends == null || roleAppends.length == 0)) {
|
||||
if (exists.length == 0 && roleAppends.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (roleAppends == null || roleAppends.length == 0) {
|
||||
if (roleAppends.length == 0) {
|
||||
for (Object[] o : exists) {
|
||||
super.delete((ID) o[0]);
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ public class CombinedRole extends Role {
|
|||
|
||||
// 实体自定义权限
|
||||
|
||||
final Map<String, JSON> customFilters = new HashMap<>();
|
||||
Map<String, JSON> useCustomFilters = new HashMap<>();
|
||||
|
||||
for (Permission action : CustomEntityPrivileges.PERMISSION_DEFS) {
|
||||
JSONObject aCustom = ((CustomEntityPrivileges) a).getCustomFilter(action);
|
||||
|
@ -182,15 +182,22 @@ public class CombinedRole extends Role {
|
|||
|
||||
int gt = isGreaterThan(action.getMask(), aDefMap, bDefMap);
|
||||
if (gt == 1) {
|
||||
if (aCustom != null) customFilters.put(action.getName(), aCustom);
|
||||
if (aCustom != null) useCustomFilters.put(action.getName(), aCustom);
|
||||
} else if (gt == 2) {
|
||||
if (bCustom != null) customFilters.put(action.getName(), bCustom);
|
||||
if (bCustom != null) useCustomFilters.put(action.getName(), bCustom);
|
||||
}
|
||||
// gt == 0 无自定义权限
|
||||
}
|
||||
|
||||
// 字段权限
|
||||
Map<String, Object> useFpDefinition;
|
||||
Map<String, Object> aFpDefinition = ((CustomEntityPrivileges) a).getFpDefinition();
|
||||
Map<String, Object> bFpDefinition = ((CustomEntityPrivileges) b).getFpDefinition();
|
||||
if (aFpDefinition == null || bFpDefinition == null) useFpDefinition = null;
|
||||
else useFpDefinition = aFpDefinition;
|
||||
|
||||
String definition = StringUtils.join(defs.iterator(), ",");
|
||||
return new CustomEntityPrivileges(((EntityPrivileges) a).getEntity(), definition, customFilters);
|
||||
return new CustomEntityPrivileges(((EntityPrivileges) a).getEntity(), definition, useCustomFilters, useFpDefinition);
|
||||
}
|
||||
|
||||
private Map<String, Integer> parseDefinitionMasks(String d) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.alibaba.fastjson.JSON;
|
|||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.service.query.ParseHelper;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -35,6 +36,8 @@ public class CustomEntityPrivileges extends EntityPrivileges {
|
|||
|
||||
// 自定义权限 <Action, Filter>
|
||||
private final Map<String, JSON> customFilters = new HashMap<>();
|
||||
// 字段权限
|
||||
private final Map<String, Object> fpDefinition;
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
|
@ -52,17 +55,21 @@ public class CustomEntityPrivileges extends EntityPrivileges {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fpDefinition = rawDefinition.getJSONObject("FP");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param definition
|
||||
* @param customFilters
|
||||
* @param fpDefinition
|
||||
*/
|
||||
protected CustomEntityPrivileges(Integer entity, String definition, Map<String, JSON> customFilters) {
|
||||
protected CustomEntityPrivileges(Integer entity, String definition, Map<String, JSON> customFilters, Map<String, Object> fpDefinition) {
|
||||
super(entity, definition);
|
||||
this.customFilters.clear();
|
||||
this.customFilters.putAll(customFilters);
|
||||
this.fpDefinition = fpDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -169,7 +176,7 @@ public class CustomEntityPrivileges extends EntityPrivileges {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取自定义权限
|
||||
* 获取自定义权限定义
|
||||
*
|
||||
* @param action
|
||||
* @return
|
||||
|
@ -178,6 +185,15 @@ public class CustomEntityPrivileges extends EntityPrivileges {
|
|||
return (JSONObject) customFilters.getOrDefault(action.getName(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段权限定义
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Map<String, Object> getFpDefinition() {
|
||||
return fpDefinition == null ? null : Collections.unmodifiableMap(fpDefinition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getDefinition() + ";" + getCustomFilters();
|
||||
|
|
|
@ -59,6 +59,7 @@ public enum ZeroEntry {
|
|||
|
||||
/**
|
||||
* 允许撤销审批
|
||||
* v3.8 起添加撤回权限
|
||||
*/
|
||||
AllowRevokeApproval(false),
|
||||
|
||||
|
|
|
@ -19,10 +19,10 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* 基础 CRUD 服务,使用请注意:
|
||||
* 基础 CRUD 服务,使用须知:
|
||||
* <br>- 此类有事物
|
||||
* <br>- 此类不经过用户权限验证 {@link PrivilegesGuardInterceptor}
|
||||
* <br>- 此类不对多值字段进行处理 {@link BaseService}
|
||||
* <br>- 此类不字段值做任何处理,如多值、附件 {@link BaseService}
|
||||
* <br>- 此类无任何系统规则,如默认值、重复检查、自动编号等
|
||||
* <br>- 有权限的实体使用此类需要指定 `strictMode=false`
|
||||
*
|
||||
|
|
|
@ -7,6 +7,9 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.service;
|
||||
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
import com.rebuild.core.service.trigger.RobotTriggerObserver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
@ -34,7 +37,12 @@ public class SafeObservable {
|
|||
}
|
||||
|
||||
public void notifyObservers(Object arg) {
|
||||
boolean quickMode = GeneralEntityServiceContextHolder.isQuickMode(false);
|
||||
for (SafeObserver o : obs) {
|
||||
if (quickMode) {
|
||||
if (o instanceof RobotTriggerObserver) continue;
|
||||
}
|
||||
|
||||
o.update(this, arg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,21 +47,26 @@ public class ApprovalFields2Schema extends Field2Schema {
|
|||
* @throws MetadataModificationException
|
||||
*/
|
||||
public boolean createFields(Entity entity) throws MetadataModificationException {
|
||||
// 补充后加字段
|
||||
if (MetadataHelper.hasApprovalField(entity)) {
|
||||
List<Field> complement = new ArrayList<>();
|
||||
if (!entity.containsField(EntityHelper.ApprovalLastUser)) {
|
||||
return schema2DatabaseInternal(entity, buildApporvalLastUser(entity));
|
||||
complement.add(buildApporvalLastUser(entity));
|
||||
}
|
||||
if (!entity.containsField(EntityHelper.ApprovalLastTime)) {
|
||||
return schema2DatabaseInternal(entity, buildApporvalLastTime(entity));
|
||||
complement.add(buildApporvalLastTime(entity));
|
||||
}
|
||||
if (!entity.containsField(EntityHelper.ApprovalLastRemark)) {
|
||||
return schema2DatabaseInternal(entity, buildApporvalLastRemark(entity));
|
||||
complement.add(buildApporvalLastRemark(entity));
|
||||
}
|
||||
if (!entity.containsField(EntityHelper.ApprovalStepUsers)) {
|
||||
return schema2DatabaseInternal(entity,
|
||||
buildApprovalStepUsers(entity), buildApprovalStepNodeName(entity));
|
||||
complement.add(buildApprovalStepUsers(entity));
|
||||
complement.add(buildApprovalStepNodeName(entity));
|
||||
}
|
||||
return false;
|
||||
|
||||
if (complement.isEmpty()) return false;
|
||||
schema2DatabaseInternal(entity, complement.toArray(new Field[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(MetadataHelper.hasPrivilegesField(entity)
|
||||
|
@ -157,7 +162,7 @@ public class ApprovalFields2Schema extends Field2Schema {
|
|||
try {
|
||||
Application.getSqlExecutor().execute(ddl, DDL_TIMEOUT);
|
||||
} catch (Throwable ex) {
|
||||
log.error("DDL ERROR : \n" + ddl, ex);
|
||||
log.error("DDL ERROR : \n{}", ddl, ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ import com.rebuild.core.configuration.ConfigurationException;
|
|||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
|
||||
import com.rebuild.core.privileges.UserHelper;
|
||||
import com.rebuild.core.service.general.EntityService;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
import com.rebuild.core.service.notification.MessageBuilder;
|
||||
import com.rebuild.core.support.SetUser;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
|
@ -54,11 +54,13 @@ public class ApprovalProcessor extends SetUser {
|
|||
|
||||
// 最大撤销次数
|
||||
private static final int MAX_REVOKED = 100;
|
||||
// 自主退回
|
||||
private static final String KEY_CANCEL38 = "PREV_APPROVER_BACKED";
|
||||
|
||||
final private ID recordId;
|
||||
|
||||
// 如未传递,会在需要时根据 record 确定
|
||||
private ID approval;
|
||||
private ID approvalId;
|
||||
// 流程定义
|
||||
private FlowParser flowParser;
|
||||
|
||||
|
@ -71,11 +73,11 @@ public class ApprovalProcessor extends SetUser {
|
|||
|
||||
/**
|
||||
* @param recordId
|
||||
* @param approval
|
||||
* @param approvalId
|
||||
*/
|
||||
public ApprovalProcessor(ID recordId, ID approval) {
|
||||
public ApprovalProcessor(ID recordId, ID approvalId) {
|
||||
this.recordId = recordId;
|
||||
this.approval = approval;
|
||||
this.approvalId = approvalId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -107,7 +109,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
Set<String> ccAccounts = nextNodes.getCcAccounts(this.recordId);
|
||||
|
||||
Record recordOfMain = EntityHelper.forUpdate(this.recordId, this.getUser(), false);
|
||||
recordOfMain.setID(EntityHelper.ApprovalId, this.approval);
|
||||
recordOfMain.setID(EntityHelper.ApprovalId, this.approvalId);
|
||||
recordOfMain.setInt(EntityHelper.ApprovalState, ApprovalState.PROCESSING.getState());
|
||||
recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNodes.getApprovalNode().getNodeId());
|
||||
Application.getBean(ApprovalStepService.class).txSubmit(recordOfMain, ccUsers, ccAccounts, nextApprovers);
|
||||
|
@ -174,7 +176,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
approvedStep.setString("attrMore", attrMore.toJSONString());
|
||||
}
|
||||
|
||||
this.approval = (ID) stepApprover[3];
|
||||
this.approvalId = (ID) stepApprover[3];
|
||||
FlowNodeGroup nextNodes = getNextNodes((String) stepApprover[2]);
|
||||
|
||||
Set<ID> nextApprovers = null;
|
||||
|
@ -224,6 +226,34 @@ public class ApprovalProcessor extends SetUser {
|
|||
this.recordId, status.getApprovalId(), getCurrentNodeId(status), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. 由(上一步)审批人撤回
|
||||
*
|
||||
* @param approver
|
||||
* @throws ApprovalException
|
||||
* @see #approve(ID, ApprovalState, String, JSONObject)
|
||||
*/
|
||||
public void cancel38(ID approver) throws ApprovalException {
|
||||
ApprovalStatus status = checkApprovalState(ApprovalState.PROCESSING);
|
||||
this.approvalId = status.getApprovalId();
|
||||
|
||||
String prevNode = status.getPrevStepNode();
|
||||
if (prevNode == null) throw new ApprovalException(Language.L("无效审批状态"));
|
||||
|
||||
// 由当前审批人代理其退回
|
||||
ID proxyApprover = approver;
|
||||
JSONArray current = getCurrentStep(status);
|
||||
for (Object o : current) {
|
||||
JSONObject step = (JSONObject) o;
|
||||
proxyApprover = ID.valueOf(step.getString("approver"));
|
||||
break;
|
||||
}
|
||||
|
||||
// BACKED
|
||||
String key = KEY_CANCEL38 + ":" + approver;
|
||||
approve(proxyApprover, ApprovalState.REJECTED, key, null, null, null, prevNode, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2.1.催审
|
||||
*
|
||||
|
@ -231,9 +261,9 @@ public class ApprovalProcessor extends SetUser {
|
|||
*/
|
||||
public int urge() {
|
||||
final ApprovalStatus status = checkApprovalState(ApprovalState.PROCESSING);
|
||||
this.approval = status.getApprovalId();
|
||||
this.approvalId = status.getApprovalId();
|
||||
|
||||
final String sentKey = String.format("URGE:%s-%s", approval, recordId);
|
||||
final String sentKey = String.format("URGE:%s-%s", approvalId, recordId);
|
||||
if (Application.getCommonsCache().getx(sentKey) != null) {
|
||||
return -1;
|
||||
}
|
||||
|
@ -272,7 +302,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
Object[] instepApprover = Application.createQueryNoFilter(
|
||||
"select state from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and approver = ? and isCanceled = 'F'")
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.approval)
|
||||
.setParameter(2, this.approvalId)
|
||||
.setParameter(3, getCurrentNodeId(null))
|
||||
.setParameter(4, toUser)
|
||||
.unique();
|
||||
|
@ -416,13 +446,13 @@ public class ApprovalProcessor extends SetUser {
|
|||
* @return
|
||||
*/
|
||||
private FlowParser getFlowParser() {
|
||||
Assert.notNull(approval, "[approval] cannot be null");
|
||||
Assert.notNull(approvalId, "[approval] cannot be null");
|
||||
if (flowParser != null) {
|
||||
return flowParser;
|
||||
}
|
||||
|
||||
FlowDefinition flowDefinition = RobotApprovalManager.instance.getFlowDefinition(
|
||||
MetadataHelper.getEntity(this.recordId.getEntityCode()), this.approval);
|
||||
MetadataHelper.getEntity(this.recordId.getEntityCode()), this.approvalId);
|
||||
flowParser = flowDefinition.createFlowParser();
|
||||
return flowParser;
|
||||
}
|
||||
|
@ -435,7 +465,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
try {
|
||||
return getFlowParser().getNode(nodeNo);
|
||||
} catch (ApprovalException | ConfigurationException ex) {
|
||||
log.warn("Cannot parse node : {} with {}", nodeNo, approval, ex);
|
||||
log.warn("Cannot parse node : {} with {}", nodeNo, approvalId, ex);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -456,7 +486,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
" where recordId = ? and approvalId = ? and node = ? and isCanceled = 'F' and isBacked = 'F' order by createdOn desc";
|
||||
Object[] lastNode = Application.createQueryNoFilter(sql)
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.approval)
|
||||
.setParameter(2, this.approvalId)
|
||||
.setParameter(3, currentNode)
|
||||
.unique();
|
||||
String nodeBatch = lastNode == null || lastNode[0] == null ? null : (String) lastNode[0];
|
||||
|
@ -468,7 +498,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
|
||||
Object[][] array = Application.createQueryNoFilter(sql)
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.approval)
|
||||
.setParameter(2, this.approvalId)
|
||||
.setParameter(3, currentNode)
|
||||
.array();
|
||||
|
||||
|
@ -481,13 +511,13 @@ public class ApprovalProcessor extends SetUser {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获取已执行步骤
|
||||
* 获取已审批节点
|
||||
*
|
||||
* @return returns [ [S,S], [S], [SSS], [S] ]
|
||||
*/
|
||||
public JSONArray getWorkedSteps() {
|
||||
final ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId);
|
||||
this.approval = status.getApprovalId();
|
||||
this.approvalId = status.getApprovalId();
|
||||
|
||||
Object[][] array = Application.createQueryNoFilter(
|
||||
"select approver,state,remark,approvedTime,createdOn,createdBy,node,prevNode,nodeBatch,ccUsers,ccAccounts,attrMore from RobotApprovalStep" +
|
||||
|
@ -553,10 +583,10 @@ public class ApprovalProcessor extends SetUser {
|
|||
if (FlowNode.NODE_AUTOAPPROVAL.equals(nodeNo)) {
|
||||
// No name
|
||||
} else if (FlowNode.NODE_REVOKED.equals(nodeNo)) {
|
||||
String nodeName = Language.L("管理员撤销");
|
||||
String nodeName = Language.L("撤销");
|
||||
s.put("nodeName", nodeName);
|
||||
} else if (FlowNode.NODE_CANCELED.equals(nodeNo)) {
|
||||
String nodeName = Language.L("提交人撤回");
|
||||
String nodeName = Language.L("撤回");
|
||||
s.put("nodeName", nodeName);
|
||||
} else {
|
||||
String nodeName = flowNode == null ? null : flowNode.getNodeName();
|
||||
|
@ -580,6 +610,15 @@ public class ApprovalProcessor extends SetUser {
|
|||
}
|
||||
}
|
||||
|
||||
// v3.8
|
||||
String remark = s.getString("remark");
|
||||
if (remark != null && remark.startsWith(KEY_CANCEL38 + ":")) {
|
||||
ID realApprover = ID.valueOf(remark.split(":")[1]);
|
||||
s.put("remark", KEY_CANCEL38);
|
||||
s.put("approver", realApprover);
|
||||
s.put("approverName", UserHelper.getName(realApprover));
|
||||
}
|
||||
|
||||
s.put("node", nodeNo);
|
||||
step.add(s);
|
||||
}
|
||||
|
@ -635,7 +674,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
*/
|
||||
public JSONArray getBackSteps() {
|
||||
ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId);
|
||||
this.approval = status.getApprovalId();
|
||||
this.approvalId = status.getApprovalId();
|
||||
|
||||
String currentNode = getCurrentNodeId(status);
|
||||
if (FlowNode.NODE_ROOT.equals(currentNode)) return JSONUtils.EMPTY_ARRAY;
|
||||
|
@ -680,6 +719,30 @@ public class ApprovalProcessor extends SetUser {
|
|||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上一步审批用户
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public Set<ID> getPrevApprovedUsers() {
|
||||
ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId);
|
||||
String prevNode = status.getPrevStepNode();
|
||||
if (prevNode == null) return Collections.emptySet();
|
||||
|
||||
String sql = "select approver,stepId from RobotApprovalStep " +
|
||||
"where recordId = ? and approvalId = ? and node = ? and isCanceled = 'F' and state = 10 and isBacked = 'F'";
|
||||
Object[][] prevNodeApprovers = Application.getQueryFactory().createQueryNoFilter(sql)
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.approvalId)
|
||||
.setParameter(3, prevNode)
|
||||
.array();
|
||||
if (prevNodeApprovers.length == 0) return Collections.emptySet();
|
||||
|
||||
Set<ID> approvedUsers = new HashSet<>();
|
||||
for (Object[] o : prevNodeApprovers) approvedUsers.add((ID) o[0]);
|
||||
return approvedUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* 会签时自选的审批人
|
||||
*
|
||||
|
@ -693,7 +756,7 @@ public class ApprovalProcessor extends SetUser {
|
|||
Object[][] array = Application.createQueryNoFilter(
|
||||
"select approver from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and isWaiting = 'T' and isCanceled = 'F'")
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.approval)
|
||||
.setParameter(2, this.approvalId)
|
||||
.setParameter(3, node)
|
||||
.array();
|
||||
|
||||
|
@ -716,12 +779,11 @@ public class ApprovalProcessor extends SetUser {
|
|||
final EntityService es = Application.getEntityService(recordId.getEntityCode());
|
||||
for (ID user : shareTo) {
|
||||
if (!Application.getPrivilegesManager().allowRead(user, recordId)) {
|
||||
// force share
|
||||
PrivilegesGuardContextHolder.setSkipGuard(recordId);
|
||||
GeneralEntityServiceContextHolder.setSkipGuard(recordId);
|
||||
try {
|
||||
es.share(recordId, user, null);
|
||||
} finally {
|
||||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -737,13 +799,13 @@ public class ApprovalProcessor extends SetUser {
|
|||
|
||||
private Object[] findProcessingStepApprover(ID approver) {
|
||||
final ApprovalStatus status = checkApprovalState(ApprovalState.PROCESSING);
|
||||
this.approval = status.getApprovalId();
|
||||
this.approvalId = status.getApprovalId();
|
||||
|
||||
String currentNodeId = getCurrentNodeId(status);
|
||||
Object[] stepApprover = Application.createQueryNoFilter(
|
||||
"select stepId,state,approver from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and approver = ? and isCanceled = 'F'")
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.approval)
|
||||
.setParameter(2, this.approvalId)
|
||||
.setParameter(3, currentNodeId)
|
||||
.setParameter(4, approver)
|
||||
.unique();
|
||||
|
|
|
@ -9,7 +9,6 @@ package com.rebuild.core.service.approval;
|
|||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.core.Application;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
* @author devezhao zhaofang123@gmail.com
|
||||
|
@ -51,23 +50,17 @@ public class ApprovalStatus {
|
|||
return currentStepNode;
|
||||
}
|
||||
|
||||
public String getLastComment() {
|
||||
if (currentStepNode == null || lastComment != null) {
|
||||
return StringUtils.defaultIfBlank(lastComment, null);
|
||||
}
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public String getPrevStepNode() {
|
||||
if (currentStepNode == null) return null;
|
||||
|
||||
Object[] last = Application.createQueryNoFilter(
|
||||
"select remark from RobotApprovalStep where recordId = ? and node = ? order by modifiedOn desc")
|
||||
Object[] o = Application.createQueryNoFilter(
|
||||
"select prevNode from RobotApprovalStep where recordId = ? and node = ? order by modifiedOn desc")
|
||||
.setParameter(1, this.recordId)
|
||||
.setParameter(2, this.currentStepNode)
|
||||
.unique();
|
||||
|
||||
if (last == null) {
|
||||
lastComment = StringUtils.EMPTY;
|
||||
} else {
|
||||
lastComment = StringUtils.defaultIfBlank((String) last[0], StringUtils.EMPTY);
|
||||
}
|
||||
|
||||
return StringUtils.defaultIfBlank(lastComment, null);
|
||||
return o == null ? null : (String) o[0];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import com.rebuild.core.privileges.OperationDeniedException;
|
|||
import com.rebuild.core.privileges.UserHelper;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.privileges.bizz.InternalPermission;
|
||||
import com.rebuild.core.privileges.bizz.ZeroEntry;
|
||||
import com.rebuild.core.service.BaseService;
|
||||
import com.rebuild.core.service.DataSpecificationNoRollbackException;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
|
@ -54,6 +53,8 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.rebuild.core.privileges.bizz.ZeroEntry.AllowRevokeApproval;
|
||||
|
||||
/**
|
||||
* 审批流程。此类所有方法不应直接调用,而是通过 ApprovalProcessor 封装类
|
||||
* <p>
|
||||
|
@ -324,13 +325,14 @@ public class ApprovalStepService extends BaseService {
|
|||
final boolean isAdmin = UserHelper.isAdmin(opUser);
|
||||
|
||||
if (isRevoke) {
|
||||
boolean canRevoke = Application.getPrivilegesManager().allow(opUser, ZeroEntry.AllowRevokeApproval);
|
||||
boolean canRevoke = Application.getPrivilegesManager().allow(opUser, AllowRevokeApproval);
|
||||
if (!(isAdmin || canRevoke)) {
|
||||
throw new OperationDeniedException(Language.L("你无权撤销审批"));
|
||||
}
|
||||
} else {
|
||||
boolean canRevoke = Application.getPrivilegesManager().allow(opUser, AllowRevokeApproval);
|
||||
ID s = ApprovalHelper.getSubmitter(recordId, approvalId);
|
||||
if (!(isAdmin || opUser.equals(s))) {
|
||||
if (!(canRevoke || opUser.equals(s))) {
|
||||
throw new OperationDeniedException(Language.L("你无权撤回审批"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*!
|
||||
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.dashboard.charts;
|
||||
|
||||
import lombok.ToString;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
* @since 8/3/2024
|
||||
*/
|
||||
@ToString
|
||||
public class AxisEntry {
|
||||
|
||||
final private int index;
|
||||
final private Object[] key;
|
||||
final private Object value;
|
||||
|
||||
public AxisEntry(Object[] rowValue, int index) {
|
||||
this.key = ArrayUtils.subarray(rowValue, 0, rowValue.length - 1);
|
||||
this.value = rowValue[rowValue.length - 1];
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public int getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public Object[] getKeyRaw() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public String getKey() {
|
||||
return Arrays.toString(getKeyRaw());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
|
@ -72,7 +72,7 @@ public class CNMapChart extends ChartData {
|
|||
Numerical[] nums = getNumericals();
|
||||
|
||||
if (nums.length > 0) {
|
||||
return buildSql(dims[0], nums);
|
||||
return buildSql(dims[0], nums, false);
|
||||
}
|
||||
|
||||
String sql = "select {0} from {1} where {2}";
|
||||
|
|
|
@ -37,8 +37,10 @@ import org.apache.commons.lang.StringUtils;
|
|||
import java.text.DecimalFormat;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -205,16 +207,16 @@ public abstract class ChartData extends SetUser implements ChartSpec {
|
|||
/**
|
||||
* 获取过滤 SQL
|
||||
*
|
||||
* @param withNumericalFilter
|
||||
* @return
|
||||
*/
|
||||
protected String getFilterSql(Numerical withAxisFilter) {
|
||||
String where = getFilterSql();
|
||||
if (withAxisFilter != null && ParseHelper.validAdvFilter(withAxisFilter.getFilter())) {
|
||||
AdvFilterParser filterParser = new AdvFilterParser(withAxisFilter.getFilter());
|
||||
String fieldWhere = filterParser.toSqlWhere();
|
||||
if (fieldWhere != null) where = String.format("((%s) and (%s))", where, fieldWhere);
|
||||
protected String getFilterSql(Numerical withNumericalFilter) {
|
||||
String filterSql = getFilterSql();
|
||||
if (withNumericalFilter != null && withNumericalFilter.getFilter() != null) {
|
||||
String filter = new AdvFilterParser(withNumericalFilter.getFilter()).toSqlWhere();
|
||||
if (filter != null) filterSql = String.format("((%s) and (%s))", filterSql, filter);
|
||||
}
|
||||
return where;
|
||||
return filterSql;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -438,9 +440,10 @@ public abstract class ChartData extends SetUser implements ChartSpec {
|
|||
*
|
||||
* @param dim
|
||||
* @param nums
|
||||
* @param withFilter
|
||||
* @return
|
||||
*/
|
||||
protected String buildSql(Dimension dim, Numerical[] nums) {
|
||||
protected String buildSql(Dimension dim, Numerical[] nums, boolean withFilter) {
|
||||
List<String> numSqlItems = new ArrayList<>();
|
||||
for (Numerical num : nums) {
|
||||
numSqlItems.add(num.getSqlName());
|
||||
|
@ -450,7 +453,7 @@ public abstract class ChartData extends SetUser implements ChartSpec {
|
|||
sql = MessageFormat.format(sql,
|
||||
dim.getSqlName(),
|
||||
StringUtils.join(numSqlItems, ", "),
|
||||
getSourceEntity().getName(), getFilterSql());
|
||||
getSourceEntity().getName(), getFilterSql(withFilter ? nums[0] : null));
|
||||
return appendSqlSort(sql);
|
||||
}
|
||||
|
||||
|
@ -495,25 +498,6 @@ public abstract class ChartData extends SetUser implements ChartSpec {
|
|||
return appendSqlSort(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* [1-9]N
|
||||
*
|
||||
* @param nums
|
||||
* @return
|
||||
*/
|
||||
protected String buildSql(Numerical[] nums) {
|
||||
List<String> numSqlItems = new ArrayList<>();
|
||||
for (Numerical num : nums) {
|
||||
numSqlItems.add(num.getSqlName());
|
||||
}
|
||||
|
||||
String sql = "select {0} from {1} where {2}";
|
||||
sql = MessageFormat.format(sql,
|
||||
StringUtils.join(numSqlItems, ", "),
|
||||
getSourceEntity().getName(), getFilterSql());
|
||||
return appendSqlSort(sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param num
|
||||
* @param withFilter
|
||||
|
@ -538,4 +522,53 @@ public abstract class ChartData extends SetUser implements ChartSpec {
|
|||
if (sorts != null) sql += " order by " + sorts;
|
||||
return sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nums
|
||||
* @return
|
||||
* @see Numerical#getFilter()
|
||||
*/
|
||||
protected boolean hasNumericalFilter(Numerical[] nums) {
|
||||
for (Numerical num : nums) {
|
||||
if (num.getFilter() != null) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param axisValues
|
||||
* @param indexAndSize
|
||||
* @return
|
||||
*/
|
||||
protected Object[][] mergeAxisEntry2Data(List<AxisEntry> axisValues, int indexAndSize) {
|
||||
// 1.同组合并
|
||||
Map<String, AxisEntry[]> merged = new LinkedHashMap<>();
|
||||
for (AxisEntry e : axisValues) {
|
||||
AxisEntry[] eee = merged.computeIfAbsent(e.getKey(), k -> new AxisEntry[indexAndSize]);
|
||||
eee[e.getIndex()] = e;
|
||||
}
|
||||
|
||||
// 2.数据合并
|
||||
int startIndex = getDimensions().length;
|
||||
List<Object[]> dataRawList = new ArrayList<>();
|
||||
for (AxisEntry[] group : merged.values()) {
|
||||
AxisEntry keyItem = group[0];
|
||||
for (AxisEntry item : group) {
|
||||
if (keyItem != null) break;
|
||||
keyItem = item;
|
||||
}
|
||||
|
||||
Object[] data = keyItem.getKeyRaw();
|
||||
data = Arrays.copyOf(data, startIndex + indexAndSize);
|
||||
|
||||
for (AxisEntry item : group) {
|
||||
if (item != null) {
|
||||
data[startIndex + item.getIndex()] = item.getValue();
|
||||
}
|
||||
}
|
||||
dataRawList.add(data);
|
||||
}
|
||||
|
||||
return dataRawList.toArray(new Object[0][]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ public class LineChart extends ChartData {
|
|||
Numerical[] nums = getNumericals();
|
||||
final Dimension dim1 = dims[0];
|
||||
|
||||
// 部分支持
|
||||
// 日期连续仅部分支持
|
||||
final Type dim1Type = dim1.getField().getType();
|
||||
final FormatCalc dim1Calc = dim1.getFormatCalc();
|
||||
boolean dateContinuous = renderOption.getBooleanValue("dateContinuous")
|
||||
|
@ -58,14 +58,14 @@ public class LineChart extends ChartData {
|
|||
JSONArray yyyAxis = new JSONArray();
|
||||
List<String> dataFlags = new ArrayList<>();
|
||||
|
||||
// 2DIM + 1NUM
|
||||
// 模式1: 2-DIM + 1-NUM
|
||||
// FIXME 多余AXIS会舍弃
|
||||
if (dims.length > 1) {
|
||||
Numerical num1 = nums[0];
|
||||
Object[][] dataRaw = createQuery(buildSql(dims, num1)).array();
|
||||
// 连续日期
|
||||
if (dateContinuous && dataRaw.length > 0) {
|
||||
dataRaw = putFullDates2Data(dataRaw, dim1, 2);
|
||||
dataRaw = putContinuousDate2Data(dataRaw, dim1, 2);
|
||||
}
|
||||
|
||||
List<Object> dim1Set = new ArrayList<>();
|
||||
|
@ -118,12 +118,27 @@ public class LineChart extends ChartData {
|
|||
dataFlags.add(num1Flag);
|
||||
}
|
||||
}
|
||||
// 1DIM + 多NUM
|
||||
// 模式2: 1-DIM + N-NUM
|
||||
else {
|
||||
Object[][] dataRaw = createQuery(buildSql(dim1, nums)).array();
|
||||
Object[][] dataRaw;
|
||||
if (nums.length > 1 && hasNumericalFilter(nums)) {
|
||||
// 分别查询
|
||||
List<AxisEntry> axisValues = new ArrayList<>();
|
||||
int indexAndSize = 0;
|
||||
for (Numerical num : nums) {
|
||||
Object[][] array = createQuery(buildSql(dim1, new Numerical[]{num}, true)).array();
|
||||
for (Object[] o : array) axisValues.add(new AxisEntry(o, indexAndSize));
|
||||
indexAndSize++;
|
||||
}
|
||||
|
||||
dataRaw = mergeAxisEntry2Data(axisValues, indexAndSize);
|
||||
} else {
|
||||
dataRaw = createQuery(buildSql(dim1, nums, false)).array();
|
||||
}
|
||||
|
||||
// 连续日期
|
||||
if (dateContinuous && dataRaw.length > 0) {
|
||||
dataRaw = putFullDates2Data(dataRaw, dim1, 1);
|
||||
dataRaw = putContinuousDate2Data(dataRaw, dim1, 1);
|
||||
}
|
||||
|
||||
Object[] numsAxis = new Object[nums.length];
|
||||
|
@ -161,7 +176,7 @@ public class LineChart extends ChartData {
|
|||
new Object[]{JSON.toJSON(dimAxis), JSON.toJSON(yyyAxis), renderOption});
|
||||
}
|
||||
|
||||
private Object[][] putFullDates2Data(Object[][] dataRaw, Dimension date1, int numStartIndex) {
|
||||
private Object[][] putContinuousDate2Data(Object[][] dataRaw, Dimension date1, int numStartIndex) {
|
||||
Date min = null;
|
||||
Date max = null;
|
||||
for (Object[] o : dataRaw) {
|
||||
|
|
|
@ -9,8 +9,9 @@ package com.rebuild.core.service.dashboard.charts;
|
|||
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.service.query.ParseHelper;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
/**
|
||||
|
@ -21,7 +22,7 @@ import org.apache.commons.lang.StringUtils;
|
|||
*/
|
||||
public class Numerical extends Axis {
|
||||
|
||||
private JSONObject filter;
|
||||
private JSONObject filter = null;
|
||||
private int scale = 2;
|
||||
|
||||
/**
|
||||
|
@ -37,7 +38,7 @@ public class Numerical extends Axis {
|
|||
JSONObject filter, Field parentField) {
|
||||
super(field, sort, calc, label, parentField);
|
||||
if (scale != null) this.scale = scale;
|
||||
this.filter = filter;
|
||||
if (ParseHelper.validAdvFilter(filter)) this.filter = filter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -36,7 +36,7 @@ public class RadarChart extends ChartData {
|
|||
Numerical[] nums = getNumericals();
|
||||
|
||||
Dimension dim1 = dims[0];
|
||||
Object[][] dataRaw = createQuery(buildSql(dim1, nums)).array();
|
||||
Object[][] dataRaw = createQuery(buildSql(dim1, nums, false)).array();
|
||||
|
||||
JSONArray indicator = new JSONArray();
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ import com.alibaba.fastjson.JSON;
|
|||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -35,6 +37,7 @@ public class ScatterChart extends ChartData {
|
|||
JSONArray series = new JSONArray();
|
||||
List<String> dataFlags = new ArrayList<>();
|
||||
|
||||
// 模式1: 0-DMI + N-NUM
|
||||
if (dims.length == 0) {
|
||||
Object[][] dataRaw = createQuery(buildSql(nums)).array();
|
||||
for (Object[] item : dataRaw) {
|
||||
|
@ -47,10 +50,11 @@ public class ScatterChart extends ChartData {
|
|||
new String[]{"data"},
|
||||
new Object[]{dataRaw});
|
||||
series.add(item);
|
||||
|
||||
} else {
|
||||
}
|
||||
// 模式2: N-DMI + N-NUM
|
||||
else {
|
||||
for (Dimension dim : dims) {
|
||||
Object[][] dataRaw = createQuery(buildSql(dim, nums)).array();
|
||||
Object[][] dataRaw = createQuery(buildSql(dim, nums, false)).array();
|
||||
for (Object[] item : dataRaw) {
|
||||
String label = wrapAxisValue(dim, item[0]);
|
||||
for (int i = 1; i < item.length; i++) {
|
||||
|
@ -80,4 +84,17 @@ public class ScatterChart extends ChartData {
|
|||
new String[]{"series", "dataLabel", "_renderOption"},
|
||||
new Object[]{series, dataLabel, renderOption});
|
||||
}
|
||||
|
||||
private String buildSql(Numerical[] nums) {
|
||||
List<String> numSqlItems = new ArrayList<>();
|
||||
for (Numerical num : nums) {
|
||||
numSqlItems.add(num.getSqlName());
|
||||
}
|
||||
|
||||
String sql = "select {0} from {1} where {2}";
|
||||
sql = MessageFormat.format(sql,
|
||||
StringUtils.join(numSqlItems, ", "),
|
||||
getSourceEntity().getName(), getFilterSql());
|
||||
return appendSqlSort(sql);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,21 @@ public class TableChart extends ChartData {
|
|||
Dimension[] dims = getDimensions();
|
||||
Numerical[] nums = getNumericals();
|
||||
|
||||
Object[][] dataRaw = createQuery(buildSql(dims, nums)).array();
|
||||
Object[][] dataRaw;
|
||||
if (nums.length > 1 && hasNumericalFilter(nums)) {
|
||||
// 分别查询
|
||||
List<AxisEntry> axisValues = new ArrayList<>();
|
||||
int indexAndSize = 0;
|
||||
for (Numerical num : nums) {
|
||||
Object[][] array = createQuery(buildSql(dims, new Numerical[]{num})).array();
|
||||
for (Object[] o : array) axisValues.add(new AxisEntry(o, indexAndSize));
|
||||
indexAndSize++;
|
||||
}
|
||||
|
||||
dataRaw = mergeAxisEntry2Data(axisValues, indexAndSize);
|
||||
} else {
|
||||
dataRaw = createQuery(buildSql(dims, nums)).array();
|
||||
}
|
||||
|
||||
// 行号
|
||||
if (this.showLineNumber && dataRaw.length > 0) {
|
||||
|
@ -130,10 +144,12 @@ public class TableChart extends ChartData {
|
|||
} else if (numSqlItems.isEmpty()) {
|
||||
sql = "select {0} from {2} where {3} group by {0}";
|
||||
}
|
||||
|
||||
sql = MessageFormat.format(sql,
|
||||
StringUtils.join(dimSqlItems, ", "),
|
||||
StringUtils.join(numSqlItems, ", "),
|
||||
getSourceEntity().getName(), getFilterSql());
|
||||
getSourceEntity().getName(), getFilterSql(nums.length > 0 ? nums[0] : null));
|
||||
System.out.println(sql);
|
||||
|
||||
return appendSqlSort(sql);
|
||||
}
|
||||
|
|
|
@ -13,8 +13,12 @@ import com.alibaba.fastjson.JSON;
|
|||
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.metadata.easymeta.EasyField;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.service.dashboard.charts.ChartData;
|
||||
import com.rebuild.core.service.notification.MessageBuilder;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import com.rebuild.core.support.i18n.I18nUtils;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
import com.rebuild.utils.JSONUtils;
|
||||
|
@ -50,28 +54,32 @@ public class FeedsSchedule extends ChartData implements BuiltinChart {
|
|||
@Override
|
||||
public JSON build() {
|
||||
Object[][] array = Application.createQueryNoFilter(
|
||||
"select feedsId,scheduleTime,content,contentMore from Feeds" +
|
||||
"select feedsId,scheduleTime,content,contentMore,relatedRecord from Feeds" +
|
||||
" where createdBy = ? and type = 4 and scheduleTime > ? order by scheduleTime")
|
||||
.setParameter(1, getUser())
|
||||
.setParameter(2, CalendarUtils.addDay(-30)) // 忽略30天前的
|
||||
.setLimit(200)
|
||||
.array();
|
||||
|
||||
final EasyField relatedRecordMeta = EasyMetaFactory.valueOf(
|
||||
MetadataHelper.getField("Feeds", "relatedRecord"));
|
||||
|
||||
JSONArray list = new JSONArray();
|
||||
for (Object[] o : array) {
|
||||
// 有完成时间表示已完成
|
||||
JSONObject state = JSON.parseObject((String) o[3]);
|
||||
if (state.getString("finishTime") != null) {
|
||||
continue;
|
||||
}
|
||||
if (state.getString("finishTime") != null) continue;
|
||||
|
||||
String scheduleTime = I18nUtils.formatDate((Date) o[1]);
|
||||
String content = (String) o[2];
|
||||
content = MessageBuilder.formatMessage(content);
|
||||
|
||||
Object relatedRecord = o[4] == null
|
||||
? null : FieldValueHelper.wrapFieldValue(o[4], relatedRecordMeta);
|
||||
|
||||
JSONObject item = JSONUtils.toJSONObject(
|
||||
new String[]{"id", "scheduleTime", "content"},
|
||||
new Object[]{o[0], scheduleTime, content});
|
||||
new String[]{"id", "scheduleTime", "content", "relatedRecord"},
|
||||
new Object[]{o[0], scheduleTime, content, relatedRecord});
|
||||
list.add(item);
|
||||
}
|
||||
|
||||
|
|
|
@ -105,8 +105,10 @@ public class DataExporter extends SetUser {
|
|||
final List<String> head = this.buildHead(builder);
|
||||
|
||||
// Excel
|
||||
if ("xls".equalsIgnoreCase(csvOrExcel)) {
|
||||
File file = RebuildConfiguration.getFileOfTemp(String.format("RBEXPORT-%d.xls", System.currentTimeMillis()));
|
||||
// xlsx: 无 65535 行数限制
|
||||
if ("xls".equalsIgnoreCase(csvOrExcel) || "xlsx".equalsIgnoreCase(csvOrExcel)) {
|
||||
File file = RebuildConfiguration.getFileOfTemp(
|
||||
String.format("RBEXPORT-%d.%s", System.currentTimeMillis(), csvOrExcel.toLowerCase()));
|
||||
|
||||
List<List<String>> head4Excel = new ArrayList<>();
|
||||
for (String h : head) {
|
||||
|
|
|
@ -88,7 +88,7 @@ public class DataReportManager implements ConfigManager {
|
|||
* @return
|
||||
*/
|
||||
public ConfigBean[] getReportsRaw(Entity entity) {
|
||||
final String cKey = "DataReportManager35-" + entity.getName();
|
||||
final String cKey = "DataReportManager38-" + entity.getName();
|
||||
ConfigBean[] cached = (ConfigBean[]) Application.getCommonsCache().getx(cKey);
|
||||
if (cached != null) return cached;
|
||||
|
||||
|
@ -106,6 +106,7 @@ public class DataReportManager implements ConfigManager {
|
|||
|
||||
int type = ObjectUtils.toInt(o[4], TYPE_RECORD);
|
||||
if (type == TYPE_WORD && outputType.contains("excel")) outputType += ",word";
|
||||
else if (type == TYPE_HTML5) outputType = "html5";
|
||||
|
||||
ConfigBean cb = new ConfigBean()
|
||||
.set("id", o[0])
|
||||
|
@ -116,7 +117,8 @@ public class DataReportManager implements ConfigManager {
|
|||
.set("outputType", outputType)
|
||||
.set("templateVersion", templateVersion)
|
||||
.set("useFilter", useFilter)
|
||||
.set("templateContent", o[6]);
|
||||
.set("templateContent", o[6])
|
||||
.set("entity", entity.getName());
|
||||
alist.add(cb);
|
||||
}
|
||||
|
||||
|
@ -126,26 +128,55 @@ public class DataReportManager implements ConfigManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param entity
|
||||
* @param reportId
|
||||
* @return
|
||||
* @see #getReportsRaw(Entity)
|
||||
*/
|
||||
private ConfigBean getReportRaw(ID reportId, Entity entity) {
|
||||
if (entity == null) {
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(reportId, "belongEntity");
|
||||
if (o == null || !MetadataHelper.containsEntity((String) o[0])) {
|
||||
throw new ConfigurationException("No config of report found : " + reportId);
|
||||
}
|
||||
entity = MetadataHelper.getEntity((String) o[0]);
|
||||
}
|
||||
|
||||
ConfigBean[] cbs = getReportsRaw(entity);
|
||||
for (ConfigBean cb : cbs) {
|
||||
if (reportId.equals(cb.getID("id"))) return cb;
|
||||
}
|
||||
throw new ConfigurationException("No config of report found : " + reportId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取报表配置
|
||||
*
|
||||
* @param reportId
|
||||
* @return
|
||||
*/
|
||||
public TemplateFile getTemplateFile(Entity entity, ID reportId) {
|
||||
String templateFile = null;
|
||||
String templateContent = null;
|
||||
int type = DataReportManager.TYPE_RECORD;
|
||||
boolean isV33 = false;
|
||||
public ConfigBean getReportRaw(ID reportId) {
|
||||
return getReportRaw(reportId, null);
|
||||
}
|
||||
|
||||
for (ConfigBean e : getReportsRaw(entity)) {
|
||||
if (e.getID("id").equals(reportId)) {
|
||||
templateFile = e.getString("template");
|
||||
templateContent = e.getString("templateContent");
|
||||
type = e.getInteger("type");
|
||||
isV33 = e.getInteger("templateVersion") == 3;
|
||||
break;
|
||||
}
|
||||
/**
|
||||
* @param reportId
|
||||
* @param entity
|
||||
* @return
|
||||
*/
|
||||
public TemplateFile buildTemplateFile(ID reportId, Entity entity) {
|
||||
final ConfigBean conf = getReportRaw(reportId, entity);
|
||||
String templateFile = conf.getString("template");
|
||||
String templateContent = conf.getString("templateContent");
|
||||
int type = conf.getInteger("type");
|
||||
boolean isV33 = conf.getInteger("templateVersion") == 3;
|
||||
if (type == TYPE_HTML5) {
|
||||
if (templateContent == null) templateContent = "";
|
||||
} else {
|
||||
templateContent = null;
|
||||
}
|
||||
|
||||
if (entity == null) entity = MetadataHelper.getEntity(conf.getString("entity"));
|
||||
|
||||
// v35 HTML5
|
||||
if (templateContent != null) {
|
||||
return new TemplateFile(templateContent, entity, reportId);
|
||||
|
@ -166,20 +197,15 @@ public class DataReportManager implements ConfigManager {
|
|||
/**
|
||||
* @param reportId
|
||||
* @return
|
||||
* @see #getTemplateFile(Entity, ID) 性能好
|
||||
* @see #buildTemplateFile(ID, Entity)
|
||||
*/
|
||||
public TemplateFile getTemplateFile(ID reportId) {
|
||||
Object[] o = Application.getQueryFactory().uniqueNoFilter(reportId, "belongEntity");
|
||||
if (o == null || !MetadataHelper.containsEntity((String) o[0])) {
|
||||
throw new ConfigurationException("No config of report found : " + reportId);
|
||||
}
|
||||
|
||||
return getTemplateFile(MetadataHelper.getEntity((String) o[0]), reportId);
|
||||
public TemplateFile buildTemplateFile(ID reportId) {
|
||||
return buildTemplateFile(reportId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clean(Object entity) {
|
||||
final String cKey = "DataReportManager35-" + ((Entity) entity).getName();
|
||||
final String cKey = "DataReportManager38-" + ((Entity) entity).getName();
|
||||
Application.getCommonsCache().evict(cKey);
|
||||
}
|
||||
|
||||
|
@ -193,31 +219,25 @@ public class DataReportManager implements ConfigManager {
|
|||
* @param fileName
|
||||
* @return
|
||||
*/
|
||||
public static String getReportName(ID reportId, Object idOrEntity, String fileName) {
|
||||
public static String getPrettyReportName(ID reportId, Object idOrEntity, String fileName) {
|
||||
final Entity be = idOrEntity instanceof ID
|
||||
? MetadataHelper.getEntity(((ID) idOrEntity).getEntityCode())
|
||||
: MetadataHelper.getEntity((String) idOrEntity);
|
||||
|
||||
String name = null;
|
||||
for (ConfigBean cb : DataReportManager.instance.getReportsRaw(be)) {
|
||||
if (cb.getID("id").equals(reportId)) {
|
||||
name = cb.getString("name");
|
||||
if (ContentWithFieldVars.matchsVars(name).isEmpty()) {
|
||||
name = String.format("%s-%s", name, CalendarUtils.getPlainDateFormat().format(CalendarUtils.now()));
|
||||
} else if (idOrEntity instanceof ID) {
|
||||
name = ContentWithFieldVars.replaceWithRecord(name, (ID) idOrEntity);
|
||||
}
|
||||
|
||||
// Suffix
|
||||
if (fileName.endsWith(".pdf")) name += ".pdf";
|
||||
else if (fileName.endsWith(".docx")) name += ".docx";
|
||||
else if (fileName.endsWith(".doc")) name += ".doc";
|
||||
else if (fileName.endsWith(".xlsx")) name += ".xlsx";
|
||||
else if (fileName.endsWith(".xls")) name += ".xls";
|
||||
else if (fileName.endsWith(".csv")) name += ".csv";
|
||||
break;
|
||||
}
|
||||
ConfigBean conf = DataReportManager.instance.getReportRaw(reportId, be);
|
||||
String name = conf.getString("name");
|
||||
if (ContentWithFieldVars.matchsVars(name).isEmpty()) {
|
||||
name = String.format("%s-%s", name, CalendarUtils.getPlainDateFormat().format(CalendarUtils.now()));
|
||||
} else if (idOrEntity instanceof ID) {
|
||||
name = ContentWithFieldVars.replaceWithRecord(name, (ID) idOrEntity);
|
||||
}
|
||||
// Suffix
|
||||
if (fileName.endsWith(".pdf")) name += ".pdf";
|
||||
else if (fileName.endsWith(".docx")) name += ".docx";
|
||||
else if (fileName.endsWith(".doc")) name += ".doc";
|
||||
else if (fileName.endsWith(".xlsx")) name += ".xlsx";
|
||||
else if (fileName.endsWith(".xls")) name += ".xls";
|
||||
else if (fileName.endsWith(".csv")) name += ".csv";
|
||||
|
||||
return StringUtils.defaultIfBlank(name, "UNTITLE");
|
||||
}
|
||||
|
|
|
@ -365,13 +365,17 @@ public class EasyExcelGenerator extends SetUser {
|
|||
if (dt == DisplayType.BARCODE) {
|
||||
data.put(varName, buildBarcodeData(easyField.getRawMeta(), record.getPrimary()));
|
||||
} else if (fieldValue == null) {
|
||||
data.put(varName, StringUtils.EMPTY);
|
||||
// v3.8
|
||||
Object funcValue = ValueConvertFunc.convert(easyField, null, varName);
|
||||
data.put(varName, funcValue == null ? StringUtils.EMPTY : funcValue);
|
||||
} else {
|
||||
|
||||
if (dt == DisplayType.SIGN) {
|
||||
fieldValue = buildSignData((String) fieldValue);
|
||||
} else if (dt == DisplayType.IMAGE) {
|
||||
fieldValue = buildImageData((String) fieldValue);
|
||||
// TODO Excel 指定图片大小(可通过 Excel 单元格大小控制?)
|
||||
|
||||
} else {
|
||||
|
||||
if (dt == DisplayType.NUMBER) {
|
||||
|
@ -522,7 +526,7 @@ public class EasyExcelGenerator extends SetUser {
|
|||
return create(reportId, recordId);
|
||||
} else {
|
||||
TemplateFile tt = DataReportManager.instance
|
||||
.getTemplateFile(MetadataHelper.getEntity(recordId.getEntityCode()), reportId);
|
||||
.buildTemplateFile(reportId, MetadataHelper.getEntity(recordId.getEntityCode()));
|
||||
return new EasyExcelGenerator33(tt.templateFile, recordIds);
|
||||
}
|
||||
}
|
||||
|
@ -534,7 +538,7 @@ public class EasyExcelGenerator extends SetUser {
|
|||
*/
|
||||
public static EasyExcelGenerator create(ID reportId, ID recordId) {
|
||||
TemplateFile tt = DataReportManager.instance
|
||||
.getTemplateFile(MetadataHelper.getEntity(recordId.getEntityCode()), reportId);
|
||||
.buildTemplateFile(reportId, MetadataHelper.getEntity(recordId.getEntityCode()));
|
||||
return create(tt.templateFile, recordId, tt.isV33);
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFI
|
|||
@Slf4j
|
||||
public class EasyExcelGenerator33 extends EasyExcelGenerator {
|
||||
|
||||
// 支持多记录导出,会合并到一个 Excel 文件
|
||||
final private List<ID> recordIdMultiple;
|
||||
|
||||
private Set<String> inShapeVars;
|
||||
|
|
|
@ -61,9 +61,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
|
|||
|
||||
for (Map.Entry<String, String> e : varsMap.entrySet()) {
|
||||
String varName = e.getKey();
|
||||
if (varName.startsWith(NROW_PREFIX + PLACEHOLDER)) {
|
||||
continue;
|
||||
}
|
||||
if (varName.startsWith(NROW_PREFIX + PLACEHOLDER)) continue;
|
||||
|
||||
String validField = e.getValue();
|
||||
if (validField != null && e.getKey().startsWith(NROW_PREFIX)) {
|
||||
|
@ -113,9 +111,9 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
|
|||
* @return
|
||||
*/
|
||||
public static EasyExcelListGenerator create(ID reportId, JSONObject queryData) {
|
||||
TemplateFile tb = DataReportManager.instance.getTemplateFile(
|
||||
MetadataHelper.getEntity(queryData.getString("entity")), reportId);
|
||||
return create(tb.templateFile, queryData);
|
||||
TemplateFile tt = DataReportManager.instance.buildTemplateFile(
|
||||
reportId, MetadataHelper.getEntity(queryData.getString("entity")));
|
||||
return create(tt.templateFile, queryData);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -92,12 +92,15 @@ public class TemplateExtractor {
|
|||
|
||||
Map<String, String> map = new HashMap<>();
|
||||
for (final String varName : vars) {
|
||||
// v3.8
|
||||
String thatName = ValueConvertFunc.splitName(varName);
|
||||
|
||||
// 列表型字段
|
||||
if (varName.startsWith(NROW_PREFIX)) {
|
||||
String listField = varName.substring(1);
|
||||
if (thatName.startsWith(NROW_PREFIX)) {
|
||||
String listField = thatName.substring(1);
|
||||
|
||||
// 审批流程
|
||||
if (!this.isListType && varName.startsWith(APPROVAL_PREFIX)) {
|
||||
if (!this.isListType && thatName.startsWith(APPROVAL_PREFIX)) {
|
||||
String stepNodeField = listField.substring(APPROVAL_PREFIX.length());
|
||||
if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) {
|
||||
map.put(varName, stepNodeField);
|
||||
|
@ -120,10 +123,10 @@ public class TemplateExtractor {
|
|||
map.put(varName, transformRealField(entity, listField));
|
||||
}
|
||||
|
||||
} 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;
|
||||
|
|
|
@ -8,14 +8,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
package com.rebuild.core.service.datareport;
|
||||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.commons.ObjectUtils;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import com.deepoove.poi.data.PictureRenderData;
|
||||
import com.deepoove.poi.data.Pictures;
|
||||
import com.rebuild.core.configuration.ConfigBean;
|
||||
import com.rebuild.core.configuration.general.MultiSelectManager;
|
||||
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.ArrayUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
|
||||
|
@ -25,7 +29,10 @@ import java.io.ByteArrayInputStream;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
|
@ -34,8 +41,18 @@ import java.util.Date;
|
|||
@Slf4j
|
||||
public class ValueConvertFunc {
|
||||
|
||||
// # 函数
|
||||
protected static final String FUNC_SPLITER = "#";
|
||||
// # 函数分隔符
|
||||
private static final String FUNC_SPLITER = "#";
|
||||
private static final String FVAL_SPLITER = ":";
|
||||
// 支持的函数
|
||||
private static final String CHINESE_4DATE_NUM = "CHINESE";
|
||||
private static final String CHINESEYUAN_4NUM = "CHINESEYUAN";
|
||||
private static final String THOUSANDS_4NUM = "THOUSANDS";
|
||||
private static final String CHECKBOX_4OPTION = "CHECKBOX";
|
||||
private static final String CHECKBOX2_4OPTION = "CHECKBOX" + FVAL_SPLITER + "2";
|
||||
private static final String PICKAT_4CLASS_DATE = "PICKAT"; // eg. PICKAT:2
|
||||
private static final String SIZE_4IMG = "SIZE"; // eg. SIZE:100*200, SIZE:100
|
||||
private static final String EMPTY = "EMPTY"; // eg. EMPTY:无
|
||||
|
||||
/**
|
||||
* @param field
|
||||
|
@ -47,41 +64,102 @@ public class ValueConvertFunc {
|
|||
String thatFunc = splitFunc(varName);
|
||||
if (thatFunc == null) return value;
|
||||
|
||||
// 默认值
|
||||
if (thatFunc.startsWith(EMPTY)) {
|
||||
if (value == null || StringUtils.isBlank(value.toString())) {
|
||||
return extractFuncValue(thatFunc);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
// 空值也处理
|
||||
if (type == DisplayType.MULTISELECT || type == DisplayType.PICKLIST) {
|
||||
if (CHECKBOX_4OPTION.equals(thatFunc) || CHECKBOX2_4OPTION.equals(thatFunc)) {
|
||||
String[] m = value == null ? new String[0] : value.toString().split(", ");
|
||||
ConfigBean[] items = MultiSelectManager.instance.getPickListRaw(field.getRawMeta(), false);
|
||||
|
||||
String[] flags = new String[]{"■", "□"};
|
||||
if (CHECKBOX2_4OPTION.equals(thatFunc)) flags = new String[]{"●", "○"};
|
||||
|
||||
List<String> chk = new ArrayList<>();
|
||||
for (ConfigBean item : items) {
|
||||
String itemText = item.getString("text");
|
||||
if (ArrayUtils.contains(m, itemText)) chk.add(flags[0] + itemText);
|
||||
else chk.add(flags[1] + itemText);
|
||||
}
|
||||
return new DecimalFormat(format).format(value);
|
||||
|
||||
return StringUtils.join(chk, " ");
|
||||
}
|
||||
}
|
||||
|
||||
if (value == null) return null;
|
||||
|
||||
if (type == DisplayType.NUMBER || type == DisplayType.DECIMAL) {
|
||||
switch (thatFunc) {
|
||||
case CHINESEYUAN_4NUM:
|
||||
return Convert.digitToChinese((Number) value);
|
||||
case CHINESE_4DATE_NUM:
|
||||
return Convert.numberToChinese(((Number) value).doubleValue(), true);
|
||||
case THOUSANDS_4NUM:
|
||||
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;
|
||||
} else if (type == DisplayType.DATE || type == DisplayType.DATETIME || type == DisplayType.TIME) {
|
||||
if (CHINESE_4DATE_NUM.equals(thatFunc) || "CHINESEDATE".equals(thatFunc)) {
|
||||
if (type == DisplayType.TIME) {
|
||||
String s = "2024-01-01 " + value;
|
||||
if (s.length() == 13) s += ":00";
|
||||
Date d = CommonsUtils.parseDate(s);
|
||||
if (d == null) return value;
|
||||
|
||||
int len = field.wrapValue(CalendarUtils.now()).toString().length();
|
||||
if (len <= 10) len += 1; // yyyy-MM-dd
|
||||
else len += 2;
|
||||
int len = field.wrapValue(LocalTime.now()).toString().length() + 1;
|
||||
String format = CalendarUtils.CN_TIME_FORMAT.substring(0, len);
|
||||
return CalendarUtils.getDateFormat(format).format(d);
|
||||
} else {
|
||||
Date d = CommonsUtils.parseDate(value.toString());
|
||||
if (d == null) return value;
|
||||
|
||||
String format = CalendarUtils.CN_DATETIME_FORMAT.substring(0, len);
|
||||
return CalendarUtils.getDateFormat(format).format(d);
|
||||
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);
|
||||
}
|
||||
} else if (thatFunc.startsWith(PICKAT_4CLASS_DATE) && type != DisplayType.TIME) {
|
||||
String[] m = value.toString().replace(" ", "-").split("[-:]");
|
||||
int pickIndex = ObjectUtils.toInt(extractFuncValue(thatFunc)) - 1;
|
||||
|
||||
if (pickIndex < 0) return m[0];
|
||||
if (m.length > pickIndex) return m[pickIndex];
|
||||
return m[0]; // first
|
||||
}
|
||||
|
||||
} else if (type == DisplayType.CLASSIFICATION) {
|
||||
if (thatFunc.startsWith(PICKAT_4CLASS_DATE)) {
|
||||
int pickIndex = ObjectUtils.toInt(extractFuncValue(thatFunc)) - 1;
|
||||
String[] m = value.toString().split("\\.");
|
||||
|
||||
if (pickIndex < 0) return m[m.length - 1];
|
||||
if (m.length > pickIndex) return m[pickIndex];
|
||||
return m[m.length - 1]; // last
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换图片
|
||||
* 转换图片(WORD 格式)
|
||||
*
|
||||
* @param value
|
||||
* @param varName
|
||||
|
@ -93,8 +171,8 @@ public class ValueConvertFunc {
|
|||
String thatFunc = splitFunc(varName);
|
||||
if (thatFunc == null) return builder.create();
|
||||
|
||||
if (thatFunc.startsWith("SIZE") && thatFunc.length() > 4) {
|
||||
String[] wh = thatFunc.substring(4).split("\\*");
|
||||
if (thatFunc.startsWith(SIZE_4IMG)) {
|
||||
String[] wh = extractFuncValue(thatFunc).split("\\*");
|
||||
int width = NumberUtils.toInt(wh[0]);
|
||||
int height = -1;
|
||||
|
||||
|
@ -116,19 +194,30 @@ public class ValueConvertFunc {
|
|||
height = NumberUtils.toInt(wh[1]);
|
||||
}
|
||||
|
||||
if (height < 0) height = width;
|
||||
builder = Pictures.ofBytes(value).size(width, height);
|
||||
builder = Pictures.ofBytes(value).size(width, height > 0 ? height : width);
|
||||
}
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
// 提取函数附加值
|
||||
private static String extractFuncValue(String func) {
|
||||
// v3.7 兼容
|
||||
if (func.startsWith(SIZE_4IMG) && !func.startsWith(SIZE_4IMG + FVAL_SPLITER)) {
|
||||
func = SIZE_4IMG + FVAL_SPLITER + func.substring(4);
|
||||
}
|
||||
|
||||
String[] nn = func.split(FVAL_SPLITER);
|
||||
if (nn.length == 2) return nn[1];
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param varName
|
||||
* @return
|
||||
*/
|
||||
public static String splitName(String varName) {
|
||||
return varName.split("#")[0].trim();
|
||||
return varName.split(FUNC_SPLITER)[0].trim();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,6 +225,6 @@ public class ValueConvertFunc {
|
|||
* @return
|
||||
*/
|
||||
public static String splitFunc(String varName) {
|
||||
return varName.contains(FUNC_SPLITER) ? varName.split("#")[1].trim() : null;
|
||||
return varName.contains(FUNC_SPLITER) ? varName.split(FUNC_SPLITER)[1].trim() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ public abstract class BaseFeedsService extends ObservableService {
|
|||
|
||||
@Override
|
||||
public Record update(Record record) {
|
||||
record = super.update(converContent4Mentions((record)));
|
||||
record = super.update(converContent4Mentions(record));
|
||||
|
||||
awareMention(record, false);
|
||||
return record;
|
||||
|
|
|
@ -91,7 +91,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
|
|||
addObserver(new RobotTriggerObserver());
|
||||
try {
|
||||
addObserver((SafeObserver) ReflectUtils.newObject("com.rebuild.rbv.sop.RobotSopObserver"));
|
||||
} catch (Exception ignoredClassNotFound){}
|
||||
} catch (Exception ignoredRbvClassMiss){}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -8,12 +8,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
package com.rebuild.core.service.general;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.core.NamedThreadLocal;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* @author devezhao
|
||||
* @since 2020/9/29
|
||||
*/
|
||||
@Slf4j
|
||||
public class GeneralEntityServiceContextHolder {
|
||||
|
||||
private static final ThreadLocal<Boolean> SKIP_SERIES_VALUE = new NamedThreadLocal<>("Skip series value");
|
||||
|
@ -24,6 +27,10 @@ public class GeneralEntityServiceContextHolder {
|
|||
|
||||
private static final ThreadLocal<ID> FROM_TRIGGERS = new NamedThreadLocal<>("From triggers");
|
||||
|
||||
private static final ThreadLocal<Boolean> QUICK_MODE = new NamedThreadLocal<>("Quick mode");
|
||||
|
||||
private static final ThreadLocal<ID> SKIP_GUARD = new NamedThreadLocal<>("Skip some check once");
|
||||
|
||||
/**
|
||||
* 新建记录时允许跳过自动编号字段
|
||||
*/
|
||||
|
@ -105,4 +112,48 @@ public class GeneralEntityServiceContextHolder {
|
|||
if (mode != null) REPEATED_CHECK_MODE.remove();
|
||||
return mode == null ? 0 : mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* 忽略一些操作/触发器的执行,达到快速操作的目的
|
||||
*/
|
||||
public static void setQuickMode() {
|
||||
QUICK_MODE.set(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param once
|
||||
* @return
|
||||
* @see #setQuickMode()
|
||||
*/
|
||||
public static boolean isQuickMode(boolean once) {
|
||||
Boolean is = QUICK_MODE.get();
|
||||
if (is != null && once) QUICK_MODE.remove();
|
||||
return is != null && is;
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许无权限操作一次
|
||||
*
|
||||
* @param recordId
|
||||
*/
|
||||
public static void setSkipGuard(ID recordId) {
|
||||
Assert.notNull(recordId, "[recordId] cannot be null");
|
||||
|
||||
ID existsWarn = SKIP_GUARD.get();
|
||||
if (existsWarn != null) {
|
||||
log.warn("Not removed skip record : {}", existsWarn);
|
||||
SKIP_GUARD.remove();
|
||||
}
|
||||
SKIP_GUARD.set(recordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @see #setSkipGuard(ID)
|
||||
*/
|
||||
public static ID isSkipGuardOnce() {
|
||||
ID recordId = SKIP_GUARD.get();
|
||||
if (recordId != null) SKIP_GUARD.remove();
|
||||
return recordId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,6 +127,8 @@ public class RecentlyUsedHelper {
|
|||
* @param type
|
||||
*/
|
||||
public static void add(ID user, ID id, String type) {
|
||||
if (EntityHelper.isUnsavedId(id)) return;
|
||||
|
||||
final String key = formatKey(user, MetadataHelper.getEntityName(id), type);
|
||||
@SuppressWarnings("unchecked")
|
||||
LinkedList<ID> cached = (LinkedList<ID>) Application.getCommonsCache().getx(key);
|
||||
|
|
|
@ -48,6 +48,8 @@ public class SeriesReindexTask extends HeavyTask<Integer> {
|
|||
if (ONLY_REINDEX_BLANK) {
|
||||
sql += String.format(" where %s is null or %s = ''", field.getName(), field.getName());
|
||||
}
|
||||
if (field.getOwnEntity().containsField(EntityHelper.AutoId)) sql += " order by autoId asc";
|
||||
|
||||
Query query = Application.createQueryNoFilter(sql);
|
||||
Object[][] array = QueryHelper.readArray(query);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ import org.springframework.util.Assert;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 数字自增系列
|
||||
|
@ -31,7 +31,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
public class IncreasingVar extends SeriesVar {
|
||||
|
||||
private static final Object INCREASINGS_LOCK = new Object();
|
||||
private static final Map<String, AtomicInteger> INCREASINGS = new ConcurrentHashMap<>();
|
||||
private static final Map<String, AtomicLong> INCREASINGS = new ConcurrentHashMap<>();
|
||||
|
||||
private Field field;
|
||||
private String zeroFlag;
|
||||
|
@ -55,6 +55,11 @@ public class IncreasingVar extends SeriesVar {
|
|||
this.field = field;
|
||||
}
|
||||
|
||||
private String getNameKey() {
|
||||
Assert.notNull(this.field, "[this.field] cannot be null");
|
||||
return String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
// Preview mode
|
||||
|
@ -62,19 +67,18 @@ public class IncreasingVar extends SeriesVar {
|
|||
return StringUtils.leftPad("1", getSymbols().length(), '0');
|
||||
}
|
||||
|
||||
final String nameKey = String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName());
|
||||
|
||||
final String nameKey = getNameKey();
|
||||
synchronized (INCREASINGS_LOCK) {
|
||||
AtomicInteger incr = INCREASINGS.get(nameKey);
|
||||
AtomicLong incr = INCREASINGS.get(nameKey);
|
||||
if (incr == null) {
|
||||
String val = KVStorage.getCustomValue(nameKey);
|
||||
int init = val == null ? countFromDb() : ObjectUtils.toInt(val);
|
||||
long init = val == null ? countFromDb() : ObjectUtils.toLong(val);
|
||||
|
||||
incr = new AtomicInteger(init);
|
||||
incr = new AtomicLong(init);
|
||||
INCREASINGS.put(nameKey, incr);
|
||||
}
|
||||
|
||||
int nextValue = incr.incrementAndGet();
|
||||
long nextValue = incr.incrementAndGet();
|
||||
RebuildConfiguration.setCustomValue(nameKey, nextValue, Boolean.TRUE);
|
||||
|
||||
return StringUtils.leftPad(nextValue + "", getSymbols().length(), '0');
|
||||
|
@ -82,16 +86,27 @@ public class IncreasingVar extends SeriesVar {
|
|||
}
|
||||
|
||||
/**
|
||||
* 清空序号缓存
|
||||
* 重置序号
|
||||
*
|
||||
* @param reset
|
||||
*/
|
||||
protected void clean() {
|
||||
Assert.notNull(this.field, "[this.field] cannot be null");
|
||||
|
||||
final String nameKey = String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName());
|
||||
|
||||
protected void clean(long reset) {
|
||||
Assert.isTrue(reset >= 0, "[reset] must be greater than 0");
|
||||
final String nameKey = getNameKey();
|
||||
synchronized (INCREASINGS_LOCK) {
|
||||
INCREASINGS.remove(nameKey);
|
||||
RebuildConfiguration.setCustomValue(nameKey, 0, Boolean.TRUE);
|
||||
RebuildConfiguration.setCustomValue(nameKey, reset, Boolean.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public long getCurrentValue() {
|
||||
final String nameKey = getNameKey();
|
||||
synchronized (INCREASINGS_LOCK) {
|
||||
String val = KVStorage.getCustomValue(nameKey);
|
||||
return val == null ? 0L : ObjectUtils.toLong(val);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +117,7 @@ public class IncreasingVar extends SeriesVar {
|
|||
*
|
||||
* @return
|
||||
*/
|
||||
private int countFromDb() {
|
||||
private long countFromDb() {
|
||||
String dateLimit = null;
|
||||
if ("Y".equals(zeroFlag)) {
|
||||
dateLimit = CalendarUtils.format("yyyy", CalendarUtils.now()) + "-01-01";
|
||||
|
@ -121,6 +136,6 @@ public class IncreasingVar extends SeriesVar {
|
|||
String sql = String.format("select count(%s) from %s where %s",
|
||||
field.getName(), field.getOwnEntity().getName(), dateLimit);
|
||||
Object[] count = Application.createQueryNoFilter(sql).unique();
|
||||
return ObjectUtils.toInt(count[0]);
|
||||
return ObjectUtils.toLong(count[0]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,25 @@ public class SeriesGeneratorFactory {
|
|||
* @param field
|
||||
*/
|
||||
public static void zero(Field field) {
|
||||
new IncreasingVar(field).clean();
|
||||
zero(field, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置序号
|
||||
*
|
||||
* @param field
|
||||
* @param reset
|
||||
*/
|
||||
public static void zero(Field field, long reset) {
|
||||
new IncreasingVar(field).clean(reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前序号
|
||||
*
|
||||
* @param field
|
||||
*/
|
||||
public static long getCurrentIncreasingVarValue(Field field) {
|
||||
return new IncreasingVar(field).getCurrentValue();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,6 @@ import com.rebuild.core.metadata.EntityRecordCreator;
|
|||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.metadata.easymeta.EasyField;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.service.general.GeneralEntityService;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
|
@ -174,7 +173,7 @@ public class RecordTransfomer extends SetUser {
|
|||
}
|
||||
|
||||
protected ID saveRecord(Record record, List<Record> detailsList) {
|
||||
if (this.skipGuard) PrivilegesGuardContextHolder.setSkipGuard(EntityHelper.UNSAVED_ID);
|
||||
if (this.skipGuard) GeneralEntityServiceContextHolder.setSkipGuard(EntityHelper.UNSAVED_ID);
|
||||
|
||||
if (detailsList != null && !detailsList.isEmpty()) {
|
||||
record.setObjectValue(GeneralEntityService.HAS_DETAILS, detailsList);
|
||||
|
@ -187,8 +186,8 @@ public class RecordTransfomer extends SetUser {
|
|||
record = Application.getEntityService(targetEntity.getEntityCode()).createOrUpdate(record);
|
||||
return record.getPrimary();
|
||||
} finally {
|
||||
if (this.skipGuard) GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
|
||||
if (this.skipGuard) PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,9 +26,9 @@ import com.rebuild.core.metadata.EntityHelper;
|
|||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.privileges.UserHelper;
|
||||
import com.rebuild.core.privileges.bizz.Department;
|
||||
import com.rebuild.core.support.CommandArgs;
|
||||
import com.rebuild.core.support.License;
|
||||
import com.rebuild.core.support.SetUser;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
|
@ -147,26 +147,7 @@ public class AdvFilterParser extends SetUser {
|
|||
|
||||
// 自动确定查询项
|
||||
if (MODE_QUICK.equalsIgnoreCase(filterExpr.getString("type"))) {
|
||||
String quickFields = filterExpr.getString("quickFields");
|
||||
JSONArray quickItems = buildQuickFilterItems(quickFields, 1);
|
||||
|
||||
// TODO v3.6-b4,3.7 值1|值2 UNTEST
|
||||
// 转义可输入 \|
|
||||
JSONObject values = filterExpr.getJSONObject("values");
|
||||
String[] valuesPlus = values.values().iterator().next().toString().split("(?<!\\\\)\\|");
|
||||
if (valuesPlus.length > 1) {
|
||||
values.clear();
|
||||
values.put("1", valuesPlus[0].trim());
|
||||
|
||||
for (int i = 2; i <= valuesPlus.length; i++) {
|
||||
JSONArray quickItemsPlus = buildQuickFilterItems(quickFields, i);
|
||||
values.put(String.valueOf(i), valuesPlus[i - 1].trim());
|
||||
quickItems.addAll(quickItemsPlus);
|
||||
}
|
||||
filterExpr.put("values", values);
|
||||
}
|
||||
|
||||
filterExpr.put("items", quickItems);
|
||||
rebuildQuickFilter38();
|
||||
}
|
||||
|
||||
JSONArray items = filterExpr.getJSONArray("items");
|
||||
|
@ -302,6 +283,9 @@ public class AdvFilterParser extends SetUser {
|
|||
String value = (String) checkValue;
|
||||
String valueEnd = null;
|
||||
|
||||
// v3.8
|
||||
if (useFulltextOp(field) && "LK".equals(op)) op = "FT";
|
||||
|
||||
if (dt == DisplayType.N2NREFERENCE) {
|
||||
String inWhere = null;
|
||||
boolean forceNot = false;
|
||||
|
@ -505,6 +489,25 @@ public class AdvFilterParser extends SetUser {
|
|||
}
|
||||
op = ParseHelper.BW;
|
||||
|
||||
} else if (ParseHelper.YYY.equalsIgnoreCase(op)
|
||||
|| ParseHelper.MMM.equalsIgnoreCase(op)) {
|
||||
|
||||
int xValue = NumberUtils.toInt(value);
|
||||
Calendar now = CalendarUtils.getInstance();
|
||||
|
||||
if (ParseHelper.YYY.equalsIgnoreCase(op)) {
|
||||
now.add(Calendar.YEAR, xValue);
|
||||
value = now.get(Calendar.YEAR) + "-01-01";
|
||||
valueEnd = now.get(Calendar.YEAR) + "-12-31";
|
||||
} else {
|
||||
now.set(Calendar.DAY_OF_MONTH, 1);
|
||||
now.add(Calendar.MONTH, xValue);
|
||||
|
||||
value = CalendarUtils.getUTCDateFormat().format(now.getTime());
|
||||
Moment last = Moment.moment(now.getTime()).endOf(Moment.UNIT_MONTH);
|
||||
valueEnd = CalendarUtils.getUTCDateFormat().format(last.date());
|
||||
}
|
||||
op = ParseHelper.BW;
|
||||
}
|
||||
|
||||
} else if (dt == DisplayType.TIME) {
|
||||
|
@ -631,6 +634,10 @@ public class AdvFilterParser extends SetUser {
|
|||
// LIKE
|
||||
if (op.equalsIgnoreCase(ParseHelper.LK) || op.equalsIgnoreCase(ParseHelper.NLK)) {
|
||||
value = '%' + value + '%';
|
||||
} else if (op.equalsIgnoreCase(ParseHelper.LK1)) {
|
||||
value = '%' + value;
|
||||
} else if (op.equalsIgnoreCase(ParseHelper.LK2)) {
|
||||
value = value + '%';
|
||||
}
|
||||
sb.append(quoteValue(value, lastFieldMeta.getType()));
|
||||
}
|
||||
|
@ -687,21 +694,18 @@ public class AdvFilterParser extends SetUser {
|
|||
value += (fullTime ? ParseHelper.FULL_TIME : ParseHelper.ZERO_TIME); // 含当日
|
||||
}
|
||||
}
|
||||
// 修正月、日
|
||||
else if (field.getType() == FieldType.DATE && valueLen == 10) {
|
||||
String dateFormat = StringUtils.defaultIfBlank(
|
||||
EasyMetaFactory.valueOf(field).getExtraAttr(EasyFieldConfigProps.DATE_FORMAT),
|
||||
DisplayType.DATE.getDefaultFormat());
|
||||
|
||||
// v3.7 BW 无需修正,值由用户提供
|
||||
if (ParseHelper.BW.equals(op)) dateFormat = "0";
|
||||
|
||||
if (dateFormat.length() == 4) {
|
||||
value = value.substring(0, 4) + "-01-01";
|
||||
} else if (dateFormat.length() == 7) {
|
||||
value = value.substring(0, 7) + "-01";
|
||||
}
|
||||
}
|
||||
// v3.8 不修正了,否则因为格式问题(如日期带日、不带日)就带来不同的查询结果,这很怪异
|
||||
// // 修正月、日
|
||||
// else if (field.getType() == FieldType.DATE && valueLen == 10) {
|
||||
// String dateFormat = StringUtils.defaultIfBlank(
|
||||
// EasyMetaFactory.valueOf(field).getExtraAttr(EasyFieldConfigProps.DATE_FORMAT),
|
||||
// DisplayType.DATE.getDefaultFormat());
|
||||
// if (dateFormat.length() == 4) {
|
||||
// value = value.substring(0, 4) + "-01-01";
|
||||
// } else if (dateFormat.length() == 7) {
|
||||
// value = value.substring(0, 7) + "-01";
|
||||
// }
|
||||
// }
|
||||
|
||||
// 多个值的情况下,兼容 | 号分割
|
||||
if (op.equalsIgnoreCase(ParseHelper.IN) || op.equalsIgnoreCase(ParseHelper.NIN)
|
||||
|
@ -750,25 +754,69 @@ public class AdvFilterParser extends SetUser {
|
|||
|
||||
/**
|
||||
* @param date
|
||||
* @param paddingType 0=无, 1=FULLTIME, 2=ZEROTIME
|
||||
* @param paddingTimeType 0=无, 1=FULLTIME, 2=ZEROTIME
|
||||
* @return
|
||||
*/
|
||||
private String formatDate(Date date, int paddingType) {
|
||||
private String formatDate(Date date, int paddingTimeType) {
|
||||
String s = CalendarUtils.getUTCDateFormat().format(date);
|
||||
if (paddingType == 1) s += ParseHelper.FULL_TIME;
|
||||
else if (paddingType == 2) s += ParseHelper.ZERO_TIME;
|
||||
if (paddingTimeType == 1) s += ParseHelper.FULL_TIME;
|
||||
else if (paddingTimeType == 2) s += ParseHelper.ZERO_TIME;
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速查询
|
||||
*
|
||||
*/
|
||||
private void rebuildQuickFilter38() {
|
||||
String quickFields = filterExpr.getString("quickFields");
|
||||
JSONArray quickItems = buildQuickFilterItems(quickFields, 1);
|
||||
|
||||
JSONObject values = filterExpr.getJSONObject("values");
|
||||
final String quickValue = values.values().iterator().next().toString();
|
||||
|
||||
// eg: =完全相等, *后匹配, 前匹配*
|
||||
if (quickValue.length() > 2
|
||||
&& (quickValue.startsWith("=") || quickValue.startsWith("*") || quickValue.endsWith("*"))) {
|
||||
String op2 = ParseHelper.EQ;
|
||||
String value2;
|
||||
if (quickValue.startsWith("*")) op2 = ParseHelper.LK1;
|
||||
else if (quickValue.endsWith("*")) op2 = ParseHelper.LK2;
|
||||
if (quickValue.endsWith("*")) value2 = quickValue.substring(0, quickValue.length() - 1);
|
||||
else value2 = quickValue.substring(1);
|
||||
|
||||
for (Object o : quickItems) {
|
||||
JSONObject item = (JSONObject) o;
|
||||
item.put("op", op2);
|
||||
item.put("value", value2);
|
||||
}
|
||||
|
||||
} else {
|
||||
// v3.6-b4,3.7: 多值查询(转义可输入 \|)。eg: 值1|值2
|
||||
String[] m = quickValue.split("(?<!\\\\)\\|");
|
||||
if (m.length > 1) {
|
||||
values.clear();
|
||||
values.put("1", m[0].trim());
|
||||
|
||||
for (int i = 2; i <= m.length; i++) {
|
||||
JSONArray quickItemsPlus = buildQuickFilterItems(quickFields, i);
|
||||
values.put(String.valueOf(i), m[i - 1].trim());
|
||||
quickItems.addAll(quickItemsPlus);
|
||||
}
|
||||
filterExpr.put("values", values);
|
||||
}
|
||||
}
|
||||
|
||||
// 覆盖
|
||||
filterExpr.put("items", quickItems);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param quickFields
|
||||
* @param valueIndex
|
||||
* @return
|
||||
*/
|
||||
private JSONArray buildQuickFilterItems(String quickFields, int valueIndex) {
|
||||
Set<String> usesFields = ParseHelper.buildQuickFields(rootEntity, quickFields);
|
||||
|
||||
JSONArray items = new JSONArray();
|
||||
for (String field : usesFields) {
|
||||
items.add(JSON.parseObject("{ op:'LK', value:'{" + valueIndex + "}', field:'" + field + "' }"));
|
||||
|
@ -776,6 +824,22 @@ public class AdvFilterParser extends SetUser {
|
|||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用全文索引查詢
|
||||
*
|
||||
* @param fieldName
|
||||
* @return
|
||||
*/
|
||||
private boolean useFulltextOp(String fieldName) {
|
||||
if (!CommandArgs.getBoolean(CommandArgs._UseDbFullText)) return false;
|
||||
|
||||
Set<String> canFulltexts = new HashSet<>();
|
||||
canFulltexts.add("40#content");
|
||||
|
||||
String key = rootEntity.getEntityCode() + "#" + fieldName;
|
||||
return canFulltexts.contains(key);
|
||||
}
|
||||
|
||||
// 字段变量 {@FIELD}
|
||||
private static final String PATT_FIELDVAR = "\\{@([\\w.]+)}";
|
||||
// `当前`变量(当前日期、时间、用户)
|
||||
|
|
|
@ -43,6 +43,8 @@ public class ParseHelper {
|
|||
public static final String NL = "NL";
|
||||
public static final String NT = "NT";
|
||||
public static final String LK = "LK";
|
||||
public static final String LK1 = "LK1"; // *后匹配
|
||||
public static final String LK2 = "LK2"; // 前匹配*
|
||||
public static final String NLK = "NLK";
|
||||
public static final String IN = "IN";
|
||||
public static final String NIN = "NIN";
|
||||
|
@ -95,6 +97,8 @@ public class ParseHelper {
|
|||
public static final String PUY = "PUY"; // 本年-
|
||||
public static final String CUY = "CUY"; // 本年
|
||||
public static final String NUY = "NUY"; // 本年+
|
||||
public static final String YYY = "YYY"; // 指定年+-
|
||||
public static final String MMM = "MMM"; // 指定月+-
|
||||
public static final String DDD = "DDD"; // 指定天+-
|
||||
public static final String HHH = "HHH"; // 指定时+-
|
||||
public static final String EVW = "EVW"; // 每周几
|
||||
|
@ -130,7 +134,7 @@ public class ParseHelper {
|
|||
return "is null";
|
||||
} else if (NT.equalsIgnoreCase(token)) {
|
||||
return "is not null";
|
||||
} else if (LK.equalsIgnoreCase(token)) {
|
||||
} else if (LK.equalsIgnoreCase(token) || LK1.equalsIgnoreCase(token) || LK2.equalsIgnoreCase(token)) {
|
||||
return "like";
|
||||
} else if (NLK.equalsIgnoreCase(token)) {
|
||||
return "not like";
|
||||
|
@ -185,7 +189,8 @@ public class ParseHelper {
|
|||
} else if (
|
||||
CUW.equalsIgnoreCase(token) || CUM.equalsIgnoreCase(token) || CUQ.equalsIgnoreCase(token) || CUY.equalsIgnoreCase(token) ||
|
||||
PUW.equalsIgnoreCase(token) || PUM.equalsIgnoreCase(token) || PUQ.equalsIgnoreCase(token) || PUY.equalsIgnoreCase(token) ||
|
||||
NUW.equalsIgnoreCase(token) || NUM.equalsIgnoreCase(token) || NUQ.equalsIgnoreCase(token) || NUY.equalsIgnoreCase(token)) {
|
||||
NUW.equalsIgnoreCase(token) || NUM.equalsIgnoreCase(token) || NUQ.equalsIgnoreCase(token) || NUY.equalsIgnoreCase(token) ||
|
||||
YYY.equalsIgnoreCase(token) || MMM.equalsIgnoreCase(token)) {
|
||||
return "between";
|
||||
} else if (DDD.equalsIgnoreCase(token) || HHH.equalsIgnoreCase(token)
|
||||
|| EVW.equalsIgnoreCase(token) || EVM.equalsIgnoreCase(token)) {
|
||||
|
@ -211,7 +216,7 @@ public class ParseHelper {
|
|||
if (dt == DisplayType.REFERENCE) {
|
||||
Field nameField = field.getReferenceEntity().getNameField();
|
||||
if (nameField.getType() == FieldType.REFERENCE) {
|
||||
log.warn("Quick field cannot be circular-reference : " + nameField);
|
||||
log.warn("Quick field cannot be circular-reference : {}", nameField);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -266,7 +271,7 @@ public class ParseHelper {
|
|||
if (can != null) usesFields.add(can);
|
||||
|
||||
} else {
|
||||
log.warn("No field found in `quickFields` : " + field + " in " + entity.getName());
|
||||
log.warn("No field found in `quickFields` : {} in {}", field, entity.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +304,7 @@ public class ParseHelper {
|
|||
}
|
||||
|
||||
if (usesFields.isEmpty()) {
|
||||
log.warn("No fields of search found : " + usesFields);
|
||||
log.warn("No fields of search found : {}", usesFields);
|
||||
}
|
||||
return usesFields;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.service.trigger;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.rebuild.core.service.DataSpecificationException;
|
||||
|
||||
/**
|
||||
|
@ -19,18 +20,28 @@ import com.rebuild.core.service.DataSpecificationException;
|
|||
public class DataValidateException extends DataSpecificationException {
|
||||
private static final long serialVersionUID = 4178910284594338317L;
|
||||
|
||||
private boolean weakMode = false;
|
||||
final private boolean weakMode;
|
||||
final private ID weakModeTriggerId;
|
||||
|
||||
public DataValidateException(String msg) {
|
||||
super(msg);
|
||||
this(msg, false, null);
|
||||
}
|
||||
|
||||
public DataValidateException(String msg, boolean weakMode) {
|
||||
this(msg, weakMode, null);
|
||||
}
|
||||
|
||||
public DataValidateException(String msg, boolean weakMode, ID triggerId) {
|
||||
super(msg);
|
||||
this.weakMode = weakMode;
|
||||
this.weakModeTriggerId = triggerId;
|
||||
}
|
||||
|
||||
public boolean isWeakMode() {
|
||||
return weakMode;
|
||||
}
|
||||
|
||||
public ID getWeakModeTriggerId() {
|
||||
return weakModeTriggerId;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,10 +97,6 @@ public class RobotTriggerManager implements ConfigManager {
|
|||
recordId, StringUtils.join(TRIGGERS_CHAIN_4DEBUG.get(), "\n > "));
|
||||
}
|
||||
|
||||
// ActionContext ctx = new ActionContext(recordId, entity,
|
||||
// JSONUtils.EMPTY_OBJECT, EntityHelper.newUnsavedId(EntityHelper.RobotTriggerConfig));
|
||||
// actions.add(new GeneralTriggerAction(ctx));
|
||||
|
||||
return actions.toArray(new TriggerAction[0]);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.rebuild.core.service.general.OperatingContext;
|
|||
import com.rebuild.core.service.general.OperatingObserver;
|
||||
import com.rebuild.core.service.general.RepeatedRecordsException;
|
||||
import com.rebuild.core.service.trigger.impl.FieldAggregation;
|
||||
import com.rebuild.core.support.CommandArgs;
|
||||
import com.rebuild.core.support.CommonsLog;
|
||||
import com.rebuild.core.support.general.FieldValueHelper;
|
||||
import com.rebuild.core.support.i18n.Language;
|
||||
|
@ -48,6 +49,9 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
|
||||
private static final ThreadLocal<String> ALLOW_TRIGGERS_ONAPPROVED = new NamedThreadLocal<>("Allow triggers on approve-node");
|
||||
|
||||
// 少量触发器日志
|
||||
public static final boolean _TriggerLessLog = CommandArgs.getBoolean(CommandArgs._TriggerLessLog);
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 4;
|
||||
|
@ -191,7 +195,7 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
|
||||
final int t = triggerSource.incrTriggerTimes();
|
||||
final String w = String.format("Trigger.%s.%d [ %s ] executing on record (%s) : %s", sourceId, t, action, when, primaryId);
|
||||
log.info(w);
|
||||
if (!_TriggerLessLog) log.info(w);
|
||||
|
||||
try {
|
||||
Object res = action.execute(context);
|
||||
|
@ -247,7 +251,7 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
|
||||
} finally {
|
||||
if (originTriggerSource) {
|
||||
log.info("Clear trigger-source : {}", getTriggerSource());
|
||||
if (!_TriggerLessLog) log.info("Clear trigger-source : {}", getTriggerSource());
|
||||
TRIGGER_SOURCE.remove();
|
||||
}
|
||||
}
|
||||
|
@ -296,7 +300,7 @@ public class RobotTriggerObserver extends OperatingObserver {
|
|||
if (ctx == null) return 0;
|
||||
LAZY_TRIGGERS_CTX.remove();
|
||||
|
||||
log.info("Will execute lazy triggers : {}", ctx);
|
||||
if (!_TriggerLessLog) log.info("Will execute lazy triggers : {}", ctx);
|
||||
RobotTriggerObserver observer = new RobotTriggerObserver();
|
||||
for (Object context : ctx) {
|
||||
observer.update(o, context);
|
||||
|
|
|
@ -46,7 +46,7 @@ public class AviatorDate extends AviatorObject {
|
|||
return dateValue.compareTo((Date) $date);
|
||||
}
|
||||
|
||||
log.warn("Could not compare " + desc(env) + " with " + other.desc(env));
|
||||
log.warn("Could not compare {} with {}", desc(env), other.desc(env));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*!
|
||||
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.trigger.aviator;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.googlecode.aviator.runtime.type.AviatorObject;
|
||||
import com.googlecode.aviator.runtime.type.AviatorType;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Wrap {@link ID[]}
|
||||
*
|
||||
* @author devezhao
|
||||
* @since 2024/5/14
|
||||
*/
|
||||
@Slf4j
|
||||
public class AviatorIdArray extends AviatorObject {
|
||||
private static final long serialVersionUID = 7227725706972057447L;
|
||||
|
||||
final private ID[] idArray;
|
||||
|
||||
public AviatorIdArray(ID[] value) {
|
||||
super();
|
||||
this.idArray = value;
|
||||
}
|
||||
|
||||
public AviatorIdArray(Collection<ID> value) {
|
||||
this(value.toArray(new ID[0]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int innerCompare(AviatorObject other, Map<String, Object> env) {
|
||||
log.warn("Could not compare {} with {}", desc(env), other.desc(env));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AviatorType getAviatorType() {
|
||||
return AviatorType.JavaType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue(Map<String, Object> env) {
|
||||
return this.idArray;
|
||||
}
|
||||
}
|
|
@ -7,19 +7,24 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.service.trigger.aviator;
|
||||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.googlecode.aviator.AviatorEvaluator;
|
||||
import com.googlecode.aviator.AviatorEvaluatorInstance;
|
||||
import com.googlecode.aviator.Options;
|
||||
import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
|
||||
import com.googlecode.aviator.lexer.token.OperatorType;
|
||||
import com.googlecode.aviator.runtime.function.FunctionUtils;
|
||||
import com.googlecode.aviator.runtime.function.system.AssertFunction;
|
||||
import com.googlecode.aviator.runtime.type.AviatorFunction;
|
||||
import com.googlecode.aviator.runtime.type.AviatorNil;
|
||||
import com.googlecode.aviator.runtime.type.AviatorObject;
|
||||
import com.googlecode.aviator.runtime.type.Sequence;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -132,7 +137,7 @@ public class AviatorUtils {
|
|||
* @param function
|
||||
*/
|
||||
public static void addCustomFunction(final AviatorFunction function) {
|
||||
log.info("Add custom function : {}", function);
|
||||
log.info("Add custom function : {}", function.getName());
|
||||
AVIATOR.addFunction(function);
|
||||
}
|
||||
|
||||
|
@ -154,4 +159,16 @@ public class AviatorUtils {
|
|||
|
||||
throw new UnsupportedOperationException("Unsupport type : " + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ret
|
||||
* @return
|
||||
* @see FunctionUtils#wrapReturn(Object)
|
||||
*/
|
||||
public static AviatorObject wrapReturn(final Object ret) {
|
||||
if (ret == null) return AviatorNil.NIL;
|
||||
if (ret instanceof Date) return new AviatorDate((Date) ret);
|
||||
if (ret instanceof ID) return new AviatorId((ID) ret);
|
||||
return FunctionUtils.wrapReturn(ret);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,11 @@ package com.rebuild.core.service.trigger.aviator;
|
|||
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.googlecode.aviator.runtime.function.AbstractFunction;
|
||||
import com.googlecode.aviator.runtime.type.AviatorNil;
|
||||
import com.googlecode.aviator.runtime.type.AviatorObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.UserContextHolder;
|
||||
import com.rebuild.core.privileges.bizz.User;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -27,9 +29,10 @@ public class CurrentBizunitFunction extends AbstractFunction {
|
|||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env) {
|
||||
ID user = UserContextHolder.getUser();
|
||||
ID bizunit = (ID) Application.getUserStore().getUser(user).getOwningBizUnit().getIdentity();
|
||||
return new AviatorId(bizunit);
|
||||
ID user = UserContextHolder.getReplacedUser();
|
||||
User ub = Application.getUserStore().getUser(user);
|
||||
if (ub.getOwningDept() == null) return AviatorNil.NIL;
|
||||
return AviatorUtils.wrapReturn(ub.getOwningDept().getIdentity());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -26,8 +26,8 @@ public class CurrentUserFunction extends AbstractFunction {
|
|||
|
||||
@Override
|
||||
public AviatorObject call(Map<String, Object> env) {
|
||||
ID user = UserContextHolder.getUser();
|
||||
return new AviatorId(user);
|
||||
ID user = UserContextHolder.getReplacedUser();
|
||||
return AviatorUtils.wrapReturn(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -13,7 +13,6 @@ 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.GeneralEntityServiceContextHolder;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
|
@ -103,7 +102,7 @@ public class AutoAssign extends TriggerAction {
|
|||
cascades = hasCascades.split(",");
|
||||
}
|
||||
|
||||
PrivilegesGuardContextHolder.setSkipGuard(recordId);
|
||||
GeneralEntityServiceContextHolder.setSkipGuard(recordId);
|
||||
GeneralEntityServiceContextHolder.setFromTrigger(recordId);
|
||||
|
||||
try {
|
||||
|
@ -116,7 +115,7 @@ public class AutoAssign extends TriggerAction {
|
|||
}
|
||||
|
||||
} finally {
|
||||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isFromTrigger(true);
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@ 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;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
|
@ -78,13 +77,13 @@ public class AutoShare extends TriggerAction {
|
|||
|
||||
final EntityService es = Application.getEntityService(actionContext.getSourceEntity().getEntityCode());
|
||||
for (ID toUser : toUsers) {
|
||||
PrivilegesGuardContextHolder.setSkipGuard(recordId);
|
||||
GeneralEntityServiceContextHolder.setSkipGuard(recordId);
|
||||
GeneralEntityServiceContextHolder.setFromTrigger(recordId);
|
||||
|
||||
try {
|
||||
es.share(recordId, toUser, cascades, shareRights);
|
||||
} finally {
|
||||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import com.rebuild.core.metadata.EntityHelper;
|
|||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
import com.rebuild.core.service.general.OperatingContext;
|
||||
|
@ -32,6 +31,7 @@ import com.rebuild.core.service.query.ParseHelper;
|
|||
import com.rebuild.core.service.query.QueryHelper;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import com.rebuild.core.service.trigger.ActionType;
|
||||
import com.rebuild.core.service.trigger.RobotTriggerObserver;
|
||||
import com.rebuild.core.service.trigger.TriggerAction;
|
||||
import com.rebuild.core.service.trigger.TriggerException;
|
||||
import com.rebuild.core.service.trigger.TriggerResult;
|
||||
|
@ -123,7 +123,7 @@ public class FieldAggregation extends TriggerAction {
|
|||
// 在整个触发链上只触发1次,避免循环调用
|
||||
// FIXME 20220804 某些场景是否允许2次,而非1次???
|
||||
if (tschain.contains(chainName)) {
|
||||
log.warn(w + "!!! TRIGGER ONCE ONLY");
|
||||
log.warn("{}!!! TRIGGER ONCE ONLY", w);
|
||||
return null;
|
||||
} else {
|
||||
log.info(w);
|
||||
|
@ -230,13 +230,13 @@ public class FieldAggregation extends TriggerAction {
|
|||
|
||||
// 有需要才执行
|
||||
if (targetRecord.isEmpty()) {
|
||||
log.info("No data of target record : {}", targetRecordId);
|
||||
if (!RobotTriggerObserver._TriggerLessLog) log.info("No data of target record : {}", targetRecordId);
|
||||
return TriggerResult.targetEmpty();
|
||||
}
|
||||
|
||||
// 相等则不更新
|
||||
if (isCurrentSame(targetRecord)) {
|
||||
log.info("Ignore execution because the record are same : {}", targetRecordId);
|
||||
if (!RobotTriggerObserver._TriggerLessLog) log.info("Ignore execution because the record are same : {}", targetRecordId);
|
||||
return TriggerResult.targetSame();
|
||||
}
|
||||
|
||||
|
@ -244,28 +244,30 @@ public class FieldAggregation extends TriggerAction {
|
|||
final boolean stopPropagation = ((JSONObject) actionContext.getActionContent()).getBooleanValue("stopPropagation");
|
||||
|
||||
// 跳过权限
|
||||
PrivilegesGuardContextHolder.setSkipGuard(targetRecordId);
|
||||
GeneralEntityServiceContextHolder.setSkipGuard(targetRecordId);
|
||||
|
||||
// 强制更新 (v2.9)
|
||||
if (forceUpdate) {
|
||||
GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId);
|
||||
}
|
||||
// 快速模式 (v3.8)
|
||||
if (stopPropagation) {
|
||||
GeneralEntityServiceContextHolder.setQuickMode();
|
||||
}
|
||||
|
||||
tschain.add(chainName);
|
||||
TRIGGER_CHAIN.set(tschain);
|
||||
|
||||
targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now());
|
||||
targetRecord.setID(EntityHelper.ModifiedBy, UserService.SYSTEM_USER);
|
||||
|
||||
try {
|
||||
if (stopPropagation) {
|
||||
Application.getCommonsService().update(targetRecord, false);
|
||||
} else {
|
||||
Application.getBestService(targetEntity).update(targetRecord);
|
||||
}
|
||||
Application.getBestService(targetEntity).update(targetRecord);
|
||||
|
||||
} finally {
|
||||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
if (forceUpdate) GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
|
||||
if (stopPropagation) GeneralEntityServiceContextHolder.isQuickMode(true);
|
||||
}
|
||||
|
||||
if (operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == FieldAggregation.class) {
|
||||
|
|
|
@ -28,7 +28,6 @@ import com.rebuild.core.metadata.easymeta.EasyDateTime;
|
|||
import com.rebuild.core.metadata.easymeta.EasyField;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.easymeta.MultiValue;
|
||||
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
import com.rebuild.core.privileges.bizz.InternalPermission;
|
||||
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
|
||||
|
@ -36,6 +35,7 @@ import com.rebuild.core.service.general.OperatingContext;
|
|||
import com.rebuild.core.service.query.QueryHelper;
|
||||
import com.rebuild.core.service.trigger.ActionContext;
|
||||
import com.rebuild.core.service.trigger.ActionType;
|
||||
import com.rebuild.core.service.trigger.RobotTriggerObserver;
|
||||
import com.rebuild.core.service.trigger.TriggerException;
|
||||
import com.rebuild.core.service.trigger.TriggerResult;
|
||||
import com.rebuild.core.service.trigger.aviator.AviatorUtils;
|
||||
|
@ -50,7 +50,6 @@ import org.apache.commons.lang.StringUtils;
|
|||
import java.math.BigDecimal;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
|
@ -60,6 +59,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 字段更新,场景 1>1 1>N
|
||||
|
@ -72,6 +72,8 @@ import java.util.Set;
|
|||
@Slf4j
|
||||
public class FieldWriteback extends FieldAggregation {
|
||||
|
||||
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||
|
||||
private FieldWritebackRefresh fieldWritebackRefresh;
|
||||
|
||||
/**
|
||||
|
@ -109,6 +111,26 @@ public class FieldWriteback extends FieldAggregation {
|
|||
|
||||
@Override
|
||||
public Object execute(OperatingContext operatingContext) throws TriggerException {
|
||||
boolean lockMode38 = ((JSONObject) actionContext.getActionContent()).getBooleanValue("lockMode");
|
||||
if (lockMode38) {
|
||||
long s = System.currentTimeMillis();
|
||||
log.info("Lock resources for {}", actionContext.getConfigId());
|
||||
LOCK.lock();
|
||||
|
||||
s = System.currentTimeMillis() - s;
|
||||
if (s > 1000) log.warn("Lock acquired use {}ms. {}", s, actionContext.getConfigId());
|
||||
|
||||
try {
|
||||
return this.execute38(operatingContext);
|
||||
} finally {
|
||||
LOCK.unlock();
|
||||
}
|
||||
} else {
|
||||
return this.execute38(operatingContext);
|
||||
}
|
||||
}
|
||||
|
||||
private Object execute38(OperatingContext operatingContext) throws TriggerException {
|
||||
final String chainName = String.format("%s:%s:%s", actionContext.getConfigId(),
|
||||
operatingContext.getFixedRecordId(), operatingContext.getAction().getName());
|
||||
final List<String> tschain = checkTriggerChain(chainName);
|
||||
|
@ -122,7 +144,7 @@ public class FieldWriteback extends FieldAggregation {
|
|||
}
|
||||
|
||||
if (targetRecordData.isEmpty()) {
|
||||
log.info("No data of target record : {}", targetRecordIds);
|
||||
if (!RobotTriggerObserver._TriggerLessLog) log.info("No data of target record : {}", targetRecordIds);
|
||||
return TriggerResult.targetEmpty();
|
||||
}
|
||||
|
||||
|
@ -148,21 +170,26 @@ public class FieldWriteback extends FieldAggregation {
|
|||
Record targetRecord = targetRecordData.clone();
|
||||
targetRecord.setID(targetEntity.getPrimaryField().getName(), targetRecordId);
|
||||
targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now());
|
||||
targetRecord.setID(EntityHelper.ModifiedBy, UserService.SYSTEM_USER);
|
||||
|
||||
// 相等则不更新
|
||||
if (isCurrentSame(targetRecord)) {
|
||||
log.info("Ignore execution because the record are same : {}", targetRecordId);
|
||||
if (!RobotTriggerObserver._TriggerLessLog) log.info("Ignore execution because the record are same : {}", targetRecordId);
|
||||
targetSame = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 跳过权限
|
||||
PrivilegesGuardContextHolder.setSkipGuard(targetRecordId);
|
||||
GeneralEntityServiceContextHolder.setSkipGuard(targetRecordId);
|
||||
|
||||
// 强制更新 (v2.9)
|
||||
if (forceUpdate) {
|
||||
GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId);
|
||||
}
|
||||
// 快速模式 (v3.8)
|
||||
if (stopPropagation) {
|
||||
GeneralEntityServiceContextHolder.setQuickMode();
|
||||
}
|
||||
|
||||
// 重复检查模式
|
||||
GeneralEntityServiceContextHolder.setRepeatedCheckMode(GeneralEntityServiceContextHolder.RCM_CHECK_MAIN);
|
||||
|
@ -173,16 +200,13 @@ public class FieldWriteback extends FieldAggregation {
|
|||
if (CommonsUtils.DEVLOG) System.out.println("[dev] Use current-loop tschain : " + tschainCurrentLoop);
|
||||
|
||||
try {
|
||||
if (stopPropagation) {
|
||||
Application.getCommonsService().update(targetRecord, false);
|
||||
} else {
|
||||
Application.getBestService(targetEntity).createOrUpdate(targetRecord);
|
||||
}
|
||||
Application.getBestService(targetEntity).createOrUpdate(targetRecord);
|
||||
affected.add(targetRecord.getPrimary());
|
||||
|
||||
} finally {
|
||||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
if (forceUpdate) GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
|
||||
if (stopPropagation) GeneralEntityServiceContextHolder.isQuickMode(true);
|
||||
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
|
||||
}
|
||||
}
|
||||
|
@ -208,7 +232,7 @@ public class FieldWriteback extends FieldAggregation {
|
|||
// v35
|
||||
if (TARGET_ANY.equals(targetFieldEntity[0])) {
|
||||
TargetWithMatchFields targetWithMatchFields = new TargetWithMatchFields();
|
||||
ID[] ids = targetWithMatchFields.matchMulti(actionContext);
|
||||
ID[] ids = targetWithMatchFields.matchMultiple(actionContext);
|
||||
CollectionUtils.addAll(targetRecordIds, ids);
|
||||
}
|
||||
// 自己更新自己
|
||||
|
@ -545,19 +569,16 @@ public class FieldWriteback extends FieldAggregation {
|
|||
|
||||
// v3.7 增强兼容
|
||||
Object[] ids;
|
||||
if (value instanceof Collection) {
|
||||
//noinspection unchecked
|
||||
ids = ((Collection<Object>) value).toArray(new Object[0]);
|
||||
} else if (value instanceof Object[]) {
|
||||
ids = (Object[]) value;
|
||||
} else {
|
||||
ids = value.toString().split(",");
|
||||
}
|
||||
if (value instanceof String) ids = value.toString().split(",");
|
||||
else ids = CommonsUtils.toArray(value);
|
||||
|
||||
Set<ID> idsSet = new LinkedHashSet<>();
|
||||
for (Object id : ids) {
|
||||
id = id.toString().trim();
|
||||
if (ID.isId(id)) idsSet.add(ID.valueOf(id.toString()));
|
||||
if (id instanceof ID) idsSet.add((ID) id);
|
||||
else {
|
||||
id = id.toString().trim();
|
||||
if (ID.isId(id)) idsSet.add(ID.valueOf(id.toString()));
|
||||
}
|
||||
}
|
||||
// v3.5.5: 目标值为多引用时保持 `ID[]`
|
||||
newValue = idsSet.toArray(new ID[0]);
|
||||
|
|
|
@ -22,8 +22,8 @@ import com.rebuild.core.metadata.easymeta.DisplayType;
|
|||
import com.rebuild.core.metadata.easymeta.EasyField;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
|
||||
import com.rebuild.core.privileges.UserService;
|
||||
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;
|
||||
|
@ -281,12 +281,12 @@ public class GroupAggregation extends FieldAggregation {
|
|||
}
|
||||
}
|
||||
|
||||
PrivilegesGuardContextHolder.setSkipGuard(EntityHelper.UNSAVED_ID);
|
||||
GeneralEntityServiceContextHolder.setSkipGuard(EntityHelper.UNSAVED_ID);
|
||||
|
||||
try {
|
||||
Application.getBestService(targetEntity).create(newTargetRecord);
|
||||
} finally {
|
||||
PrivilegesGuardContextHolder.getSkipGuardOnce();
|
||||
GeneralEntityServiceContextHolder.isSkipGuardOnce();
|
||||
}
|
||||
|
||||
targetRecordId = newTargetRecord.getPrimary();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class TargetWithMatchFields {
|
|||
@Getter
|
||||
private Object targetRecordId;
|
||||
|
||||
protected TargetWithMatchFields() {
|
||||
public TargetWithMatchFields() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
@ -70,7 +70,7 @@ public class TargetWithMatchFields {
|
|||
* @param actionContext
|
||||
* @return
|
||||
*/
|
||||
public ID[] matchMulti(ActionContext actionContext) {
|
||||
public ID[] matchMultiple(ActionContext actionContext) {
|
||||
Object o = match(actionContext, true);
|
||||
return o == null ? new ID[0] : (ID[]) o;
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ public class TargetWithMatchFields {
|
|||
if (MetadataHelper.getLastJoinField(sourceEntity, sourceField) == null) {
|
||||
throw new MissingMetaExcetion(sourceField, sourceEntity.getName());
|
||||
}
|
||||
if (!targetEntity.containsField(targetField)) {
|
||||
if (MetadataHelper.getLastJoinField(targetEntity, targetField) == null) {
|
||||
throw new MissingMetaExcetion(targetField, targetEntity.getName());
|
||||
}
|
||||
matchFieldsMapping.put(sourceField, targetField);
|
||||
|
@ -136,7 +136,7 @@ public class TargetWithMatchFields {
|
|||
String targetField = e.getValue();
|
||||
// @see Dimension#getSqlName
|
||||
EasyField sourceFieldEasy = EasyMetaFactory.valueOf(MetadataHelper.getLastJoinField(sourceEntity, sourceField));
|
||||
EasyField targetFieldEasy = EasyMetaFactory.valueOf(targetEntity.getField(targetField));
|
||||
EasyField targetFieldEasy = EasyMetaFactory.valueOf(MetadataHelper.getLastJoinField(targetEntity, targetField));
|
||||
|
||||
// fix: 3.7.1
|
||||
boolean isDateField = sourceFieldEasy.getDisplayType() == DisplayType.DATE
|
||||
|
|
|
@ -32,6 +32,8 @@ public class CommandArgs {
|
|||
public static final String _StartEntityTypeCode = "_StartEntityTypeCode";
|
||||
public static final String _UseFrontJSAnywhere = "_UseFrontJSAnywhere";
|
||||
public static final String _TriggerMaxDepth = "_TriggerMaxDepth";
|
||||
public static final String _ProtectedAdmin = "_ProtectedAdmin";
|
||||
public static final String _TriggerLessLog = "_TriggerLessLog";
|
||||
|
||||
/**
|
||||
* @param name
|
||||
|
|
|
@ -124,6 +124,7 @@ public enum ConfigurationItem {
|
|||
SecurityEnhanced(false), // 安全增强
|
||||
TrustedAllUrl(false), // 可信外部地址
|
||||
LibreofficeBin, // Libreoffice 命令
|
||||
MysqldumpBin, // mysqldump 命令
|
||||
UnsafeImgAccess(false), // 不安全图片访问
|
||||
|
||||
;
|
||||
|
@ -143,8 +144,9 @@ public enum ConfigurationItem {
|
|||
|| SecurityEnhanced.name().equalsIgnoreCase(name)
|
||||
|| TrustedAllUrl.name().equalsIgnoreCase(name)
|
||||
|| LibreofficeBin.name().equalsIgnoreCase(name)
|
||||
|| MysqldumpBin.name().equalsIgnoreCase(name)
|
||||
|| UnsafeImgAccess.name().equals(name)
|
||||
|| SN.name().equalsIgnoreCase(name);
|
||||
|| SN.name().equals(name);
|
||||
}
|
||||
|
||||
private Object defaultVal;
|
||||
|
|
|
@ -119,6 +119,12 @@ public class KVStorage {
|
|||
*/
|
||||
protected static String getValue(final String key, boolean noCache, Object defaultValue) {
|
||||
String value = null;
|
||||
// be:v3.8
|
||||
if (ConfigurationItem.SN.name().equals(key)) {
|
||||
value = BootEnvironmentPostProcessor.getProperty(key);
|
||||
} else if (ConfigurationItem.inJvmArgs(key)) {
|
||||
return BootEnvironmentPostProcessor.getProperty(key);
|
||||
}
|
||||
|
||||
if (Application.isReady()) {
|
||||
// 0. 从缓存
|
||||
|
|
|
@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.support;
|
||||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.commons.CodecUtils;
|
||||
import cn.devezhao.commons.ExpiresMap;
|
||||
import cn.devezhao.commons.identifier.ComputerIdentifier;
|
||||
|
@ -19,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeansException;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -53,11 +55,12 @@ public final class License {
|
|||
}
|
||||
|
||||
if (SN == null) {
|
||||
SN = String.format("RB%s%s-%s-%s",
|
||||
SN = String.format("RB%s%s-%s%s%s",
|
||||
Application.VER.charAt(0),
|
||||
Locale.getDefault().getCountry().substring(0, 2),
|
||||
ComputerIdentifier.generateIdentifierKey(),
|
||||
CodecUtils.randomCode(9)).toUpperCase();
|
||||
CalendarUtils.format("wwyy", new Date()),
|
||||
CodecUtils.randomCode(6)).toUpperCase();
|
||||
RebuildConfiguration.setValue(ConfigurationItem.SN.name(), SN);
|
||||
siteApiNoCache("api/authority/query");
|
||||
}
|
||||
|
@ -160,4 +163,5 @@ public final class License {
|
|||
return JSONUtils.toJSONObject("error", "Call site api failed");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -45,15 +45,18 @@ public class SysbaseSupport {
|
|||
}
|
||||
log.warn(confLog.append("----------").toString());
|
||||
|
||||
StringBuilder osLog = new StringBuilder("OS/VM Info :\n----------\n");
|
||||
StringBuilder osLog = new StringBuilder("OS/VM INFO :\n----------\n");
|
||||
osLog.append(StringUtils.rightPad("OS", 31)).append(" : ").append(SystemUtils.OS_NAME).append("/").append(SystemUtils.OS_VERSION).append("\n");
|
||||
osLog.append(StringUtils.rightPad("VM", 31)).append(" : ").append(SystemUtils.JAVA_VM_NAME).append("/").append(SystemUtils.JAVA_VERSION).append(SystemUtils.OS_VERSION).append("\n");
|
||||
osLog.append(StringUtils.rightPad("TimeZone", 31)).append(" : ").append(CalendarUtils.DEFAULT_TIME_ZONE).append("\n");
|
||||
osLog.append(StringUtils.rightPad("Date", 31)).append(" : ").append(CalendarUtils.now()).append("\n");
|
||||
osLog.append(StringUtils.rightPad("Headless", 31)).append(" : ").append(SystemUtils.isJavaAwtHeadless()).append("\n");
|
||||
|
||||
log.warn(osLog.append("----------").toString());
|
||||
|
||||
StringBuilder vmLog = new StringBuilder("VM ARGUMENTS :\n----------\n");
|
||||
vmLog.append(System.getProperties());
|
||||
log.warn(vmLog.append("----------").toString());
|
||||
|
||||
File logFile = SysbaseHeartbeat.getLogbackFile();
|
||||
|
||||
JSONObject resJson;
|
||||
|
|
|
@ -10,11 +10,16 @@ package com.rebuild.core.support.general;
|
|||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.engine.ID;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.EncodeHintType;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.MultiFormatWriter;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.WriterException;
|
||||
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
|
||||
import com.google.zxing.client.j2se.MatrixToImageWriter;
|
||||
import com.google.zxing.common.BitMatrix;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
import com.google.zxing.oned.Code128Writer;
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
|
||||
import com.rebuild.core.RebuildException;
|
||||
|
@ -25,6 +30,7 @@ import com.rebuild.utils.AppUtils;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
|
@ -250,4 +256,26 @@ public class BarCodeSupport {
|
|||
|
||||
return fm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别。支持条码与二维码
|
||||
*
|
||||
* @param image
|
||||
* @return
|
||||
*/
|
||||
public static String decode(File image) {
|
||||
try {
|
||||
BufferedImage bufferedImage = ImageIO.read(image);
|
||||
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(bufferedImage)));
|
||||
MultiFormatReader reader = new MultiFormatReader();
|
||||
|
||||
Result result = reader.decode(bitmap);
|
||||
bufferedImage.flush();
|
||||
return result.getText();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Cannot decode image : {}", image);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
/*!
|
||||
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.general;
|
||||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import cn.devezhao.commons.ObjectUtils;
|
||||
import cn.devezhao.persist4j.Entity;
|
||||
import cn.devezhao.persist4j.Field;
|
||||
import cn.devezhao.persist4j.Record;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.configuration.general.AutoFillinManager;
|
||||
import com.rebuild.core.metadata.MetadataSorter;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
import com.rebuild.core.metadata.easymeta.EasyDateTime;
|
||||
import com.rebuild.core.metadata.easymeta.EasyDecimal;
|
||||
import com.rebuild.core.metadata.easymeta.EasyField;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.service.trigger.aviator.AviatorUtils;
|
||||
import com.rebuild.utils.CommonsUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 表单计算公式
|
||||
*
|
||||
* @author ZHAO
|
||||
* @since 2024/8/19
|
||||
* @see AutoFillinManager
|
||||
*/
|
||||
@Slf4j
|
||||
public class CalcFormulaSupport {
|
||||
|
||||
/**
|
||||
* 表单计算公式,后端计算。
|
||||
* FIXME 字段计算存在路径依赖:例如字段 B=A+1, 但 A 也是计算字段
|
||||
*
|
||||
* @param record
|
||||
* @return
|
||||
*/
|
||||
public static int calcFormulaBackend(Record record) {
|
||||
// 从数据库访问
|
||||
Record recordInDb = null;
|
||||
if (record.getPrimary() != null) {
|
||||
recordInDb = Application.getQueryFactory().recordNoFilter(record.getPrimary());
|
||||
}
|
||||
|
||||
int calc = 0;
|
||||
for (Field field
|
||||
: MetadataSorter.sortFields(record.getEntity(), DisplayType.DECIMAL, DisplayType.NUMBER, DisplayType.DATE, DisplayType.DATETIME)) {
|
||||
final EasyField targetField = EasyMetaFactory.valueOf(field);
|
||||
String formula = targetField.getExtraAttr(EasyFieldConfigProps.NUMBER_CALCFORMULA);
|
||||
String backend = targetField.getExtraAttr(EasyFieldConfigProps.NUMBER_CALCFORMULABACKEND);
|
||||
if (StringUtils.isBlank(formula)) continue;
|
||||
if (!BooleanUtils.toBoolean(backend)) continue;
|
||||
|
||||
Map<String, Object> varsInFormula = new HashMap<>();
|
||||
Set<String> fieldVars = ContentWithFieldVars.matchsVars(formula);
|
||||
for (String fieldName : fieldVars) {
|
||||
Object v = record.getObjectValue(fieldName);
|
||||
if (v == null && recordInDb != null) {
|
||||
v = recordInDb.getObjectValue(fieldName);
|
||||
}
|
||||
|
||||
if (v == null) {
|
||||
varsInFormula = null;
|
||||
break;
|
||||
}
|
||||
varsInFormula.put(fieldName, v);
|
||||
}
|
||||
if (varsInFormula == null) continue;
|
||||
|
||||
Object evalVal = evalValue(formula, varsInFormula, targetField, false);
|
||||
// 无值忽略
|
||||
if (evalVal == null) continue;
|
||||
// 同值忽略
|
||||
if (recordInDb != null) {
|
||||
Object dbVal = recordInDb.getObjectValue(field.getName());
|
||||
if (CommonsUtils.isSame(evalVal, dbVal)) continue;
|
||||
}
|
||||
|
||||
record.setObjectValue(field.getName(), evalVal);
|
||||
}
|
||||
|
||||
return calc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算
|
||||
*
|
||||
* @param targetField
|
||||
* @param varsInFormula
|
||||
* @return
|
||||
*/
|
||||
public static Object evalCalcFormula(Field targetField, Map<String, Object> varsInFormula) {
|
||||
final Entity entity = targetField.getOwnEntity();
|
||||
final EasyField easyField = EasyMetaFactory.valueOf(targetField);
|
||||
final String formula = easyField.getExtraAttr(EasyFieldConfigProps.NUMBER_CALCFORMULA);
|
||||
|
||||
boolean calcReady = true;
|
||||
Set<String> fieldVars = ContentWithFieldVars.matchsVars(formula);
|
||||
for (String fieldName : fieldVars) {
|
||||
if (EasyDateTime.VAR_NOW.equals(fieldName) || "NOW".equals(fieldName)) {
|
||||
varsInFormula.put(fieldName, CalendarUtils.now());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entity.containsField(fieldName)) {
|
||||
calcReady = false;
|
||||
break;
|
||||
}
|
||||
|
||||
Object fieldValue = varsInFormula.get(fieldName);
|
||||
if (fieldValue == null) {
|
||||
calcReady = false;
|
||||
break;
|
||||
}
|
||||
|
||||
String val2str = fieldValue.toString();
|
||||
DisplayType dt = EasyMetaFactory.valueOf(entity.getField(fieldName)).getDisplayType();
|
||||
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
|
||||
fieldValue = CalendarUtils.parse(val2str, CalendarUtils.UTC_DATETIME_FORMAT.substring(0, val2str.length()));
|
||||
} else if (dt == DisplayType.NUMBER || dt == DisplayType.DECIMAL) {
|
||||
if (StringUtils.isNotBlank(val2str)) {
|
||||
// v3.6.3 整数/小数强制使用 BigDecimal 高精度
|
||||
if (dt == DisplayType.NUMBER) fieldValue = BigDecimal.valueOf(ObjectUtils.toLong(fieldValue));
|
||||
else fieldValue = BigDecimal.valueOf(ObjectUtils.toDouble(fieldValue));
|
||||
} else {
|
||||
fieldValue = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (fieldValue == null) {
|
||||
calcReady = false;
|
||||
break;
|
||||
}
|
||||
varsInFormula.put(fieldName, fieldValue);
|
||||
}
|
||||
|
||||
return calcReady ? evalValue(formula, varsInFormula, easyField, true) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行计算
|
||||
*
|
||||
* @param formula
|
||||
* @param varsInFormula
|
||||
* @param targetField
|
||||
* @param wrapValue
|
||||
* @return
|
||||
*/
|
||||
private static Object evalValue(String formula, Map<String, Object> varsInFormula, EasyField targetField, boolean wrapValue) {
|
||||
String clearFormula = formula
|
||||
.replace("{", "").replace("}", "")
|
||||
.replace("×", "*").replace("÷", "/");
|
||||
|
||||
Object evalVal = AviatorUtils.eval(clearFormula, varsInFormula, true);
|
||||
if (evalVal == null) return null;
|
||||
if (!wrapValue) return evalVal;
|
||||
|
||||
DisplayType dt = targetField.getDisplayType();
|
||||
if (dt == DisplayType.DATE || dt == DisplayType.DATETIME) {
|
||||
if (evalVal instanceof Date) {
|
||||
return targetField.wrapValue(evalVal);
|
||||
}
|
||||
} else if (dt == DisplayType.NUMBER || dt == DisplayType.DECIMAL) {
|
||||
if (evalVal instanceof Number) {
|
||||
evalVal = targetField.wrapValue(evalVal);
|
||||
evalVal = EasyDecimal.clearFlaged(evalVal);
|
||||
return evalVal;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -16,7 +16,9 @@ import com.alibaba.fastjson.JSON;
|
|||
import com.alibaba.fastjson.JSONArray;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.UserContextHolder;
|
||||
import com.rebuild.core.configuration.ConfigBean;
|
||||
import com.rebuild.core.configuration.general.ClassificationManager;
|
||||
import com.rebuild.core.configuration.general.MultiSelectManager;
|
||||
import com.rebuild.core.configuration.general.PickListManager;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
|
@ -31,6 +33,7 @@ import org.apache.commons.lang.ObjectUtils;
|
|||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -56,6 +59,8 @@ public class DataListWrapper {
|
|||
|
||||
private boolean mixWrapper = true;
|
||||
|
||||
private Map<ID, Object> cacheRefValue = new HashMap<>();
|
||||
|
||||
/**
|
||||
* @param total
|
||||
* @param data
|
||||
|
@ -111,6 +116,10 @@ public class DataListWrapper {
|
|||
|
||||
Object nameValue = null;
|
||||
for (int colIndex = 0; colIndex < selectFieldsLen; colIndex++) {
|
||||
if (!checkHasFieldPrivileges(selectFields[colIndex])) {
|
||||
row[colIndex] = FieldValueHelper.NO_READ_PRIVILEGES;
|
||||
continue;
|
||||
}
|
||||
if (!checkHasJoinFieldPrivileges(selectFields[colIndex], raw)) {
|
||||
row[colIndex] = FieldValueHelper.NO_READ_PRIVILEGES;
|
||||
continue;
|
||||
|
@ -169,6 +178,11 @@ public class DataListWrapper {
|
|||
|
||||
final DisplayType dt = easyField.getDisplayType();
|
||||
final Object originValue = value;
|
||||
final boolean isCacheRefValue = dt == DisplayType.REFERENCE && value instanceof ID;
|
||||
|
||||
if (isCacheRefValue) {
|
||||
if (cacheRefValue.containsKey((ID) value)) return cacheRefValue.get(value);
|
||||
}
|
||||
|
||||
boolean unpack = dt == DisplayType.CLASSIFICATION || dt == DisplayType.PICKLIST
|
||||
|| dt == DisplayType.STATE || dt == DisplayType.BOOL;
|
||||
|
@ -232,9 +246,17 @@ public class DataListWrapper {
|
|||
new String[]{ "name", "color" }, new Object[]{ name, colorNames.get(name) }));
|
||||
}
|
||||
value = colorValue;
|
||||
|
||||
} else if (easyField.getDisplayType() == DisplayType.CLASSIFICATION) {
|
||||
String color = ClassificationManager.instance.getColor((ID) originValue);
|
||||
if (StringUtils.isNotBlank(color)) {
|
||||
value = JSONUtils.toJSONObject(
|
||||
new String[]{ "text", "color" }, new Object[]{ value, color });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCacheRefValue) cacheRefValue.put((ID) originValue, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
|
@ -251,6 +273,7 @@ public class DataListWrapper {
|
|||
* @param field
|
||||
* @param original
|
||||
* @return
|
||||
* @see #checkHasFieldPrivileges(SelectItem)
|
||||
*/
|
||||
protected boolean checkHasJoinFieldPrivileges(SelectItem field, Object[] original) {
|
||||
if (this.queryJoinFields == null || UserHelper.isAdmin(user)) {
|
||||
|
@ -267,6 +290,15 @@ public class DataListWrapper {
|
|||
return check == null || Application.getPrivilegesManager().allowRead(user, (ID) check);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param field
|
||||
* @return
|
||||
*/
|
||||
protected boolean checkHasFieldPrivileges(SelectItem field) {
|
||||
ID u = user == null ? UserContextHolder.getUser() : user;
|
||||
return Application.getPrivilegesManager().getFieldPrivileges().isReadble(field.getField(), u);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进一步封装查询结果
|
||||
*
|
||||
|
|
|
@ -130,7 +130,6 @@ public class FieldValueHelper {
|
|||
} catch (JSONException ex) {
|
||||
log.error("Bad JSONArray format : {} < {}", value, field.getRawMeta());
|
||||
return JSONUtils.EMPTY_ARRAY;
|
||||
// throw new RebuildException("BAD VALUE FORMAT:" + value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,21 +7,19 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
|
||||
package com.rebuild.core.support.general;
|
||||
|
||||
import cn.devezhao.commons.ObjectUtils;
|
||||
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.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.configuration.ConfigBean;
|
||||
import com.rebuild.core.configuration.general.AdvFilterManager;
|
||||
import com.rebuild.core.configuration.general.ClassificationManager;
|
||||
import com.rebuild.core.configuration.general.DataListCategory;
|
||||
import com.rebuild.core.configuration.general.DataListCategory38;
|
||||
import com.rebuild.core.configuration.general.ViewAddonsManager;
|
||||
import com.rebuild.core.metadata.EntityHelper;
|
||||
import com.rebuild.core.metadata.MetadataHelper;
|
||||
import com.rebuild.core.metadata.easymeta.DisplayType;
|
||||
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
|
||||
import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
|
||||
import com.rebuild.core.service.dashboard.ChartManager;
|
||||
|
@ -33,7 +31,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.text.MessageFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -232,45 +229,13 @@ public class ProtocolFilterParser {
|
|||
* @param value
|
||||
* @return
|
||||
* @see #P_CATEGORY
|
||||
* @see DataListCategory
|
||||
* @see DataListCategory38#buildParentFilters(Entity, Object[])
|
||||
*/
|
||||
protected String parseCategory(String entity, String value) {
|
||||
Entity rootEntity = MetadataHelper.getEntity(entity);
|
||||
Field categoryField = DataListCategory.instance.getFieldOfCategory(rootEntity);
|
||||
if (categoryField == null || StringUtils.isBlank(value)) return "(9=9)";
|
||||
|
||||
DisplayType dt = EasyMetaFactory.getDisplayType(categoryField);
|
||||
value = CommonsUtils.escapeSql(value);
|
||||
|
||||
if (dt == DisplayType.MULTISELECT) {
|
||||
return String.format("%s && %d", categoryField.getName(), ObjectUtils.toInt(value));
|
||||
|
||||
} else if (dt == DisplayType.N2NREFERENCE) {
|
||||
return String.format(
|
||||
"exists (select recordId from NreferenceItem where ^%s = recordId and belongField = '%s' and referenceId = '%s')",
|
||||
rootEntity.getPrimaryField().getName(), categoryField.getName(), value);
|
||||
|
||||
} else if (dt == DisplayType.DATETIME || dt == DisplayType.DATE) {
|
||||
String s = value + "0000-01-01 00:00:00".substring(value.length());
|
||||
String e = value + "0000-12-31 23:59:59".substring(value.length());
|
||||
if (dt == DisplayType.DATE) {
|
||||
s = s.substring(0, 10);
|
||||
e = e.substring(0, 10);
|
||||
}
|
||||
return MessageFormat.format("({0} >= ''{1}'' and {0} <= ''{2}'')", categoryField.getName(), s, e);
|
||||
|
||||
} else if (dt == DisplayType.CLASSIFICATION) {
|
||||
int level = ClassificationManager.instance.getOpenLevel(categoryField);
|
||||
List<String> parentSql = new ArrayList<>();
|
||||
parentSql.add(String.format("%s = '%s'", categoryField.getName(), value));
|
||||
if (level > 0) parentSql.add(String.format("%s.parent = '%s'", categoryField.getName(), value));
|
||||
if (level > 1) parentSql.add(String.format("%s.parent.parent = '%s'", categoryField.getName(), value));
|
||||
if (level > 2) parentSql.add(String.format("%s.parent.parent.parent = '%s'", categoryField.getName(), value));
|
||||
|
||||
return "( " + StringUtils.join(parentSql, " or ") + " )";
|
||||
}
|
||||
|
||||
return String.format("%s = '%s'", categoryField.getName(), value);
|
||||
String[] filterValues = value.split(CommonsUtils.COMM_SPLITER_RE);
|
||||
String where = DataListCategory38.instance.buildParentFilters(MetadataHelper.getEntity(entity), filterValues);
|
||||
if (Application.devMode()) log.info("[dev] Parse category : {} >> {}", value, where);
|
||||
return where;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -278,7 +243,6 @@ public class ProtocolFilterParser {
|
|||
* @param mainid
|
||||
* @return
|
||||
* @see #P_RELATED
|
||||
* @see com.rebuild.web.general.RelatedListController#buildBaseSql(ID, String, String, boolean, ID)
|
||||
*/
|
||||
public String parseRelated(String relatedExpr, ID mainid) {
|
||||
// format: Entity.Field
|
||||
|
@ -297,7 +261,7 @@ public class ProtocolFilterParser {
|
|||
relatedField.getOwnEntity().getPrimaryField().getName(), relatedField.getName(), mainid);
|
||||
}
|
||||
|
||||
// 附件过滤条件
|
||||
// 附加过滤条件
|
||||
|
||||
Map<String, JSONObject> vtabFilters = ViewAddonsManager.instance.getViewTabFilters(
|
||||
MetadataHelper.getEntity(mainid.getEntityCode()).getName());
|
||||
|
|
|
@ -277,6 +277,7 @@ public class SMSender {
|
|||
* @return
|
||||
*/
|
||||
protected static Element getMailTemplate() {
|
||||
if (Application.devMode()) MT_CACHE = null;
|
||||
if (MT_CACHE != null) return MT_CACHE.clone();
|
||||
|
||||
String content = CommonsUtils.getStringOfRes("i18n/email.zh_CN.html");
|
||||
|
|
|
@ -9,6 +9,7 @@ package com.rebuild.core.support.setup;
|
|||
|
||||
import cn.devezhao.commons.CalendarUtils;
|
||||
import com.rebuild.core.BootEnvironmentPostProcessor;
|
||||
import com.rebuild.core.support.ConfigurationItem;
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.utils.CommandUtils;
|
||||
import com.rebuild.utils.CompressUtils;
|
||||
|
@ -70,12 +71,13 @@ public class DatabaseBackup {
|
|||
String destName = "backup_database." + CalendarUtils.getPlainDateTimeFormat().format(CalendarUtils.now());
|
||||
File dest = new File(backups, destName);
|
||||
|
||||
String mysqldump = RebuildConfiguration.get(ConfigurationItem.MysqldumpBin);
|
||||
if (StringUtils.isBlank(mysqldump)) mysqldump = SystemUtils.IS_OS_WINDOWS ? "mysqldump.exe" : "mysqldump";
|
||||
// https://blog.csdn.net/liaowenxiong/article/details/120587358
|
||||
// --master-data --flush-logs
|
||||
String cmd = String.format(
|
||||
"%s -u%s -p\"%s\" -h%s -P%s --default-character-set=utf8 --opt --extended-insert=true --triggers --hex-blob --single-transaction -R %s>%s",
|
||||
SystemUtils.IS_OS_WINDOWS ? "mysqldump.exe" : "mysqldump",
|
||||
user, passwd, host, port, dbname, dest.getAbsolutePath());
|
||||
"%s -u%s -p\"%s\" -h%s -P%s --default-character-set=utf8 --opt --extended-insert=true --triggers --hex-blob --single-transaction -R %s>\"%s\"",
|
||||
mysqldump, user, passwd, host, port, dbname, dest.getAbsolutePath());
|
||||
|
||||
if (ignoreTables != null) {
|
||||
String igPrefix = " --ignore-table=" + dbname + ".";
|
||||
|
@ -83,7 +85,7 @@ public class DatabaseBackup {
|
|||
cmd = cmd.replaceFirst(" -R ", " -R" + ig + " ");
|
||||
}
|
||||
|
||||
String echo = CommandUtils.execFor(cmd);
|
||||
String echo = CommandUtils.execFor(cmd, true);
|
||||
boolean isGotError = echo.contains("Got error");
|
||||
if (isGotError) throw new RuntimeException(echo);
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.rebuild.core.support.ConfigurationItem;
|
|||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.core.support.i18n.LanguageBundle;
|
||||
import com.rebuild.web.admin.AdminVerfiyController;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
|
|||
* @author Zixin (RB)
|
||||
* @since 05/19/2018
|
||||
*/
|
||||
@Slf4j
|
||||
public class AppUtils {
|
||||
|
||||
// Token 认证
|
||||
|
@ -91,7 +93,13 @@ public class AppUtils {
|
|||
* @see #getRequestUserViaToken(HttpServletRequest, boolean)
|
||||
*/
|
||||
public static ID getRequestUser(HttpServletRequest request, boolean refreshToken) {
|
||||
Object user = request.getSession().getAttribute(WebUtils.CURRENT_USER);
|
||||
Object user = null;
|
||||
try {
|
||||
user = request.getSession().getAttribute(WebUtils.CURRENT_USER);
|
||||
} catch (Exception resHasBeenCommitted) {
|
||||
log.warn("resHasBeenCommitted", resHasBeenCommitted);
|
||||
}
|
||||
|
||||
if (user == null) user = getRequestUserViaToken(request, refreshToken);
|
||||
return user == null ? null : (ID) user;
|
||||
}
|
||||
|
|
|
@ -26,14 +26,15 @@ public class CommandUtils {
|
|||
* 执行命令行
|
||||
*
|
||||
* @param cmd
|
||||
* @param secure
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static String execFor(String cmd) throws IOException {
|
||||
public static String execFor(String cmd, boolean secure) throws IOException {
|
||||
ProcessBuilder builder = new ProcessBuilder();
|
||||
String encoding = "UTF-8";
|
||||
|
||||
log.info("CMD : {}", cmd);
|
||||
if (!secure) log.info("CMD : {}", cmd);
|
||||
|
||||
if (SystemUtils.IS_OS_WINDOWS) {
|
||||
builder.command("cmd.exe", "/c", cmd);
|
||||
|
|
|
@ -32,9 +32,11 @@ import java.math.BigDecimal;
|
|||
import java.math.RoundingMode;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
@ -256,6 +258,7 @@ public class CommonsUtils {
|
|||
|
||||
Class<?>[] paramTypes = new Class<?>[args.length];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
if (args[i] == null) args[i] = new Object();
|
||||
paramTypes[i] = args[i].getClass();
|
||||
}
|
||||
|
||||
|
@ -371,4 +374,23 @@ public class CommonsUtils {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转为数组
|
||||
*
|
||||
* @param o
|
||||
* @return
|
||||
*/
|
||||
public static Object[] toArray(Object o) {
|
||||
if (o == null) return new Object[0];
|
||||
if (o instanceof Object[]) return (Object[]) o;
|
||||
if (o instanceof Collection) return ((Collection<?>) o).toArray();
|
||||
|
||||
if (o instanceof Iterable) {
|
||||
List<Object> c = new ArrayList<>();
|
||||
for (Object item : (Iterable<?>) o) c.add(item);
|
||||
return c.toArray();
|
||||
}
|
||||
return new Object[]{o};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,8 @@ import javax.servlet.http.HttpServletResponse;
|
|||
*/
|
||||
public class Etag {
|
||||
|
||||
private static final String DIRECTIVE_NO_STORE = "no-store";
|
||||
|
||||
private String responseETag;
|
||||
transient private HttpServletResponse response;
|
||||
final private String responseEtag;
|
||||
transient final private HttpServletResponse response;
|
||||
|
||||
/**
|
||||
* @param etag
|
||||
|
@ -34,10 +32,10 @@ public class Etag {
|
|||
public Etag(String etag, HttpServletResponse response) {
|
||||
// whether the generated ETag should be weak
|
||||
// SPEC: length of W/ + " + 0 + 32bits md5 hash + "
|
||||
String responseETag = String.format("W/\"0%s\"", etag);
|
||||
response.setHeader(HttpHeaders.ETAG, responseETag);
|
||||
String responseEtag = String.format("W/\"0%s\"", etag);
|
||||
response.setHeader(HttpHeaders.ETAG, responseEtag);
|
||||
|
||||
this.responseETag = responseETag;
|
||||
this.responseEtag = responseEtag;
|
||||
this.response = response;
|
||||
}
|
||||
|
||||
|
@ -48,7 +46,7 @@ public class Etag {
|
|||
*/
|
||||
protected boolean isForceNoCache() {
|
||||
String cacheControl = response.getHeader(HttpHeaders.CACHE_CONTROL);
|
||||
return cacheControl != null && cacheControl.contains(DIRECTIVE_NO_STORE);
|
||||
return cacheControl != null && cacheControl.contains("no-store");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,14 +57,11 @@ public class Etag {
|
|||
* @return
|
||||
*/
|
||||
protected boolean isMatchEtag(HttpServletRequest request, boolean writeStatusIfMatch) {
|
||||
String requestETag = request.getHeader(HttpHeaders.IF_NONE_MATCH);
|
||||
if (requestETag != null &&
|
||||
("*".equals(requestETag) || responseETag.equals(requestETag) ||
|
||||
responseETag.replaceFirst("^W/", "").equals(requestETag.replaceFirst("^W/", "")))) {
|
||||
if (writeStatusIfMatch) {
|
||||
response.setStatus(HttpStatus.NOT_MODIFIED.value());
|
||||
}
|
||||
|
||||
String requestEtag = request.getHeader(HttpHeaders.IF_NONE_MATCH);
|
||||
if (requestEtag != null &&
|
||||
("*".equals(requestEtag) || responseEtag.equals(requestEtag) ||
|
||||
responseEtag.replaceFirst("^W/", "").equals(requestEtag.replaceFirst("^W/", "")))) {
|
||||
if (writeStatusIfMatch) response.setStatus(HttpStatus.NOT_MODIFIED.value());
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
|
|
@ -8,30 +8,20 @@ See LICENSE and COMMERCIAL in the project root for license information.
|
|||
package com.rebuild.utils;
|
||||
|
||||
import com.rebuild.core.Application;
|
||||
import com.rebuild.core.service.datareport.ReportsException;
|
||||
import com.rebuild.core.support.ConfigurationItem;
|
||||
import com.rebuild.core.support.RebuildConfiguration;
|
||||
import com.rebuild.utils.poi.ToHtml;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.lang.SystemUtils;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.springframework.util.Assert;
|
||||
import org.zwobble.mammoth.DocumentConverter;
|
||||
import org.zwobble.mammoth.Result;
|
||||
import org.jsoup.nodes.Element;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Office 文件转换
|
||||
|
@ -93,7 +83,6 @@ public class PdfConverter {
|
|||
|
||||
final String pathFileName = path.getFileName().toString();
|
||||
final boolean isExcel = pathFileName.endsWith(".xlsx") || pathFileName.endsWith(".xls");
|
||||
final boolean isWord = pathFileName.endsWith(".docx") || pathFileName.endsWith(".doc");
|
||||
// Excel 公式生效
|
||||
if (isExcel) {
|
||||
ExcelUtils.reSaveAndCalcFormula(path);
|
||||
|
@ -108,97 +97,55 @@ public class PdfConverter {
|
|||
else return dest.toPath();
|
||||
}
|
||||
|
||||
if (TYPE_HTML.equalsIgnoreCase(type)) {
|
||||
if (isWord) {
|
||||
convertWord2Html(path, dest);
|
||||
return dest.toPath();
|
||||
}
|
||||
if (isExcel) {
|
||||
convertExcel2Html(path, dest);
|
||||
return dest.toPath();
|
||||
}
|
||||
|
||||
throw new ReportsException("CANNOT CONVERT TO HTML : " + pathFileName);
|
||||
}
|
||||
|
||||
// alias
|
||||
String soffice = RebuildConfiguration.get(ConfigurationItem.LibreofficeBin);
|
||||
if (StringUtils.isBlank(soffice)) soffice = SystemUtils.IS_OS_WINDOWS ? "soffice.exe" : "libreoffice";
|
||||
String cmd = String.format("%s --headless --convert-to %s \"%s\" --outdir \"%s\"", soffice, type, path, outDir);
|
||||
|
||||
String echo = CommandUtils.execFor(cmd);
|
||||
String echo = CommandUtils.execFor(cmd, false);
|
||||
if (!echo.isEmpty()) log.info(echo);
|
||||
|
||||
if (dest.exists()) return dest.toPath();
|
||||
if (dest.exists()) {
|
||||
if (TYPE_HTML.equalsIgnoreCase(type)) fixHtml(dest, null);
|
||||
return dest.toPath();
|
||||
}
|
||||
|
||||
throw new PdfConverterException("Cannot convert to PDF : " + StringUtils.defaultIfBlank(echo, "<empty>"));
|
||||
throw new PdfConverterException("Cannot convert to <" + type + "> : " + StringUtils.defaultIfBlank(echo, "<empty>"));
|
||||
}
|
||||
|
||||
private static String TEMPALTE_HTML;
|
||||
/**
|
||||
* Word to HTML
|
||||
*
|
||||
* @param source
|
||||
* @param dest
|
||||
* @param sourceHtml
|
||||
* @param title
|
||||
* @throws IOException
|
||||
*/
|
||||
protected static void convertWord2Html(Path source, File dest) throws IOException {
|
||||
if (TEMPALTE_HTML == null || Application.devMode()) {
|
||||
TEMPALTE_HTML = CommonsUtils.getStringOfRes("i18n/html-report.html");
|
||||
}
|
||||
Assert.notNull(TEMPALTE_HTML, "TEMPALTE_HTML MISSING");
|
||||
private static void fixHtml(File sourceHtml, String title) throws IOException {
|
||||
if (TEMPALTE_HTML == null || Application.devMode()) TEMPALTE_HTML = CommonsUtils.getStringOfRes("i18n/html-report.html");
|
||||
if (TEMPALTE_HTML == null) return;
|
||||
|
||||
DocumentConverter converter = new DocumentConverter();
|
||||
Result<String> result = converter.convertToHtml(source.toFile());
|
||||
String cHtml = result.getValue();
|
||||
Set<String> cWarnings = result.getWarnings();
|
||||
if (!cWarnings.isEmpty()) {
|
||||
log.warn("HTML convert warnings : {}", cWarnings);
|
||||
final Document template = Jsoup.parse(TEMPALTE_HTML);
|
||||
final Element body = template.body();
|
||||
|
||||
final Document source = Jsoup.parse(sourceHtml);
|
||||
|
||||
// 提取表格
|
||||
for (Element table : source.select("body>table")) {
|
||||
Element page = body.appendElement("div").addClass("page");
|
||||
page.appendChild(table);
|
||||
}
|
||||
|
||||
Document html = Jsoup.parse(TEMPALTE_HTML);
|
||||
html.body().append("<div class=\"paper word\">" + cHtml + "</div>");
|
||||
html.title(source.getFileName().toString());
|
||||
|
||||
FileUtils.writeStringToFile(dest, html.html(), AppUtils.UTF8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Excel to HTML.
|
||||
* 1. 不能合并
|
||||
*
|
||||
* @param source
|
||||
* @param dest
|
||||
* @throws IOException
|
||||
*/
|
||||
protected static void convertExcel2Html(Path source, File dest) throws IOException {
|
||||
if (TEMPALTE_HTML == null || Application.devMode()) {
|
||||
TEMPALTE_HTML = CommonsUtils.getStringOfRes("i18n/html-report.html");
|
||||
}
|
||||
Assert.notNull(TEMPALTE_HTML, "TEMPALTE_HTML MISSING");
|
||||
|
||||
StringWriter output = new StringWriter();
|
||||
try (Workbook wb = WorkbookFactory.create(Files.newInputStream(source))) {
|
||||
ToHtml toHtml = ToHtml.create(wb, output);
|
||||
output.append("<style>");
|
||||
toHtml.printStyles();
|
||||
output.append("</style>");
|
||||
|
||||
for (Iterator<Sheet> iter = wb.sheetIterator(); iter.hasNext(); ) {
|
||||
final Sheet sheet = iter.next();
|
||||
String paperClass = "paper excel";
|
||||
if (sheet.getPrintSetup().getLandscape()) paperClass += " landscape"; // 横向
|
||||
|
||||
output.append("<div class=\"").append(paperClass).append("\">");
|
||||
toHtml.printSheet(sheet);
|
||||
output.append("</div>");
|
||||
// 图片添加 temp=yes
|
||||
for (Element img : body.select("img")) {
|
||||
String src = img.attr("src");
|
||||
if (!src.startsWith("data:")) {
|
||||
img.attr("src", src + "?temp=yes");
|
||||
}
|
||||
}
|
||||
|
||||
Document html = Jsoup.parse(TEMPALTE_HTML);
|
||||
html.body().append(output.toString());
|
||||
html.title(source.getFileName().toString());
|
||||
// TITLE
|
||||
if (title == null) title = sourceHtml.getName();
|
||||
Objects.requireNonNull(template.head().selectFirst("title")).text(title);
|
||||
|
||||
FileUtils.writeStringToFile(dest, html.html(), AppUtils.UTF8);
|
||||
FileUtils.writeStringToFile(sourceHtml, template.html(), "UTF-8");
|
||||
}
|
||||
}
|
|
@ -22,10 +22,9 @@ public class RbAssert {
|
|||
* @param message
|
||||
*/
|
||||
public static void isCommercial(String message) {
|
||||
if (!License.isCommercial()) {
|
||||
if (message == null) message = Language.L("免费版不支持此功能");
|
||||
throw new NeedRbvException(message);
|
||||
}
|
||||
if (License.isRbvAttached()) return;
|
||||
if (message == null) message = Language.L("免费版不支持此功能");
|
||||
throw new NeedRbvException(message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
==================================================================== */
|
||||
package com.rebuild.utils.poi;
|
||||
|
||||
import java.util.Formatter;
|
||||
|
||||
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
|
||||
import org.apache.poi.hssf.usermodel.HSSFPalette;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.hssf.util.HSSFColor;
|
||||
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
|
||||
/**
|
||||
* Implementation of {@link HtmlHelper} for HSSF files.
|
||||
*/
|
||||
public class HSSFHtmlHelper implements HtmlHelper {
|
||||
private final HSSFWorkbook wb;
|
||||
private final HSSFPalette colors;
|
||||
|
||||
private static final HSSFColor HSSF_AUTO = HSSFColorPredefined.AUTOMATIC.getColor();
|
||||
|
||||
public HSSFHtmlHelper(HSSFWorkbook wb) {
|
||||
this.wb = wb;
|
||||
// If there is no custom palette, then this creates a new one that is
|
||||
// a copy of the default
|
||||
colors = wb.getCustomPalette();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void colorStyles(CellStyle style, Formatter out) {
|
||||
HSSFCellStyle cs = (HSSFCellStyle) style;
|
||||
out.format(" /* fill pattern = %d */%n", cs.getFillPattern().getCode());
|
||||
styleColor(out, "background-color", cs.getFillForegroundColor());
|
||||
styleColor(out, "color", cs.getFont(wb).getColor());
|
||||
styleColor(out, "border-left-color", cs.getLeftBorderColor());
|
||||
styleColor(out, "border-right-color", cs.getRightBorderColor());
|
||||
styleColor(out, "border-top-color", cs.getTopBorderColor());
|
||||
styleColor(out, "border-bottom-color", cs.getBottomBorderColor());
|
||||
}
|
||||
|
||||
private void styleColor(Formatter out, String attr, short index) {
|
||||
HSSFColor color = colors.getColor(index);
|
||||
if (index == HSSF_AUTO.getIndex() || color == null) {
|
||||
out.format(" /* %s: index = %d */%n", attr, index);
|
||||
} else {
|
||||
short[] rgb = color.getTriplet();
|
||||
out.format(" %s: #%02x%02x%02x; /* index = %d */%n", attr, rgb[0], rgb[1], rgb[2], index);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue