|
|
|
@ -1193,7 +1193,9 @@ public class OnlineOperationServiceImpl implements OnlineOperationService { |
|
|
|
} |
|
|
|
} |
|
|
|
List<JSONObject> masterList = new LinkedList<>(); |
|
|
|
List<JSONObject> masterList = new LinkedList<>(); |
|
|
|
List<JSONObject> slaveList = new LinkedList<>(); |
|
|
|
List<JSONObject> slaveList = new LinkedList<>(); |
|
|
|
// 同一批次里发号生成的主键集合,用于防御生成器回绕或配置问题导致的重复主键。
|
|
|
|
// 防重复用:已经出现在本批数据里的主键(含调用方预生成的,如 BOTP 转换预发号的 id)。
|
|
|
|
|
|
|
|
Set<String> usedIdSet = new HashSet<>(); |
|
|
|
|
|
|
|
// 同一批次里由后端发号生成的主键集合,用于防御生成器回绕导致的重复主键。
|
|
|
|
Set<Long> newIdSet = new HashSet<>(); |
|
|
|
Set<Long> newIdSet = new HashSet<>(); |
|
|
|
String primaryKeyName = onlineTable.getPrimaryKeyColumn().getColumnName(); |
|
|
|
String primaryKeyName = onlineTable.getPrimaryKeyColumn().getColumnName(); |
|
|
|
for (int i = 0; i < masterArray.size(); i++) { |
|
|
|
for (int i = 0; i < masterArray.size(); i++) { |
|
|
|
@ -1202,11 +1204,32 @@ public class OnlineOperationServiceImpl implements OnlineOperationService { |
|
|
|
return this.makeFailedResult("数据验证失败,第 " + (i + 1) + " 张单据的数据格式不正确!"); |
|
|
|
return this.makeFailedResult("数据验证失败,第 " + (i + 1) + " 张单据的数据格式不正确!"); |
|
|
|
} |
|
|
|
} |
|
|
|
JSONObject master = (JSONObject) element; |
|
|
|
JSONObject master = (JSONObject) element; |
|
|
|
master.put(primaryKeyName, this.generatePrimaryKey(onlineTable.getPrimaryKeyColumn(), newIdSet)); |
|
|
|
// 方案 A:允许每张单据自带分录(masterData[i].slaveData),父子关系由结构保证,不再依赖下标。
|
|
|
|
Object slaveElement = slaveArray == null ? slaveData : slaveArray.get(i); |
|
|
|
// 这条分录键必须从表头里摘掉,否则会被当成表头字段交给 ORM。
|
|
|
|
|
|
|
|
JSONObject ownSlave = null; |
|
|
|
|
|
|
|
if (master.get("slaveData") instanceof JSONObject) { |
|
|
|
|
|
|
|
ownSlave = master.getJSONObject("slaveData"); |
|
|
|
|
|
|
|
master.remove("slaveData"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 已有主键必须保留:BOTP 转换(formStatus=2)在转换阶段就预发了主键,
|
|
|
|
|
|
|
|
// 转换关系表 convert_relation.targetId 记的正是这个 id,覆盖会导致关系对不上。
|
|
|
|
|
|
|
|
if (this.isBlankPrimaryKey(master, primaryKeyName)) { |
|
|
|
|
|
|
|
master.put(primaryKeyName, this.generatePrimaryKey(onlineTable.getPrimaryKeyColumn(), newIdSet)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
String masterId = master.get(primaryKeyName).toString(); |
|
|
|
|
|
|
|
if (!usedIdSet.add("H:" + masterId)) { |
|
|
|
|
|
|
|
return this.makeFailedResult("数据验证失败,第 " + (i + 1) + " 张单据的主键 " + masterId |
|
|
|
|
|
|
|
+ " 与前面的单据重复,请检查数据!"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// 内嵌分录优先;否则回退到平行数组或整批共享的分录对象(向后兼容)。
|
|
|
|
|
|
|
|
Object slaveElement = ownSlave != null ? ownSlave |
|
|
|
|
|
|
|
: (slaveArray == null ? slaveData : slaveArray.get(i)); |
|
|
|
JSONObject slave = slaveElement instanceof JSONObject ? (JSONObject) slaveElement : null; |
|
|
|
JSONObject slave = slaveElement instanceof JSONObject ? (JSONObject) slaveElement : null; |
|
|
|
if (slave != null) { |
|
|
|
if (slave != null) { |
|
|
|
this.makeupEntryPrimaryKey(slave, newIdSet); |
|
|
|
String entryError = this.makeupEntryPrimaryKey(slave, onlineTable, newIdSet, usedIdSet); |
|
|
|
|
|
|
|
if (entryError != null) { |
|
|
|
|
|
|
|
return this.makeFailedResult("数据验证失败,第 " + (i + 1) + " 张单据的分录" + entryError); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
masterList.add(master); |
|
|
|
masterList.add(master); |
|
|
|
slaveList.add(slave); |
|
|
|
slaveList.add(slave); |
|
|
|
@ -1301,19 +1324,32 @@ public class OnlineOperationServiceImpl implements OnlineOperationService { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* 给缺少主键的分录行补发主键,使每张单据的分录落到各自的表头下。 |
|
|
|
* 给缺少主键的分录行补发主键,并校验分录挂在合法的分录表下。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* <p>key 同时接受 tableId 与表名(见 {@link #resolveEntryTable})。查不到表或该表不是本表单 |
|
|
|
|
|
|
|
* 主表下挂的分录表时直接报错——原先静默跳过会让整段分录被无声丢弃。</p> |
|
|
|
* |
|
|
|
* |
|
|
|
* @param slaveData 一张单据的分录数据,key 为分录表 tableId。 |
|
|
|
* @param slaveData 一张单据的分录数据。 |
|
|
|
* @param newIdSet 本批次已生成的主键集合,用于防重复。 |
|
|
|
* @param masterTable 表单主表,用于校验分录表归属。 |
|
|
|
|
|
|
|
* @param newIdSet 本批次后端发号生成的主键集合,用于防重复。 |
|
|
|
|
|
|
|
* @param usedIdSet 本批次出现过的全部主键(含调用方预生成的),用于查重。 |
|
|
|
|
|
|
|
* @return 出错信息;无错返回 null。 |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
private void makeupEntryPrimaryKey(JSONObject slaveData, Set<Long> newIdSet) { |
|
|
|
private String makeupEntryPrimaryKey(JSONObject slaveData, OnlineTable masterTable, |
|
|
|
for (String tableIdStr : slaveData.keySet()) { |
|
|
|
Set<Long> newIdSet, Set<String> usedIdSet) { |
|
|
|
OnlineTable entryTable = onlineTableService.getOnlineTableFromCache(this.convertToLongId(tableIdStr)); |
|
|
|
for (String entryKey : slaveData.keySet()) { |
|
|
|
if (entryTable == null || entryTable.getPrimaryKeyColumn() == null) { |
|
|
|
OnlineTable entryTable = this.resolveEntryTable(entryKey); |
|
|
|
|
|
|
|
if (entryTable == null) { |
|
|
|
|
|
|
|
return "的表分键 [" + entryKey + "] 不是有效的表(应为分录表的 tableId 或表名),请检查!"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!this.isEntryTableOf(masterTable, entryTable)) { |
|
|
|
|
|
|
|
return "的表分键 [" + entryKey + "] 不是当前表单的分录表,请检查!"; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (entryTable.getPrimaryKeyColumn() == null) { |
|
|
|
continue; |
|
|
|
continue; |
|
|
|
} |
|
|
|
} |
|
|
|
String primaryKeyName = entryTable.getPrimaryKeyColumn().getColumnName(); |
|
|
|
String primaryKeyName = entryTable.getPrimaryKeyColumn().getColumnName(); |
|
|
|
Object entryValue = slaveData.get(tableIdStr); |
|
|
|
Object entryValue = slaveData.get(entryKey); |
|
|
|
// 分录数据既可能是「行数组」,也可能是「单行对象」(一对一关系),两种都补齐主键。
|
|
|
|
// 分录数据既可能是「行数组」,也可能是「单行对象」(一对一关系),两种都补齐主键。
|
|
|
|
List<JSONObject> entryRowList = new LinkedList<>(); |
|
|
|
List<JSONObject> entryRowList = new LinkedList<>(); |
|
|
|
if (entryValue instanceof JSONArray) { |
|
|
|
if (entryValue instanceof JSONArray) { |
|
|
|
@ -1326,11 +1362,77 @@ public class OnlineOperationServiceImpl implements OnlineOperationService { |
|
|
|
entryRowList.add((JSONObject) entryValue); |
|
|
|
entryRowList.add((JSONObject) entryValue); |
|
|
|
} |
|
|
|
} |
|
|
|
for (JSONObject entryRow : entryRowList) { |
|
|
|
for (JSONObject entryRow : entryRowList) { |
|
|
|
if (entryRow.get(primaryKeyName) == null) { |
|
|
|
// 已有主键必须保留(BOTP 转换预发号的分录 id 已被转换关系表引用),只给缺主键的行补号。
|
|
|
|
|
|
|
|
if (this.isBlankPrimaryKey(entryRow, primaryKeyName)) { |
|
|
|
entryRow.put(primaryKeyName, this.generatePrimaryKey(entryTable.getPrimaryKeyColumn(), newIdSet)); |
|
|
|
entryRow.put(primaryKeyName, this.generatePrimaryKey(entryTable.getPrimaryKeyColumn(), newIdSet)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
String entryId = entryRow.get(primaryKeyName).toString(); |
|
|
|
|
|
|
|
if (!usedIdSet.add("E:" + entryId)) { |
|
|
|
|
|
|
|
return "主键 " + entryId + " 与其他分录行重复,请检查数据!"; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 解析分录表的 key:数字按 tableId 查,非数字按表名查。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param entryKey slaveData 的 key。 |
|
|
|
|
|
|
|
* @return 在线表;解析不到返回 null。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private OnlineTable resolveEntryTable(String entryKey) { |
|
|
|
|
|
|
|
Long tableId = this.convertToLongId(StrUtil.trim(entryKey)); |
|
|
|
|
|
|
|
if (tableId != null) { |
|
|
|
|
|
|
|
return onlineTableService.getOnlineTableFromCache(tableId); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
OnlineTable table = onlineTableService.getOne(new LambdaQueryWrapper<OnlineTable>() |
|
|
|
|
|
|
|
.eq(OnlineTable::getTableName, StrUtil.trim(entryKey)).last("LIMIT 1")); |
|
|
|
|
|
|
|
return table; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 校验一张表是否为本表单主表下挂的分录表(ORM 元数据里 relationType=1 的链接列)。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param masterTable 表单主表。 |
|
|
|
|
|
|
|
* @param entryTable 待校验的表。 |
|
|
|
|
|
|
|
* @return 是分录表返回 true。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private boolean isEntryTableOf(OnlineTable masterTable, OnlineTable entryTable) { |
|
|
|
|
|
|
|
if (masterTable == null || entryTable == null |
|
|
|
|
|
|
|
|| masterTable.getTableId().equals(entryTable.getTableId())) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
EntityTable entityTable = ormGenDataSourceUtil.getEntityTable(masterTable.getTableName()); |
|
|
|
|
|
|
|
if (entityTable == null || entityTable.getEntityColumnList() == null) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
for (EntityColumn entityColumn : entityTable.getEntityColumnList()) { |
|
|
|
|
|
|
|
if (!(entityColumn instanceof LinkEntityColumn)) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
EntityLinkRelation relation = ((LinkEntityColumn) entityColumn).getEntityLinkRelation(); |
|
|
|
|
|
|
|
if (relation == null || relation.getRelationType() == null || relation.getRelationType() != 1) { |
|
|
|
|
|
|
|
continue; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (relation.getSuperTable() != null |
|
|
|
|
|
|
|
&& entryTable.getTableName().equals(relation.getSuperTable().getTableName())) { |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* 判断主键是否缺失:null、空串、"null" 都视为缺失。 |
|
|
|
|
|
|
|
* |
|
|
|
|
|
|
|
* @param data 行数据。 |
|
|
|
|
|
|
|
* @param primaryKeyName 主键字段名。 |
|
|
|
|
|
|
|
* @return 需要后端补号时返回 true。 |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private boolean isBlankPrimaryKey(JSONObject data, String primaryKeyName) { |
|
|
|
|
|
|
|
Object id = data.get(primaryKeyName); |
|
|
|
|
|
|
|
return id == null || StrUtil.isBlank(id.toString()) || "null".equalsIgnoreCase(id.toString()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
|