diff --git a/src/main/java/com/rebuild/core/service/approval/ApprovalStepService.java b/src/main/java/com/rebuild/core/service/approval/ApprovalStepService.java index 3663dac05..3f8acbf6e 100644 --- a/src/main/java/com/rebuild/core/service/approval/ApprovalStepService.java +++ b/src/main/java/com/rebuild/core/service/approval/ApprovalStepService.java @@ -418,6 +418,9 @@ public class ApprovalStepService extends InternalPersistService { if (useApprover == null) useApprover = UserService.SYSTEM_USER; if (useApproval == null) useApproval = APPROVAL_NOID; + // 作废之前 + cancelAliveSteps(recordId, null, null, null, false); + ID stepId = createStepIfNeed(recordId, useApproval, FlowNode.NODE_AUTOAPPROVAL, useApprover, false, FlowNode.NODE_ROOT, CalendarUtils.now(), getBatchNo(recordId, useApproval, FlowNode.NODE_ROOT)); diff --git a/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java b/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java index d5d5eaf51..17cb774cf 100644 --- a/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java +++ b/src/main/java/com/rebuild/core/service/general/GeneralEntityService.java @@ -34,6 +34,7 @@ import com.rebuild.core.service.general.series.SeriesGeneratorFactory; import com.rebuild.core.service.notification.NotificationObserver; import com.rebuild.core.service.query.QueryHelper; import com.rebuild.core.service.trigger.*; +import com.rebuild.core.service.trigger.impl.AutoApproval; import com.rebuild.core.service.trigger.impl.GroupAggregation; import com.rebuild.core.support.i18n.Language; import com.rebuild.core.support.task.TaskExecutors; @@ -95,44 +96,63 @@ public class GeneralEntityService extends ObservableService implements EntitySer } } - record = super.createOrUpdate(record); - if (details == null || details.isEmpty()) return record; + // 含明细 + final boolean hasDetails = details != null && !details.isEmpty(); - // 主记录+明细记录处理 - - String dtf = MetadataHelper.getDetailToMainField(record.getEntity().getDetailEntity()).getName(); - ID mainid = record.getPrimary(); - - boolean checkDetailsRepeated = rcm == GeneralEntityServiceContextHolder.RCM_CHECK_DETAILS - || rcm == GeneralEntityServiceContextHolder.RCM_CHECK_ALL; - - // 先删除 - for (Record d : details) { - if (d instanceof DeleteRecord) delete(d.getPrimary()); + boolean hasAutoApprovalForDetails = false; + if (hasDetails) { + Entity de = record.getEntity().getDetailEntity(); + TriggerAction[] hasTriggers = de == null ? null + : RobotTriggerManager.instance.getActions(de, TriggerWhen.APPROVED); + hasAutoApprovalForDetails = hasTriggers != null && hasTriggers.length > 0; + AutoApproval.setLazyAutoApproval(); } - // 再保存 - for (Record d : details) { - if (d instanceof DeleteRecord) continue; + try { + record = super.createOrUpdate(record); + if (!hasDetails) return record; - if (checkDetailsRepeated) { - d.setID(dtf, mainid); // for check + // 主记录+明细记录处理 - List repeated = getAndCheckRepeated(d, 20); - if (!repeated.isEmpty()) { - throw new RepeatedRecordsException(repeated); + final String dtf = MetadataHelper.getDetailToMainField(record.getEntity().getDetailEntity()).getName(); + final ID mainid = record.getPrimary(); + + final boolean checkDetailsRepeated = rcm == GeneralEntityServiceContextHolder.RCM_CHECK_DETAILS + || rcm == GeneralEntityServiceContextHolder.RCM_CHECK_ALL; + + // 先删除 + for (Record d : details) { + if (d instanceof DeleteRecord) delete(d.getPrimary()); + } + + // 再保存 + for (Record d : details) { + if (d instanceof DeleteRecord) continue; + + if (checkDetailsRepeated) { + d.setID(dtf, mainid); // for check + + List repeated = getAndCheckRepeated(d, 20); + if (!repeated.isEmpty()) { + throw new RepeatedRecordsException(repeated); + } + } + + if (d.getPrimary() == null) { + d.setID(dtf, mainid); + create(d); + } else { + update(d); } } - if (d.getPrimary() == null) { - d.setID(dtf, mainid); - create(d); - } else { - update(d); + return record; + + } finally { + if (hasAutoApprovalForDetails) { + AutoApproval.executeLazyAutoApproval(); } } - - return record; } @Override @@ -152,8 +172,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer // 先更新 record = super.update(record); - // 传导给明细(若有) - // 仅分组聚合触发器 + // 主记录修改时传导给明细(若有),以便触发分组聚合触发器 Entity de = record.getEntity().getDetailEntity(); if (de != null) { TriggerAction[] hasTriggers = RobotTriggerManager.instance.getActions(de, TriggerWhen.UPDATE); @@ -699,6 +718,7 @@ public class GeneralEntityService extends ObservableService implements EntitySer RobotTriggerManual triggerManual = new RobotTriggerManual(); // 传导给明细(若有) + // FIXME 此时明细可能尚未做好变更(例如新建自动审批) Entity de = approvalRecord.getEntity().getDetailEntity(); TriggerAction[] hasTriggers = de == null ? null : RobotTriggerManager.instance.getActions(de, diff --git a/src/main/java/com/rebuild/core/service/trigger/impl/AutoApproval.java b/src/main/java/com/rebuild/core/service/trigger/impl/AutoApproval.java index b61afd9e5..8e5826b3d 100644 --- a/src/main/java/com/rebuild/core/service/trigger/impl/AutoApproval.java +++ b/src/main/java/com/rebuild/core/service/trigger/impl/AutoApproval.java @@ -17,13 +17,23 @@ import com.rebuild.core.service.trigger.ActionContext; import com.rebuild.core.service.trigger.ActionType; import com.rebuild.core.service.trigger.TriggerAction; import com.rebuild.core.service.trigger.TriggerException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.NamedThreadLocal; + +import java.util.ArrayList; +import java.util.List; /** * @author devezhao * @since 2020/7/31 */ +@Slf4j public class AutoApproval extends TriggerAction { + private static final ThreadLocal> LAZY_AUTOAPPROVAL = new NamedThreadLocal<>("Lazy AutoApproval"); + + private OperatingContext operatingContext; + public AutoApproval(ActionContext context) { super(context); } @@ -40,6 +50,15 @@ public class AutoApproval extends TriggerAction { @Override public void execute(OperatingContext operatingContext) throws TriggerException { + this.operatingContext = operatingContext; + + List lazyed; + if ((lazyed = isLazyAutoApproval(false)) != null) { + lazyed.add(this); + log.info("Lazy AutoApproval : {}", lazyed); + return; + } + ID recordId = operatingContext.getAnyRecord().getPrimary(); String useApprover = ((JSONObject) actionContext.getActionContent()).getString("useApprover"); String useApproval = ((JSONObject) actionContext.getActionContent()).getString("useApproval"); @@ -49,4 +68,44 @@ public class AutoApproval extends TriggerAction { ID.isId(useApprover) ? ID.valueOf(useApprover) : null, ID.isId(useApproval) ? ID.valueOf(useApproval) : null); } + + @Override + public String toString() { + String s = super.toString(); + if (operatingContext != null) s += "#OperatingContext:" + operatingContext; + return s; + } + + // -- + + /** + * 跳过自动审批 + * @see #isLazyAutoApproval(boolean) + */ + public static void setLazyAutoApproval() { + LAZY_AUTOAPPROVAL.set(new ArrayList<>()); + } + + /** + * @return + */ + public static List isLazyAutoApproval(boolean once) { + List lazyed = LAZY_AUTOAPPROVAL.get(); + if (lazyed != null && once) LAZY_AUTOAPPROVAL.remove(); + return lazyed; + } + + /** + * @return + */ + public static int executeLazyAutoApproval() { + List lazyed = isLazyAutoApproval(true); + if (lazyed != null) { + for (AutoApproval a : lazyed) { + log.info("Lazy AutoApproval execute : {}", a); + a.execute(a.operatingContext); + } + } + return lazyed == null ? 0 : lazyed.size(); + } } diff --git a/src/main/resources/web/assets/js/rb-forms.js b/src/main/resources/web/assets/js/rb-forms.js index edcfd5ca6..99db0b473 100644 --- a/src/main/resources/web/assets/js/rb-forms.js +++ b/src/main/resources/web/assets/js/rb-forms.js @@ -610,13 +610,13 @@ class RbFormElement extends React.Component { */ checkValue() { const err = this.isValueError() + this.setState({ hasError: err || null }) const errMsg = err ? this.props.label + err : null if (this.isValueUnchanged() && !this.props.$$$parent.isNew) { if (err) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg) else this.props.$$$parent.setFieldUnchanged(this.props.field, this.state.value) } else { - this.setState({ hasError: err }) this.props.$$$parent.setFieldValue(this.props.field, this.state.value, errMsg) } }