Add editable record mode to approval flow nodes

This commit is contained in:
RB 2025-08-14 16:06:38 +08:00
parent 76431d61c5
commit efd2a5fd67
6 changed files with 114 additions and 56 deletions

View file

@ -20,6 +20,7 @@ import com.rebuild.core.privileges.UserHelper;
import com.rebuild.core.privileges.bizz.Department;
import com.rebuild.utils.JSONUtils;
import lombok.Getter;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import java.util.ArrayList;
@ -66,6 +67,12 @@ public class FlowNode {
public static final String SIGN_OR = "OR"; // 或签
public static final String SIGN_ALL = "ALL"; // 逐个审批暂未用
// 可修改记录
public static final int EDITABLE_MODE_NONE = 0;
public static final int EDITABLE_MODE_RECORD = 1;
public static final int EDITABLE_MODE_FIELDS = 10;
// --
@Getter
@ -167,7 +174,7 @@ public class FlowNode {
*/
public Set<ID> getSpecUsers(ID operator, ID record) {
JSONArray userDefs = getDataMap().getJSONArray("users");
if (userDefs == null || userDefs.isEmpty()) return Collections.emptySet();
if (CollectionUtils.isEmpty(userDefs)) return Collections.emptySet();
String userType = userDefs.getString(0);
if (USER_SELF.equalsIgnoreCase(userType)) {
@ -248,7 +255,7 @@ public class FlowNode {
*/
public Set<String> getCcAccounts(ID record) {
JSONArray accountFields = getDataMap().getJSONArray("accounts");
if (accountFields == null || accountFields.isEmpty()) return Collections.emptySet();
if (CollectionUtils.isEmpty(accountFields)) return Collections.emptySet();
Entity useEntity = MetadataHelper.getEntity(record.getEntityCode());
List<String> useFields = new ArrayList<>();
@ -292,20 +299,32 @@ public class FlowNode {
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == null) return false;
return obj instanceof FlowNode && obj.hashCode() == this.hashCode();
}
/**
* 节点可修改记录
*
* @return 0=不可修改, 1=可修改, 10=可修改字段
* @see #getEditableFields()
*/
public int getEditableMode() {
if (getDataMap().containsKey("editableMode")) {
return getDataMap().getIntValue("editableMode");
}
// v4.2 兼容
return CollectionUtils.isEmpty(getEditableFields()) ? EDITABLE_MODE_NONE : EDITABLE_MODE_FIELDS;
}
/**
* 节点可编辑字段
*
* @return
*/
public JSONArray getEditableFields() {
JSONArray editableFields = dataMap == null ? null : dataMap.getJSONArray("editableFields");
if (editableFields == null) return null;
JSONArray editableFields = getDataMap().getJSONArray("editableFields");
if (CollectionUtils.isEmpty(editableFields)) return JSONUtils.EMPTY_ARRAY;
editableFields = (JSONArray) JSONUtils.clone(editableFields);
for (Object o : editableFields) {
@ -322,7 +341,7 @@ public class FlowNode {
* @return
*/
public JSONObject getExpiresAuto() {
JSONObject expiresAuto = dataMap == null ? null : dataMap.getJSONObject("expiresAuto");
JSONObject expiresAuto = getDataMap().getJSONObject("expiresAuto");
if (expiresAuto == null) return null;
if (expiresAuto.getIntValue("expiresAuto") <= 0) return null;
return expiresAuto;

View file

@ -207,10 +207,14 @@ public class ApprovalController extends BaseController {
if (reqType < 2) data.put("remarkReq", reqType);
else data.put("remarkReq", expTime == null || expTime < 0 ? 0 : 1);
// 可修改字段
JSONArray editableFields = currentFlowNode.getEditableFields();
if (editableFields != null && !editableFields.isEmpty()) {
data.putAll(new EditableFields(editableFields).buildForms(recordId, user));
// 可修改记录
int editableMode = currentFlowNode.getEditableMode();
data.put("editableMode", editableMode);
if (editableMode ==FlowNode.EDITABLE_MODE_FIELDS) {
JSONArray editableFields = currentFlowNode.getEditableFields();
if (!CollectionUtils.isEmpty(editableFields)) {
data.putAll(new EditableFields(editableFields).buildForms(recordId, user));
}
}
return data;

View file

@ -714,7 +714,8 @@ See LICENSE and COMMERCIAL in the project root for license information.
width: 100%;
}
.expires-notify-set {
.expires-notify-set,
.editable-mode-set {
background-color: #eee;
padding: 20px;
padding-top: 15px;

View file

@ -233,6 +233,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
background-color: #eceff1;
border-width: 1px;
font-weight: normal;
font-weight: bold;
}
.chart.ctable .table.line-number thead th:first-child,
@ -309,6 +310,7 @@ See LICENSE and COMMERCIAL in the project root for license information.
padding-top: 0;
padding-bottom: 6px;
font-weight: normal;
font-weight: bold;
}
.chart.ApprovalList .table td,
@ -748,6 +750,10 @@ See LICENSE and COMMERCIAL in the project root for license information.
background-color: transparent;
}
.chart-box.TABLE.gradient-bg .chart.ctable .table th {
background-color: rgba(255, 255, 255, 0.2);
}
/* gradient-backgrounds.css */
.gradient-bg-1 {

View file

@ -224,7 +224,7 @@ class SimpleNode extends NodeSpec {
// if (data.allowBatch) descs.push($L('允许批量'))
descs.push(data.signMode === 'AND' ? $L('会签') : data.signMode === 'ALL' ? $L('依次审批') : $L('或签'))
if (data.expiresAuto && ~~data.expiresAuto.expiresAuto > 0) descs.push($L('限时审批'))
if (data.editableFields && data.editableFields.length > 0) descs.push($L('可修改字段'))
if (~~data.editableMode > 0) descs.push($L('可修改记录'))
} else if (this.nodeType === 'start') {
if (data.unallowCancel) descs.push($L('禁止撤回'))
}
@ -635,6 +635,10 @@ class ApproverNodeConfig extends StartNodeConfig {
if (!props.users || props.users.length === 0) this.state.users = 'SPEC'
else if (props.users[0] === 'SELF') this.state.users = 'SELF'
else this.state.users = 'SPEC'
// v4.2 兼容
if (props.editableFields && props.editableFields.length > 0 && !props.editableMode) {
this.state.editableMode = '10'
}
}
render() {
@ -804,43 +808,57 @@ class ApproverNodeConfig extends StartNodeConfig {
</div>
</div>
<div className="form-group mt-5">
<label className="text-bold">{$L('可修改字段')}</label>
<div style={{ position: 'relative' }}>
<table className={`table table-sm fields-table ${(this.state.editableFields || []).length === 0 && 'hide'}`}>
<tbody ref={(c) => (this._$editableFields = c)}>
{(this.state.editableFields || []).map((item) => {
return (
<tr key={`field-${item.field}`}>
<td>{this.__fieldLabel(item.field)}</td>
<td width="140" data-field={item.field}>
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline">
<input className="custom-control-input" type="checkbox" name="notNull" defaultChecked={item.notNull === true} />
<span className="custom-control-label">{$L('必填')}</span>
</label>
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline ml-3">
<input className="custom-control-input" type="checkbox" name="readOnly" defaultChecked={item.readOnly === true} />
<span className="custom-control-label">{$L('只读')}</span>
</label>
</td>
<td width="40">
<a className="close" title={$L('移除')} onClick={() => this.removeEditableField(item.field)}>
<i className="zmdi icon zmdi-close" />
</a>
</td>
</tr>
)
})}
</tbody>
</table>
<div className="pb-4">
<button className="btn btn-secondary btn-sm" onClick={() => renderRbcomp(<DlgFields selected={this.state.editableFields} call={(fs) => this.setEditableFields(fs)} />)}>
+ {$L('选择字段')}
</button>
<div className="form-group mt-5 pb-5">
<label className="text-bold">{$L('修改记录')}</label>
<div className="row">
<div className="col">
<select className="form-control form-control-sm" name="editableMode" defaultValue={this.state.editableMode || null} onChange={this.handleChange}>
<option value="0">{$L('不可修改')}</option>
<option value="1">{$L('可修改')}</option>
<option value="10">{$L('可修改指定字段')} </option>
</select>
</div>
<div className="col pl-0" />
</div>
<div className={`editable-mode-set mt-3 ${~~this.state.editableMode !== 10 && 'hide'}`}>
<label className="text-bold">{$L('指定字段')}</label>
<div className="position-relative">
<table className={`table table-sm fields-table ${(this.state.editableFields || []).length === 0 && 'hide'}`}>
<tbody ref={(c) => (this._$editableFields = c)}>
{(this.state.editableFields || []).map((item) => {
return (
<tr key={`field-${item.field}`}>
<td width="57%">{this.__fieldLabel(item.field)}</td>
<td width="35%" data-field={item.field}>
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline">
<input className="custom-control-input" type="checkbox" name="notNull" defaultChecked={item.notNull === true} />
<span className="custom-control-label">{$L('必填')}</span>
</label>
<label className="custom-control custom-control-sm custom-checkbox custom-control-inline ml-3">
<input className="custom-control-input" type="checkbox" name="readOnly" defaultChecked={item.readOnly === true} />
<span className="custom-control-label">{$L('只读')}</span>
</label>
</td>
<td width="8%">
<a className="close" title={$L('移除')} onClick={() => this.removeEditableField(item.field)}>
<i className="zmdi icon zmdi-close" />
</a>
</td>
</tr>
)
})}
</tbody>
</table>
<div>
<button className="btn btn-secondary btn-sm" onClick={() => renderRbcomp(<DlgFields selected={this.state.editableFields} call={(fs) => this.setEditableFields(fs)} />)}>
+ {$L('选择字段')}
</button>
</div>
</div>
</div>
</div>
</div>
{this.renderButton()}
</div>
)
@ -907,14 +925,6 @@ class ApproverNodeConfig extends StartNodeConfig {
}
save = () => {
const editableFields = []
$(this._$editableFields)
.find('td[data-field]')
.each(function () {
const $this = $(this)
editableFields.push({ field: $this.data('field'), notNull: $this.find('input:eq(0)').prop('checked'), readOnly: $this.find('input:eq(1)').prop('checked') })
})
const expiresAuto = {}
$(this._$expiresAuto)
.find('input, select, textarea')
@ -929,11 +939,27 @@ class ApproverNodeConfig extends StartNodeConfig {
return
}
const editableFields = []
if (~~this.state.editableMode === 10) {
$(this._$editableFields)
.find('td[data-field]')
.each(function () {
const $this = $(this)
editableFields.push({ field: $this.data('field'), notNull: $this.find('input:eq(0)').prop('checked'), readOnly: $this.find('input:eq(1)').prop('checked') })
})
if (editableFields.length === 0) {
RbHighbar.create($L('请指定可修改字段'))
return
}
}
const d = {
nodeName: this.state.nodeName,
users: this.state.users === 'SPEC' ? this._UserSelector.getSelected() : [this.state.users],
signMode: this.state.signMode,
selfSelecting: this.state.selfSelecting,
editableMode: this.state.editableMode,
editableFields: editableFields,
allowReferral: this.state.allowReferral,
allowCountersign: this.state.allowCountersign,

View file

@ -52,7 +52,9 @@ class BaseChart extends React.Component {
)
return (
<div className={`chart-box ${this.props.type} ${this.state.useBgcolor && `gradient-bg-${this.state.useBgcolor}`} ${this.props.type === 'DATALIST2' && 'DataList'}`} ref={(c) => (this._$box = c)}>
<div
className={`chart-box ${this.props.type} ${this.state.useBgcolor && `gradient-bg gradient-bg-${this.state.useBgcolor}`} ${this.props.type === 'DATALIST2' && 'DataList'}`}
ref={(c) => (this._$box = c)}>
<div className="chart-head">
<div className="chart-title text-truncate">{this.state.title}</div>
{opActions}