Browse Source

feat(online): 新增批量新增接口,一次提交多张新单据

- 新增 /executePluginBatch:masterData 为数组,每个元素一张单据;
  slaveData 可整批共享一个对象,也可传与表头等长的数组(下标对应)
- OnlineOperationService 新增 executePluginBatch / executeBatchSaveInTransaction
- executeBatchSaveInTransaction 单独暴露并经 Spring 代理调用,
  保证 @Transactional 真正生效:逐条保存,任一条失败整批回滚
- 补齐主键:表头与分录主键统一由 IdGeneratorWrapper 发号,
  分录 parent_id 挂到对应表头主键上,避免多单之间串行;
  发号结果进入 newIdSet 防御重复主键
- 批量前期校验:按钮必须绑定保存插件、slaveData 格式与条数需与表头一致
- SaveOperationServicePlugIn 支持多单保存:优先取插件参数集合、为空退回 model,
  去重后逐条保存并各自触发反写;仅一条时保持原有行为
- DeleteOperationServicePlugIn 修复 EAS 分支批量删除只删第一条:
  改为汇总集合内全部主键后下发
dev-lml
lml 4 days ago
parent
commit
86d231908b
  1. 19
      common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java
  2. 22
      common/common-online/src/main/java/apelet/common/online/plugin/pre/DeleteOperationServicePlugIn.java
  3. 66
      common/common-online/src/main/java/apelet/common/online/plugin/pre/SaveOperationServicePlugIn.java
  4. 36
      common/common-online/src/main/java/apelet/common/online/service/OnlineOperationService.java
  5. 323
      common/common-online/src/main/java/apelet/common/online/service/impl/OnlineOperationServiceImpl.java

19
common/common-online/src/main/java/apelet/common/online/controller/OnlineOperationController.java

