From 86d231908b34f5e2b78b259ec481441e68d11f4d Mon Sep 17 00:00:00 2001 From: lml <1297555865@qq.com> Date: Mon, 26 Oct 2026 18:05:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(online):=20=E6=96=B0=E5=A2=9E=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E6=96=B0=E5=A2=9E=E6=8E=A5=E5=8F=A3=EF=BC=8C=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=E6=8F=90=E4=BA=A4=E5=A4=9A=E5=BC=A0=E6=96=B0=E5=8D=95?= =?UTF-8?q?=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 /executePluginBatch:masterData 为数组,每个元素一张单据; slaveData 可整批共享一个对象,也可传与表头等长的数组(下标对应) - OnlineOperationService 新增 executePluginBatch / executeBatchSaveInTransaction - executeBatchSaveInTransaction 单独暴露并经 Spring 代理调用, 保证 @Transactional 真正生效:逐条保存,任一条失败整批回滚 - 补齐主键:表头与分录主键统一由 IdGeneratorWrapper 发号, 分录 parent_id 挂到对应表头主键上,避免多单之间串行; 发号结果进入 newIdSet 防御重复主键 - 批量前期校验:按钮必须绑定保存插件、slaveData 格式与条数需与表头一致 - SaveOperationServicePlugIn 支持多单保存:优先取插件参数集合、为空退回 model, 去重后逐条保存并各自触发反写;仅一条时保持原有行为 - DeleteOperationServicePlugIn 修复 EAS 分支批量删除只删第一条: 改为汇总集合内全部主键后下发 --- .../controller/OnlineOperationController.java | 19 ++ .../plugin/pre/DeleteOperationServicePlugIn.java | 22 +- .../plugin/pre/SaveOperationServicePlugIn.java | 66 ++++- .../online/service/OnlineOperationService.java | 36 ++- .../service/impl/OnlineOperationServiceImpl.java | 323 ++++++++++++++++++++- 5 files changed, 448 insertions(+), 18 deletions(-) diff --git a/common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java b/common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java index 27d9551..290294a 100644 --- a/common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java +++ b/common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java @@ -179,6 +179,25 @@ public class OnlineOperationController { } /** + * 批量执行功能插件(用于批量新增:一次提交多张新单据)。 + * + *

与 /executePlugin 的区别只在 masterData 是数组、每个元素一张单据; + * 分录数据 slaveData 可整批共享一个对象,也可传与 masterData 等长的数组(下标对应)。

+ * + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param masterData 表头数据数组。 + * @param slaveData 分录数据。 + * @return 操作结果。 + */ + @PostMapping("/executePluginBatch") + public ResponseResult executePluginBatch( + @MyRequestBody OnlinePluginExecuteDto onlinePluginExecuteDto, + @MyRequestBody JSONArray masterData, + @MyRequestBody Object slaveData) { + return ResponseResult.success(onlineOperationService.executePluginBatch(onlinePluginExecuteDto, masterData, slaveData)); + } + + /** * 新增数据接口。 * * @param datasourceVariableName 数据源名称。 diff --git a/common/common-online/src/main/java/apelet/common/online/plugin/pre/DeleteOperationServicePlugIn.java b/common/common-online/src/main/java/apelet/common/online/plugin/pre/DeleteOperationServicePlugIn.java index 7a35c0b..fbdca86 100644 --- a/common/common-online/src/main/java/apelet/common/online/plugin/pre/DeleteOperationServicePlugIn.java +++ b/common/common-online/src/main/java/apelet/common/online/plugin/pre/DeleteOperationServicePlugIn.java @@ -50,12 +50,30 @@ public class DeleteOperationServicePlugIn extends OperationServicePlugIn { OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(objectValue.getTableName()); // 判断 数据源 if (onlFormHead != null && onlFormHead.getIsomerismMark() != null) { - // eas数据 + // eas数据:与 ORM 分支保持一致,批量删除时把所有主键一起下发,不能只删清单里的第一条。 + ObjectCollection deleteCollection = e.getModelCollcetion(); + String billIds = objectValue.get("id") == null ? null : objectValue.get("id").toString(); + if (deleteCollection != null && deleteCollection.size() > 0) { + StringBuilder idBuilder = new StringBuilder(); + for (int i = 0; i < deleteCollection.size(); i++) { + ObjectValue deleteValue = deleteCollection.getObject(i); + if (deleteValue == null || deleteValue.get("id") == null) { + continue; + } + if (idBuilder.length() > 0) { + idBuilder.append(","); + } + idBuilder.append(deleteValue.get("id")); + } + if (idBuilder.length() > 0) { + billIds = idBuilder.toString(); + } + } HashMap map = new HashMap<>(); map.put("method", "deleteData"); map.put("bosType", onlFormHead.getIsomerismMark()); map.put("personNumber", TokenData.takeFromRequest().getLoginName()); - ormGenDataSourceUtil.dataDelete("eas", onlFormHead.getTableName(), objectValue.getString("id"), map); + ormGenDataSourceUtil.dataDelete("eas", onlFormHead.getTableName(), billIds, map); } else { ObjectCollection objectCollection = e.getModelCollcetion(); StringBuilder errorMessge = new StringBuilder(); diff --git a/common/common-online/src/main/java/apelet/common/online/plugin/pre/SaveOperationServicePlugIn.java b/common/common-online/src/main/java/apelet/common/online/plugin/pre/SaveOperationServicePlugIn.java index 15df9f9..9e43dde 100644 --- a/common/common-online/src/main/java/apelet/common/online/plugin/pre/SaveOperationServicePlugIn.java +++ b/common/common-online/src/main/java/apelet/common/online/plugin/pre/SaveOperationServicePlugIn.java @@ -4,6 +4,7 @@ import apelet.common.core.botp.BotpReverseWriteSupport; import apelet.common.core.botp.BotpWriteBackRequest; import apelet.common.core.exception.MyRuntimeException; import apelet.common.core.object.ColumnEncodedRule; +import apelet.common.core.object.ObjectCollection; import apelet.common.core.object.ObjectValue; import apelet.common.core.object.TokenData; import apelet.common.core.util.ApplicationContextHolder; @@ -25,6 +26,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; +import java.util.LinkedList; import java.util.List; /** @@ -71,20 +73,68 @@ public class SaveOperationServicePlugIn extends OperationServicePlugIn { @Override public void beginOperationTransaction(BeginOperationTransactionArgs e) { ObjectValue objectValue = e.getModel(); - String tableName = objectValue.getTableName(); + // 批量保存:入参 model 与 modelCollcetion 由 PlugInArgs#setObjectVelue 写的是同一份数据, + // 集合里只有一条时即单条保存,等价于原来的「取 model 保存一次」。 + // 集合里有多条时按单条保存的逻辑逐条保存(各自算 id / 更新或新增、各自触发反写),查不到主键的条目跳过。 + List saveList = this.resolveSaveList(objectValue, e.getModelCollcetion()); + if (saveList.isEmpty()) { + return; + } + String tableName = saveList.get(0).getTableName(); + List savedIdList = new LinkedList<>(); try { OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(tableName); - if (onlFormHead != null && onlFormHead.getIsomerismMark() != null) { - ormGenDataSourceUtil.saveData(tableName, objectValue); - this.getOperationResult().setInfo(objectValue.get("id") + ""); - } else { - ormSaveOrUpdate(objectValue, onlFormHead); - this.getOperationResult().setInfo(objectValue.get("id") + ""); + boolean isomerism = onlFormHead != null && onlFormHead.getIsomerismMark() != null; + for (ObjectValue value : saveList) { + if (isomerism) { + ormGenDataSourceUtil.saveData(tableName, value); + } else { + ormSaveOrUpdate(value, onlFormHead); + } + savedIdList.add(value.get("id") + ""); } } catch (Exception ex) { throw new MyRuntimeException(ex.getMessage()); } - triggerWriteBack(tableName, objectValue); + if (saveList.size() == 1) { + this.getOperationResult().setInfo(savedIdList.get(0)); + } else { + this.getOperationResult().setInfo(String.join(",", savedIdList)); + this.getOperationResult().setShowMessage(true); + this.getOperationResult().setMessage("保存成功,共保存 " + saveList.size() + " 条数据!"); + } + for (ObjectValue value : saveList) { + triggerWriteBack(tableName, value); + } + } + + /** + * 解析本次要保存的数据。 + * + *

优先用插件参数里的集合(多单保存时由调用方逐条放入),集合为空时退回 model; + * 两种来源去重后合并,保证单条保存时不会重复保存同一条数据。

+ * + * @param model 批量参数中代表当前单据的 model。 + * @param objectCollection 插件参数中的单据集合。 + * @return 需要保存的单据列表,为空表示无可保存的数据。 + */ + private List resolveSaveList(ObjectValue model, ObjectCollection objectCollection) { + List saveList = new LinkedList<>(); + if (objectCollection != null) { + for (int i = 0; i < objectCollection.size(); i++) { + ObjectValue value = objectCollection.getObject(i); + if (value == null || saveList.contains(value)) { + continue; + } + saveList.add(value); + } + } + if (model != null && !saveList.contains(model)) { + saveList.add(0, model); + } + // 主键为空的条目无法定位数据,非异构表保存时又只能当作新增,这里统一跳过,避免脏数据。 + saveList.removeIf(value -> value.get("id") == null || StringUtils.isBlank(value.get("id").toString())); + return saveList; } /** diff --git a/common/common-online/src/main/java/apelet/common/online/service/OnlineOperationService.java b/common/common-online/src/main/java/apelet/common/online/service/OnlineOperationService.java index 207cee0..b18c4a6 100644 --- a/common/common-online/src/main/java/apelet/common/online/service/OnlineOperationService.java +++ b/common/common-online/src/main/java/apelet/common/online/service/OnlineOperationService.java @@ -8,6 +8,7 @@ import apelet.common.online.abstractplugin.model.GridData; import apelet.common.online.dto.OnlineEventPluginExecuteDto; import apelet.common.online.dto.OnlineFilterDto; import apelet.common.online.dto.OnlinePluginExecuteDto; +import apelet.common.online.dto.OnlinePluginExecuteSortDto; import apelet.common.generator.model.OnlFormHead; import apelet.common.online.model.*; import apelet.common.online.object.TransactionalBusinessData; @@ -15,6 +16,7 @@ import apelet.common.online.plugin.OperationResult; import apelet.common.online.plugin.PlugInArgs; import apelet.common.orm.impl.Filter; import apelet.common.orm.impl.Selector; +import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import java.util.List; @@ -312,11 +314,43 @@ public interface OnlineOperationService { /** - * 执行插件 + * 执行插件(单条)。 */ OperationResult executePlugin(OnlinePluginExecuteDto onlinePluginExecuteDto, JSONObject masterData, JSONObject slaveData); /** + * 批量新增:一次提交多张新单据(库里不存在的数据)。 + * + *

与 executePlugin 同名同义,只是 masterData 是数组、每个元素一张单据; + * 分录数据可整批共享一个对象,也可传与表头等长的数组(下标对应)。

+ * + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param masterDataArray 表头数据数组,每个元素一张单据。 + * @param slaveData 分录数据对象,或与表头数组同长度的数组,或 null。 + * @return 操作结果。 + */ + OperationResult executePluginBatch(OnlinePluginExecuteDto onlinePluginExecuteDto, + JSONArray masterDataArray, Object slaveData); + + /** + * 批量新增的事务边界:逐条保存,任一条失败整批回滚。 + * + *

单独暴露成接口方法是为了让调用经过 Spring 代理,保证 {@code @Transactional} 生效 + * (service 内部的 this 调用不会触发事务)。

+ * + * @param masterList 表头数据列表,主键已补齐。 + * @param slaveList 分录数据列表,下标与表头对齐。 + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param onlineForm 当前表单。 + * @param sortDtoList 已解析并按执行顺序排好的插件列表。 + * @return 操作结果。 + */ + OperationResult executeBatchSaveInTransaction(List masterList, List slaveList, + OnlinePluginExecuteDto onlinePluginExecuteDto, + OnlineForm onlineForm, + List sortDtoList); + + /** * 执行插件 */ void executePluginTransaction(PlugInArgs plugInArgs); diff --git a/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineOperationServiceImpl.java b/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineOperationServiceImpl.java index ed05370..0bc78e5 100644 --- a/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineOperationServiceImpl.java +++ b/common/common-online/src/main/java/apelet/common/online/service/impl/OnlineOperationServiceImpl.java @@ -19,11 +19,11 @@ import apelet.common.generator.model.OnlFormHead; import apelet.common.generator.service.IOnlFormFieldService; import apelet.common.generator.service.IOnlFormHeadService; import apelet.common.generator.utils.OrmGenDataSourceUtil; -import apelet.common.online.config.OnlineProperties; import apelet.common.online.abstractplugin.ListDataEasService; import apelet.common.online.abstractplugin.ListDataOrmService; import apelet.common.online.abstractplugin.ListDataService; import apelet.common.online.abstractplugin.model.GridData; +import apelet.common.online.config.OnlineProperties; import apelet.common.online.dao.ConvertRelationFOnLineMapper; import apelet.common.online.dao.OnlineOperationMapper; import apelet.common.online.dto.OnlineEventPluginExecuteDto; @@ -32,11 +32,8 @@ import apelet.common.online.dto.OnlinePluginExecuteDto; import apelet.common.online.dto.OnlinePluginExecuteSortDto; import apelet.common.online.exception.OnlineRuntimeException; import apelet.common.online.model.*; -import apelet.common.online.model.constant.EventEnum; +import apelet.common.online.model.constant.*; import apelet.common.online.model.constant.FieldFilterType; -import apelet.common.online.model.constant.FieldKind; -import apelet.common.online.model.constant.RelationType; -import apelet.common.online.model.constant.VirtualType; import apelet.common.online.object.ColumnData; import apelet.common.online.object.ConstDictInfo; import apelet.common.online.object.JoinTableInfo; @@ -53,7 +50,6 @@ import apelet.common.orm.impl.*; import apelet.common.redis.util.CommonRedisUtil; import apelet.common.sequence.wrapper.IdGeneratorWrapper; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; @@ -1132,6 +1128,319 @@ public class OnlineOperationServiceImpl implements OnlineOperationService { } /** + * 批量新增:一次提交多张新单据(库里不存在的数据)。 + * + *

入参与单条 executePlugin 同名同义,只是 masterData 变成数组、每个元素一张单据; + * 每张单据补齐主键后逐条走同一条单条保存链路(附件、反写等逻辑全部复用), + * 整批一个事务,任一条失败整批回滚。

+ * + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param masterDataArray 表头数据数组,每个元素一张单据。 + * @param slaveData 分录数据对象(整批共享),或与表头数组同长度的数组,或 null。 + * @return 操作结果。 + */ + @Override + public OperationResult executePluginBatch(OnlinePluginExecuteDto onlinePluginExecuteDto, + JSONArray masterDataArray, Object slaveData) { + if (masterDataArray == null || masterDataArray.isEmpty()) { + return this.makeFailedResult("没有可保存的数据!"); + } + OnlineForm onlineForm = onlineFormService.getOnlineFormFromCache(onlinePluginExecuteDto.getFormId()); + if (onlineForm == null) { + return this.makeFailedResult("数据验证失败,表单不存在或已失效,请刷新后重试!"); + } + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(onlineForm.getMasterTableId()); + if (onlineTable == null || onlineTable.getPrimaryKeyColumn() == null) { + return this.makeFailedResult("数据验证失败,表单主表或主键配置缺失,无法批量新增!"); + } + List sortDtoList = this.getSortDtoList(onlineForm, onlinePluginExecuteDto.getButtonName()); + if (!this.containsPlugin(sortDtoList, SaveOperationServicePlugIn.class)) { + return this.makeFailedResult("批量新增仅支持绑定保存插件的按钮!"); + } + return this.executeBatchSave(onlinePluginExecuteDto, masterDataArray, slaveData, + onlineForm, onlineTable, sortDtoList); + } + + /** + * 批量新增:把多张新单据拆成 N 份单条入参,补齐主键后逐条走单条保存链路。 + * + *

补齐主键的原因:保存插件里表头主键为空会被当作「没有可保存的数据」跳过, + * 而且分录的 parent_id 也要挂到表头主键上,所以先由 IdGeneratorWrapper 统一发号。 + * 分录没有主键时同样补号,保证与新主键一起按每张单据各自保存、不会互相串。

+ * + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param masterArray 表头数据数组。 + * @param slaveData 分录数据对象,或与表头数组同长度的数组,或 null。 + * @param onlineForm 当前表单。 + * @param onlineTable 表头对应的在线数据表。 + * @param sortDtoList 已解析并按执行顺序排好的插件列表(整批只需解析一次)。 + * @return 操作结果。 + */ + private OperationResult executeBatchSave(OnlinePluginExecuteDto onlinePluginExecuteDto, JSONArray masterArray, + Object slaveData, OnlineForm onlineForm, OnlineTable onlineTable, + List sortDtoList) { + if (masterArray.isEmpty()) { + return this.makeFailedResult("没有可保存的数据!"); + } + JSONArray slaveArray = null; + if (slaveData instanceof JSONArray) { + slaveArray = (JSONArray) slaveData; + if (slaveArray.size() != masterArray.size()) { + return this.makeFailedResult("数据验证失败,分录数据条数与待保存单据条数不一致!"); + } + } else if (slaveData != null && !(slaveData instanceof JSONObject)) { + return this.makeFailedResult("数据验证失败,分录数据格式不正确!"); + } + List masterList = new LinkedList<>(); + List slaveList = new LinkedList<>(); + // 同一批次里发号生成的主键集合,用于防御生成器回绕或配置问题导致的重复主键。 + Set newIdSet = new HashSet<>(); + String primaryKeyName = onlineTable.getPrimaryKeyColumn().getColumnName(); + for (int i = 0; i < masterArray.size(); i++) { + Object element = masterArray.get(i); + if (!(element instanceof JSONObject)) { + return this.makeFailedResult("数据验证失败,第 " + (i + 1) + " 张单据的数据格式不正确!"); + } + JSONObject master = (JSONObject) element; + master.put(primaryKeyName, this.generatePrimaryKey(onlineTable.getPrimaryKeyColumn(), newIdSet)); + Object slaveElement = slaveArray == null ? slaveData : slaveArray.get(i); + JSONObject slave = slaveElement instanceof JSONObject ? (JSONObject) slaveElement : null; + if (slave != null) { + this.makeupEntryPrimaryKey(slave, newIdSet); + } + masterList.add(master); + slaveList.add(slave); + } + return onlineOperationService.executeBatchSaveInTransaction(masterList, slaveList, + onlinePluginExecuteDto, onlineForm, sortDtoList); + } + + /** + * 批量新增的事务边界:整批逐条保存,任一条抛异常则整批回滚。 + * + *

必须经 Spring 代理调用(见 {@code executeBatchSave} 里的 + * {@code onlineOperationService.executeBatchSaveInTransaction}), + * {@link #executePluginsForBill} 内部调用的 {@code executePluginTransaction} 是自调用,其事务注解不生效。

+ * + * @param masterList 表头数据列表。 + * @param slaveList 分录数据列表,下标与表头对齐。 + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param onlineForm 当前表单。 + * @return 操作结果。 + */ + @Transactional(rollbackFor = Exception.class) + @Override + public OperationResult executeBatchSaveInTransaction(List masterList, List slaveList, + OnlinePluginExecuteDto onlinePluginExecuteDto, + OnlineForm onlineForm, + List sortDtoList) { + List savedIdList = new LinkedList<>(); + for (int i = 0; i < masterList.size(); i++) { + JSONObject master = masterList.get(i); + // 逐条复用单条链路:附件摘取/回写、插件链顺序、反写触发等都与编辑页保存完全一致。 + OperationResult singleResult = + this.executePluginsForBill(onlinePluginExecuteDto, master, slaveList.get(i), sortDtoList); + if (singleResult != null && !singleResult.isSuccess()) { + throw new OnlineRuntimeException(singleResult.getMessage()); + } + savedIdList.add(String.valueOf(master.get(this.resolvePrimaryKeyName(master)))); + } + OperationResult operationResult = new OperationResult(); + operationResult.setInfo(String.join(",", savedIdList)); + operationResult.setShowMessage(true); + operationResult.setMessage("保存成功,共保存 " + savedIdList.size() + " 条数据!"); + return operationResult; + } + + /** + * 取单据主键在 JSON 中的实际 key。 + *

ORM 约定主键列为 id,这里兼容大小写差异,避免前端或配置写成 Id 时取不到结果。

+ * + * @param master 单据数据。 + * @return 主键 key。 + */ + private String resolvePrimaryKeyName(JSONObject master) { + if (master.containsKey("id")) { + return "id"; + } + for (String key : master.keySet()) { + if ("id".equalsIgnoreCase(key)) { + return key; + } + } + return "id"; + } + + /** + * 生成主键:与 makeupColumnValue 的发号规则保持一致,Long 列走 nextLongId, + * 其它类型(Integer / String / BigDecimal 等)走 nextStringId 再按列类型转换, + * 避免把 Long 直接塞进字符串主键列而引发类型转换失败。 + * + * @param primaryKeyColumn 主键字段,为空表示表未配置主键。 + * @param newIdSet 本批次已生成的主键集合,用于防重复。 + * @return 新主键。 + */ + private Object generatePrimaryKey(OnlineColumn primaryKeyColumn, Set newIdSet) { + if (primaryKeyColumn == null) { + throw new OnlineRuntimeException("数据验证失败,表未配置主键字段,无法批量新增!"); + } + String objectFieldType = primaryKeyColumn.getObjectFieldType(); + if (ObjectFieldType.LONG.equals(objectFieldType)) { + long id; + do { + id = idGenerator.nextLongId(); + } while (!newIdSet.add(id)); + return id; + } + // 非 Long 主键(Integer / String 等):发号后按列类型转换,避免把 Long 塞进字符串主键列。 + String textId = idGenerator.nextStringId(); + if (ObjectFieldType.INTEGER.equals(objectFieldType)) { + return Integer.valueOf(textId); + } + return textId; + } + + /** + * 给缺少主键的分录行补发主键,使每张单据的分录落到各自的表头下。 + * + * @param slaveData 一张单据的分录数据,key 为分录表 tableId。 + * @param newIdSet 本批次已生成的主键集合,用于防重复。 + */ + private void makeupEntryPrimaryKey(JSONObject slaveData, Set newIdSet) { + for (String tableIdStr : slaveData.keySet()) { + OnlineTable entryTable = onlineTableService.getOnlineTableFromCache(this.convertToLongId(tableIdStr)); + if (entryTable == null || entryTable.getPrimaryKeyColumn() == null) { + continue; + } + String primaryKeyName = entryTable.getPrimaryKeyColumn().getColumnName(); + Object entryValue = slaveData.get(tableIdStr); + // 分录数据既可能是「行数组」,也可能是「单行对象」(一对一关系),两种都补齐主键。 + List entryRowList = new LinkedList<>(); + if (entryValue instanceof JSONArray) { + for (Object row : (JSONArray) entryValue) { + if (row instanceof JSONObject) { + entryRowList.add((JSONObject) row); + } + } + } else if (entryValue instanceof JSONObject) { + entryRowList.add((JSONObject) entryValue); + } + for (JSONObject entryRow : entryRowList) { + if (entryRow.get(primaryKeyName) == null) { + entryRow.put(primaryKeyName, this.generatePrimaryKey(entryTable.getPrimaryKeyColumn(), newIdSet)); + } + } + } + } + + /** + * 构造失败结果:前端统一按红色提示展示 message。 + */ + private OperationResult makeFailedResult(String message) { + OperationResult operationResult = new OperationResult(); + operationResult.setSuccess(false); + operationResult.setShowMessage(true); + operationResult.setMessage(message); + return operationResult; + } + + /** + * 从表单 widgetJson 中解析出指定按钮按执行顺序排好的插件列表。 + * + * @param onlineForm 当前表单。 + * @param buttonName 按钮名称。 + * @return 排序后的插件列表。 + */ + private List getSortDtoList(OnlineForm onlineForm, String buttonName) { + JSONObject jsonObject = JSONObject.parseObject(onlineForm.getWidgetJson()); + JSONArray jsonArray = jsonObject.getJSONObject(AppDeviceType.getDeviceType()).getJSONArray("operationList"); + List sortDtoList = new ArrayList<>(); + for (int i = 0; i < jsonArray.size(); i++) { + JSONObject object = jsonArray.getJSONObject(i); + if (object.getString("name").equals(buttonName) && object.containsKey("pluginList") && !object.getJSONArray("pluginList").isEmpty()) { + if (object.getBoolean("enabled")) { + JSONArray pluginList = object.getJSONArray("pluginList"); + for (int j = 0; j < pluginList.size(); j++) { + JSONObject pluginListJSONObject = pluginList.getJSONObject(j); + //是否启用 + boolean isEnable = pluginListJSONObject.getBoolean("isEnable"); + if (isEnable) { + String aClass = pluginListJSONObject.getString("class"); + String order = pluginListJSONObject.getString("order"); + OnlinePluginExecuteSortDto executeSortDto = new OnlinePluginExecuteSortDto(); + executeSortDto.setClassPath(aClass); + executeSortDto.setOrder(order); + sortDtoList.add(executeSortDto); + } + } + } + } + } + return sortDtoList.stream().sorted(Comparator.comparing(OnlinePluginExecuteSortDto::getOrder)).collect(Collectors.toList()); + } + + + /** + * 针对一张单据执行完整的插件链(表头/分录构建、附件摘取与回写、反写触发)。 + * + *

批量新增复用此方法:保证每张单据走的插件链与 executePlugin 完全一致, + * 只是插件列表由调用方传入,整批只需解析一次 widgetJson。

+ * + * @param onlinePluginExecuteDto 操作按钮等参数。 + * @param masterData 表头数据。 + * @param slaveData 分录数据。 + * @param list 按执行顺序排好的插件列表。 + * @return 操作结果。 + */ + private OperationResult executePluginsForBill(OnlinePluginExecuteDto onlinePluginExecuteDto, JSONObject masterData, + JSONObject slaveData, List list) { + OnlineForm onlineForm = onlineFormService.getOnlineFormFromCache(onlinePluginExecuteDto.getFormId()); + OnlineTable onlineTable = onlineTableService.getOnlineTableFromCache(onlineForm.getMasterTableId()); + OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(onlineTable.getTableName()); + if (BooleanUtil.isTrue(onlinePluginExecuteDto.getIsSelectAll())) { + List allBillIds = this.queryAllBillIds(onlineTable, onlinePluginExecuteDto.getQueryParams()); + if (CollUtil.isEmpty(allBillIds)) { + OperationResult empty = new OperationResult(); + empty.setMessage("没有符合条件的数据!"); + return empty; + } + onlinePluginExecuteDto.setBillId(CollUtil.join(allBillIds, ",")); + } + // executePlugin 是所有按钮的统一入口,附件的写入/清理必须按插件类型分别设闸, + // 否则「审核」这类只提交部分字段的按钮会把附件全量覆盖掉,「删除」则不会清理附件。 + boolean saveOperation = this.containsPlugin(list, SaveOperationServicePlugIn.class); + boolean deleteOperation = this.containsPlugin(list, DeleteOperationServicePlugIn.class); + ObjectValue finalObjectValue = null; + List pendingFileList = new LinkedList<>(); + if (onlFormHead.getIsomerismMark() != null) { + finalObjectValue = this.cntEasValue(onlinePluginExecuteDto, + masterData, + slaveData, + onlFormHead, + onlineTable); + } else { + if(masterData != null){ + finalObjectValue = onlineFormService.getObjectValue(masterData, slaveData, onlineTable.getTableName()); + if (saveOperation) { + // 附件上传/图片上传字段:先摘出入参值并把业务列置NULL,等ORM保存回写主键后再落 zz_online_file。 + pendingFileList = this.extractFileColumn(finalObjectValue, onlineTable, slaveData); + } + } + } + + // 删除会被ORM级联删掉分录行,附件归属的分录主键必须在删除前查出来,删完就查不到了。 + Map> removeFileBusinessIdMap = new LinkedHashMap<>(); + if (deleteOperation && onlFormHead.getIsomerismMark() == null) { + removeFileBusinessIdMap = this.collectFileBusinessIdForRemove(onlineTable, onlinePluginExecuteDto.getBillId()); + } + OperationResult operationResult = exePlugin(list, onlinePluginExecuteDto.getBillId(), onlineTable.getTableName(), finalObjectValue, onlineForm); + // ORM保存会把生成的主键回写到入参的ObjectValue实例上,此时才能确定附件归属的业务主键。 + this.flushPendingFileColumn(pendingFileList); + this.removeFileByBusinessIdMap(removeFileBusinessIdMap); + return operationResult; + } + + /** * 按列表筛选条件查询主表全部主键,用于"选择全部按钮"。 * queryParams 为 {列名: 值} 的等值条件;为空表示无筛选(即整表)。 */ @@ -1524,7 +1833,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService { boolean isHasValue = false; for(int j = 0; j < objectCollection.size() ; j++){ - if(billId.equals(objectCollection.getObject(0).getPkValue()+"")){ + if(billId.equals(objectCollection.getObject(j).getPkValue()+"")){ isHasValue = true; break; }