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:
REBUILD 企业管理系统 2024-09-17 11:29:16 +08:00 committed by GitHub
parent 82e51fe46c
commit 0478a8e08e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
685 changed files with 68321 additions and 27455 deletions

View file

@ -145,5 +145,6 @@ module.exports = {
$hex2rgb: true, $hex2rgb: true,
$isImage: true, $isImage: true,
$dropUpload: true, $dropUpload: true,
$tagStyle2: true,
}, },
} }

2
@rbv

@ -1 +1 @@
Subproject commit fb639d01abceb0dbb85760e475a153e2f69237b5 Subproject commit 325da768fde9291914a1132283e62fe8cb83053c

View file

@ -15,18 +15,18 @@
> **福利:加入 REBUILD VIP 用户 QQ 交流群 819865721 1013051587 GET 使用技能** > **福利:加入 REBUILD VIP 用户 QQ 交流群 819865721 1013051587 GET 使用技能**
## V3.7 新特性 ## V3.8 新特性
本次更新为你带来众多功能增强与优化。 本次更新为你带来众多功能增强与优化。
1. [新增] 限时审批 1. [新增] HTML 报表模版
2. [新增] 新建任务(触发器) 2. [新增] 多表单布局
3. [新增] 地图等多个图表 3. [新增] 明细支持 Excel 粘贴录入
4. [新增] 自动明细记录导入(记录转换) 4. [新增] 图片/附件支持摄像头上传模式
5. [新增] 数据列表之卡片模式 5. [新增] 手机版数据列表可导出报表
6. [新增] 多个 FrontJS 函数 6. [新增] 多个 FrontJS 函数
7. [优化] 图表支持多轴显示、横向显示、显示背景 7. [新增] 用户支持批量操作
8. [优化] 手机版全新列表搜索组件 8. [优化] 20+ 细节/BUG/安全性更新
9. ... 9. ...
更多更新详情请参见 [更新日志](https://getrebuild.com/docs/dev/changelog) 更多更新详情请参见 [更新日志](https://getrebuild.com/docs/dev/changelog)

38
pom.xml
View file

@ -10,7 +10,7 @@
</parent> </parent>
<groupId>com.rebuild</groupId> <groupId>com.rebuild</groupId>
<artifactId>rebuild</artifactId> <artifactId>rebuild</artifactId>
<version>3.7.7</version> <version>3.8.0-beta1</version>
<name>rebuild</name> <name>rebuild</name>
<description>Building your business-systems freely!</description> <description>Building your business-systems freely!</description>
<url>https://getrebuild.com/</url> <url>https://getrebuild.com/</url>
@ -86,6 +86,11 @@
<configuration> <configuration>
<nodeVersion>v10.22.0</nodeVersion> <nodeVersion>v10.22.0</nodeVersion>
<workingDirectory>.deploy</workingDirectory> <workingDirectory>.deploy</workingDirectory>
<!-- UNCOMMENT USE NODE MIRROR -->
<!--
<nodeDownloadRoot>https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/</nodeDownloadRoot>
<npmRegistryURL>https://registry.npmmirror.com</npmRegistryURL>
-->
</configuration> </configuration>
</plugin> </plugin>
<!-- USE COMMERCIAL MODULES IF EXISTS --> <!-- USE COMMERCIAL MODULES IF EXISTS -->
@ -187,9 +192,9 @@
<repositories> <repositories>
<repository> <repository>
<id>alimaven</id> <id>alimaven2</id>
<name>public</name> <name>public</name>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url> <url>https://maven.aliyun.com/repository/public</url>
</repository> </repository>
<!-- Unpublished libs can be found on https://github.com/devezhao/ --> <!-- Unpublished libs can be found on https://github.com/devezhao/ -->
<repository> <repository>
@ -298,7 +303,7 @@
<dependency> <dependency>
<groupId>com.github.devezhao</groupId> <groupId>com.github.devezhao</groupId>
<artifactId>persist4j</artifactId> <artifactId>persist4j</artifactId>
<version>1.7.10</version> <version>1.7.11</version>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
@ -334,12 +339,12 @@
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid</artifactId> <artifactId>druid</artifactId>
<version>1.2.22</version> <version>1.2.23</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId> <artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version> <version>8.4.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.sf.ehcache</groupId> <groupId>net.sf.ehcache</groupId>
@ -349,12 +354,12 @@
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>4.4.7</version> <version>4.4.8</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.qiniu</groupId> <groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId> <artifactId>qiniu-java-sdk</artifactId>
<version>7.15.0</version> <version>7.15.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.whvcse</groupId> <groupId>com.github.whvcse</groupId>
@ -364,7 +369,7 @@
<dependency> <dependency>
<groupId>com.github.oshi</groupId> <groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId> <artifactId>oshi-core</artifactId>
<version>6.5.0</version> <version>6.6.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jsoup</groupId> <groupId>org.jsoup</groupId>
@ -470,7 +475,7 @@
<dependency> <dependency>
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson</artifactId> <artifactId>redisson</artifactId>
<version>3.27.2</version> <version>3.33.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@ -496,7 +501,7 @@
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId> <artifactId>hutool-core</artifactId>
<version>5.8.26</version> <version>5.8.29</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
@ -516,15 +521,10 @@
<artifactId>jansi</artifactId> <artifactId>jansi</artifactId>
<version>2.4.1</version> <version>2.4.1</version>
</dependency> </dependency>
<dependency>
<groupId>org.zwobble.mammoth</groupId>
<artifactId>mammoth</artifactId>
<version>1.7.0</version>
</dependency>
<dependency> <dependency>
<groupId>com.getui.push</groupId> <groupId>com.getui.push</groupId>
<artifactId>restful-sdk</artifactId> <artifactId>restful-sdk</artifactId>
<version>1.0.0.17</version> <version>1.0.2.1</version>
</dependency> </dependency>
<!-- fix: CVEs --> <!-- fix: CVEs -->
@ -536,7 +536,7 @@
<dependency> <dependency>
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId> <artifactId>commons-compress</artifactId>
<version>1.26.1</version> <version>1.26.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
@ -546,7 +546,7 @@
<dependency> <dependency>
<groupId>com.fasterxml.woodstox</groupId> <groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId> <artifactId>woodstox-core</artifactId>
<version>6.6.1</version> <version>6.6.2</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>

View file

@ -141,25 +141,4 @@ public class AuthTokenManager {
return ID.valueOf(descs[1]); 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]);
}
} }

View file