@ -179,6 +179,25 @@ public class OnlineOperationController {
} }
/** /**
* 批量执行功能插件(用于批量新增:一次提交多张新单据)。
*
* <p>与 /executePlugin 的区别只在 masterData 是数组、每个元素一张单据;
* 分录数据 slaveData 可整批共享一个对象,也可传与 masterData 等长的数组(下标对应)。</p>
*
* @param onlinePluginExecuteDto 操作按钮等参数。
* @param masterData 表头数据数组。
* @param slaveData 分录数据。
* @return 操作结果。
*/
@PostMapping("/executePluginBatch")
public ResponseResult<Object> executePluginBatch(
@MyRequestBody OnlinePluginExecuteDto onlinePluginExecuteDto,
@MyRequestBody JSONArray masterData,
@MyRequestBody Object slaveData) {
return ResponseResult.success(onlineOperationService.executePluginBatch(onlinePluginExecuteDto, masterData, slaveData));
}
/**
* 新增数据接口。 * 新增数据接口。
* *
* @param datasourceVariableName 数据源名称。 * @param datasourceVariableName 数据源名称。

22
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()); OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(objectValue.getTableName());
// 判断 数据源 // 判断 数据源
if (onlFormHead != null && onlFormHead.getIsomerismMark() != null) { 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<String, Object> map = new HashMap<>(); HashMap<String, Object> map = new HashMap<>();
map.put("method", "deleteData"); map.put("method", "deleteData");
map.put("bosType", onlFormHead.getIsomerismMark()); map.put("bosType", onlFormHead.getIsomerismMark());
map.put("personNumber", TokenData.takeFromRequest().getLoginName()); map.put("personNumber", TokenData.takeFromRequest().getLoginName());
ormGenDataSourceUtil.dataDelete("eas", onlFormHead.getTableName(), objectValue.getString("id"), map); ormGenDataSourceUtil.dataDelete("eas", onlFormHead.getTableName(), billIds, map);
} else { } else {
ObjectCollection objectCollection = e.getModelCollcetion(); ObjectCollection objectCollection = e.getModelCollcetion();
StringBuilder errorMessge = new StringBuilder(); StringBuilder errorMessge = new StringBuilder();

66
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.botp.BotpWriteBackRequest;
import apelet.common.core.exception.MyRuntimeException; import apelet.common.core.exception.MyRuntimeException;
import apelet.common.core.object.ColumnEncodedRule; import apelet.common.core.object.ColumnEncodedRule;
import apelet.common.core.object.ObjectCollection;
import apelet.common.core.object.ObjectValue; import apelet.common.core.object.ObjectValue;
import apelet.common.core.object.TokenData; import apelet.common.core.object.TokenData;
import apelet.common.core.util.ApplicationContextHolder; import apelet.common.core.util.ApplicationContextHolder;
@ -25,6 +26,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import java.util.LinkedList;
import java.util.List; import java.util.List;
/** /**
@ -71,20 +73,68 @@ public class SaveOperationServicePlugIn extends OperationServicePlugIn {
@Override @Override
public void beginOperationTransaction(BeginOperationTransactionArgs e) { public void beginOperationTransaction(BeginOperationTransactionArgs e) {
ObjectValue objectValue = e.getModel(); ObjectValue objectValue = e.getModel();
String tableName = objectValue.getTableName(); // 批量保存:入参 model 与 modelCollcetion 由 PlugInArgs#setObjectVelue 写的是同一份数据,
// 集合里只有一条时即单条保存,等价于原来的「取 model 保存一次」。
// 集合里有多条时按单条保存的逻辑逐条保存(各自算 id / 更新或新增、各自触发反写),查不到主键的条目跳过。
List<ObjectValue> saveList = this.resolveSaveList(objectValue, e.getModelCollcetion());
if (saveList.isEmpty()) {
return;
}
String tableName = saveList.get(0).getTableName();
List<String> savedIdList = new LinkedList<>();
try { try {
OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(tableName); OnlFormHead onlFormHead = onlFormHeadService.getHeadTableNameCache(tableName);
if (onlFormHead != null && onlFormHead.getIsomerismMark() != null) { boolean isomerism = onlFormHead != null && onlFormHead.getIsomerismMark() != null;
ormGenDataSourceUtil.saveData(tableName, objectValue); for (ObjectValue value : saveList) {
this.getOperationResult().setInfo(objectValue.get("id") + ""); if (isomerism) {
} else { ormGenDataSourceUtil.saveData(tableName, value);
ormSaveOrUpdate(objectValue, onlFormHead); } else {
this.getOperationResult().setInfo(objectValue.get("id") + ""); ormSaveOrUpdate(value, onlFormHead);
}
savedIdList.add(value.get("id") + "");
} }
} catch (Exception ex) { } catch (Exception ex) {
throw new MyRuntimeException(ex.getMessage()); 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);
}
}
/**
* 解析本次要保存的数据。
*
* <p>优先用插件参数里的集合(多单保存时由调用方逐条放入),集合为空时退回 model;
* 两种来源去重后合并,保证单条保存时不会重复保存同一条数据。</p>
*
* @param model 批量参数中代表当前单据的 model。
* @param objectCollection 插件参数中的单据集合。
* @return 需要保存的单据列表,为空表示无可保存的数据。
*/
private List<ObjectValue> resolveSaveList(ObjectValue model, ObjectCollection objectCollection) {
List<ObjectValue> 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;
} }
/** /**

36
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.OnlineEventPluginExecuteDto;
import apelet.common.online.dto.OnlineFilterDto; import apelet.common.online.dto.OnlineFilterDto;
import apelet.common.online.dto.OnlinePluginExecuteDto; import apelet.common.online.dto.OnlinePluginExecuteDto;
import apelet.common.online.dto.OnlinePluginExecuteSortDto;
import apelet.common.generator.model.OnlFormHead; import apelet.common.generator.model.OnlFormHead;
import apelet.common.online.model.*; import apelet.common.online.model.*;
import apelet.common.online.object.TransactionalBusinessData; 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.online.plugin.PlugInArgs;
import apelet.common.orm.impl.Filter; import apelet.common.orm.impl.Filter;
import apelet.common.orm.impl.Selector; import apelet.common.orm.impl.Selector;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.JSONObject;
import java.util.List; import java.util.List;
@ -312,11 +314,43 @@ public interface OnlineOperationService {
/** /**
* 执行插件 * 执行插件(单条)。
*/ */
OperationResult executePlugin(OnlinePluginExecuteDto onlinePluginExecuteDto, JSONObject masterData, JSONObject slaveData); OperationResult executePlugin(OnlinePluginExecuteDto onlinePluginExecuteDto, JSONObject masterData, JSONObject slaveData);
/** /**
* 批量新增:一次提交多张新单据(库里不存在的数据)。
*
* <p>与 executePlugin 同名同义,只是 masterData 是数组、每个元素一张单据;
* 分录数据可整批共享一个对象,也可传与表头等长的数组(下标对应)。</p>
*
* @param onlinePluginExecuteDto 操作按钮等参数。
* @param masterDataArray 表头数据数组,每个元素一张单据。
* @param slaveData 分录数据对象,或与表头数组同长度的数组,或 null。
* @return 操作结果。
*/
OperationResult executePluginBatch(OnlinePluginExecuteDto onlinePluginExecuteDto,
JSONArray masterDataArray, Object slaveData);
/**
* 批量新增的事务边界:逐条保存,任一条失败整批回滚。
*
* <p>单独暴露成接口方法是为了让调用经过 Spring 代理,保证 {@code @Transactional} 生效
* (service 内部的 this 调用不会触发事务)。</p>
*
* @param masterList 表头数据列表,主键已补齐。
* @param slaveList 分录数据列表,下标与表头对齐。
* @param onlinePluginExecuteDto 操作按钮等参数。
* @param onlineForm 当前表单。
* @param sortDtoList 已解析并按执行顺序排好的插件列表。
* @return 操作结果。
*/
OperationResult executeBatchSaveInTransaction(List<JSONObject> masterList, List<JSONObject> slaveList,
OnlinePluginExecuteDto onlinePluginExecuteDto,
OnlineForm onlineForm,
List<OnlinePluginExecuteSortDto> sortDtoList);
/**
* 执行插件 * 执行插件
*/ */
void executePluginTransaction(PlugInArgs plugInArgs); void executePluginTransaction(PlugInArgs plugInArgs);

