@ -1193,7 +1193,9 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -1193,7 +1193,9 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
List < JSONObject > masterList = new LinkedList < > ( ) ;
List < JSONObject > slaveList = new LinkedList < > ( ) ;
// 同一批次里发号生成的主键集合,用于防御生成器回绕或配置问题导致的重复主键。
// 防重复用:已经出现在本批数据里的主键(含调用方预生成的,如 BOTP 转换预发号的 id)。
Set < String > usedIdSet = new HashSet < > ( ) ;
// 同一批次里由后端发号生成的主键集合,用于防御生成器回绕导致的重复主键。
Set < Long > newIdSet = new HashSet < > ( ) ;
String primaryKeyName = onlineTable . getPrimaryKeyColumn ( ) . getColumnName ( ) ;
for ( int i = 0 ; i < masterArray . size ( ) ; i + + ) {
@ -1202,11 +1204,32 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -1202,11 +1204,32 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
return this . makeFailedResult ( "数据验证失败,第 " + ( i + 1 ) + " 张单据的数据格式不正确!" ) ;
}
JSONObject master = ( JSONObject ) element ;
// 方案 A:允许每张单据自带分录(masterData[i].slaveData),父子关系由结构保证,不再依赖下标。
// 这条分录键必须从表头里摘掉,否则会被当成表头字段交给 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 ) ) ;
Object slaveElement = slaveArray = = null ? slaveData : slaveArray . get ( i ) ;
}
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 ;
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 ) ;
slaveList . add ( slave ) ;
@ -1301,19 +1324,32 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -1301,19 +1324,32 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
/ * *
* 给缺少主键的分录行补发主键 , 使每张单据的分录落到各自的表头 下。
* 给缺少主键的分录行补发主键 , 并校验分录挂在合法的分录表 下。
*
* @param slaveData 一张单据的分录数据 , key 为分录表 tableId 。
* @param newIdSet 本批次已生成的主键集合 , 用于防重复 。
* < p > key 同时接受 tableId 与表名 ( 见 { @link # resolveEntryTable } ) 。 查不到表或该表不是本表单
* 主表下挂的分录表时直接报错 — — 原先静默跳过会让整段分录被无声丢弃 。 < / p >
*
* @param slaveData 一张单据的分录数据 。
* @param masterTable 表单主表 , 用于校验分录表归属 。
* @param newIdSet 本批次后端发号生成的主键集合 , 用于防重复 。
* @param usedIdSet 本批次出现过的全部主键 ( 含调用方预生成的 ) , 用于查重 。
* @return 出错信息 ; 无错返回 null 。
* /
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 ) {
private String makeupEntryPrimaryKey ( JSONObject slaveData , OnlineTable masterTable ,
Set < Long > newIdSet , Set < String > usedIdSet ) {
for ( String entryKey : slaveData . keySet ( ) ) {
OnlineTable entryTable = this . resolveEntryTable ( entryKey ) ;
if ( entryTable = = null ) {
return "的表分键 [" + entryKey + "] 不是有效的表(应为分录表的 tableId 或表名),请检查!" ;
}
if ( ! this . isEntryTableOf ( masterTable , entryTable ) ) {
return "的表分键 [" + entryKey + "] 不是当前表单的分录表,请检查!" ;
}
if ( entryTable . getPrimaryKeyColumn ( ) = = null ) {
continue ;
}
String primaryKeyName = entryTable . getPrimaryKeyColumn ( ) . getColumnName ( ) ;
Object entryValue = slaveData . get ( tableIdStr ) ;
Object entryValue = slaveData . get ( entryKey ) ;
// 分录数据既可能是「行数组」,也可能是「单行对象」(一对一关系),两种都补齐主键。
List < JSONObject > entryRowList = new LinkedList < > ( ) ;
if ( entryValue instanceof JSONArray ) {
@ -1326,11 +1362,77 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -1326,11 +1362,77 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
entryRowList . add ( ( JSONObject ) entryValue ) ;
}
for ( JSONObject entryRow : entryRowList ) {
if ( entryRow . get ( primaryKeyName ) = = null ) {
// 已有主键必须保留(BOTP 转换预发号的分录 id 已被转换关系表引用),只给缺主键的行补号。
if ( this . isBlankPrimaryKey ( entryRow , primaryKeyName ) ) {
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 ( ) ) ;
}
/ * *