@ -74,11 +74,11 @@ public class Application implements ApplicationListener<ApplicationStartedEvent>
/** /**
* Rebuild Version * 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} * Rebuild Build [MAJOR]{1}[MINOR]{2}[PATCH]{2}[BUILD]{2}
*/ */
public static final int BUILD = 3070713; public static final int BUILD = 3080001;
static { static {
// Driver for DB // Driver for DB

View file

@ -127,7 +127,7 @@ public class BootApplication extends SpringBootServletInitializer {
try { try {
initTomcatPort(); initTomcatPort();
} catch (Exception ex) { } 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) " : ""); log.info("Initializing SpringBoot context {}...", devMode() ? "(dev) " : "");

View file

@ -18,7 +18,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.RandomAccessFile;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.util.ArrayList; import java.util.ArrayList;
@ -114,22 +114,23 @@ public final class ServerStatus {
* @return * @return
*/ */
static Status checkCreateFile() { static Status checkCreateFile() {
String name = "Create File"; String name = "CreateFile";
FileWriter fw = null; File test = null;
RandomAccessFile raf = null;
try { try {
File test = new File(FileUtils.getTempDirectory(), "ServerStatus.test"); test = new File(FileUtils.getTempDirectory(), "ServerStatus.test");
fw = new FileWriter(test); raf = new RandomAccessFile(test, "rw");
IOUtils.write(CodecUtils.randomCode(1024), fw); raf.setLength(1024 * 1024 * 5); // 5M
if (!test.exists()) { if (!test.exists()) {
return Status.error(name, "Cannot create file in temp-directory"); return Status.error(name, "Cannot create file in temp-directory");
} else {
FileUtils.deleteQuietly(test);
} }
} catch (Exception ex) { } catch (Exception ex) {
return Status.error(name, ThrowableUtils.getRootCause(ex).getLocalizedMessage()); return Status.error(name, ThrowableUtils.getRootCause(ex).getLocalizedMessage());
} finally { } finally {
IOUtils.closeQuietly(fw); if (raf != null) IOUtils.closeQuietly(raf);
if (test != null) FileUtils.deleteQuietly(test);
} }
return Status.success(name); return Status.success(name);
} }
@ -184,9 +185,9 @@ public final class ServerStatus {
this.error = error; this.error = error;
if (success) { if (success) {
log.debug("Checking " + this); log.debug("Checking {}", this);
} else { } else {
log.error("Checking " + this); log.error("Checking {}", this);
} }
} }

View file

@ -516,8 +516,10 @@ public class NavBuilder extends NavManager {
for (Object[] nd : topNav) { for (Object[] nd : topNav) {
String url = AppUtils.getContextPath("/app/home?def=" + nd[0]); String url = AppUtils.getContextPath("/app/home?def=" + nd[0]);
if (nd[1] != null) url += ":" + nd[1]; if (nd[1] != null) url += ":" + nd[1];
topNavHtml.append(String.format( 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(); return topNavHtml.toString();
} }

View file

@ -30,6 +30,7 @@ import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.easymeta.EasyN2NReference; import com.rebuild.core.metadata.easymeta.EasyN2NReference;
import com.rebuild.core.metadata.easymeta.MixValue; import com.rebuild.core.metadata.easymeta.MixValue;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.support.general.CalcFormulaSupport;
import com.rebuild.core.support.general.FieldValueHelper; import com.rebuild.core.support.general.FieldValueHelper;
import com.rebuild.utils.JSONUtils; import com.rebuild.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -47,7 +48,8 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
/** /**
* 表单自动回填 * 表单自动回填
* 请注意此功能的优先级回填实在构建 Record 时发生因此相对触发器其优先级较低
* *
* @author devezhao zhaofang123@gmail.com * @author devezhao zhaofang123@gmail.com
* @since 2019/05/17 * @since 2019/05/17
@ -237,6 +239,10 @@ public class AutoFillinManager implements ConfigManager {
fillin += fillinRecordItem(easyField.getRawMeta(), record.getObjectValue(fieldName), isNew, fillinForce, record); fillin += fillinRecordItem(easyField.getRawMeta(), record.getObjectValue(fieldName), isNew, fillinForce, record);
} }
// v3.8 借用贵宝地
CalcFormulaSupport.calcFormulaBackend(record);
return fillin; return fillin;
} }

View file

@ -57,25 +57,43 @@ public class ClassificationManager implements ConfigManager {
return item == null ? null : item.FullName; 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 * @param itemId
* @return * @return
*/ */
private Item getItem(ID itemId) { private Item getItem(ID itemId) {
final String ckey = "ClassificationITEM31-" + itemId; final String ckey = "ClassificationITEM38-" + itemId;
Item ditem = (Item) Application.getCommonsCache().getx(ckey); Item ditem = (Item) Application.getCommonsCache().getx(ckey);
if (ditem != null) { if (ditem != null) {
return DELETED_ITEM.equals(ditem.Name) ? null : ditem; return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
} }
Object[] o = Application.createQueryNoFilter( Object[] o = Application.createQueryNoFilter(
"select name,fullName,code from ClassificationData where itemId = ?") "select name,fullName,code,color from ClassificationData where itemId = ?")
.setParameter(1, itemId) .setParameter(1, itemId)
.unique(); .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); Application.getCommonsCache().putx(ckey, ditem);
return DELETED_ITEM.equals(ditem.Name) ? null : ditem; return DELETED_ITEM.equals(ditem.Name) ? null : ditem;
@ -163,7 +181,7 @@ public class ClassificationManager implements ConfigManager {
public void clean(Object cid) { public void clean(Object cid) {
ID id2 = (ID) cid; ID id2 = (ID) cid;
if (id2.getEntityCode() == EntityHelper.ClassificationData) { if (id2.getEntityCode() == EntityHelper.ClassificationData) {
Application.getCommonsCache().evict("ClassificationITEM31-" + cid); Application.getCommonsCache().evict("ClassificationITEM38-" + cid);
} else if (id2.getEntityCode() == EntityHelper.Classification) { } else if (id2.getEntityCode() == EntityHelper.Classification) {
Application.getCommonsCache().evict("ClassificationLEVEL-" + cid); Application.getCommonsCache().evict("ClassificationLEVEL-" + cid);
} }
@ -172,14 +190,16 @@ public class ClassificationManager implements ConfigManager {
// Bean // Bean
static class Item implements Serializable { static class Item implements Serializable {
private static final long serialVersionUID = -1903227875771376652L; 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.Name = name;
this.FullName = fullName; this.FullName = fullName;
this.Code = code; this.Code = code;
this.Color = color;
} }
final String Name; final String Name;
final String FullName; final String FullName;
final String Code; final String Code;
final String Color;
} }
} }

View file

@ -77,12 +77,7 @@ public class DataListCategory {
// 使用全部 // 使用全部
if (dt == DisplayType.MULTISELECT || dt == DisplayType.PICKLIST) { if (dt == DisplayType.MULTISELECT || dt == DisplayType.PICKLIST) {
ConfigBean[] cbs = MultiSelectManager.instance.getPickListRaw(categoryField, true); dataList = datasOptions(categoryField, dt);
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")));
}
} else if (dt == DisplayType.CLASSIFICATION) { } else if (dt == DisplayType.CLASSIFICATION) {
// 分类 // 分类
@ -153,6 +148,24 @@ public class DataListCategory {
return res; 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 * Max. 4L
* @param field * @param field

View file

@ -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;
}
}

View file

@ -77,4 +77,11 @@ public class EasyActionManager extends BaseLayoutManager {
public ConfigBean getEasyActionRaw(String entity) { public ConfigBean getEasyActionRaw(String entity) {
return getLayout(UserService.SYSTEM_USER, entity, TYPE_EASYACTION, null); return getLayout(UserService.SYSTEM_USER, entity, TYPE_EASYACTION, null);
} }
@Override
public void clean(Object layoutId) {
super.clean(layoutId);
// TODO JS 支持 ES6 > ES5
}
} }

View file

@ -27,6 +27,7 @@ import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyEntityConfigProps; import com.rebuild.core.metadata.impl.EasyEntityConfigProps;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.FieldPrivileges;
import com.rebuild.core.privileges.UserFilters; import com.rebuild.core.privileges.UserFilters;
import com.rebuild.core.privileges.bizz.Department; import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.privileges.bizz.User; import com.rebuild.core.privileges.bizz.User;
@ -129,7 +130,7 @@ public class FormsBuilder extends FormsManager {
} }
} }
// 明细实体有主实体 // 明细实体
final Entity hasMainEntity = entityMeta.getMainEntity(); final Entity hasMainEntity = entityMeta.getMainEntity();
// 审批流程状态 // 审批流程状态
ApprovalState approvalState; ApprovalState approvalState;
@ -142,7 +143,7 @@ public class FormsBuilder extends FormsManager {
if (recordId == null) { if (recordId == null) {
if (hasMainEntity != null) { if (hasMainEntity != null) {
ID mainid = FormsBuilderContextHolder.getMainIdOfDetail(false); 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); approvalState = EntityHelper.isUnsavedId(mainid) ? null : getHadApproval(hasMainEntity, mainid);
if ((approvalState == ApprovalState.PROCESSING || approvalState == ApprovalState.APPROVED)) { if ((approvalState == ApprovalState.PROCESSING || approvalState == ApprovalState.APPROVED)) {
@ -176,7 +177,7 @@ public class FormsBuilder extends FormsManager {
// 编辑 // 编辑
else { else {
if (!Application.getPrivilegesManager().allowUpdate(user, recordId)) { if (!Application.getPrivilegesManager().allowUpdate(user, recordId)) {
return formatModelError(Language.L("你没有修改此记录的权限")); return formatModelError(Language.L("你没有编辑此记录的权限"));
} }
approvalState = getHadApproval(entityMeta, recordId); approvalState = getHadApproval(entityMeta, recordId);
@ -205,8 +206,8 @@ public class FormsBuilder extends FormsManager {
} }
} }
int applyType = recordId == null ? FormsManager.APPLY_ONNEW : FormsManager.APPLY_ONEDIT; int applyType = recordId == null ? FormsManager.APPLY_NEW : FormsManager.APPLY_EDIT;
if (viewMode) applyType = FormsManager.APPLY_ONVIEW; if (viewMode) applyType = FormsManager.APPLY_VIEW;
ConfigBean model = getFormLayout(entity, recordOrLayoutId, applyType); ConfigBean model = getFormLayout(entity, recordOrLayoutId, applyType);
JSONArray elements = (JSONArray) model.getJSON("elements"); JSONArray elements = (JSONArray) model.getJSON("elements");
@ -353,6 +354,10 @@ public class FormsBuilder extends FormsManager {
final boolean isNew = recordData == null || recordData.getPrimary() == null final boolean isNew = recordData == null || recordData.getPrimary() == null
|| EntityHelper.isUnsavedId(recordData.getPrimary()); || EntityHelper.isUnsavedId(recordData.getPrimary());
final FieldPrivileges fp = Application.getPrivilegesManager().getFieldPrivileges();
// 在共同编辑时对于明细应该是编辑而非新建
final boolean isProTableLayout = FormsBuilderContextHolder.getMainIdOfDetail(false) != null;
// Check and clean // Check and clean
for (Iterator<Object> iter = elements.iterator(); iter.hasNext(); ) { for (Iterator<Object> iter = elements.iterator(); iter.hasNext(); ) {
JSONObject el = (JSONObject) iter.next(); 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)); el.put("options", ObjectUtils.defaultIfNull(el.remove("tagList"), JSONUtils.EMPTY_ARRAY));
} else if (dt == DisplayType.DATETIME) { } else if (dt == DisplayType.DATETIME) {
String format = StringUtils.defaultIfBlank( String format = StringUtils.defaultIfBlank(
easyField.getExtraAttr(EasyFieldConfigProps.DATETIME_FORMAT), easyField.getExtraAttr(EasyFieldConfigProps.DATETIME_FORMAT), dt.getDefaultFormat());
easyField.getDisplayType().getDefaultFormat());
el.put(EasyFieldConfigProps.DATETIME_FORMAT, format); el.put(EasyFieldConfigProps.DATETIME_FORMAT, format);
} else if (dt == DisplayType.DATE) { } else if (dt == DisplayType.DATE) {
String format = StringUtils.defaultIfBlank( String format = StringUtils.defaultIfBlank(
easyField.getExtraAttr(EasyFieldConfigProps.DATE_FORMAT), easyField.getExtraAttr(EasyFieldConfigProps.DATE_FORMAT), dt.getDefaultFormat());
easyField.getDisplayType().getDefaultFormat());
el.put(EasyFieldConfigProps.DATE_FORMAT, format); el.put(EasyFieldConfigProps.DATE_FORMAT, format);
} else if (dt == DisplayType.TIME) { } else if (dt == DisplayType.TIME) {
String format = StringUtils.defaultIfBlank( String format = StringUtils.defaultIfBlank(
easyField.getExtraAttr(EasyFieldConfigProps.TIME_FORMAT), easyField.getExtraAttr(EasyFieldConfigProps.TIME_FORMAT), dt.getDefaultFormat());
easyField.getDisplayType().getDefaultFormat());
el.put(EasyFieldConfigProps.TIME_FORMAT, format); el.put(EasyFieldConfigProps.TIME_FORMAT, format);
} else if (dt == DisplayType.CLASSIFICATION) { } else if (dt == DisplayType.CLASSIFICATION) {
el.put("openLevel", ClassificationManager.instance.getOpenLevel(fieldMeta)); el.put("openLevel", ClassificationManager.instance.getOpenLevel(fieldMeta));
@ -529,7 +531,7 @@ public class FormsBuilder extends FormsManager {
if (defaultValue != null) { if (defaultValue != null) {
defaultValue = easyField.wrapValue(defaultValue); defaultValue = easyField.wrapValue(defaultValue);
// `wrapValue` 会添加格式符号 // `wrapValue` 会添加格式符号
if (easyField.getDisplayType() == DisplayType.DECIMAL) { if (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER) {
defaultValue = EasyDecimal.clearFlaged(defaultValue); defaultValue = EasyDecimal.clearFlaged(defaultValue);
} }
el.put("value", defaultValue); el.put("value", defaultValue);
@ -570,7 +572,7 @@ public class FormsBuilder extends FormsManager {
Object value = wrapFieldValue(recordData, easyField, user); Object value = wrapFieldValue(recordData, easyField, user);
if (value != null) { if (value != null) {
// `wrapValue` 会添加格式符号 // `wrapValue` 会添加格式符号
if (!viewModel && easyField.getDisplayType() == DisplayType.DECIMAL) { if (!viewModel && (dt == DisplayType.DECIMAL || dt == DisplayType.NUMBER)) {
value = EasyDecimal.clearFlaged(value); value = EasyDecimal.clearFlaged(value);
} }
el.put("value", value); el.put("value", value);
@ -595,6 +597,14 @@ public class FormsBuilder extends FormsManager {
if (decimalType != null && decimalType.contains("%s")) { if (decimalType != null && decimalType.contains("%s")) {
el.put("decimalType", decimalType.replace("%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)) { else if (entity.containsField(field)) {
EasyField easyField = EasyMetaFactory.valueOf(entity.getField(field)); final EasyField easyField = EasyMetaFactory.valueOf(entity.getField(field));
if (easyField.getDisplayType() == DisplayType.REFERENCE || easyField.getDisplayType() == DisplayType.N2NREFERENCE) { final DisplayType dt = easyField.getDisplayType();
if (dt == DisplayType.REFERENCE || dt == DisplayType.N2NREFERENCE) {
// v3.4 如果字段设置了附加过滤条件从相关项新建时要检查是否符合 // v3.4 如果字段设置了附加过滤条件从相关项新建时要检查是否符合
if (!FieldValueHelper.checkRefDataFilter(easyField, ID.valueOf(value))) { if (!FieldValueHelper.checkRefDataFilter(easyField, ID.valueOf(value))) {
((JSONObject) formModel).put("alertMessage", ((JSONObject) formModel).put("alertMessage",
@ -758,7 +769,7 @@ public class FormsBuilder extends FormsManager {
Object mixValue = inFormFields.contains(field) ? getReferenceMixValue(value) : value; Object mixValue = inFormFields.contains(field) ? getReferenceMixValue(value) : value;
if (mixValue != null) { if (mixValue != null) {
if (easyField.getDisplayType() == DisplayType.REFERENCE) { if (dt == DisplayType.REFERENCE) {
initialValReady.put(field, mixValue); initialValReady.put(field, mixValue);
} else { } else {
// N2N 是数组 // N2N 是数组

View file

@ -35,16 +35,16 @@ public class FormsManager extends BaseLayoutManager {
protected FormsManager() {} protected FormsManager() {}
// 表单布局适用于 // 表单布局适用于
public static int APPLY_ONNEW = 1; public static int APPLY_NEW = 1;
public static int APPLY_ONEDIT = 2; public static int APPLY_EDIT = 2;
public static int APPLY_ONVIEW = 4; public static int APPLY_VIEW = 4;
/** /**
* @param entity * @param entity
* @return * @return
*/ */
public ConfigBean getNewFormLayout(String entity) { 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.指定布局 // 1.指定布局
if (recordOrLayoutId != null && recordOrLayoutId.getEntityCode() == EntityHelper.LayoutConfig) { if (recordOrLayoutId != null && recordOrLayoutId.getEntityCode() == EntityHelper.LayoutConfig) {
for (Object[] o : alls) { use = findConfigBean(alls, recordOrLayoutId);
if (recordOrLayoutId.equals(o[0])) {
use = findConfigBean(alls, (ID) o[0]);;
break;
}
}
if (use == null) { if (use == null) {
log.warn("Spec layout not longer exists : {}", recordOrLayoutId); log.warn("Spec layout not longer exists : {}", recordOrLayoutId);
recordOrLayoutId = null; recordOrLayoutId = null;
} }
} }
// 2.使用布局 // 2.查找布局
if (use == null) { if (use == null) {
// 优先使用条件匹配的
for (Object[] o : alls) { for (Object[] o : alls) {
ConfigBean cb = findConfigBean(alls, (ID) o[0]); ConfigBean cb = findConfigBean(alls, (ID) o[0]);
ShareToAttr attr = new ShareToAttr(cb); ShareToAttr attr = new ShareToAttr(cb);
if (recordOrLayoutId == null) { if (recordOrLayoutId == null) {
if (attr.isFallback()) { if (attr.isFallback() || attr.isForNew()) {
use = cb; use = cb;
break; break;
} }
@ -92,9 +87,14 @@ public class FormsManager extends BaseLayoutManager {
} }
} }
} }
// 默认优先级
if (recordOrLayoutId == null) {
use = findDefault(alls);
}
} }
// 3.默认布局fallback // 3.默认布局
if (use == null && recordOrLayoutId != null) { if (use == null && recordOrLayoutId != null) {
for (Object[] o : alls) { for (Object[] o : alls) {
ConfigBean cb = findConfigBean(alls, (ID) o[0]); ConfigBean cb = findConfigBean(alls, (ID) o[0]);
@ -121,6 +121,16 @@ public class FormsManager extends BaseLayoutManager {
.set("elements", JSONUtils.EMPTY_ARRAY); .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 // -- ADMIN
/** /**
@ -130,6 +140,14 @@ public class FormsManager extends BaseLayoutManager {
*/ */
public ConfigBean getFormLayout(ID formConfigId, String entity) { public ConfigBean getFormLayout(ID formConfigId, String entity) {
final Object[][] alls = getAllConfig(entity, TYPE_FORM); final Object[][] alls = getAllConfig(entity, TYPE_FORM);
// 高优先级
if (formConfigId == null) {
ConfigBean best = findDefault(alls);
if (best != null) return best;
}
// 次优先级
for (Object[] o : alls) { for (Object[] o : alls) {
if (formConfigId == null) { if (formConfigId == null) {
return findConfigBean(alls, (ID) o[0]); return findConfigBean(alls, (ID) o[0]);
@ -148,12 +166,26 @@ public class FormsManager extends BaseLayoutManager {
* @return * @return
*/ */
public List<ConfigBean> getAllFormsAttr(String entity) { 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); final Object[][] alls = getAllConfig(entity, TYPE_FORM);
List<ConfigBean> flist = new ArrayList<>(); List<ConfigBean> flist = new ArrayList<>();
for (Object[] o : alls) { for (Object[] o : alls) {
ConfigBean cb = findConfigBean(alls, (ID) o[0]).remove("config"); 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 // A-Z
@ -194,6 +226,7 @@ public class FormsManager extends BaseLayoutManager {
static class ShareToAttr { static class ShareToAttr {
private final JSONObject attrs; private final JSONObject attrs;
private final boolean sysDefault;
protected ShareToAttr(ConfigBean cb) { protected ShareToAttr(ConfigBean cb) {
Object s = cb.getObject("shareTo"); Object s = cb.getObject("shareTo");
if (s instanceof JSON) { if (s instanceof JSON) {
@ -202,11 +235,17 @@ public class FormsManager extends BaseLayoutManager {
// shareTo=ALL // shareTo=ALL
this.attrs = JSONUtils.toJSONObject("fallback", true); this.attrs = JSONUtils.toJSONObject("fallback", true);
} }
this.sysDefault = cb.getString("name") == null; // 系统默认的
} }
// 默认 // 默认
boolean isFallback() { boolean isFallback() {
return this.attrs.getBooleanValue("fallback"); return this.sysDefault || this.attrs.getBooleanValue("fallback");
}
// 新建
boolean isForNew() {
return this.sysDefault || this.attrs.getBooleanValue("fornew");
} }
// 符合使用条件 // 符合使用条件

View file

@ -11,6 +11,7 @@ import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Record; import cn.devezhao.persist4j.Record;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.rebuild.core.DefinedException;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.service.NoRecordFoundException; import com.rebuild.core.service.NoRecordFoundException;
import com.rebuild.utils.JSONUtils; import com.rebuild.utils.JSONUtils;
@ -59,6 +60,10 @@ public class LiteFormBuilder {
* @return * @return
*/ */
public JSONArray build(JSONArray fieldElements) { public JSONArray build(JSONArray fieldElements) {
if (fieldElements == null || fieldElements.isEmpty()) {
throw new DefinedException("No field elements");
}
Record recordData = null; Record recordData = null;
if (recordId != null) { if (recordId != null) {
recordData = FormsBuilder.instance.findRecord(recordId, user, fieldElements); recordData = FormsBuilder.instance.findRecord(recordId, user, fieldElements);

View file

@ -8,8 +8,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.metadata.easymeta; package com.rebuild.core.metadata.easymeta;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.configuration.general.ClassificationManager;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
/** /**
@ -26,7 +28,11 @@ public class EasyClassification extends EasyReference {
@Override @Override
public Object wrapValue(Object value) { public Object wrapValue(Object value) {
JSONObject map = (JSONObject) super.wrapValue(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; return map;
} }

View file

@ -86,6 +86,18 @@ public class EasyDecimal extends EasyField {
return format.substring(dotIndex).length() - 1; 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) { public static BigDecimal fixedDecimalScale(Object decimalValue, EasyField decimalField) {
int scale = ((EasyDecimal) decimalField).getScale(); int scale = ((EasyDecimal) decimalField).getScale();
RoundingMode roundingMode = ((EasyDecimal) decimalField).getRoundingMode();
if (decimalValue instanceof BigDecimal) { if (decimalValue instanceof BigDecimal) {
return ((BigDecimal) decimalValue).setScale(scale, RoundingMode.HALF_UP); return ((BigDecimal) decimalValue).setScale(scale, roundingMode);
} else { } else {
double d = ObjectUtils.round(ObjectUtils.toDouble(decimalValue), scale); BigDecimal v = BigDecimal.valueOf(ObjectUtils.toDouble(decimalValue));
return BigDecimal.valueOf(d); return v.setScale(scale, roundingMode);
} }
} }

View file

@ -39,11 +39,15 @@ public class EasyLocation extends EasyField implements MixValue {
@Override @Override
public Object wrapValue(Object value) { public Object wrapValue(Object value) {
if (value == null) return null; if (value == null) return null;
String[] vals = value.toString().split(MetadataHelper.SPLITER_RE);
JSONObject mixVal = JSONUtils.toJSONObject("text", vals[0]); // be: v3.8
if (vals.length >= 2) { final String val2str = value.toString();
String[] lnglat = vals[vals.length - 1].split(","); 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("lng", lnglat[0]);
mixVal.put("lat", lnglat.length == 2 ? lnglat[1] : null); mixVal.put("lat", lnglat.length == 2 ? lnglat[1] : null);
} }

View file

@ -53,4 +53,15 @@ public class EasyNumber extends EasyField {
getExtraAttr(EasyFieldConfigProps.NUMBER_FORMAT), getDisplayType().getDefaultFormat()); getExtraAttr(EasyFieldConfigProps.NUMBER_FORMAT), getDisplayType().getDefaultFormat());
return new DecimalFormat(format).format(value); return new DecimalFormat(format).format(value);
} }
// --
/**
* @param flagedValue
* @return
* @see EasyDecimal#clearFlaged(Object)
*/
public static String clearFlaged(Object flagedValue) {
return EasyDecimal.clearFlaged(flagedValue);
}
} }

View file

@ -39,7 +39,7 @@ public class EasyPickList extends EasyField implements MixValue {
ID itemId = PickListManager.instance.findItemByLabel(text, targetField.getRawMeta()); ID itemId = PickListManager.instance.findItemByLabel(text, targetField.getRawMeta());
if (itemId == null) { if (itemId == null) {
log.warn("Cannot find value in PickList : " + text + " << " + targetField); log.warn("Cannot find value in PickList : {} << {}", text, targetField);
} }
return itemId; return itemId;
} }

View file

@ -7,14 +7,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.metadata.easymeta; package com.rebuild.core.metadata.easymeta;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.record.RecordVisitor; import cn.devezhao.persist4j.record.RecordVisitor;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.support.general.FieldValueHelper;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import java.time.LocalTime; import java.time.LocalTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor; import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
/** /**
* @author devezhao * @author devezhao
@ -44,9 +48,15 @@ public class EasyTime extends EasyDateTime {
String valueExpr = (String) getRawMeta().getDefaultValue(); String valueExpr = (String) getRawMeta().getDefaultValue();
if (StringUtils.isBlank(valueExpr)) return null; if (StringUtils.isBlank(valueExpr)) return null;
if (valueExpr.contains("NOW")) { // {NOW} // 表达式
return LocalTime.now(); if (valueExpr.contains(VAR_NOW) || valueExpr.contains("NOW")) {
} else { 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); return RecordVisitor.tryParseTime(valueExpr);
} }
} }

View file

@ -77,16 +77,19 @@ public class EasyEntityConfigProps {
public static final String ENABLE_RECORD_MERGER = "enableRecordMerger"; public static final String ENABLE_RECORD_MERGER = "enableRecordMerger";
/** /**
* 列表模式 * 详情模式:字段
*/ */
public static final String ADVLIST_MODE2_SHOWFIELDS = "mode2ShowFields"; public static final String ADVLIST_MODE2_SHOWFIELDS = "mode2ShowFields";
/**
* 卡片模式:字段
*/
public static final String ADVLIST_MODE3_SHOWFIELDS = "mode3ShowFields"; public static final String ADVLIST_MODE3_SHOWFIELDS = "mode3ShowFields";
/** /**
* @see #ADVLIST_HIDE_FILTERS * @see #ADVLIST_HIDE_FILTERS
*/ */
public static final String ADVLIST_MODE3_SHOWFILTERS = "mode3ShowFilters"; public static final String ADVLIST_MODE3_SHOWFILTERS = "mode3ShowFilters";
/** /**
* @see #ADVLIST_SHOWCATEGORY TODO * @see #ADVLIST_SHOWCATEGORY
*/ */
public static final String ADVLIST_MODE3_SHOWCATEGORY = "mode3ShowCategory"; public static final String ADVLIST_MODE3_SHOWCATEGORY = "mode3ShowCategory";
} }

View file

@ -46,7 +46,10 @@ public class EasyFieldConfigProps {
* 表单公式 * 表单公式
*/ */
public static final String NUMBER_CALCFORMULA = "calcFormula"; 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_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; public static final String IMAGE_UPLOADNUMBER = FILE_UPLOADNUMBER;
/** /**
* 图片获取方式仅H5 * 图片获取方式兼容附件
*/ */
public static final String IMAGE_CAPTURE = "imageCapture"; public static final String IMAGE_CAPTURE = "imageCapture";
/**
* 图片获取方式兼容附件
*/
public static final String IMAGE_CAPTURE_DEF = "imageCaptureDef";
/** /**
* 自动编号规则 * 自动编号规则

View file

@ -47,11 +47,10 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; 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.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.DATE_FORMAT;
import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.NUMBER_CALCFORMULA; 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; import static com.rebuild.core.metadata.impl.EasyFieldConfigProps.NUMBER_NOTNEGATIVE;
/** /**
@ -396,7 +395,7 @@ public class Field2Schema extends SetUser {
String identifier = text; 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)) { if (!CharSet.ASCII_ALPHA.contains(identifier.charAt(0)) || BlockList.isBlock(identifier)) {
identifier = "rb" + identifier; identifier = "rb" + identifier;
} }
@ -458,14 +457,15 @@ public class Field2Schema extends SetUser {
extraAttrs.remove(DATETIME_FORMAT); extraAttrs.remove(DATETIME_FORMAT);
Object notNegative = extraAttrs.remove(NUMBER_NOTNEGATIVE); Object notNegative = extraAttrs.remove(NUMBER_NOTNEGATIVE);
Object calcFormula = extraAttrs.remove(NUMBER_CALCFORMULA); Object calcFormula = extraAttrs.remove(NUMBER_CALCFORMULA);
Object calcFormulaBackend = extraAttrs.remove(NUMBER_CALCFORMULABACKEND);
extraAttrs.clear(); extraAttrs.clear();
if (notNegative != null) extraAttrs.put(NUMBER_NOTNEGATIVE, notNegative); if (notNegative != null) extraAttrs.put(NUMBER_NOTNEGATIVE, notNegative);
if (calcFormula != null) extraAttrs.put(NUMBER_CALCFORMULA, calcFormula); if (calcFormula != null) extraAttrs.put(NUMBER_CALCFORMULA, calcFormula);
if (calcFormulaBackend instanceof Boolean && (Boolean) calcFormulaBackend) extraAttrs.put(NUMBER_CALCFORMULABACKEND, true);
if (!extraAttrs.isEmpty()) { if (extraAttrs.isEmpty()) fieldMeta.setNull("extConfig");
fieldMeta.setString("extConfig", extraAttrs.toJSONString()); else fieldMeta.setString("extConfig", extraAttrs.toJSONString());
}
} }
Application.getCommonsService().update(fieldMeta, false); Application.getCommonsService().update(fieldMeta, false);

View file

@ -41,7 +41,7 @@ public class ChangeOwningDeptTask extends HeavyTask<Integer> {
@Override @Override
protected Integer exec() { 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); this.setTotal(MetadataHelper.getEntities().length);
final StopWatch sw = new StopWatch("ChangeOwningDeptTask"); final StopWatch sw = new StopWatch("ChangeOwningDeptTask");
@ -68,7 +68,7 @@ public class ChangeOwningDeptTask extends HeavyTask<Integer> {
sw.stop(); 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; return changed;
} }
} }

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -23,6 +23,7 @@ import com.rebuild.core.privileges.bizz.InternalPermission;
import com.rebuild.core.service.CommonsService; import com.rebuild.core.service.CommonsService;
import com.rebuild.core.service.general.BulkContext; import com.rebuild.core.service.general.BulkContext;
import com.rebuild.core.service.general.EntityService; import com.rebuild.core.service.general.EntityService;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInterceptor;
@ -110,7 +111,7 @@ public class PrivilegesGuardInterceptor implements MethodInterceptor, Guard {
// 跳过 // 跳过
ID skipGuardId; ID skipGuardId;
if ((skipGuardId = PrivilegesGuardContextHolder.getSkipGuardOnce()) != null) { if ((skipGuardId = GeneralEntityServiceContextHolder.isSkipGuardOnce()) != null) {
log.debug("Allow no permission({}) passed once : {}", action.getName(), skipGuardId); log.debug("Allow no permission({}) passed once : {}", action.getName(), skipGuardId);
return; return;
} }

View file

@ -554,4 +554,11 @@ public class PrivilegesManager {
} }
throw new BizzException("Unknown Permission : " + name); throw new BizzException("Unknown Permission : " + name);
} }
/**
* @return
*/
public FieldPrivileges getFieldPrivileges() {
return Application.getBean(FieldPrivileges.class);
}
} }

View file

@ -311,13 +311,16 @@ public class UserService extends BaseService {
super.update(record); super.update(record);
} }
if (updateRoleAppends(user, roleAppends)) changed = true; if (roleAppends != null) {
if (updateRoleAppends(user, roleAppends)) changed = true;
}
if (changed) { if (changed) {
Application.getUserStore().refreshUser(user); Application.getUserStore().refreshUser(user);
} }
// 改变记录的所属部门 // 改变记录的所属部门
// 并发修改可能导致数据紊乱
if (deptOld != null) { if (deptOld != null) {
TaskExecutors.submit(new ChangeOwningDeptTask(user, deptNew), UserContextHolder.getUser()); TaskExecutors.submit(new ChangeOwningDeptTask(user, deptNew), UserContextHolder.getUser());
} }
@ -372,15 +375,17 @@ public class UserService extends BaseService {
* @return * @return
*/ */
protected boolean updateRoleAppends(ID user, ID[] roleAppends) { protected boolean updateRoleAppends(ID user, ID[] roleAppends) {
if (roleAppends == null) return false;
Object[][] exists = Application.createQueryNoFilter( Object[][] exists = Application.createQueryNoFilter(
"select memberId,roleId from RoleMember where userId = ?") "select memberId,roleId from RoleMember where userId = ?")
.setParameter(1, user) .setParameter(1, user)
.array(); .array();
if (exists.length == 0 && (roleAppends == null || roleAppends.length == 0)) { if (exists.length == 0 && roleAppends.length == 0) {
return false; return false;
} }
if (roleAppends == null || roleAppends.length == 0) { if (roleAppends.length == 0) {
for (Object[] o : exists) { for (Object[] o : exists) {
super.delete((ID) o[0]); super.delete((ID) o[0]);
} }

View file

@ -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) { for (Permission action : CustomEntityPrivileges.PERMISSION_DEFS) {
JSONObject aCustom = ((CustomEntityPrivileges) a).getCustomFilter(action); JSONObject aCustom = ((CustomEntityPrivileges) a).getCustomFilter(action);
@ -182,15 +182,22 @@ public class CombinedRole extends Role {
int gt = isGreaterThan(action.getMask(), aDefMap, bDefMap); int gt = isGreaterThan(action.getMask(), aDefMap, bDefMap);
if (gt == 1) { if (gt == 1) {
if (aCustom != null) customFilters.put(action.getName(), aCustom); if (aCustom != null) useCustomFilters.put(action.getName(), aCustom);
} else if (gt == 2) { } else if (gt == 2) {
if (bCustom != null) customFilters.put(action.getName(), bCustom); if (bCustom != null) useCustomFilters.put(action.getName(), bCustom);
} }
// gt == 0 无自定义权限 // 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(), ","); 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) { private Map<String, Integer> parseDefinitionMasks(String d) {

View file

@ -14,6 +14,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.service.query.ParseHelper; import com.rebuild.core.service.query.ParseHelper;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -35,6 +36,8 @@ public class CustomEntityPrivileges extends EntityPrivileges {
// 自定义权限 <Action, Filter> // 自定义权限 <Action, Filter>
private final Map<String, JSON> customFilters = new HashMap<>(); private final Map<String, JSON> customFilters = new HashMap<>();
// 字段权限
private final Map<String, Object> fpDefinition;
/** /**
* @param entity * @param entity
@ -52,17 +55,21 @@ public class CustomEntityPrivileges extends EntityPrivileges {
} }
} }
} }
fpDefinition = rawDefinition.getJSONObject("FP");
} }
/** /**
* @param entity * @param entity
* @param definition * @param definition
* @param customFilters * @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); super(entity, definition);
this.customFilters.clear(); this.customFilters.clear();
this.customFilters.putAll(customFilters); this.customFilters.putAll(customFilters);
this.fpDefinition = fpDefinition;
} }
/** /**
@ -169,7 +176,7 @@ public class CustomEntityPrivileges extends EntityPrivileges {
} }
/** /**
* 获取自定义权限 * 获取自定义权限定义
* *
* @param action * @param action
* @return * @return
@ -178,6 +185,15 @@ public class CustomEntityPrivileges extends EntityPrivileges {
return (JSONObject) customFilters.getOrDefault(action.getName(), null); return (JSONObject) customFilters.getOrDefault(action.getName(), null);
} }
/**
* 获取字段权限定义
*
* @return
*/
public Map<String, Object> getFpDefinition() {
return fpDefinition == null ? null : Collections.unmodifiableMap(fpDefinition);
}
@Override @Override
public String toString() { public String toString() {
return getDefinition() + ";" + getCustomFilters(); return getDefinition() + ";" + getCustomFilters();

View file

@ -59,6 +59,7 @@ public enum ZeroEntry {
/** /**
* 允许撤销审批 * 允许撤销审批
* v3.8 起添加撤回权限
*/ */
AllowRevokeApproval(false), AllowRevokeApproval(false),

View file

@ -19,10 +19,10 @@ import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* 基础 CRUD 服务使用请注意 * 基础 CRUD 服务使用须知
* <br>- 此类有事物 * <br>- 此类有事物
* <br>- 此类不经过用户权限验证 {@link PrivilegesGuardInterceptor} * <br>- 此类不经过用户权限验证 {@link PrivilegesGuardInterceptor}
* <br>- 此类不对多值字段进行处理 {@link BaseService} * <br>- 此类不字段值做任何处理如多值附件 {@link BaseService}
* <br>- 此类无任何系统规则如默认值重复检查自动编号等 * <br>- 此类无任何系统规则如默认值重复检查自动编号等
* <br>- 有权限的实体使用此类需要指定 `strictMode=false` * <br>- 有权限的实体使用此类需要指定 `strictMode=false`
* *

View file

@ -7,6 +7,9 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service; 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.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -34,7 +37,12 @@ public class SafeObservable {
} }
public void notifyObservers(Object arg) { public void notifyObservers(Object arg) {
boolean quickMode = GeneralEntityServiceContextHolder.isQuickMode(false);
for (SafeObserver o : obs) { for (SafeObserver o : obs) {
if (quickMode) {
if (o instanceof RobotTriggerObserver) continue;
}
o.update(this, arg); o.update(this, arg);
} }
} }

View file

@ -47,21 +47,26 @@ public class ApprovalFields2Schema extends Field2Schema {
* @throws MetadataModificationException * @throws MetadataModificationException
*/ */
public boolean createFields(Entity entity) throws MetadataModificationException { public boolean createFields(Entity entity) throws MetadataModificationException {
// 补充后加字段
if (MetadataHelper.hasApprovalField(entity)) { if (MetadataHelper.hasApprovalField(entity)) {
List<Field> complement = new ArrayList<>();
if (!entity.containsField(EntityHelper.ApprovalLastUser)) { if (!entity.containsField(EntityHelper.ApprovalLastUser)) {
return schema2DatabaseInternal(entity, buildApporvalLastUser(entity)); complement.add(buildApporvalLastUser(entity));
} }
if (!entity.containsField(EntityHelper.ApprovalLastTime)) { if (!entity.containsField(EntityHelper.ApprovalLastTime)) {
return schema2DatabaseInternal(entity, buildApporvalLastTime(entity)); complement.add(buildApporvalLastTime(entity));
} }
if (!entity.containsField(EntityHelper.ApprovalLastRemark)) { if (!entity.containsField(EntityHelper.ApprovalLastRemark)) {
return schema2DatabaseInternal(entity, buildApporvalLastRemark(entity)); complement.add(buildApporvalLastRemark(entity));
} }
if (!entity.containsField(EntityHelper.ApprovalStepUsers)) { if (!entity.containsField(EntityHelper.ApprovalStepUsers)) {
return schema2DatabaseInternal(entity, complement.add(buildApprovalStepUsers(entity));
buildApprovalStepUsers(entity), buildApprovalStepNodeName(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) if (!(MetadataHelper.hasPrivilegesField(entity)
@ -157,7 +162,7 @@ public class ApprovalFields2Schema extends Field2Schema {
try { try {
Application.getSqlExecutor().execute(ddl, DDL_TIMEOUT); Application.getSqlExecutor().execute(ddl, DDL_TIMEOUT);
} catch (Throwable ex) { } catch (Throwable ex) {
log.error("DDL ERROR : \n" + ddl, ex); log.error("DDL ERROR : \n{}", ddl, ex);
return false; return false;
} }

View file

@ -20,9 +20,9 @@ import com.rebuild.core.configuration.ConfigurationException;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.general.EntityService; 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.service.notification.MessageBuilder;
import com.rebuild.core.support.SetUser; import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.i18n.Language; 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 int MAX_REVOKED = 100;
// 自主退回
private static final String KEY_CANCEL38 = "PREV_APPROVER_BACKED";
final private ID recordId; final private ID recordId;
// 如未传递会在需要时根据 record 确定 // 如未传递会在需要时根据 record 确定
private ID approval; private ID approvalId;
// 流程定义 // 流程定义
private FlowParser flowParser; private FlowParser flowParser;
@ -71,11 +73,11 @@ public class ApprovalProcessor extends SetUser {
/** /**
* @param recordId * @param recordId
* @param approval * @param approvalId
*/ */
public ApprovalProcessor(ID recordId, ID approval) { public ApprovalProcessor(ID recordId, ID approvalId) {
this.recordId = recordId; 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); Set<String> ccAccounts = nextNodes.getCcAccounts(this.recordId);
Record recordOfMain = EntityHelper.forUpdate(this.recordId, this.getUser(), false); 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.setInt(EntityHelper.ApprovalState, ApprovalState.PROCESSING.getState());
recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNodes.getApprovalNode().getNodeId()); recordOfMain.setString(EntityHelper.ApprovalStepNode, nextNodes.getApprovalNode().getNodeId());
Application.getBean(ApprovalStepService.class).txSubmit(recordOfMain, ccUsers, ccAccounts, nextApprovers); Application.getBean(ApprovalStepService.class).txSubmit(recordOfMain, ccUsers, ccAccounts, nextApprovers);
@ -174,7 +176,7 @@ public class ApprovalProcessor extends SetUser {
approvedStep.setString("attrMore", attrMore.toJSONString()); approvedStep.setString("attrMore", attrMore.toJSONString());
} }
this.approval = (ID) stepApprover[3]; this.approvalId = (ID) stepApprover[3];
FlowNodeGroup nextNodes = getNextNodes((String) stepApprover[2]); FlowNodeGroup nextNodes = getNextNodes((String) stepApprover[2]);
Set<ID> nextApprovers = null; Set<ID> nextApprovers = null;
@ -224,6 +226,34 @@ public class ApprovalProcessor extends SetUser {
this.recordId, status.getApprovalId(), getCurrentNodeId(status), false); 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.催审 * 2.1.催审
* *
@ -231,9 +261,9 @@ public class ApprovalProcessor extends SetUser {
*/ */
public int urge() { public int urge() {
final ApprovalStatus status = checkApprovalState(ApprovalState.PROCESSING); 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) { if (Application.getCommonsCache().getx(sentKey) != null) {
return -1; return -1;
} }
@ -272,7 +302,7 @@ public class ApprovalProcessor extends SetUser {
Object[] instepApprover = Application.createQueryNoFilter( Object[] instepApprover = Application.createQueryNoFilter(
"select state from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and approver = ? and isCanceled = 'F'") "select state from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and approver = ? and isCanceled = 'F'")
.setParameter(1, this.recordId) .setParameter(1, this.recordId)
.setParameter(2, this.approval) .setParameter(2, this.approvalId)
.setParameter(3, getCurrentNodeId(null)) .setParameter(3, getCurrentNodeId(null))
.setParameter(4, toUser) .setParameter(4, toUser)
.unique(); .unique();
@ -416,13 +446,13 @@ public class ApprovalProcessor extends SetUser {
* @return * @return
*/ */
private FlowParser getFlowParser() { private FlowParser getFlowParser() {
Assert.notNull(approval, "[approval] cannot be null"); Assert.notNull(approvalId, "[approval] cannot be null");
if (flowParser != null) { if (flowParser != null) {
return flowParser; return flowParser;
} }
FlowDefinition flowDefinition = RobotApprovalManager.instance.getFlowDefinition( FlowDefinition flowDefinition = RobotApprovalManager.instance.getFlowDefinition(
MetadataHelper.getEntity(this.recordId.getEntityCode()), this.approval); MetadataHelper.getEntity(this.recordId.getEntityCode()), this.approvalId);
flowParser = flowDefinition.createFlowParser(); flowParser = flowDefinition.createFlowParser();
return flowParser; return flowParser;
} }
@ -435,7 +465,7 @@ public class ApprovalProcessor extends SetUser {
try { try {
return getFlowParser().getNode(nodeNo); return getFlowParser().getNode(nodeNo);
} catch (ApprovalException | ConfigurationException ex) { } catch (ApprovalException | ConfigurationException ex) {
log.warn("Cannot parse node : {} with {}", nodeNo, approval, ex); log.warn("Cannot parse node : {} with {}", nodeNo, approvalId, ex);
} }
return null; 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"; " where recordId = ? and approvalId = ? and node = ? and isCanceled = 'F' and isBacked = 'F' order by createdOn desc";
Object[] lastNode = Application.createQueryNoFilter(sql) Object[] lastNode = Application.createQueryNoFilter(sql)
.setParameter(1, this.recordId) .setParameter(1, this.recordId)
.setParameter(2, this.approval) .setParameter(2, this.approvalId)
.setParameter(3, currentNode) .setParameter(3, currentNode)
.unique(); .unique();
String nodeBatch = lastNode == null || lastNode[0] == null ? null : (String) lastNode[0]; 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) Object[][] array = Application.createQueryNoFilter(sql)
.setParameter(1, this.recordId) .setParameter(1, this.recordId)
.setParameter(2, this.approval) .setParameter(2, this.approvalId)
.setParameter(3, currentNode) .setParameter(3, currentNode)
.array(); .array();
@ -481,13 +511,13 @@ public class ApprovalProcessor extends SetUser {
} }
/** /**
* 获取已执行步骤 * 获取已审批节点
* *
* @return returns [ [S,S], [S], [SSS], [S] ] * @return returns [ [S,S], [S], [SSS], [S] ]
*/ */
public JSONArray getWorkedSteps() { public JSONArray getWorkedSteps() {
final ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId); final ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId);
this.approval = status.getApprovalId(); this.approvalId = status.getApprovalId();
Object[][] array = Application.createQueryNoFilter( Object[][] array = Application.createQueryNoFilter(
"select approver,state,remark,approvedTime,createdOn,createdBy,node,prevNode,nodeBatch,ccUsers,ccAccounts,attrMore from RobotApprovalStep" + "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)) { if (FlowNode.NODE_AUTOAPPROVAL.equals(nodeNo)) {
// No name // No name
} else if (FlowNode.NODE_REVOKED.equals(nodeNo)) { } else if (FlowNode.NODE_REVOKED.equals(nodeNo)) {
String nodeName = Language.L("管理员撤销"); String nodeName = Language.L("撤销");
s.put("nodeName", nodeName); s.put("nodeName", nodeName);
} else if (FlowNode.NODE_CANCELED.equals(nodeNo)) { } else if (FlowNode.NODE_CANCELED.equals(nodeNo)) {
String nodeName = Language.L("提交人撤回"); String nodeName = Language.L("撤回");
s.put("nodeName", nodeName); s.put("nodeName", nodeName);
} else { } else {
String nodeName = flowNode == null ? null : flowNode.getNodeName(); 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); s.put("node", nodeNo);
step.add(s); step.add(s);
} }
@ -635,7 +674,7 @@ public class ApprovalProcessor extends SetUser {
*/ */
public JSONArray getBackSteps() { public JSONArray getBackSteps() {
ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId); ApprovalStatus status = ApprovalHelper.getApprovalStatus(this.recordId);
this.approval = status.getApprovalId(); this.approvalId = status.getApprovalId();
String currentNode = getCurrentNodeId(status); String currentNode = getCurrentNodeId(status);
if (FlowNode.NODE_ROOT.equals(currentNode)) return JSONUtils.EMPTY_ARRAY; if (FlowNode.NODE_ROOT.equals(currentNode)) return JSONUtils.EMPTY_ARRAY;
@ -680,6 +719,30 @@ public class ApprovalProcessor extends SetUser {
return res; 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( Object[][] array = Application.createQueryNoFilter(
"select approver from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and isWaiting = 'T' and isCanceled = 'F'") "select approver from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and isWaiting = 'T' and isCanceled = 'F'")
.setParameter(1, this.recordId) .setParameter(1, this.recordId)
.setParameter(2, this.approval) .setParameter(2, this.approvalId)
.setParameter(3, node) .setParameter(3, node)
.array(); .array();
@ -716,12 +779,11 @@ public class ApprovalProcessor extends SetUser {
final EntityService es = Application.getEntityService(recordId.getEntityCode()); final EntityService es = Application.getEntityService(recordId.getEntityCode());
for (ID user : shareTo) { for (ID user : shareTo) {
if (!Application.getPrivilegesManager().allowRead(user, recordId)) { if (!Application.getPrivilegesManager().allowRead(user, recordId)) {
// force share GeneralEntityServiceContextHolder.setSkipGuard(recordId);
PrivilegesGuardContextHolder.setSkipGuard(recordId);
try { try {
es.share(recordId, user, null); es.share(recordId, user, null);
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); GeneralEntityServiceContextHolder.isSkipGuardOnce();
} }
} }
} }
@ -737,13 +799,13 @@ public class ApprovalProcessor extends SetUser {
private Object[] findProcessingStepApprover(ID approver) { private Object[] findProcessingStepApprover(ID approver) {
final ApprovalStatus status = checkApprovalState(ApprovalState.PROCESSING); final ApprovalStatus status = checkApprovalState(ApprovalState.PROCESSING);
this.approval = status.getApprovalId(); this.approvalId = status.getApprovalId();
String currentNodeId = getCurrentNodeId(status); String currentNodeId = getCurrentNodeId(status);
Object[] stepApprover = Application.createQueryNoFilter( Object[] stepApprover = Application.createQueryNoFilter(
"select stepId,state,approver from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and approver = ? and isCanceled = 'F'") "select stepId,state,approver from RobotApprovalStep where recordId = ? and approvalId = ? and node = ? and approver = ? and isCanceled = 'F'")
.setParameter(1, this.recordId) .setParameter(1, this.recordId)
.setParameter(2, this.approval) .setParameter(2, this.approvalId)
.setParameter(3, currentNodeId) .setParameter(3, currentNodeId)
.setParameter(4, approver) .setParameter(4, approver)
.unique(); .unique();

View file

@ -9,7 +9,6 @@ package com.rebuild.core.service.approval;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import org.apache.commons.lang.StringUtils;
/** /**
* @author devezhao zhaofang123@gmail.com * @author devezhao zhaofang123@gmail.com
@ -51,23 +50,17 @@ public class ApprovalStatus {
return currentStepNode; return currentStepNode;
} }
public String getLastComment() { /**
if (currentStepNode == null || lastComment != null) { * @return
return StringUtils.defaultIfBlank(lastComment, null); */
} public String getPrevStepNode() {
if (currentStepNode == null) return null;
Object[] last = Application.createQueryNoFilter( Object[] o = Application.createQueryNoFilter(
"select remark from RobotApprovalStep where recordId = ? and node = ? order by modifiedOn desc") "select prevNode from RobotApprovalStep where recordId = ? and node = ? order by modifiedOn desc")
.setParameter(1, this.recordId) .setParameter(1, this.recordId)
.setParameter(2, this.currentStepNode) .setParameter(2, this.currentStepNode)
.unique(); .unique();
return o == null ? null : (String) o[0];
if (last == null) {
lastComment = StringUtils.EMPTY;
} else {
lastComment = StringUtils.defaultIfBlank((String) last[0], StringUtils.EMPTY);
}
return StringUtils.defaultIfBlank(lastComment, null);
} }
} }

View file

@ -27,7 +27,6 @@ import com.rebuild.core.privileges.OperationDeniedException;
import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.UserService; import com.rebuild.core.privileges.UserService;
import com.rebuild.core.privileges.bizz.InternalPermission; 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.BaseService;
import com.rebuild.core.service.DataSpecificationNoRollbackException; import com.rebuild.core.service.DataSpecificationNoRollbackException;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
@ -54,6 +53,8 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static com.rebuild.core.privileges.bizz.ZeroEntry.AllowRevokeApproval;
/** /**
* 审批流程此类所有方法不应直接调用而是通过 ApprovalProcessor 封装类 * 审批流程此类所有方法不应直接调用而是通过 ApprovalProcessor 封装类
* <p> * <p>
@ -324,13 +325,14 @@ public class ApprovalStepService extends BaseService {
final boolean isAdmin = UserHelper.isAdmin(opUser); final boolean isAdmin = UserHelper.isAdmin(opUser);
if (isRevoke) { if (isRevoke) {
boolean canRevoke = Application.getPrivilegesManager().allow(opUser, ZeroEntry.AllowRevokeApproval); boolean canRevoke = Application.getPrivilegesManager().allow(opUser, AllowRevokeApproval);
if (!(isAdmin || canRevoke)) { if (!(isAdmin || canRevoke)) {
throw new OperationDeniedException(Language.L("你无权撤销审批")); throw new OperationDeniedException(Language.L("你无权撤销审批"));
} }
} else { } else {
boolean canRevoke = Application.getPrivilegesManager().allow(opUser, AllowRevokeApproval);
ID s = ApprovalHelper.getSubmitter(recordId, approvalId); ID s = ApprovalHelper.getSubmitter(recordId, approvalId);
if (!(isAdmin || opUser.equals(s))) { if (!(canRevoke || opUser.equals(s))) {
throw new OperationDeniedException(Language.L("你无权撤回审批")); throw new OperationDeniedException(Language.L("你无权撤回审批"));
} }
} }

View file

@ -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;
}
}

View file

@ -72,7 +72,7 @@ public class CNMapChart extends ChartData {
Numerical[] nums = getNumericals(); Numerical[] nums = getNumericals();
if (nums.length > 0) { if (nums.length > 0) {
return buildSql(dims[0], nums); return buildSql(dims[0], nums, false);
} }
String sql = "select {0} from {1} where {2}"; String sql = "select {0} from {1} where {2}";

View file

@ -37,8 +37,10 @@ import org.apache.commons.lang.StringUtils;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -205,16 +207,16 @@ public abstract class ChartData extends SetUser implements ChartSpec {
/** /**
* 获取过滤 SQL * 获取过滤 SQL
* *
* @param withNumericalFilter
* @return * @return
*/ */
protected String getFilterSql(Numerical withAxisFilter) { protected String getFilterSql(Numerical withNumericalFilter) {
String where = getFilterSql(); String filterSql = getFilterSql();
if (withAxisFilter != null && ParseHelper.validAdvFilter(withAxisFilter.getFilter())) { if (withNumericalFilter != null && withNumericalFilter.getFilter() != null) {
AdvFilterParser filterParser = new AdvFilterParser(withAxisFilter.getFilter()); String filter = new AdvFilterParser(withNumericalFilter.getFilter()).toSqlWhere();
String fieldWhere = filterParser.toSqlWhere(); if (filter != null) filterSql = String.format("((%s) and (%s))", filterSql, filter);
if (fieldWhere != null) where = String.format("((%s) and (%s))", where, fieldWhere);
} }
return where; return filterSql;
} }
/** /**
@ -438,9 +440,10 @@ public abstract class ChartData extends SetUser implements ChartSpec {
* *
* @param dim * @param dim
* @param nums * @param nums
* @param withFilter
* @return * @return
*/ */
protected String buildSql(Dimension dim, Numerical[] nums) { protected String buildSql(Dimension dim, Numerical[] nums, boolean withFilter) {
List<String> numSqlItems = new ArrayList<>(); List<String> numSqlItems = new ArrayList<>();
for (Numerical num : nums) { for (Numerical num : nums) {
numSqlItems.add(num.getSqlName()); numSqlItems.add(num.getSqlName());
@ -450,7 +453,7 @@ public abstract class ChartData extends SetUser implements ChartSpec {
sql = MessageFormat.format(sql, sql = MessageFormat.format(sql,
dim.getSqlName(), dim.getSqlName(),
StringUtils.join(numSqlItems, ", "), StringUtils.join(numSqlItems, ", "),
getSourceEntity().getName(), getFilterSql()); getSourceEntity().getName(), getFilterSql(withFilter ? nums[0] : null));
return appendSqlSort(sql); return appendSqlSort(sql);
} }
@ -495,25 +498,6 @@ public abstract class ChartData extends SetUser implements ChartSpec {
return appendSqlSort(sql); 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 num
* @param withFilter * @param withFilter
@ -538,4 +522,53 @@ public abstract class ChartData extends SetUser implements ChartSpec {
if (sorts != null) sql += " order by " + sorts; if (sorts != null) sql += " order by " + sorts;
return sql; 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][]);
}
} }

View file

@ -47,7 +47,7 @@ public class LineChart extends ChartData {
Numerical[] nums = getNumericals(); Numerical[] nums = getNumericals();
final Dimension dim1 = dims[0]; final Dimension dim1 = dims[0];
// 部分支持 // 日期连续仅部分支持
final Type dim1Type = dim1.getField().getType(); final Type dim1Type = dim1.getField().getType();
final FormatCalc dim1Calc = dim1.getFormatCalc(); final FormatCalc dim1Calc = dim1.getFormatCalc();
boolean dateContinuous = renderOption.getBooleanValue("dateContinuous") boolean dateContinuous = renderOption.getBooleanValue("dateContinuous")
@ -58,14 +58,14 @@ public class LineChart extends ChartData {
JSONArray yyyAxis = new JSONArray(); JSONArray yyyAxis = new JSONArray();
List<String> dataFlags = new ArrayList<>(); List<String> dataFlags = new ArrayList<>();
// 2DIM + 1NUM // 模式1: 2-DIM + 1-NUM
// FIXME 多余AXIS会舍弃 // FIXME 多余AXIS会舍弃
if (dims.length > 1) { if (dims.length > 1) {
Numerical num1 = nums[0]; Numerical num1 = nums[0];
Object[][] dataRaw = createQuery(buildSql(dims, num1)).array(); Object[][] dataRaw = createQuery(buildSql(dims, num1)).array();
// 连续日期 // 连续日期
if (dateContinuous && dataRaw.length > 0) { if (dateContinuous && dataRaw.length > 0) {
dataRaw = putFullDates2Data(dataRaw, dim1, 2); dataRaw = putContinuousDate2Data(dataRaw, dim1, 2);
} }
List<Object> dim1Set = new ArrayList<>(); List<Object> dim1Set = new ArrayList<>();
@ -118,12 +118,27 @@ public class LineChart extends ChartData {
dataFlags.add(num1Flag); dataFlags.add(num1Flag);
} }
} }
// 1DIM + NUM // 模式2: 1-DIM + N-NUM
else { 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) { if (dateContinuous && dataRaw.length > 0) {
dataRaw = putFullDates2Data(dataRaw, dim1, 1); dataRaw = putContinuousDate2Data(dataRaw, dim1, 1);
} }
Object[] numsAxis = new Object[nums.length]; Object[] numsAxis = new Object[nums.length];
@ -161,7 +176,7 @@ public class LineChart extends ChartData {
new Object[]{JSON.toJSON(dimAxis), JSON.toJSON(yyyAxis), renderOption}); 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 min = null;
Date max = null; Date max = null;
for (Object[] o : dataRaw) { for (Object[] o : dataRaw) {

View file

@ -9,8 +9,9 @@ package com.rebuild.core.service.dashboard.charts;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import com.alibaba.fastjson.JSONObject; 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.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.service.query.ParseHelper;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
/** /**
@ -21,7 +22,7 @@ import org.apache.commons.lang.StringUtils;
*/ */
public class Numerical extends Axis { public class Numerical extends Axis {
private JSONObject filter; private JSONObject filter = null;
private int scale = 2; private int scale = 2;
/** /**
@ -37,7 +38,7 @@ public class Numerical extends Axis {
JSONObject filter, Field parentField) { JSONObject filter, Field parentField) {
super(field, sort, calc, label, parentField); super(field, sort, calc, label, parentField);
if (scale != null) this.scale = scale; if (scale != null) this.scale = scale;
this.filter = filter; if (ParseHelper.validAdvFilter(filter)) this.filter = filter;
} }
/** /**

View file

@ -36,7 +36,7 @@ public class RadarChart extends ChartData {
Numerical[] nums = getNumericals(); Numerical[] nums = getNumericals();
Dimension dim1 = dims[0]; Dimension dim1 = dims[0];
Object[][] dataRaw = createQuery(buildSql(dim1, nums)).array(); Object[][] dataRaw = createQuery(buildSql(dim1, nums, false)).array();
JSONArray indicator = new JSONArray(); JSONArray indicator = new JSONArray();

View file

@ -11,7 +11,9 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.utils.JSONUtils; import com.rebuild.utils.JSONUtils;
import org.apache.commons.lang.StringUtils;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -35,6 +37,7 @@ public class ScatterChart extends ChartData {
JSONArray series = new JSONArray(); JSONArray series = new JSONArray();
List<String> dataFlags = new ArrayList<>(); List<String> dataFlags = new ArrayList<>();
// 模式1: 0-DMI + N-NUM
if (dims.length == 0) { if (dims.length == 0) {
Object[][] dataRaw = createQuery(buildSql(nums)).array(); Object[][] dataRaw = createQuery(buildSql(nums)).array();
for (Object[] item : dataRaw) { for (Object[] item : dataRaw) {
@ -47,10 +50,11 @@ public class ScatterChart extends ChartData {
new String[]{"data"}, new String[]{"data"},
new Object[]{dataRaw}); new Object[]{dataRaw});
series.add(item); series.add(item);
}
} else { // 模式2: N-DMI + N-NUM
else {
for (Dimension dim : dims) { for (Dimension dim : dims) {
Object[][] dataRaw = createQuery(buildSql(dim, nums)).array(); Object[][] dataRaw = createQuery(buildSql(dim, nums, false)).array();
for (Object[] item : dataRaw) { for (Object[] item : dataRaw) {
String label = wrapAxisValue(dim, item[0]); String label = wrapAxisValue(dim, item[0]);
for (int i = 1; i < item.length; i++) { for (int i = 1; i < item.length; i++) {
@ -80,4 +84,17 @@ public class ScatterChart extends ChartData {
new String[]{"series", "dataLabel", "_renderOption"}, new String[]{"series", "dataLabel", "_renderOption"},
new Object[]{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);
}
} }

View file

@ -46,7 +46,21 @@ public class TableChart extends ChartData {
Dimension[] dims = getDimensions(); Dimension[] dims = getDimensions();
Numerical[] nums = getNumericals(); 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) { if (this.showLineNumber && dataRaw.length > 0) {
@ -130,10 +144,12 @@ public class TableChart extends ChartData {
} else if (numSqlItems.isEmpty()) { } else if (numSqlItems.isEmpty()) {
sql = "select {0} from {2} where {3} group by {0}"; sql = "select {0} from {2} where {3} group by {0}";
} }
sql = MessageFormat.format(sql, sql = MessageFormat.format(sql,
StringUtils.join(dimSqlItems, ", "), StringUtils.join(dimSqlItems, ", "),
StringUtils.join(numSqlItems, ", "), StringUtils.join(numSqlItems, ", "),
getSourceEntity().getName(), getFilterSql()); getSourceEntity().getName(), getFilterSql(nums.length > 0 ? nums[0] : null));
System.out.println(sql);
return appendSqlSort(sql); return appendSqlSort(sql);
} }

View file

@ -13,8 +13,12 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; 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.dashboard.charts.ChartData;
import com.rebuild.core.service.notification.MessageBuilder; 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.I18nUtils;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
import com.rebuild.utils.JSONUtils; import com.rebuild.utils.JSONUtils;
@ -50,28 +54,32 @@ public class FeedsSchedule extends ChartData implements BuiltinChart {
@Override @Override
public JSON build() { public JSON build() {
Object[][] array = Application.createQueryNoFilter( 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") " where createdBy = ? and type = 4 and scheduleTime > ? order by scheduleTime")
.setParameter(1, getUser()) .setParameter(1, getUser())
.setParameter(2, CalendarUtils.addDay(-30)) // 忽略30天前的 .setParameter(2, CalendarUtils.addDay(-30)) // 忽略30天前的
.setLimit(200) .setLimit(200)
.array(); .array();
final EasyField relatedRecordMeta = EasyMetaFactory.valueOf(
MetadataHelper.getField("Feeds", "relatedRecord"));
JSONArray list = new JSONArray(); JSONArray list = new JSONArray();
for (Object[] o : array) { for (Object[] o : array) {
// 有完成时间表示已完成 // 有完成时间表示已完成
JSONObject state = JSON.parseObject((String) o[3]); JSONObject state = JSON.parseObject((String) o[3]);
if (state.getString("finishTime") != null) { if (state.getString("finishTime") != null) continue;
continue;
}
String scheduleTime = I18nUtils.formatDate((Date) o[1]); String scheduleTime = I18nUtils.formatDate((Date) o[1]);
String content = (String) o[2]; String content = (String) o[2];
content = MessageBuilder.formatMessage(content); content = MessageBuilder.formatMessage(content);
Object relatedRecord = o[4] == null
? null : FieldValueHelper.wrapFieldValue(o[4], relatedRecordMeta);
JSONObject item = JSONUtils.toJSONObject( JSONObject item = JSONUtils.toJSONObject(
new String[]{"id", "scheduleTime", "content"}, new String[]{"id", "scheduleTime", "content", "relatedRecord"},
new Object[]{o[0], scheduleTime, content}); new Object[]{o[0], scheduleTime, content, relatedRecord});
list.add(item); list.add(item);
} }

View file

@ -105,8 +105,10 @@ public class DataExporter extends SetUser {
final List<String> head = this.buildHead(builder); final List<String> head = this.buildHead(builder);
// Excel // Excel
if ("xls".equalsIgnoreCase(csvOrExcel)) { // xlsx: 65535 行数限制
File file = RebuildConfiguration.getFileOfTemp(String.format("RBEXPORT-%d.xls", System.currentTimeMillis())); 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<>(); List<List<String>> head4Excel = new ArrayList<>();
for (String h : head) { for (String h : head) {

View file

@ -88,7 +88,7 @@ public class DataReportManager implements ConfigManager {
* @return * @return
*/ */
public ConfigBean[] getReportsRaw(Entity entity) { public ConfigBean[] getReportsRaw(Entity entity) {
final String cKey = "DataReportManager35-" + entity.getName(); final String cKey = "DataReportManager38-" + entity.getName();
ConfigBean[] cached = (ConfigBean[]) Application.getCommonsCache().getx(cKey); ConfigBean[] cached = (ConfigBean[]) Application.getCommonsCache().getx(cKey);
if (cached != null) return cached; if (cached != null) return cached;
@ -106,6 +106,7 @@ public class DataReportManager implements ConfigManager {
int type = ObjectUtils.toInt(o[4], TYPE_RECORD); int type = ObjectUtils.toInt(o[4], TYPE_RECORD);
if (type == TYPE_WORD && outputType.contains("excel")) outputType += ",word"; if (type == TYPE_WORD && outputType.contains("excel")) outputType += ",word";
else if (type == TYPE_HTML5) outputType = "html5";
ConfigBean cb = new ConfigBean() ConfigBean cb = new ConfigBean()
.set("id", o[0]) .set("id", o[0])
@ -116,7 +117,8 @@ public class DataReportManager implements ConfigManager {
.set("outputType", outputType) .set("outputType", outputType)
.set("templateVersion", templateVersion) .set("templateVersion", templateVersion)
.set("useFilter", useFilter) .set("useFilter", useFilter)
.set("templateContent", o[6]); .set("templateContent", o[6])
.set("entity", entity.getName());
alist.add(cb); 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 * @param reportId
* @return * @return
*/ */
public TemplateFile getTemplateFile(Entity entity, ID reportId) { public ConfigBean getReportRaw(ID reportId) {
String templateFile = null; return getReportRaw(reportId, null);
String templateContent = null; }
int type = DataReportManager.TYPE_RECORD;
boolean isV33 = false;
for (ConfigBean e : getReportsRaw(entity)) { /**
if (e.getID("id").equals(reportId)) { * @param reportId
templateFile = e.getString("template"); * @param entity
templateContent = e.getString("templateContent"); * @return
type = e.getInteger("type"); */
isV33 = e.getInteger("templateVersion") == 3; public TemplateFile buildTemplateFile(ID reportId, Entity entity) {
break; 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 // v35 HTML5
if (templateContent != null) { if (templateContent != null) {
return new TemplateFile(templateContent, entity, reportId); return new TemplateFile(templateContent, entity, reportId);
@ -166,20 +197,15 @@ public class DataReportManager implements ConfigManager {
/** /**
* @param reportId * @param reportId
* @return * @return
* @see #getTemplateFile(Entity, ID) 性能好 * @see #buildTemplateFile(ID, Entity)
*/ */
public TemplateFile getTemplateFile(ID reportId) { public TemplateFile buildTemplateFile(ID reportId) {
Object[] o = Application.getQueryFactory().uniqueNoFilter(reportId, "belongEntity"); return buildTemplateFile(reportId, null);
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);
} }
@Override @Override
public void clean(Object entity) { public void clean(Object entity) {
final String cKey = "DataReportManager35-" + ((Entity) entity).getName(); final String cKey = "DataReportManager38-" + ((Entity) entity).getName();
Application.getCommonsCache().evict(cKey); Application.getCommonsCache().evict(cKey);
} }
@ -193,31 +219,25 @@ public class DataReportManager implements ConfigManager {
* @param fileName * @param fileName
* @return * @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 final Entity be = idOrEntity instanceof ID
? MetadataHelper.getEntity(((ID) idOrEntity).getEntityCode()) ? MetadataHelper.getEntity(((ID) idOrEntity).getEntityCode())
: MetadataHelper.getEntity((String) idOrEntity); : MetadataHelper.getEntity((String) idOrEntity);
String name = null; ConfigBean conf = DataReportManager.instance.getReportRaw(reportId, be);
for (ConfigBean cb : DataReportManager.instance.getReportsRaw(be)) { String name = conf.getString("name");
if (cb.getID("id").equals(reportId)) { if (ContentWithFieldVars.matchsVars(name).isEmpty()) {
name = cb.getString("name"); name = String.format("%s-%s", name, CalendarUtils.getPlainDateFormat().format(CalendarUtils.now()));
if (ContentWithFieldVars.matchsVars(name).isEmpty()) { } else if (idOrEntity instanceof ID) {
name = String.format("%s-%s", name, CalendarUtils.getPlainDateFormat().format(CalendarUtils.now())); name = ContentWithFieldVars.replaceWithRecord(name, (ID) idOrEntity);
} 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;
}
} }
// 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"); return StringUtils.defaultIfBlank(name, "UNTITLE");
} }

View file

@ -365,13 +365,17 @@ public class EasyExcelGenerator extends SetUser {
if (dt == DisplayType.BARCODE) { if (dt == DisplayType.BARCODE) {
data.put(varName, buildBarcodeData(easyField.getRawMeta(), record.getPrimary())); data.put(varName, buildBarcodeData(easyField.getRawMeta(), record.getPrimary()));
} else if (fieldValue == null) { } 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 { } else {
if (dt == DisplayType.SIGN) { if (dt == DisplayType.SIGN) {
fieldValue = buildSignData((String) fieldValue); fieldValue = buildSignData((String) fieldValue);
} else if (dt == DisplayType.IMAGE) { } else if (dt == DisplayType.IMAGE) {
fieldValue = buildImageData((String) fieldValue); fieldValue = buildImageData((String) fieldValue);
// TODO Excel 指定图片大小可通过 Excel 单元格大小控制?
} else { } else {
if (dt == DisplayType.NUMBER) { if (dt == DisplayType.NUMBER) {
@ -522,7 +526,7 @@ public class EasyExcelGenerator extends SetUser {
return create(reportId, recordId); return create(reportId, recordId);
} else { } else {
TemplateFile tt = DataReportManager.instance TemplateFile tt = DataReportManager.instance
.getTemplateFile(MetadataHelper.getEntity(recordId.getEntityCode()), reportId); .buildTemplateFile(reportId, MetadataHelper.getEntity(recordId.getEntityCode()));
return new EasyExcelGenerator33(tt.templateFile, recordIds); return new EasyExcelGenerator33(tt.templateFile, recordIds);
} }
} }
@ -534,7 +538,7 @@ public class EasyExcelGenerator extends SetUser {
*/ */
public static EasyExcelGenerator create(ID reportId, ID recordId) { public static EasyExcelGenerator create(ID reportId, ID recordId) {
TemplateFile tt = DataReportManager.instance TemplateFile tt = DataReportManager.instance
.getTemplateFile(MetadataHelper.getEntity(recordId.getEntityCode()), reportId); .buildTemplateFile(reportId, MetadataHelper.getEntity(recordId.getEntityCode()));
return create(tt.templateFile, recordId, tt.isV33); return create(tt.templateFile, recordId, tt.isV33);
} }

View file

@ -62,6 +62,7 @@ import static com.rebuild.core.service.datareport.TemplateExtractor33.NROW_PREFI
@Slf4j @Slf4j
public class EasyExcelGenerator33 extends EasyExcelGenerator { public class EasyExcelGenerator33 extends EasyExcelGenerator {
// 支持多记录导出会合并到一个 Excel 文件
final private List<ID> recordIdMultiple; final private List<ID> recordIdMultiple;
private Set<String> inShapeVars; private Set<String> inShapeVars;

View file

@ -61,9 +61,7 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
for (Map.Entry<String, String> e : varsMap.entrySet()) { for (Map.Entry<String, String> e : varsMap.entrySet()) {
String varName = e.getKey(); String varName = e.getKey();
if (varName.startsWith(NROW_PREFIX + PLACEHOLDER)) { if (varName.startsWith(NROW_PREFIX + PLACEHOLDER)) continue;
continue;
}
String validField = e.getValue(); String validField = e.getValue();
if (validField != null && e.getKey().startsWith(NROW_PREFIX)) { if (validField != null && e.getKey().startsWith(NROW_PREFIX)) {
@ -113,9 +111,9 @@ public class EasyExcelListGenerator extends EasyExcelGenerator {
* @return * @return
*/ */
public static EasyExcelListGenerator create(ID reportId, JSONObject queryData) { public static EasyExcelListGenerator create(ID reportId, JSONObject queryData) {
TemplateFile tb = DataReportManager.instance.getTemplateFile( TemplateFile tt = DataReportManager.instance.buildTemplateFile(
MetadataHelper.getEntity(queryData.getString("entity")), reportId); reportId, MetadataHelper.getEntity(queryData.getString("entity")));
return create(tb.templateFile, queryData); return create(tt.templateFile, queryData);
} }
/** /**

View file

@ -92,12 +92,15 @@ public class TemplateExtractor {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
for (final String varName : vars) { for (final String varName : vars) {
// v3.8
String thatName = ValueConvertFunc.splitName(varName);
// 列表型字段 // 列表型字段
if (varName.startsWith(NROW_PREFIX)) { if (thatName.startsWith(NROW_PREFIX)) {
String listField = varName.substring(1); 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()); String stepNodeField = listField.substring(APPROVAL_PREFIX.length());
if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) { if (approvalEntity != null && MetadataHelper.getLastJoinField(approvalEntity, stepNodeField) != null) {
map.put(varName, stepNodeField); map.put(varName, stepNodeField);
@ -120,10 +123,10 @@ public class TemplateExtractor {
map.put(varName, transformRealField(entity, listField)); map.put(varName, transformRealField(entity, listField));
} }
} else if (MetadataHelper.getLastJoinField(entity, varName) != null) { } else if (MetadataHelper.getLastJoinField(entity, thatName) != null) {
map.put(varName, varName); map.put(varName, thatName);
} else { } else {
map.put(varName, transformRealField(entity, varName)); map.put(varName, transformRealField(entity, thatName));
} }
} }
return map; return map;

View file

@ -8,14 +8,18 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.datareport; package com.rebuild.core.service.datareport;
import cn.devezhao.commons.CalendarUtils; import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.ObjectUtils;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import com.deepoove.poi.data.PictureRenderData; import com.deepoove.poi.data.PictureRenderData;
import com.deepoove.poi.data.Pictures; 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.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyDecimal; import com.rebuild.core.metadata.easymeta.EasyDecimal;
import com.rebuild.core.metadata.easymeta.EasyField; import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.utils.CommonsUtils; import com.rebuild.utils.CommonsUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
@ -25,7 +29,10 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List;
/** /**
* @author devezhao * @author devezhao
@ -34,8 +41,18 @@ import java.util.Date;
@Slf4j @Slf4j
public class ValueConvertFunc { 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 * @param field
@ -47,41 +64,102 @@ public class ValueConvertFunc {
String thatFunc = splitFunc(varName); String thatFunc = splitFunc(varName);
if (thatFunc == null) return value; if (thatFunc == null) return value;
// 默认值
if (thatFunc.startsWith(EMPTY)) {
if (value == null || StringUtils.isBlank(value.toString())) {
return extractFuncValue(thatFunc);
}
}
final DisplayType type = field.getDisplayType(); final DisplayType type = field.getDisplayType();
if (type == DisplayType.NUMBER || type == DisplayType.DECIMAL) {
if ("CHINESEYUAN".equals(thatFunc)) { // 空值也处理
return Convert.digitToChinese((Number) value); if (type == DisplayType.MULTISELECT || type == DisplayType.PICKLIST) {
} if (CHECKBOX_4OPTION.equals(thatFunc) || CHECKBOX2_4OPTION.equals(thatFunc)) {
if ("THOUSANDS".equals(thatFunc)) { String[] m = value == null ? new String[0] : value.toString().split(", ");
String format = "##,##0"; ConfigBean[] items = MultiSelectManager.instance.getPickListRaw(field.getRawMeta(), false);
if (type == DisplayType.DECIMAL) {
int scale = ((EasyDecimal) field).getScale(); String[] flags = new String[]{"", ""};
if (scale > 0) { if (CHECKBOX2_4OPTION.equals(thatFunc)) flags = new String[]{"", ""};
format += "." + StringUtils.leftPad("", scale, "0");
} 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) { } else if (type == DisplayType.DATE || type == DisplayType.DATETIME || type == DisplayType.TIME) {
if ("CHINESEDATE".equals(thatFunc)) { if (CHINESE_4DATE_NUM.equals(thatFunc) || "CHINESEDATE".equals(thatFunc)) {
Date d = CommonsUtils.parseDate(value.toString()); if (type == DisplayType.TIME) {
if (d == null) return value; 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(); int len = field.wrapValue(LocalTime.now()).toString().length() + 1;
if (len <= 10) len += 1; // yyyy-MM-dd String format = CalendarUtils.CN_TIME_FORMAT.substring(0, len);
else len += 2; 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); int len = field.wrapValue(CalendarUtils.now()).toString().length();
return CalendarUtils.getDateFormat(format).format(d); 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; return value;
} }
/** /**
* 转换图片 * 转换图片WORD 格式
* *
* @param value * @param value
* @param varName * @param varName
@ -93,8 +171,8 @@ public class ValueConvertFunc {
String thatFunc = splitFunc(varName); String thatFunc = splitFunc(varName);
if (thatFunc == null) return builder.create(); if (thatFunc == null) return builder.create();
if (thatFunc.startsWith("SIZE") && thatFunc.length() > 4) { if (thatFunc.startsWith(SIZE_4IMG)) {
String[] wh = thatFunc.substring(4).split("\\*"); String[] wh = extractFuncValue(thatFunc).split("\\*");
int width = NumberUtils.toInt(wh[0]); int width = NumberUtils.toInt(wh[0]);
int height = -1; int height = -1;
@ -116,19 +194,30 @@ public class ValueConvertFunc {
height = NumberUtils.toInt(wh[1]); height = NumberUtils.toInt(wh[1]);
} }
if (height < 0) height = width; builder = Pictures.ofBytes(value).size(width, height > 0 ? height : width);
builder = Pictures.ofBytes(value).size(width, height);
} }
return builder.create(); 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 * @param varName
* @return * @return
*/ */
public static String splitName(String varName) { 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 * @return
*/ */
public static String splitFunc(String varName) { 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;
} }
} }

View file

@ -51,7 +51,7 @@ public abstract class BaseFeedsService extends ObservableService {
@Override @Override
public Record update(Record record) { public Record update(Record record) {
record = super.update(converContent4Mentions((record))); record = super.update(converContent4Mentions(record));
awareMention(record, false); awareMention(record, false);
return record; return record;

View file

@ -91,7 +91,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer
addObserver(new RobotTriggerObserver()); addObserver(new RobotTriggerObserver());
try { try {
addObserver((SafeObserver) ReflectUtils.newObject("com.rebuild.rbv.sop.RobotSopObserver")); addObserver((SafeObserver) ReflectUtils.newObject("com.rebuild.rbv.sop.RobotSopObserver"));
} catch (Exception ignoredClassNotFound){} } catch (Exception ignoredRbvClassMiss){}
} }
@Override @Override

View file

@ -8,12 +8,15 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.general; package com.rebuild.core.service.general;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NamedThreadLocal; import org.springframework.core.NamedThreadLocal;
import org.springframework.util.Assert;
/** /**
* @author devezhao * @author devezhao
* @since 2020/9/29 * @since 2020/9/29
*/ */
@Slf4j
public class GeneralEntityServiceContextHolder { public class GeneralEntityServiceContextHolder {
private static final ThreadLocal<Boolean> SKIP_SERIES_VALUE = new NamedThreadLocal<>("Skip series value"); 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<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(); if (mode != null) REPEATED_CHECK_MODE.remove();
return mode == null ? 0 : mode; 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;
}
} }

View file

@ -127,6 +127,8 @@ public class RecentlyUsedHelper {
* @param type * @param type
*/ */
public static void add(ID user, ID id, String 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); final String key = formatKey(user, MetadataHelper.getEntityName(id), type);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
LinkedList<ID> cached = (LinkedList<ID>) Application.getCommonsCache().getx(key); LinkedList<ID> cached = (LinkedList<ID>) Application.getCommonsCache().getx(key);

View file

@ -48,6 +48,8 @@ public class SeriesReindexTask extends HeavyTask<Integer> {
if (ONLY_REINDEX_BLANK) { if (ONLY_REINDEX_BLANK) {
sql += String.format(" where %s is null or %s = ''", field.getName(), field.getName()); 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); Query query = Application.createQueryNoFilter(sql);
Object[][] array = QueryHelper.readArray(query); Object[][] array = QueryHelper.readArray(query);

View file

@ -19,7 +19,7 @@ import org.springframework.util.Assert;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; 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 { public class IncreasingVar extends SeriesVar {
private static final Object INCREASINGS_LOCK = new Object(); 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 Field field;
private String zeroFlag; private String zeroFlag;
@ -55,6 +55,11 @@ public class IncreasingVar extends SeriesVar {
this.field = field; 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 @Override
public String generate() { public String generate() {
// Preview mode // Preview mode
@ -62,19 +67,18 @@ public class IncreasingVar extends SeriesVar {
return StringUtils.leftPad("1", getSymbols().length(), '0'); 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) { synchronized (INCREASINGS_LOCK) {
AtomicInteger incr = INCREASINGS.get(nameKey); AtomicLong incr = INCREASINGS.get(nameKey);
if (incr == null) { if (incr == null) {
String val = KVStorage.getCustomValue(nameKey); 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); INCREASINGS.put(nameKey, incr);
} }
int nextValue = incr.incrementAndGet(); long nextValue = incr.incrementAndGet();
RebuildConfiguration.setCustomValue(nameKey, nextValue, Boolean.TRUE); RebuildConfiguration.setCustomValue(nameKey, nextValue, Boolean.TRUE);
return StringUtils.leftPad(nextValue + "", getSymbols().length(), '0'); return StringUtils.leftPad(nextValue + "", getSymbols().length(), '0');
@ -82,16 +86,27 @@ public class IncreasingVar extends SeriesVar {
} }
/** /**
* 清空序号缓存 * 重置序号
*
* @param reset
*/ */
protected void clean() { protected void clean(long reset) {
Assert.notNull(this.field, "[this.field] cannot be null"); Assert.isTrue(reset >= 0, "[reset] must be greater than 0");
final String nameKey = getNameKey();
final String nameKey = String.format("Series-%s.%s", field.getOwnEntity().getName(), field.getName());
synchronized (INCREASINGS_LOCK) { synchronized (INCREASINGS_LOCK) {
INCREASINGS.remove(nameKey); 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 * @return
*/ */
private int countFromDb() { private long countFromDb() {
String dateLimit = null; String dateLimit = null;
if ("Y".equals(zeroFlag)) { if ("Y".equals(zeroFlag)) {
dateLimit = CalendarUtils.format("yyyy", CalendarUtils.now()) + "-01-01"; 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", String sql = String.format("select count(%s) from %s where %s",
field.getName(), field.getOwnEntity().getName(), dateLimit); field.getName(), field.getOwnEntity().getName(), dateLimit);
Object[] count = Application.createQueryNoFilter(sql).unique(); Object[] count = Application.createQueryNoFilter(sql).unique();
return ObjectUtils.toInt(count[0]); return ObjectUtils.toLong(count[0]);
} }
} }

View file

@ -65,6 +65,25 @@ public class SeriesGeneratorFactory {
* @param field * @param field
*/ */
public static void zero(Field 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();
} }
} }

View file

@ -23,7 +23,6 @@ import com.rebuild.core.metadata.EntityRecordCreator;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.EasyField; import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserService; import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.general.GeneralEntityService; import com.rebuild.core.service.general.GeneralEntityService;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
@ -174,7 +173,7 @@ public class RecordTransfomer extends SetUser {
} }
protected ID saveRecord(Record record, List<Record> detailsList) { 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()) { if (detailsList != null && !detailsList.isEmpty()) {
record.setObjectValue(GeneralEntityService.HAS_DETAILS, detailsList); record.setObjectValue(GeneralEntityService.HAS_DETAILS, detailsList);
@ -187,8 +186,8 @@ public class RecordTransfomer extends SetUser {
record = Application.getEntityService(targetEntity.getEntityCode()).createOrUpdate(record); record = Application.getEntityService(targetEntity.getEntityCode()).createOrUpdate(record);
return record.getPrimary(); return record.getPrimary();
} finally { } finally {
if (this.skipGuard) GeneralEntityServiceContextHolder.isSkipGuardOnce();
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce(); GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
if (this.skipGuard) PrivilegesGuardContextHolder.getSkipGuardOnce();
} }
} }

View file

@ -26,9 +26,9 @@ import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType; import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; 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.UserHelper;
import com.rebuild.core.privileges.bizz.Department; import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.core.support.CommandArgs;
import com.rebuild.core.support.License; import com.rebuild.core.support.License;
import com.rebuild.core.support.SetUser; import com.rebuild.core.support.SetUser;
import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.i18n.Language;
@ -147,26 +147,7 @@ public class AdvFilterParser extends SetUser {
// 自动确定查询项 // 自动确定查询项
if (MODE_QUICK.equalsIgnoreCase(filterExpr.getString("type"))) { if (MODE_QUICK.equalsIgnoreCase(filterExpr.getString("type"))) {
String quickFields = filterExpr.getString("quickFields"); rebuildQuickFilter38();
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);
} }
JSONArray items = filterExpr.getJSONArray("items"); JSONArray items = filterExpr.getJSONArray("items");
@ -302,6 +283,9 @@ public class AdvFilterParser extends SetUser {
String value = (String) checkValue; String value = (String) checkValue;
String valueEnd = null; String valueEnd = null;
// v3.8
if (useFulltextOp(field) && "LK".equals(op)) op = "FT";
if (dt == DisplayType.N2NREFERENCE) { if (dt == DisplayType.N2NREFERENCE) {
String inWhere = null; String inWhere = null;
boolean forceNot = false; boolean forceNot = false;
@ -505,6 +489,25 @@ public class AdvFilterParser extends SetUser {
} }
op = ParseHelper.BW; 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) { } else if (dt == DisplayType.TIME) {
@ -631,6 +634,10 @@ public class AdvFilterParser extends SetUser {
// LIKE // LIKE
if (op.equalsIgnoreCase(ParseHelper.LK) || op.equalsIgnoreCase(ParseHelper.NLK)) { if (op.equalsIgnoreCase(ParseHelper.LK) || op.equalsIgnoreCase(ParseHelper.NLK)) {
value = '%' + value + '%'; value = '%' + value + '%';
} else if (op.equalsIgnoreCase(ParseHelper.LK1)) {
value = '%' + value;
} else if (op.equalsIgnoreCase(ParseHelper.LK2)) {
value = value + '%';
} }
sb.append(quoteValue(value, lastFieldMeta.getType())); sb.append(quoteValue(value, lastFieldMeta.getType()));
} }
@ -687,21 +694,18 @@ public class AdvFilterParser extends SetUser {
value += (fullTime ? ParseHelper.FULL_TIME : ParseHelper.ZERO_TIME); // 含当日 value += (fullTime ? ParseHelper.FULL_TIME : ParseHelper.ZERO_TIME); // 含当日
} }
} }
// 修正月 // v3.8 不修正了否则因为格式问题如日期带日不带日就带来不同的查询结果这很怪异
else if (field.getType() == FieldType.DATE && valueLen == 10) { // // 修正月
String dateFormat = StringUtils.defaultIfBlank( // else if (field.getType() == FieldType.DATE && valueLen == 10) {
EasyMetaFactory.valueOf(field).getExtraAttr(EasyFieldConfigProps.DATE_FORMAT), // String dateFormat = StringUtils.defaultIfBlank(
DisplayType.DATE.getDefaultFormat()); // EasyMetaFactory.valueOf(field).getExtraAttr(EasyFieldConfigProps.DATE_FORMAT),
// DisplayType.DATE.getDefaultFormat());
// v3.7 BW 无需修正值由用户提供 // if (dateFormat.length() == 4) {
if (ParseHelper.BW.equals(op)) dateFormat = "0"; // value = value.substring(0, 4) + "-01-01";
// } else if (dateFormat.length() == 7) {
if (dateFormat.length() == 4) { // value = value.substring(0, 7) + "-01";
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) if (op.equalsIgnoreCase(ParseHelper.IN) || op.equalsIgnoreCase(ParseHelper.NIN)
@ -750,25 +754,69 @@ public class AdvFilterParser extends SetUser {
/** /**
* @param date * @param date
* @param paddingType 0=, 1=FULLTIME, 2=ZEROTIME * @param paddingTimeType 0=, 1=FULLTIME, 2=ZEROTIME
* @return * @return
*/ */
private String formatDate(Date date, int paddingType) { private String formatDate(Date date, int paddingTimeType) {
String s = CalendarUtils.getUTCDateFormat().format(date); String s = CalendarUtils.getUTCDateFormat().format(date);
if (paddingType == 1) s += ParseHelper.FULL_TIME; if (paddingTimeType == 1) s += ParseHelper.FULL_TIME;
else if (paddingType == 2) s += ParseHelper.ZERO_TIME; else if (paddingTimeType == 2) s += ParseHelper.ZERO_TIME;
return s; 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 quickFields
* @param valueIndex
* @return * @return
*/ */
private JSONArray buildQuickFilterItems(String quickFields, int valueIndex) { private JSONArray buildQuickFilterItems(String quickFields, int valueIndex) {
Set<String> usesFields = ParseHelper.buildQuickFields(rootEntity, quickFields); Set<String> usesFields = ParseHelper.buildQuickFields(rootEntity, quickFields);
JSONArray items = new JSONArray(); JSONArray items = new JSONArray();
for (String field : usesFields) { for (String field : usesFields) {
items.add(JSON.parseObject("{ op:'LK', value:'{" + valueIndex + "}', field:'" + field + "' }")); items.add(JSON.parseObject("{ op:'LK', value:'{" + valueIndex + "}', field:'" + field + "' }"));
@ -776,6 +824,22 @@ public class AdvFilterParser extends SetUser {
return items; 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} // 字段变量 {@FIELD}
private static final String PATT_FIELDVAR = "\\{@([\\w.]+)}"; private static final String PATT_FIELDVAR = "\\{@([\\w.]+)}";
// `当前`变量当前日期时间用户 // `当前`变量当前日期时间用户

View file

@ -43,6 +43,8 @@ public class ParseHelper {
public static final String NL = "NL"; public static final String NL = "NL";
public static final String NT = "NT"; public static final String NT = "NT";
public static final String LK = "LK"; 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 NLK = "NLK";
public static final String IN = "IN"; public static final String IN = "IN";
public static final String NIN = "NIN"; public static final String NIN = "NIN";
@ -95,6 +97,8 @@ public class ParseHelper {
public static final String PUY = "PUY"; // 本年- public static final String PUY = "PUY"; // 本年-
public static final String CUY = "CUY"; // 本年 public static final String CUY = "CUY"; // 本年
public static final String NUY = "NUY"; // 本年+ 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 DDD = "DDD"; // 指定天+-
public static final String HHH = "HHH"; // 指定时+- public static final String HHH = "HHH"; // 指定时+-
public static final String EVW = "EVW"; // 每周几 public static final String EVW = "EVW"; // 每周几
@ -130,7 +134,7 @@ public class ParseHelper {
return "is null"; return "is null";
} else if (NT.equalsIgnoreCase(token)) { } else if (NT.equalsIgnoreCase(token)) {
return "is not null"; return "is not null";
} else if (LK.equalsIgnoreCase(token)) { } else if (LK.equalsIgnoreCase(token) || LK1.equalsIgnoreCase(token) || LK2.equalsIgnoreCase(token)) {
return "like"; return "like";
} else if (NLK.equalsIgnoreCase(token)) { } else if (NLK.equalsIgnoreCase(token)) {
return "not like"; return "not like";
@ -185,7 +189,8 @@ public class ParseHelper {
} else if ( } else if (
CUW.equalsIgnoreCase(token) || CUM.equalsIgnoreCase(token) || CUQ.equalsIgnoreCase(token) || CUY.equalsIgnoreCase(token) || CUW.equalsIgnoreCase(token) || CUM.equalsIgnoreCase(token) || CUQ.equalsIgnoreCase(token) || CUY.equalsIgnoreCase(token) ||
PUW.equalsIgnoreCase(token) || PUM.equalsIgnoreCase(token) || PUQ.equalsIgnoreCase(token) || PUY.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"; return "between";
} else if (DDD.equalsIgnoreCase(token) || HHH.equalsIgnoreCase(token) } else if (DDD.equalsIgnoreCase(token) || HHH.equalsIgnoreCase(token)
|| EVW.equalsIgnoreCase(token) || EVM.equalsIgnoreCase(token)) { || EVW.equalsIgnoreCase(token) || EVM.equalsIgnoreCase(token)) {
@ -211,7 +216,7 @@ public class ParseHelper {
if (dt == DisplayType.REFERENCE) { if (dt == DisplayType.REFERENCE) {
Field nameField = field.getReferenceEntity().getNameField(); Field nameField = field.getReferenceEntity().getNameField();
if (nameField.getType() == FieldType.REFERENCE) { 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; return null;
} }
@ -266,7 +271,7 @@ public class ParseHelper {
if (can != null) usesFields.add(can); if (can != null) usesFields.add(can);
} else { } 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()) { if (usesFields.isEmpty()) {
log.warn("No fields of search found : " + usesFields); log.warn("No fields of search found : {}", usesFields);
} }
return usesFields; return usesFields;
} }

View file

@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.trigger; package com.rebuild.core.service.trigger;
import cn.devezhao.persist4j.engine.ID;
import com.rebuild.core.service.DataSpecificationException; import com.rebuild.core.service.DataSpecificationException;
/** /**
@ -19,18 +20,28 @@ import com.rebuild.core.service.DataSpecificationException;
public class DataValidateException extends DataSpecificationException { public class DataValidateException extends DataSpecificationException {
private static final long serialVersionUID = 4178910284594338317L; private static final long serialVersionUID = 4178910284594338317L;
private boolean weakMode = false; final private boolean weakMode;
final private ID weakModeTriggerId;
public DataValidateException(String msg) { public DataValidateException(String msg) {
super(msg); this(msg, false, null);
} }
public DataValidateException(String msg, boolean weakMode) { public DataValidateException(String msg, boolean weakMode) {
this(msg, weakMode, null);
}
public DataValidateException(String msg, boolean weakMode, ID triggerId) {
super(msg); super(msg);
this.weakMode = weakMode; this.weakMode = weakMode;
this.weakModeTriggerId = triggerId;
} }
public boolean isWeakMode() { public boolean isWeakMode() {
return weakMode; return weakMode;
} }
public ID getWeakModeTriggerId() {
return weakModeTriggerId;
}
} }

View file

@ -97,10 +97,6 @@ public class RobotTriggerManager implements ConfigManager {
recordId, StringUtils.join(TRIGGERS_CHAIN_4DEBUG.get(), "\n > ")); 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]); return actions.toArray(new TriggerAction[0]);
} }

View file

@ -16,6 +16,7 @@ import com.rebuild.core.service.general.OperatingContext;
import com.rebuild.core.service.general.OperatingObserver; import com.rebuild.core.service.general.OperatingObserver;
import com.rebuild.core.service.general.RepeatedRecordsException; import com.rebuild.core.service.general.RepeatedRecordsException;
import com.rebuild.core.service.trigger.impl.FieldAggregation; 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.CommonsLog;
import com.rebuild.core.support.general.FieldValueHelper; import com.rebuild.core.support.general.FieldValueHelper;
import com.rebuild.core.support.i18n.Language; 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"); 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 @Override
public int getOrder() { public int getOrder() {
return 4; return 4;
@ -191,7 +195,7 @@ public class RobotTriggerObserver extends OperatingObserver {
final int t = triggerSource.incrTriggerTimes(); final int t = triggerSource.incrTriggerTimes();
final String w = String.format("Trigger.%s.%d [ %s ] executing on record (%s) : %s", sourceId, t, action, when, primaryId); 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 { try {
Object res = action.execute(context); Object res = action.execute(context);
@ -247,7 +251,7 @@ public class RobotTriggerObserver extends OperatingObserver {
} finally { } finally {
if (originTriggerSource) { if (originTriggerSource) {
log.info("Clear trigger-source : {}", getTriggerSource()); if (!_TriggerLessLog) log.info("Clear trigger-source : {}", getTriggerSource());
TRIGGER_SOURCE.remove(); TRIGGER_SOURCE.remove();
} }
} }
@ -296,7 +300,7 @@ public class RobotTriggerObserver extends OperatingObserver {
if (ctx == null) return 0; if (ctx == null) return 0;
LAZY_TRIGGERS_CTX.remove(); LAZY_TRIGGERS_CTX.remove();
log.info("Will execute lazy triggers : {}", ctx); if (!_TriggerLessLog) log.info("Will execute lazy triggers : {}", ctx);
RobotTriggerObserver observer = new RobotTriggerObserver(); RobotTriggerObserver observer = new RobotTriggerObserver();
for (Object context : ctx) { for (Object context : ctx) {
observer.update(o, context); observer.update(o, context);

View file

@ -46,7 +46,7 @@ public class AviatorDate extends AviatorObject {
return dateValue.compareTo((Date) $date); 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; return -1;
} }

View file

@ -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;
}
}

View file

@ -7,19 +7,24 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.service.trigger.aviator; package com.rebuild.core.service.trigger.aviator;
import cn.devezhao.persist4j.engine.ID;
import com.googlecode.aviator.AviatorEvaluator; import com.googlecode.aviator.AviatorEvaluator;
import com.googlecode.aviator.AviatorEvaluatorInstance; import com.googlecode.aviator.AviatorEvaluatorInstance;
import com.googlecode.aviator.Options; import com.googlecode.aviator.Options;
import com.googlecode.aviator.exception.ExpressionSyntaxErrorException; import com.googlecode.aviator.exception.ExpressionSyntaxErrorException;
import com.googlecode.aviator.lexer.token.OperatorType; 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.function.system.AssertFunction;
import com.googlecode.aviator.runtime.type.AviatorFunction; 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 com.googlecode.aviator.runtime.type.Sequence;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
@ -132,7 +137,7 @@ public class AviatorUtils {
* @param function * @param function
*/ */
public static void addCustomFunction(final AviatorFunction function) { public static void addCustomFunction(final AviatorFunction function) {
log.info("Add custom function : {}", function); log.info("Add custom function : {}", function.getName());
AVIATOR.addFunction(function); AVIATOR.addFunction(function);
} }
@ -154,4 +159,16 @@ public class AviatorUtils {
throw new UnsupportedOperationException("Unsupport type : " + value); 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);
}
} }

View file

@ -9,9 +9,11 @@ package com.rebuild.core.service.trigger.aviator;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.googlecode.aviator.runtime.function.AbstractFunction; import com.googlecode.aviator.runtime.function.AbstractFunction;
import com.googlecode.aviator.runtime.type.AviatorNil;
import com.googlecode.aviator.runtime.type.AviatorObject; import com.googlecode.aviator.runtime.type.AviatorObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.UserContextHolder; import com.rebuild.core.UserContextHolder;
import com.rebuild.core.privileges.bizz.User;
import java.util.Map; import java.util.Map;
@ -27,9 +29,10 @@ public class CurrentBizunitFunction extends AbstractFunction {
@Override @Override
public AviatorObject call(Map<String, Object> env) { public AviatorObject call(Map<String, Object> env) {
ID user = UserContextHolder.getUser(); ID user = UserContextHolder.getReplacedUser();
ID bizunit = (ID) Application.getUserStore().getUser(user).getOwningBizUnit().getIdentity(); User ub = Application.getUserStore().getUser(user);
return new AviatorId(bizunit); if (ub.getOwningDept() == null) return AviatorNil.NIL;
return AviatorUtils.wrapReturn(ub.getOwningDept().getIdentity());
} }
@Override @Override

View file

@ -26,8 +26,8 @@ public class CurrentUserFunction extends AbstractFunction {
@Override @Override
public AviatorObject call(Map<String, Object> env) { public AviatorObject call(Map<String, Object> env) {
ID user = UserContextHolder.getUser(); ID user = UserContextHolder.getReplacedUser();
return new AviatorId(user); return AviatorUtils.wrapReturn(user);
} }
@Override @Override

View file

@ -13,7 +13,6 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
import com.rebuild.core.service.general.OperatingContext; import com.rebuild.core.service.general.OperatingContext;
@ -103,7 +102,7 @@ public class AutoAssign extends TriggerAction {
cascades = hasCascades.split(","); cascades = hasCascades.split(",");
} }
PrivilegesGuardContextHolder.setSkipGuard(recordId); GeneralEntityServiceContextHolder.setSkipGuard(recordId);
GeneralEntityServiceContextHolder.setFromTrigger(recordId); GeneralEntityServiceContextHolder.setFromTrigger(recordId);
try { try {
@ -116,7 +115,7 @@ public class AutoAssign extends TriggerAction {
} }
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); GeneralEntityServiceContextHolder.isSkipGuardOnce();
GeneralEntityServiceContextHolder.isFromTrigger(true); GeneralEntityServiceContextHolder.isFromTrigger(true);
} }

View file

@ -14,7 +14,6 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserHelper; import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.service.general.EntityService; import com.rebuild.core.service.general.EntityService;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
@ -78,13 +77,13 @@ public class AutoShare extends TriggerAction {
final EntityService es = Application.getEntityService(actionContext.getSourceEntity().getEntityCode()); final EntityService es = Application.getEntityService(actionContext.getSourceEntity().getEntityCode());
for (ID toUser : toUsers) { for (ID toUser : toUsers) {
PrivilegesGuardContextHolder.setSkipGuard(recordId); GeneralEntityServiceContextHolder.setSkipGuard(recordId);
GeneralEntityServiceContextHolder.setFromTrigger(recordId); GeneralEntityServiceContextHolder.setFromTrigger(recordId);
try { try {
es.share(recordId, toUser, cascades, shareRights); es.share(recordId, toUser, cascades, shareRights);
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); GeneralEntityServiceContextHolder.isSkipGuardOnce();
GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
} }
} }

View file

@ -22,7 +22,6 @@ import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType; import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserService; import com.rebuild.core.privileges.UserService;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; import com.rebuild.core.service.general.GeneralEntityServiceContextHolder;
import com.rebuild.core.service.general.OperatingContext; 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.query.QueryHelper;
import com.rebuild.core.service.trigger.ActionContext; import com.rebuild.core.service.trigger.ActionContext;
import com.rebuild.core.service.trigger.ActionType; 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.TriggerAction;
import com.rebuild.core.service.trigger.TriggerException; import com.rebuild.core.service.trigger.TriggerException;
import com.rebuild.core.service.trigger.TriggerResult; import com.rebuild.core.service.trigger.TriggerResult;
@ -123,7 +123,7 @@ public class FieldAggregation extends TriggerAction {
// 在整个触发链上只触发1次避免循环调用 // 在整个触发链上只触发1次避免循环调用
// FIXME 20220804 某些场景是否允许2次而非1次??? // FIXME 20220804 某些场景是否允许2次而非1次???
if (tschain.contains(chainName)) { if (tschain.contains(chainName)) {
log.warn(w + "!!! TRIGGER ONCE ONLY"); log.warn("{}!!! TRIGGER ONCE ONLY", w);
return null; return null;
} else { } else {
log.info(w); log.info(w);
@ -230,13 +230,13 @@ public class FieldAggregation extends TriggerAction {
// 有需要才执行 // 有需要才执行
if (targetRecord.isEmpty()) { 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(); return TriggerResult.targetEmpty();
} }
// 相等则不更新 // 相等则不更新
if (isCurrentSame(targetRecord)) { 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(); return TriggerResult.targetSame();
} }
@ -244,28 +244,30 @@ public class FieldAggregation extends TriggerAction {
final boolean stopPropagation = ((JSONObject) actionContext.getActionContent()).getBooleanValue("stopPropagation"); final boolean stopPropagation = ((JSONObject) actionContext.getActionContent()).getBooleanValue("stopPropagation");
// 跳过权限 // 跳过权限
PrivilegesGuardContextHolder.setSkipGuard(targetRecordId); GeneralEntityServiceContextHolder.setSkipGuard(targetRecordId);
// 强制更新 (v2.9) // 强制更新 (v2.9)
if (forceUpdate) { if (forceUpdate) {
GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId); GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId);
} }
// 快速模式 (v3.8)
if (stopPropagation) {
GeneralEntityServiceContextHolder.setQuickMode();
}
tschain.add(chainName); tschain.add(chainName);
TRIGGER_CHAIN.set(tschain); TRIGGER_CHAIN.set(tschain);
targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now()); targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now());
targetRecord.setID(EntityHelper.ModifiedBy, UserService.SYSTEM_USER);
try { try {
if (stopPropagation) { Application.getBestService(targetEntity).update(targetRecord);
Application.getCommonsService().update(targetRecord, false);
} else {
Application.getBestService(targetEntity).update(targetRecord);
}
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); GeneralEntityServiceContextHolder.isSkipGuardOnce();
if (forceUpdate) GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); if (forceUpdate) GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
if (stopPropagation) GeneralEntityServiceContextHolder.isQuickMode(true);
} }
if (operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == FieldAggregation.class) { if (operatingContext.getAction() == BizzPermission.UPDATE && this.getClass() == FieldAggregation.class) {

View file

@ -28,7 +28,6 @@ import com.rebuild.core.metadata.easymeta.EasyDateTime;
import com.rebuild.core.metadata.easymeta.EasyField; import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.easymeta.MultiValue; import com.rebuild.core.metadata.easymeta.MultiValue;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserService; import com.rebuild.core.privileges.UserService;
import com.rebuild.core.privileges.bizz.InternalPermission; import com.rebuild.core.privileges.bizz.InternalPermission;
import com.rebuild.core.service.general.GeneralEntityServiceContextHolder; 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.query.QueryHelper;
import com.rebuild.core.service.trigger.ActionContext; import com.rebuild.core.service.trigger.ActionContext;
import com.rebuild.core.service.trigger.ActionType; 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.TriggerException;
import com.rebuild.core.service.trigger.TriggerResult; import com.rebuild.core.service.trigger.TriggerResult;
import com.rebuild.core.service.trigger.aviator.AviatorUtils; import com.rebuild.core.service.trigger.aviator.AviatorUtils;
@ -50,7 +50,6 @@ import org.apache.commons.lang.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
@ -60,6 +59,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
/** /**
* 字段更新场景 1>1 1>N * 字段更新场景 1>1 1>N
@ -72,6 +72,8 @@ import java.util.Set;
@Slf4j @Slf4j
public class FieldWriteback extends FieldAggregation { public class FieldWriteback extends FieldAggregation {
private static final ReentrantLock LOCK = new ReentrantLock();
private FieldWritebackRefresh fieldWritebackRefresh; private FieldWritebackRefresh fieldWritebackRefresh;
/** /**
@ -109,6 +111,26 @@ public class FieldWriteback extends FieldAggregation {
@Override @Override
public Object execute(OperatingContext operatingContext) throws TriggerException { 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(), final String chainName = String.format("%s:%s:%s", actionContext.getConfigId(),
operatingContext.getFixedRecordId(), operatingContext.getAction().getName()); operatingContext.getFixedRecordId(), operatingContext.getAction().getName());
final List<String> tschain = checkTriggerChain(chainName); final List<String> tschain = checkTriggerChain(chainName);
@ -122,7 +144,7 @@ public class FieldWriteback extends FieldAggregation {
} }
if (targetRecordData.isEmpty()) { 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(); return TriggerResult.targetEmpty();
} }
@ -148,21 +170,26 @@ public class FieldWriteback extends FieldAggregation {
Record targetRecord = targetRecordData.clone(); Record targetRecord = targetRecordData.clone();
targetRecord.setID(targetEntity.getPrimaryField().getName(), targetRecordId); targetRecord.setID(targetEntity.getPrimaryField().getName(), targetRecordId);
targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now()); targetRecord.setDate(EntityHelper.ModifiedOn, CalendarUtils.now());
targetRecord.setID(EntityHelper.ModifiedBy, UserService.SYSTEM_USER);
// 相等则不更新 // 相等则不更新
if (isCurrentSame(targetRecord)) { 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; targetSame = true;
continue; continue;
} }
// 跳过权限 // 跳过权限
PrivilegesGuardContextHolder.setSkipGuard(targetRecordId); GeneralEntityServiceContextHolder.setSkipGuard(targetRecordId);
// 强制更新 (v2.9) // 强制更新 (v2.9)
if (forceUpdate) { if (forceUpdate) {
GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId); GeneralEntityServiceContextHolder.setAllowForceUpdate(targetRecordId);
} }
// 快速模式 (v3.8)
if (stopPropagation) {
GeneralEntityServiceContextHolder.setQuickMode();
}
// 重复检查模式 // 重复检查模式
GeneralEntityServiceContextHolder.setRepeatedCheckMode(GeneralEntityServiceContextHolder.RCM_CHECK_MAIN); 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); if (CommonsUtils.DEVLOG) System.out.println("[dev] Use current-loop tschain : " + tschainCurrentLoop);
try { try {
if (stopPropagation) { Application.getBestService(targetEntity).createOrUpdate(targetRecord);
Application.getCommonsService().update(targetRecord, false);
} else {
Application.getBestService(targetEntity).createOrUpdate(targetRecord);
}
affected.add(targetRecord.getPrimary()); affected.add(targetRecord.getPrimary());
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); GeneralEntityServiceContextHolder.isSkipGuardOnce();
if (forceUpdate) GeneralEntityServiceContextHolder.isAllowForceUpdateOnce(); if (forceUpdate) GeneralEntityServiceContextHolder.isAllowForceUpdateOnce();
if (stopPropagation) GeneralEntityServiceContextHolder.isQuickMode(true);
GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce(); GeneralEntityServiceContextHolder.getRepeatedCheckModeOnce();
} }
} }
@ -208,7 +232,7 @@ public class FieldWriteback extends FieldAggregation {
// v35 // v35
if (TARGET_ANY.equals(targetFieldEntity[0])) { if (TARGET_ANY.equals(targetFieldEntity[0])) {
TargetWithMatchFields targetWithMatchFields = new TargetWithMatchFields(); TargetWithMatchFields targetWithMatchFields = new TargetWithMatchFields();
ID[] ids = targetWithMatchFields.matchMulti(actionContext); ID[] ids = targetWithMatchFields.matchMultiple(actionContext);
CollectionUtils.addAll(targetRecordIds, ids); CollectionUtils.addAll(targetRecordIds, ids);
} }
// 自己更新自己 // 自己更新自己
@ -545,19 +569,16 @@ public class FieldWriteback extends FieldAggregation {
// v3.7 增强兼容 // v3.7 增强兼容
Object[] ids; Object[] ids;
if (value instanceof Collection) { if (value instanceof String) ids = value.toString().split(",");
//noinspection unchecked else ids = CommonsUtils.toArray(value);
ids = ((Collection<Object>) value).toArray(new Object[0]);
} else if (value instanceof Object[]) {
ids = (Object[]) value;
} else {
ids = value.toString().split(",");
}
Set<ID> idsSet = new LinkedHashSet<>(); Set<ID> idsSet = new LinkedHashSet<>();
for (Object id : ids) { for (Object id : ids) {
id = id.toString().trim(); if (id instanceof ID) idsSet.add((ID) id);
if (ID.isId(id)) idsSet.add(ID.valueOf(id.toString())); else {
id = id.toString().trim();
if (ID.isId(id)) idsSet.add(ID.valueOf(id.toString()));
}
} }
// v3.5.5: 目标值为多引用时保持 `ID[]` // v3.5.5: 目标值为多引用时保持 `ID[]`
newValue = idsSet.toArray(new ID[0]); newValue = idsSet.toArray(new ID[0]);

View file

@ -22,8 +22,8 @@ import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyField; import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory; import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.privileges.PrivilegesGuardContextHolder;
import com.rebuild.core.privileges.UserService; 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.general.OperatingContext;
import com.rebuild.core.service.trigger.ActionContext; import com.rebuild.core.service.trigger.ActionContext;
import com.rebuild.core.service.trigger.ActionType; 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 { try {
Application.getBestService(targetEntity).create(newTargetRecord); Application.getBestService(targetEntity).create(newTargetRecord);
} finally { } finally {
PrivilegesGuardContextHolder.getSkipGuardOnce(); GeneralEntityServiceContextHolder.isSkipGuardOnce();
} }
targetRecordId = newTargetRecord.getPrimary(); targetRecordId = newTargetRecord.getPrimary();

View file

@ -54,7 +54,7 @@ public class TargetWithMatchFields {
@Getter @Getter
private Object targetRecordId; private Object targetRecordId;
protected TargetWithMatchFields() { public TargetWithMatchFields() {
super(); super();
} }
@ -70,7 +70,7 @@ public class TargetWithMatchFields {
* @param actionContext * @param actionContext
* @return * @return
*/ */
public ID[] matchMulti(ActionContext actionContext) { public ID[] matchMultiple(ActionContext actionContext) {
Object o = match(actionContext, true); Object o = match(actionContext, true);
return o == null ? new ID[0] : (ID[]) o; return o == null ? new ID[0] : (ID[]) o;
} }
@ -103,7 +103,7 @@ public class TargetWithMatchFields {
if (MetadataHelper.getLastJoinField(sourceEntity, sourceField) == null) { if (MetadataHelper.getLastJoinField(sourceEntity, sourceField) == null) {
throw new MissingMetaExcetion(sourceField, sourceEntity.getName()); throw new MissingMetaExcetion(sourceField, sourceEntity.getName());
} }
if (!targetEntity.containsField(targetField)) { if (MetadataHelper.getLastJoinField(targetEntity, targetField) == null) {
throw new MissingMetaExcetion(targetField, targetEntity.getName()); throw new MissingMetaExcetion(targetField, targetEntity.getName());
} }
matchFieldsMapping.put(sourceField, targetField); matchFieldsMapping.put(sourceField, targetField);
@ -136,7 +136,7 @@ public class TargetWithMatchFields {
String targetField = e.getValue(); String targetField = e.getValue();
// @see Dimension#getSqlName // @see Dimension#getSqlName
EasyField sourceFieldEasy = EasyMetaFactory.valueOf(MetadataHelper.getLastJoinField(sourceEntity, sourceField)); 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 // fix: 3.7.1
boolean isDateField = sourceFieldEasy.getDisplayType() == DisplayType.DATE boolean isDateField = sourceFieldEasy.getDisplayType() == DisplayType.DATE

View file

@ -32,6 +32,8 @@ public class CommandArgs {
public static final String _StartEntityTypeCode = "_StartEntityTypeCode"; public static final String _StartEntityTypeCode = "_StartEntityTypeCode";
public static final String _UseFrontJSAnywhere = "_UseFrontJSAnywhere"; public static final String _UseFrontJSAnywhere = "_UseFrontJSAnywhere";
public static final String _TriggerMaxDepth = "_TriggerMaxDepth"; public static final String _TriggerMaxDepth = "_TriggerMaxDepth";
public static final String _ProtectedAdmin = "_ProtectedAdmin";
public static final String _TriggerLessLog = "_TriggerLessLog";
/** /**
* @param name * @param name

View file

@ -124,6 +124,7 @@ public enum ConfigurationItem {
SecurityEnhanced(false), // 安全增强 SecurityEnhanced(false), // 安全增强
TrustedAllUrl(false), // 可信外部地址 TrustedAllUrl(false), // 可信外部地址
LibreofficeBin, // Libreoffice 命令 LibreofficeBin, // Libreoffice 命令
MysqldumpBin, // mysqldump 命令
UnsafeImgAccess(false), // 不安全图片访问 UnsafeImgAccess(false), // 不安全图片访问
; ;
@ -143,8 +144,9 @@ public enum ConfigurationItem {
|| SecurityEnhanced.name().equalsIgnoreCase(name) || SecurityEnhanced.name().equalsIgnoreCase(name)
|| TrustedAllUrl.name().equalsIgnoreCase(name) || TrustedAllUrl.name().equalsIgnoreCase(name)
|| LibreofficeBin.name().equalsIgnoreCase(name) || LibreofficeBin.name().equalsIgnoreCase(name)
|| MysqldumpBin.name().equalsIgnoreCase(name)
|| UnsafeImgAccess.name().equals(name) || UnsafeImgAccess.name().equals(name)
|| SN.name().equalsIgnoreCase(name); || SN.name().equals(name);
} }
private Object defaultVal; private Object defaultVal;

View file

@ -119,6 +119,12 @@ public class KVStorage {
*/ */
protected static String getValue(final String key, boolean noCache, Object defaultValue) { protected static String getValue(final String key, boolean noCache, Object defaultValue) {
String value = null; 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()) { if (Application.isReady()) {
// 0. 从缓存 // 0. 从缓存

View file

@ -7,6 +7,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.support; package com.rebuild.core.support;
import cn.devezhao.commons.CalendarUtils;
import cn.devezhao.commons.CodecUtils; import cn.devezhao.commons.CodecUtils;
import cn.devezhao.commons.ExpiresMap; import cn.devezhao.commons.ExpiresMap;
import cn.devezhao.commons.identifier.ComputerIdentifier; import cn.devezhao.commons.identifier.ComputerIdentifier;
@ -19,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -53,11 +55,12 @@ public final class License {
} }
if (SN == null) { if (SN == null) {
SN = String.format("RB%s%s-%s-%s", SN = String.format("RB%s%s-%s%s%s",
Application.VER.charAt(0), Application.VER.charAt(0),
Locale.getDefault().getCountry().substring(0, 2), Locale.getDefault().getCountry().substring(0, 2),
ComputerIdentifier.generateIdentifierKey(), ComputerIdentifier.generateIdentifierKey(),
CodecUtils.randomCode(9)).toUpperCase(); CalendarUtils.format("wwyy", new Date()),
CodecUtils.randomCode(6)).toUpperCase();
RebuildConfiguration.setValue(ConfigurationItem.SN.name(), SN); RebuildConfiguration.setValue(ConfigurationItem.SN.name(), SN);
siteApiNoCache("api/authority/query"); siteApiNoCache("api/authority/query");
} }
@ -160,4 +163,5 @@ public final class License {
return JSONUtils.toJSONObject("error", "Call site api failed"); return JSONUtils.toJSONObject("error", "Call site api failed");
} }
} }
} }

View file

@ -45,15 +45,18 @@ public class SysbaseSupport {
} }
log.warn(confLog.append("----------").toString()); 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("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("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("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("Date", 31)).append(" : ").append(CalendarUtils.now()).append("\n");
osLog.append(StringUtils.rightPad("Headless", 31)).append(" : ").append(SystemUtils.isJavaAwtHeadless()).append("\n"); osLog.append(StringUtils.rightPad("Headless", 31)).append(" : ").append(SystemUtils.isJavaAwtHeadless()).append("\n");
log.warn(osLog.append("----------").toString()); 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(); File logFile = SysbaseHeartbeat.getLogbackFile();
JSONObject resJson; JSONObject resJson;

View file

@ -10,11 +10,16 @@ package com.rebuild.core.support.general;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.EncodeHintType; import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.MultiFormatWriter; import com.google.zxing.MultiFormatWriter;
import com.google.zxing.Result;
import com.google.zxing.WriterException; import com.google.zxing.WriterException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.client.j2se.MatrixToImageWriter;
import com.google.zxing.common.BitMatrix; import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.oned.Code128Writer; import com.google.zxing.oned.Code128Writer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import com.rebuild.core.RebuildException; import com.rebuild.core.RebuildException;
@ -25,6 +30,7 @@ import com.rebuild.utils.AppUtils;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import javax.imageio.ImageIO;
import java.awt.*; import java.awt.*;
import java.awt.geom.AffineTransform; import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D;
@ -250,4 +256,26 @@ public class BarCodeSupport {
return fm; 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;
}
}
} }

View file

@ -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;
}
}

View file

@ -16,7 +16,9 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.UserContextHolder;
import com.rebuild.core.configuration.ConfigBean; 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.MultiSelectManager;
import com.rebuild.core.configuration.general.PickListManager; import com.rebuild.core.configuration.general.PickListManager;
import com.rebuild.core.metadata.easymeta.DisplayType; import com.rebuild.core.metadata.easymeta.DisplayType;
@ -31,6 +33,7 @@ import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -56,6 +59,8 @@ public class DataListWrapper {
private boolean mixWrapper = true; private boolean mixWrapper = true;
private Map<ID, Object> cacheRefValue = new HashMap<>();
/** /**
* @param total * @param total
* @param data * @param data
@ -111,6 +116,10 @@ public class DataListWrapper {
Object nameValue = null; Object nameValue = null;
for (int colIndex = 0; colIndex < selectFieldsLen; colIndex++) { for (int colIndex = 0; colIndex < selectFieldsLen; colIndex++) {
if (!checkHasFieldPrivileges(selectFields[colIndex])) {
row[colIndex] = FieldValueHelper.NO_READ_PRIVILEGES;
continue;
}
if (!checkHasJoinFieldPrivileges(selectFields[colIndex], raw)) { if (!checkHasJoinFieldPrivileges(selectFields[colIndex], raw)) {
row[colIndex] = FieldValueHelper.NO_READ_PRIVILEGES; row[colIndex] = FieldValueHelper.NO_READ_PRIVILEGES;
continue; continue;
@ -169,6 +178,11 @@ public class DataListWrapper {
final DisplayType dt = easyField.getDisplayType(); final DisplayType dt = easyField.getDisplayType();
final Object originValue = value; 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 boolean unpack = dt == DisplayType.CLASSIFICATION || dt == DisplayType.PICKLIST
|| dt == DisplayType.STATE || dt == DisplayType.BOOL; || dt == DisplayType.STATE || dt == DisplayType.BOOL;
@ -232,9 +246,17 @@ public class DataListWrapper {
new String[]{ "name", "color" }, new Object[]{ name, colorNames.get(name) })); new String[]{ "name", "color" }, new Object[]{ name, colorNames.get(name) }));
} }
value = colorValue; 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; return value;
} }
@ -251,6 +273,7 @@ public class DataListWrapper {
* @param field * @param field
* @param original * @param original
* @return * @return
* @see #checkHasFieldPrivileges(SelectItem)
*/ */
protected boolean checkHasJoinFieldPrivileges(SelectItem field, Object[] original) { protected boolean checkHasJoinFieldPrivileges(SelectItem field, Object[] original) {
if (this.queryJoinFields == null || UserHelper.isAdmin(user)) { if (this.queryJoinFields == null || UserHelper.isAdmin(user)) {
@ -267,6 +290,15 @@ public class DataListWrapper {
return check == null || Application.getPrivilegesManager().allowRead(user, (ID) check); 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);
}
/** /**
* 进一步封装查询结果 * 进一步封装查询结果
* *

View file

@ -130,7 +130,6 @@ public class FieldValueHelper {
} catch (JSONException ex) { } catch (JSONException ex) {
log.error("Bad JSONArray format : {} < {}", value, field.getRawMeta()); log.error("Bad JSONArray format : {} < {}", value, field.getRawMeta());
return JSONUtils.EMPTY_ARRAY; return JSONUtils.EMPTY_ARRAY;
// throw new RebuildException("BAD VALUE FORMAT:" + value);
} }
} }

View file

@ -7,21 +7,19 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.core.support.general; package com.rebuild.core.support.general;
import cn.devezhao.commons.ObjectUtils;
import cn.devezhao.persist4j.Entity; import cn.devezhao.persist4j.Entity;
import cn.devezhao.persist4j.Field; import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.dialect.FieldType; import cn.devezhao.persist4j.dialect.FieldType;
import cn.devezhao.persist4j.engine.ID; import cn.devezhao.persist4j.engine.ID;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.configuration.ConfigBean; import com.rebuild.core.configuration.ConfigBean;
import com.rebuild.core.configuration.general.AdvFilterManager; import com.rebuild.core.configuration.general.AdvFilterManager;
import com.rebuild.core.configuration.general.ClassificationManager; import com.rebuild.core.configuration.general.DataListCategory38;
import com.rebuild.core.configuration.general.DataListCategory;
import com.rebuild.core.configuration.general.ViewAddonsManager; import com.rebuild.core.configuration.general.ViewAddonsManager;
import com.rebuild.core.metadata.EntityHelper; import com.rebuild.core.metadata.EntityHelper;
import com.rebuild.core.metadata.MetadataHelper; 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.easymeta.EasyMetaFactory;
import com.rebuild.core.metadata.impl.EasyFieldConfigProps; import com.rebuild.core.metadata.impl.EasyFieldConfigProps;
import com.rebuild.core.service.dashboard.ChartManager; import com.rebuild.core.service.dashboard.ChartManager;
@ -33,7 +31,6 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.text.MessageFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -232,45 +229,13 @@ public class ProtocolFilterParser {
* @param value * @param value
* @return * @return
* @see #P_CATEGORY * @see #P_CATEGORY
* @see DataListCategory * @see DataListCategory38#buildParentFilters(Entity, Object[])
*/ */
protected String parseCategory(String entity, String value) { protected String parseCategory(String entity, String value) {
Entity rootEntity = MetadataHelper.getEntity(entity); String[] filterValues = value.split(CommonsUtils.COMM_SPLITER_RE);
Field categoryField = DataListCategory.instance.getFieldOfCategory(rootEntity); String where = DataListCategory38.instance.buildParentFilters(MetadataHelper.getEntity(entity), filterValues);
if (categoryField == null || StringUtils.isBlank(value)) return "(9=9)"; if (Application.devMode()) log.info("[dev] Parse category : {} >> {}", value, where);
return where;
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);
} }
/** /**
@ -278,7 +243,6 @@ public class ProtocolFilterParser {
* @param mainid * @param mainid
* @return * @return
* @see #P_RELATED * @see #P_RELATED
* @see com.rebuild.web.general.RelatedListController#buildBaseSql(ID, String, String, boolean, ID)
*/ */
public String parseRelated(String relatedExpr, ID mainid) { public String parseRelated(String relatedExpr, ID mainid) {
// format: Entity.Field // format: Entity.Field
@ -297,7 +261,7 @@ public class ProtocolFilterParser {
relatedField.getOwnEntity().getPrimaryField().getName(), relatedField.getName(), mainid); relatedField.getOwnEntity().getPrimaryField().getName(), relatedField.getName(), mainid);
} }
// 过滤条件 // 过滤条件
Map<String, JSONObject> vtabFilters = ViewAddonsManager.instance.getViewTabFilters( Map<String, JSONObject> vtabFilters = ViewAddonsManager.instance.getViewTabFilters(
MetadataHelper.getEntity(mainid.getEntityCode()).getName()); MetadataHelper.getEntity(mainid.getEntityCode()).getName());

View file

@ -277,6 +277,7 @@ public class SMSender {
* @return * @return
*/ */
protected static Element getMailTemplate() { protected static Element getMailTemplate() {
if (Application.devMode()) MT_CACHE = null;
if (MT_CACHE != null) return MT_CACHE.clone(); if (MT_CACHE != null) return MT_CACHE.clone();
String content = CommonsUtils.getStringOfRes("i18n/email.zh_CN.html"); String content = CommonsUtils.getStringOfRes("i18n/email.zh_CN.html");

View file

@ -9,6 +9,7 @@ package com.rebuild.core.support.setup;
import cn.devezhao.commons.CalendarUtils; import cn.devezhao.commons.CalendarUtils;
import com.rebuild.core.BootEnvironmentPostProcessor; import com.rebuild.core.BootEnvironmentPostProcessor;
import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration; import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.utils.CommandUtils; import com.rebuild.utils.CommandUtils;
import com.rebuild.utils.CompressUtils; import com.rebuild.utils.CompressUtils;
@ -70,12 +71,13 @@ public class DatabaseBackup {
String destName = "backup_database." + CalendarUtils.getPlainDateTimeFormat().format(CalendarUtils.now()); String destName = "backup_database." + CalendarUtils.getPlainDateTimeFormat().format(CalendarUtils.now());
File dest = new File(backups, destName); 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 // https://blog.csdn.net/liaowenxiong/article/details/120587358
// --master-data --flush-logs // --master-data --flush-logs
String cmd = String.format( 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", "%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", mysqldump, user, passwd, host, port, dbname, dest.getAbsolutePath());
user, passwd, host, port, dbname, dest.getAbsolutePath());
if (ignoreTables != null) { if (ignoreTables != null) {
String igPrefix = " --ignore-table=" + dbname + "."; String igPrefix = " --ignore-table=" + dbname + ".";
@ -83,7 +85,7 @@ public class DatabaseBackup {
cmd = cmd.replaceFirst(" -R ", " -R" + ig + " "); cmd = cmd.replaceFirst(" -R ", " -R" + ig + " ");
} }
String echo = CommandUtils.execFor(cmd); String echo = CommandUtils.execFor(cmd, true);
boolean isGotError = echo.contains("Got error"); boolean isGotError = echo.contains("Got error");
if (isGotError) throw new RuntimeException(echo); if (isGotError) throw new RuntimeException(echo);

View file

@ -17,6 +17,7 @@ import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration; import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.core.support.i18n.LanguageBundle; import com.rebuild.core.support.i18n.LanguageBundle;
import com.rebuild.web.admin.AdminVerfiyController; import com.rebuild.web.admin.AdminVerfiyController;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -27,6 +28,7 @@ import javax.servlet.http.HttpServletRequest;
* @author Zixin (RB) * @author Zixin (RB)
* @since 05/19/2018 * @since 05/19/2018
*/ */
@Slf4j
public class AppUtils { public class AppUtils {
// Token 认证 // Token 认证
@ -91,7 +93,13 @@ public class AppUtils {
* @see #getRequestUserViaToken(HttpServletRequest, boolean) * @see #getRequestUserViaToken(HttpServletRequest, boolean)
*/ */
public static ID getRequestUser(HttpServletRequest request, boolean refreshToken) { 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); if (user == null) user = getRequestUserViaToken(request, refreshToken);
return user == null ? null : (ID) user; return user == null ? null : (ID) user;
} }

View file

@ -26,14 +26,15 @@ public class CommandUtils {
* 执行命令行 * 执行命令行
* *
* @param cmd * @param cmd
* @param secure
* @return * @return
* @throws IOException * @throws IOException
*/ */
public static String execFor(String cmd) throws IOException { public static String execFor(String cmd, boolean secure) throws IOException {
ProcessBuilder builder = new ProcessBuilder(); ProcessBuilder builder = new ProcessBuilder();
String encoding = "UTF-8"; String encoding = "UTF-8";
log.info("CMD : {}", cmd); if (!secure) log.info("CMD : {}", cmd);
if (SystemUtils.IS_OS_WINDOWS) { if (SystemUtils.IS_OS_WINDOWS) {
builder.command("cmd.exe", "/c", cmd); builder.command("cmd.exe", "/c", cmd);

View file

@ -32,9 +32,11 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.net.URI; import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -256,6 +258,7 @@ public class CommonsUtils {
Class<?>[] paramTypes = new Class<?>[args.length]; Class<?>[] paramTypes = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
if (args[i] == null) args[i] = new Object();
paramTypes[i] = args[i].getClass(); paramTypes[i] = args[i].getClass();
} }
@ -371,4 +374,23 @@ public class CommonsUtils {
return null; 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};
}
} }

View file

@ -22,10 +22,8 @@ import javax.servlet.http.HttpServletResponse;
*/ */
public class Etag { public class Etag {
private static final String DIRECTIVE_NO_STORE = "no-store"; final private String responseEtag;
transient final private HttpServletResponse response;
private String responseETag;
transient private HttpServletResponse response;
/** /**
* @param etag * @param etag
@ -34,10 +32,10 @@ public class Etag {
public Etag(String etag, HttpServletResponse response) { public Etag(String etag, HttpServletResponse response) {
// whether the generated ETag should be weak // whether the generated ETag should be weak
// SPEC: length of W/ + " + 0 + 32bits md5 hash + " // SPEC: length of W/ + " + 0 + 32bits md5 hash + "
String responseETag = String.format("W/\"0%s\"", etag); String responseEtag = String.format("W/\"0%s\"", etag);
response.setHeader(HttpHeaders.ETAG, responseETag); response.setHeader(HttpHeaders.ETAG, responseEtag);
this.responseETag = responseETag; this.responseEtag = responseEtag;
this.response = response; this.response = response;
} }
@ -48,7 +46,7 @@ public class Etag {
*/ */
protected boolean isForceNoCache() { protected boolean isForceNoCache() {
String cacheControl = response.getHeader(HttpHeaders.CACHE_CONTROL); 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 * @return
*/ */
protected boolean isMatchEtag(HttpServletRequest request, boolean writeStatusIfMatch) { protected boolean isMatchEtag(HttpServletRequest request, boolean writeStatusIfMatch) {
String requestETag = request.getHeader(HttpHeaders.IF_NONE_MATCH); String requestEtag = request.getHeader(HttpHeaders.IF_NONE_MATCH);
if (requestETag != null && if (requestEtag != null &&
("*".equals(requestETag) || responseETag.equals(requestETag) || ("*".equals(requestEtag) || responseEtag.equals(requestEtag) ||
responseETag.replaceFirst("^W/", "").equals(requestETag.replaceFirst("^W/", "")))) { responseEtag.replaceFirst("^W/", "").equals(requestEtag.replaceFirst("^W/", "")))) {
if (writeStatusIfMatch) { if (writeStatusIfMatch) response.setStatus(HttpStatus.NOT_MODIFIED.value());
response.setStatus(HttpStatus.NOT_MODIFIED.value());
}
return true; return true;
} else { } else {
return false; return false;

View file

@ -8,30 +8,20 @@ See LICENSE and COMMERCIAL in the project root for license information.
package com.rebuild.utils; package com.rebuild.utils;
import com.rebuild.core.Application; import com.rebuild.core.Application;
import com.rebuild.core.service.datareport.ReportsException;
import com.rebuild.core.support.ConfigurationItem; import com.rebuild.core.support.ConfigurationItem;
import com.rebuild.core.support.RebuildConfiguration; import com.rebuild.core.support.RebuildConfiguration;
import com.rebuild.utils.poi.ToHtml;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.SystemUtils; 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.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.springframework.util.Assert; import org.jsoup.nodes.Element;
import org.zwobble.mammoth.DocumentConverter;
import org.zwobble.mammoth.Result;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Iterator; import java.util.Objects;
import java.util.Set;
/** /**
* Office 文件转换 * Office 文件转换
@ -93,7 +83,6 @@ public class PdfConverter {
final String pathFileName = path.getFileName().toString(); final String pathFileName = path.getFileName().toString();
final boolean isExcel = pathFileName.endsWith(".xlsx") || pathFileName.endsWith(".xls"); final boolean isExcel = pathFileName.endsWith(".xlsx") || pathFileName.endsWith(".xls");
final boolean isWord = pathFileName.endsWith(".docx") || pathFileName.endsWith(".doc");
// Excel 公式生效 // Excel 公式生效
if (isExcel) { if (isExcel) {
ExcelUtils.reSaveAndCalcFormula(path); ExcelUtils.reSaveAndCalcFormula(path);
@ -108,97 +97,55 @@ public class PdfConverter {
else return dest.toPath(); 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 // alias
String soffice = RebuildConfiguration.get(ConfigurationItem.LibreofficeBin); String soffice = RebuildConfiguration.get(ConfigurationItem.LibreofficeBin);
if (StringUtils.isBlank(soffice)) soffice = SystemUtils.IS_OS_WINDOWS ? "soffice.exe" : "libreoffice"; 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 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 (!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; private static String TEMPALTE_HTML;
/** /**
* Word to HTML * @param sourceHtml
* * @param title
* @param source
* @param dest
* @throws IOException * @throws IOException
*/ */
protected static void convertWord2Html(Path source, File dest) throws IOException { private static void fixHtml(File sourceHtml, String title) throws IOException {
if (TEMPALTE_HTML == null || Application.devMode()) { if (TEMPALTE_HTML == null || Application.devMode()) TEMPALTE_HTML = CommonsUtils.getStringOfRes("i18n/html-report.html");
TEMPALTE_HTML = CommonsUtils.getStringOfRes("i18n/html-report.html"); if (TEMPALTE_HTML == null) return;
}
Assert.notNull(TEMPALTE_HTML, "TEMPALTE_HTML MISSING");
DocumentConverter converter = new DocumentConverter(); final Document template = Jsoup.parse(TEMPALTE_HTML);
Result<String> result = converter.convertToHtml(source.toFile()); final Element body = template.body();
String cHtml = result.getValue();
Set<String> cWarnings = result.getWarnings(); final Document source = Jsoup.parse(sourceHtml);
if (!cWarnings.isEmpty()) {
log.warn("HTML convert warnings : {}", cWarnings); // 提取表格
for (Element table : source.select("body>table")) {
Element page = body.appendElement("div").addClass("page");
page.appendChild(table);
} }
Document html = Jsoup.parse(TEMPALTE_HTML); // 图片添加 temp=yes
html.body().append("<div class=\"paper word\">" + cHtml + "</div>"); for (Element img : body.select("img")) {
html.title(source.getFileName().toString()); String src = img.attr("src");
if (!src.startsWith("data:")) {
FileUtils.writeStringToFile(dest, html.html(), AppUtils.UTF8); img.attr("src", src + "?temp=yes");
}
/**
* 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>");
} }
} }
Document html = Jsoup.parse(TEMPALTE_HTML); // TITLE
html.body().append(output.toString()); if (title == null) title = sourceHtml.getName();
html.title(source.getFileName().toString()); Objects.requireNonNull(template.head().selectFirst("title")).text(title);
FileUtils.writeStringToFile(dest, html.html(), AppUtils.UTF8); FileUtils.writeStringToFile(sourceHtml, template.html(), "UTF-8");
} }
} }

View file

@ -22,10 +22,9 @@ public class RbAssert {
* @param message * @param message
*/ */
public static void isCommercial(String message) { public static void isCommercial(String message) {
if (!License.isCommercial()) { if (License.isRbvAttached()) return;
if (message == null) message = Language.L("免费版不支持此功能"); if (message == null) message = Language.L("免费版不支持此功能");
throw new NeedRbvException(message); throw new NeedRbvException(message);
}
} }
/** /**

View file

@ -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