323
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.IOnlFormFieldService;
import apelet.common.generator.service.IOnlFormHeadService; import apelet.common.generator.service.IOnlFormHeadService;
import apelet.common.generator.utils.OrmGenDataSourceUtil; import apelet.common.generator.utils.OrmGenDataSourceUtil;
import apelet.common.online.config.OnlineProperties;
import apelet.common.online.abstractplugin.ListDataEasService; import apelet.common.online.abstractplugin.ListDataEasService;
import apelet.common.online.abstractplugin.ListDataOrmService; import apelet.common.online.abstractplugin.ListDataOrmService;
import apelet.common.online.abstractplugin.ListDataService; import apelet.common.online.abstractplugin.ListDataService;
import apelet.common.online.abstractplugin.model.GridData; import apelet.common.online.abstractplugin.model.GridData;
import apelet.common.online.config.OnlineProperties;
import apelet.common.online.dao.ConvertRelationFOnLineMapper; import apelet.common.online.dao.ConvertRelationFOnLineMapper;
import apelet.common.online.dao.OnlineOperationMapper; import apelet.common.online.dao.OnlineOperationMapper;
import apelet.common.online.dto.OnlineEventPluginExecuteDto; 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.dto.OnlinePluginExecuteSortDto;
import apelet.common.online.exception.OnlineRuntimeException; import apelet.common.online.exception.OnlineRuntimeException;
import apelet.common.online.model.*; 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.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.ColumnData;
import apelet.common.online.object.ConstDictInfo; import apelet.common.online.object.ConstDictInfo;
import apelet.common.online.object.JoinTableInfo; import apelet.common.online.object.JoinTableInfo;
@ -53,7 +50,6 @@ import apelet.common.orm.impl.*;
import apelet.common.redis.util.CommonRedisUtil; import apelet.common.redis.util.CommonRedisUtil;
import apelet.common.sequence.wrapper.IdGeneratorWrapper; import apelet.common.sequence.wrapper.IdGeneratorWrapper;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
@ -1132,6 +1128,319 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
} }
/** /**
* 批量新增:一次提交多张新单据(库里不存在的数据)。
*
* <p>入参与单条 executePlugin 同名同义,只是 masterData 变成数组、每个元素一张单据;
* 每张单据补齐主键后逐条走同一条单条保存链路(附件、反写等逻辑全部复用),
* 整批一个事务,任一条失败整批回滚。</p>
*
* @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<OnlinePluginExecuteSortDto> 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 份单条入参,补齐主键后逐条走单条保存链路。
*
* <p>补齐主键的原因:保存插件里表头主键为空会被当作「没有可保存的数据」跳过,
* 而且分录的 parent_id 也要挂到表头主键上,所以先由 IdGeneratorWrapper 统一发号。
* 分录没有主键时同样补号,保证与新主键一起按每张单据各自保存、不会互相串。</p>
*
* @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<OnlinePluginExecuteSortDto> 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<JSONObject> masterList = new LinkedList<>();
List<JSONObject> slaveList = new LinkedList<>();
// 同一批次里发号生成的主键集合,用于防御生成器回绕或配置问题导致的重复主键。
Set<Long> 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);
}
/**
* 批量新增的事务边界:整批逐条保存,任一条抛异常则整批回滚。
*
* <p>必须经 Spring 代理调用(见 {@code executeBatchSave} 里的
* {@code onlineOperationService.executeBatchSaveInTransaction}),
* {@link #executePluginsForBill} 内部调用的 {@code executePluginTransaction} 是自调用,其事务注解不生效。</p>
*
* @param masterList 表头数据列表。
* @param slaveList 分录数据列表,下标与表头对齐。
* @param onlinePluginExecuteDto 操作按钮等参数。
* @param onlineForm 当前表单。
* @return 操作结果。
*/
@Transactional(rollbackFor = Exception.class)
@Override
public OperationResult executeBatchSaveInTransaction(List<JSONObject> masterList, List<JSONObject> slaveList,
OnlinePluginExecuteDto onlinePluginExecuteDto,
OnlineForm onlineForm,
List<OnlinePluginExecuteSortDto> sortDtoList) {
List<String> 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。
* <p>ORM 约定主键列为 id,这里兼容大小写差异,避免前端或配置写成 Id 时取不到结果。</p>
*
* @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<Long> 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<Long> 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<JSONObject> 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<OnlinePluginExecuteSortDto> getSortDtoList(OnlineForm onlineForm, String buttonName) {
JSONObject jsonObject = JSONObject.parseObject(onlineForm.getWidgetJson());
JSONArray jsonArray = jsonObject.getJSONObject(AppDeviceType.getDeviceType()).getJSONArray("operationList");
List<OnlinePluginExecuteSortDto> 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());
}
/**
* 针对一张单据执行完整的插件链(表头/分录构建、附件摘取与回写、反写触发)。
*
* <p>批量新增复用此方法:保证每张单据走的插件链与 executePlugin 完全一致,
* 只是插件列表由调用方传入,整批只需解析一次 widgetJson。</p>
*
* @param onlinePluginExecuteDto 操作按钮等参数。
* @param masterData 表头数据。
* @param slaveData 分录数据。
* @param list 按执行顺序排好的插件列表。
* @return 操作结果。
*/
private OperationResult executePluginsForBill(OnlinePluginExecuteDto onlinePluginExecuteDto, JSONObject masterData,
JSONObject slaveData, List<OnlinePluginExecuteSortDto> 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<String> 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<PendingFileColumn> 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<Long, List<Long>> 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 为 {列名: 值} 的等值条件;为空表示无筛选(即整表)。 * queryParams 为 {列名: 值} 的等值条件;为空表示无筛选(即整表)。
*/ */
@ -1524,7 +1833,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
boolean isHasValue = false; boolean isHasValue = false;
for(int j = 0; j < objectCollection.size() ; j++){ for(int j = 0; j < objectCollection.size() ; j++){
if(billId.equals(objectCollection.getObject(0).getPkValue()+"")){ if(billId.equals(objectCollection.getObject(j).getPkValue()+"")){
isHasValue = true; isHasValue = true;
break; break;
} }

Loading…
Cancel
Save