trigger: AutoUpdate

This commit is contained in:
devezhao 2021-03-31 02:00:33 +08:00
parent caaa7ace8f
commit a8ae03afd1
9 changed files with 402 additions and 8 deletions

View file

@ -30,6 +30,8 @@ public enum ActionType {
HOOKURL("回调 URL", "com.rebuild.rbv.trigger.HookUrl"),
AUTOTRANSFORM("自动记录转换", "com.rebuild.rbv.trigger.AutoTransform"),
AUTOUPDATE("自动更新", AutoUpdate.class),
;
private String displayName;

View file

@ -0,0 +1,108 @@
/*
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.impl;
import cn.devezhao.persist4j.Field;
import cn.devezhao.persist4j.Record;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.rebuild.core.Application;
import com.rebuild.core.metadata.MetadataHelper;
import com.rebuild.core.metadata.easymeta.DisplayType;
import com.rebuild.core.metadata.easymeta.EasyDateTime;
import com.rebuild.core.metadata.easymeta.EasyField;
import com.rebuild.core.metadata.easymeta.EasyMetaFactory;
import com.rebuild.core.service.trigger.ActionContext;
import com.rebuild.core.service.trigger.ActionType;
import org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.Map;
/**
* @author devezhao
* @since 2021/3/30
*/
public class AutoUpdate extends FieldAggregation {
private static final String EXPR_SPLIT = "#";
public AutoUpdate(ActionContext context) {
super(context, Boolean.TRUE, 5);
}
@Override
public ActionType getType() {
return ActionType.AUTOUPDATE;
}
@Override
protected void buildTargetRecord(Record record, String dataFilterSql) {
Map<String, String> exprsMap = new HashMap<>();
JSONArray items = ((JSONObject) context.getActionContent()).getJSONArray("items");
Map<String, String> t2sMap = new HashMap<>();
for (Object o : items) {
JSONObject item = (JSONObject) o;
String targetField = item.getString("targetField");
String sourceField = item.getString("sourceField");
// 公式
if (sourceField.contains(EXPR_SPLIT)) {
exprsMap.put(targetField, sourceField.split(EXPR_SPLIT)[1]);
sourceField = sourceField.split(EXPR_SPLIT)[0];
}
if (!MetadataHelper.checkAndWarnField(targetEntity, targetField)
|| MetadataHelper.getLastJoinField(sourceEntity, sourceField) == null) {
continue;
}
t2sMap.put(targetField, sourceField);
}
if (t2sMap.isEmpty()) {
return;
}
String sql = String.format("select %s from %s where %s = ?",
StringUtils.join(t2sMap.values(), ","), sourceEntity.getName(), followSourceField);
final Record o = Application.createQueryNoFilter(sql)
.setParameter(1, targetRecordId)
.record();
if (o == null) {
return;
}
for (Map.Entry<String, String> e : t2sMap.entrySet()) {
Object value = o.getObjectValue(e.getValue());
// NOTE 忽略空值
if (value == null) {
continue;
}
Field sourceField = MetadataHelper.getLastJoinField(sourceEntity, e.getValue());
if (sourceField == null) continue;
Field targetField = targetEntity.getField(e.getKey());
Object newValue;
EasyField sourceFieldEasy = EasyMetaFactory.valueOf(sourceField);
if (sourceFieldEasy.getDisplayType() == DisplayType.DATETIME
|| sourceFieldEasy.getDisplayType() == DisplayType.DATE) {
newValue = ((EasyDateTime) sourceFieldEasy)
.convertCompatibleValue(value, EasyMetaFactory.valueOf(targetField), exprsMap.get(e.getKey()));
} else {
newValue = sourceFieldEasy.convertCompatibleValue(value, EasyMetaFactory.valueOf(targetField));
}
if (newValue != null) {
record.setObjectValue(targetField.getName(), newValue);
}
}
}
}

View file

@ -31,7 +31,9 @@ import java.util.Map;
* @author devezhao
* @see AutoFillinManager
* @since 2020/2/7
* @deprecated Use {@link AutoUpdate}
*/
@Deprecated
public class FieldWriteback extends FieldAggregation {
private static final String EXPR_SPLIT = "#";

View file

@ -77,7 +77,7 @@ public class FieldWritebackController extends BaseController {
for (Field field : MetadataSorter.sortFields(targetEntity)) {
EasyField easyField = EasyMetaFactory.valueOf(field);
DisplayType dt = easyField.getDisplayType();
if (dt == DisplayType.SERIES || easyField.isBuiltin()) {
if (dt == DisplayType.SERIES || dt == DisplayType.BARCODE || easyField.isBuiltin()) {
continue;
}
targetFields.add(EasyMetaFactory.toJSON(field));

View file

@ -1330,6 +1330,10 @@
"SysConf": "系统选项",
"ViewInList": "列表页查看",
"FormAdvRequiredTip": "本选项仅做前端效验",
"UpdateRule": "更新规则",
"UpdateMode": "更新方式",
"UpdateByField": "字段值",
"UpdateByValue": "固定值",
"s.__": "状态",
"s.ApprovalState.DRAFT": "草稿",

View file

@ -99,6 +99,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
text-align: left;
}
.field-aggregation .items > div .badge.badge-light {
padding: 2px 10px;
}
.field-aggregation .items > div .col-2 > .zmdi {
margin-top: 6px;
}
@ -117,14 +121,13 @@ See LICENSE and COMMERCIAL in the project root for license information.
.field-aggregation .items .del-wrap a.del {
position: absolute;
display: inline-block;
font-size: 1.2rem;
padding: 3px 6px;
color: #999;
font-weight: bold;
right: 15px;
display: none;
top: 2px;
top: 1px;
}
.field-aggregation .items .del-wrap a.del:hover {

View file

@ -0,0 +1,277 @@
/*
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.
*/
const UPDATE_MODES = {
FIELD: $L('UpdateByField'),
VALUE: $L('UpdateByValue'),
NULLV: $L('BatchUpdateOpNULL'),
FORMULA: $L('CalcFORMULA'),
}
// ~~ 自动更新字段
// eslint-disable-next-line no-undef
class ContentAutoUpdate extends ActionContentSpec {
constructor(props) {
super(props)
this.state.updateMode = 'FIELD'
}
render() {
return (
<div className="field-aggregation">
<form className="simple">
<div className="form-group row">
<label className="col-md-12 col-lg-3 col-form-label text-lg-right">{$L('TargetEntity')}</label>
<div className="col-md-12 col-lg-9">
<div className="row">
<div className="col-5">
<select className="form-control form-control-sm" ref={(c) => (this._targetEntity = c)}>
{(this.state.targetEntities || []).map((item) => {
const val = `${item[2]}.${item[0]}`
return (
<option key={val} value={val}>
{item[1]}
</option>
)
})}
</select>
</div>
</div>
{this.state.hadApproval && (
<div className="form-text text-danger">
<i className="zmdi zmdi-alert-triangle fs-16 down-1 mr-1"></i>
{$L('TriggerTargetEntityTips')}
</div>
)}
</div>
</div>
<div className="form-group row">
<label className="col-md-12 col-lg-3 col-form-label text-lg-right">{$L('UpdateRule')}</label>
<div className="col-md-12 col-lg-9">
<div className="items">
{(this.state.items || []).length > 0 &&
this.state.items.map((item) => {
return (
<div key={item.targetField}>
<div className="row">
<div className="col-5">
<span className="badge badge-warning">{this._getFieldLabel(this.state.targetFields, item.targetField)}</span>
</div>
<div className="col-2">
<span className="zmdi zmdi-forward zmdi-hc-rotate-180"></span>
<span className="badge badge-warning">{UPDATE_MODES[item.updateMode]}</span>
</div>
<div className="col-5 del-wrap">
{item.updateMode === 'FIELD' && <span className="badge badge-warning">{this._getFieldLabel(this.state.sourceFields, item.source)}</span>}
{item.updateMode === 'VALUE' && <span className="badge badge-light">{item.source}</span>}
<a className="del" title={$L('Remove')} onClick={() => this.delItem(item.targetField)}>
<span className="zmdi zmdi-close"></span>
</a>
</div>
</div>
</div>
)
})}
</div>
<div className="row">
<div className="col-5">
<select className="form-control form-control-sm" ref={(c) => (this._targetField = c)}>
{(this.state.targetFields || []).map((item) => {
return (
<option key={item.name} value={item.name}>
{item.label}
</option>
)
})}
</select>
<p>{$L('TargetField')}</p>
</div>
<div className="col-2 pr-0">
<span className="zmdi zmdi-forward zmdi-hc-rotate-180"></span>
<select className="form-control form-control-sm" ref={(c) => (this._updateMode = c)}>
{Object.keys(UPDATE_MODES).map((item) => {
return (
<option key={item} value={item}>
{UPDATE_MODES[item]}
</option>
)
})}
</select>
<p>{$L('UpdateMode')}</p>
</div>
<div className="col-5">
<div className={this.state.updateMode === 'FIELD' ? '' : 'hide'}>
<select className="form-control form-control-sm" ref={(c) => (this._sourceField = c)}>
{(this.state.sourceFields || []).map((item) => {
return (
<option key={item.name} value={item.name}>
{item.label}
</option>
)
})}
</select>
<p>{$L('SourceField')}</p>
</div>
<div className={this.state.updateMode === 'VALUE' ? '' : 'hide'}>
<input type="text" className="form-control form-control-sm" ref={(c) => (this._sourceValue = c)} />
</div>
<div className={this.state.updateMode === 'FORMULA' ? '' : 'hide'}>
<div className="form-control-plaintext formula" _title={$L('CalcFORMULA')} ref={(c) => (this._sourceFormula = c)} onClick={this.showFormula}></div>
<p>{$L('CalcFORMULA')}</p>
</div>
</div>
</div>
<div className="mt-1">
<button type="button" className="btn btn-primary btn-sm btn-outline" onClick={() => this.addItem()}>
+ {$L('Add')}
</button>
</div>
</div>
</div>
<div className="form-group row pb-0">
<label className="col-md-12 col-lg-3 col-form-label text-lg-right"></label>
<div className="col-md-12 col-lg-9">
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline mb-0">
<input className="custom-control-input" type="checkbox" ref={(c) => (this._readonlyFields = c)} />
<span className="custom-control-label">{$L('SetTargetFieldReadonly')}</span>
</label>
</div>
</div>
</form>
</div>
)
}
componentDidMount() {
const content = this.props.content
this.__select2 = []
$.get(`/admin/robot/trigger/field-aggregation-entities?source=${this.props.sourceEntity}`, (res) => {
this.setState({ targetEntities: res.data }, () => {
const s2te = $(this._targetEntity)
.select2({ placeholder: $L('SelectSome,TargetEntity') })
.on('change', () => this.changeTargetEntity())
if (content && content.targetEntity) {
s2te.val(content.targetEntity)
if (rb.env !== 'dev') s2te.attr('disabled', true)
}
s2te.trigger('change')
this.__select2.push(s2te)
})
})
if (content) {
$(this._readonlyFields).attr('checked', content.readonlyFields === true)
}
}
changeTargetEntity() {
const te = ($(this._targetEntity).val() || '').split('.')[1]
if (!te) return
// 清空现有规则
this.setState({ items: [] })
$.get(`/admin/robot/trigger/field-writeback-fields?source=${this.props.sourceEntity}&target=${te}`, (res) => {
this.setState({ hadApproval: res.data.hadApproval })
if (this.state.targetFields) {
this.setState({ targetFields: res.data.target })
} else {
this.setState({ sourceFields: res.data.source, targetFields: res.data.target }, () => {
const s2sf = $(this._sourceField)
.select2({ placeholder: $L('SelectSome,SourceField') })
.on('change', (e) => {
console.log(e.target.value)
})
const s2um = $(this._updateMode)
.select2({ placeholder: $L('SelectSome,UpdateMode') })
.on('change', (e) => this.setState({ updateMode: e.target.value }))
const s2tf = $(this._targetField).select2({ placeholder: $L('SelectSome,TargetField') })
this.__select2.push(s2sf)
this.__select2.push(s2um)
this.__select2.push(s2tf)
})
if (this.props.content) this.setState({ items: this.props.content.items || [] })
}
})
}
_getFieldLabel(fields, field) {
let found = fields.find((x) => x[0] === field)
if (found) found = found[1]
return found || '[' + field.toUpperCase() + ']'
}
addItem() {
const tf = $(this._targetField).val()
const mode = $(this._updateMode).val()
if (!tf) {
RbHighbar.create($L('PlsSelectSome,TargetField'))
return false
}
let sourceAny = null
if (mode === 'FIELD') {
sourceAny = $(this._sourceField).val()
// 目标字段=源字段
if (sourceAny === $(this._targetEntity).val().split('.')[0] + '.' + tf) {
RbHighbar.create($L('TargetAndSourceNotSame'))
return false
}
} else if (mode === 'VALUE') {
sourceAny = $(this._sourceValue).val()
if (!sourceAny) {
RbHighbar.create('填写值')
return false
}
}
const items = this.state.items || []
const exists = items.find((x) => x.targetField === tf)
if (exists) {
RbHighbar.create($L('SomeDuplicate,TargetField'))
return false
}
items.push({ targetField: tf, updateMode: mode, source: sourceAny })
this.setState({ items: items })
}
delItem(targetField) {
const items = (this.state.items || []).filter((item) => {
return item.targetField !== targetField
})
this.setState({ items: items })
}
buildContent() {
const content = {
targetEntity: $(this._targetEntity).val(),
items: this.state.items,
readonlyFields: $(this._readonlyFields).prop('checked'),
}
if (!content.targetEntity) {
RbHighbar.create($L('PlsSelectSome,TargetEntity'))
return false
}
if (content.items.length === 0) {
RbHighbar.create($L('PlsAdd1AggregationRuleLeast'))
return false
}
return content
}
}
// eslint-disable-next-line no-undef
renderContentComp = function (props) {
renderRbcomp(<ContentAutoUpdate {...props} />, 'react-content', function () {
// eslint-disable-next-line no-undef
contentComp = this
})
}

View file

@ -45,7 +45,7 @@ class ContentFieldAggregation extends ActionContentSpec {
</div>
{this.state.hadApproval && (
<div className="form-text text-danger">
<i className="zmdi zmdi-alert-triangle fs-16 down-1"></i>
<i className="zmdi zmdi-alert-triangle fs-16 down-1 mr-1"></i>
{$L('TriggerTargetEntityTips')}
</div>
)}
@ -109,9 +109,7 @@ class ContentFieldAggregation extends ActionContentSpec {
<div className="col-5">
<div className={this.state.calcMode === 'FORMULA' ? '' : 'hide'}>
<div className="form-control-plaintext formula" _title={$L('CalcFORMULA')} ref={(c) => (this._$formula = c)} onClick={this.showFormula}></div>
<p>
{$L('CalcFORMULA')} ({$L('SourceField')})
</p>
<p>{$L('CalcFORMULA')}</p>
</div>
<div className={this.state.calcMode === 'FORMULA' ? 'hide' : ''}>
<select className="form-control form-control-sm" ref={(c) => (this._sourceField = c)}>

View file

@ -39,7 +39,7 @@ class ContentFieldWriteback extends ActionContentSpec {
</div>
{this.state.hadApproval && (
<div className="form-text text-danger">
<i className="zmdi zmdi-alert-triangle fs-16 down-1"></i>
<i className="zmdi zmdi-alert-triangle fs-16 down-1 mr-1"></i>
{$L('TriggerTargetEntityTips')}
</div>
)}