Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
HYH.APSJ
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
佟礼
HYH.APSJ
Commits
a6cb038c
Commit
a6cb038c
authored
Sep 21, 2026
by
DESKTOP-VKRD9QF\Administration
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: expand related routings through multi-level BOM
parent
ded51bc0
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
205 additions
and
58 deletions
+205
-58
MacroPlannerDataConverter.java
.../com/aps/macroplanner/data/MacroPlannerDataConverter.java
+68
-58
MacroPlannerBomLoadingTest.java
...com/aps/macroplanner/data/MacroPlannerBomLoadingTest.java
+137
-0
No files found.
src/main/java/com/aps/macroplanner/data/MacroPlannerDataConverter.java
View file @
a6cb038c
...
...
@@ -25,6 +25,7 @@ import com.aps.service.ApsTimeConfigService;
import
com.aps.service.LanuchService
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
io.swagger.v3.oas.models.security.SecurityScheme
;
import
com.baomidou.mybatisplus.core.toolkit.support.SFunction
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
...
...
@@ -38,6 +39,7 @@ import java.time.temporal.ChronoUnit;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Collection
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.HashSet
;
...
...
@@ -164,6 +166,59 @@ public class MacroPlannerDataConverter {
// ==================== 步骤1: 数据加载 ====================
static
<
T
>
LambdaQueryWrapper
<
T
>
whereIn
(
SFunction
<
T
,
?>
column
,
Collection
<?>
values
)
{
List
<?>
uniqueValues
=
new
ArrayList
<>(
new
LinkedHashSet
<>(
values
));
LambdaQueryWrapper
<
T
>
wrapper
=
new
LambdaQueryWrapper
<>();
if
(
uniqueValues
.
isEmpty
())
{
return
wrapper
.
apply
(
"1 = 0"
);
}
return
wrapper
.
and
(
group
->
{
for
(
int
offset
=
0
;
offset
<
uniqueValues
.
size
();
offset
+=
1000
)
{
if
(
offset
>
0
)
group
.
or
();
group
.
in
(
column
,
uniqueValues
.
subList
(
offset
,
Math
.
min
(
offset
+
1000
,
uniqueValues
.
size
())));
}
});
}
private
List
<
Integer
>
expandRoutingHeaders
(
ConvertContext
ctx
,
Set
<
String
>
materialIds
)
{
Map
<
Integer
,
RoutingHeader
>
headers
=
new
LinkedHashMap
<>();
for
(
RoutingHeader
header
:
ctx
.
routingHeaders
)
{
if
(
header
.
getId
()
!=
null
)
headers
.
putIfAbsent
(
header
.
getId
(),
header
);
}
Set
<
String
>
queriedMaterials
=
new
HashSet
<>(
materialIds
);
List
<
Integer
>
frontier
=
new
ArrayList
<>(
headers
.
keySet
());
int
depth
=
0
;
while
(!
frontier
.
isEmpty
())
{
List
<
Routingsupporting
>
inputs
=
routingsupportingMapper
.
selectList
(
whereIn
(
Routingsupporting:
:
getRoutingHeaderId
,
frontier
)
.
eq
(
Routingsupporting:
:
getIsdeleted
,
0
));
Set
<
String
>
nextMaterials
=
inputs
.
stream
()
.
map
(
Routingsupporting:
:
getMaterialId
)
.
filter
(
Objects:
:
nonNull
)
.
collect
(
Collectors
.
toCollection
(
LinkedHashSet:
:
new
));
materialIds
.
addAll
(
nextMaterials
);
nextMaterials
.
removeAll
(
queriedMaterials
);
queriedMaterials
.
addAll
(
nextMaterials
);
List
<
Integer
>
nextFrontier
=
new
ArrayList
<>();
if
(!
nextMaterials
.
isEmpty
())
{
List
<
RoutingHeader
>
children
=
routingHeaderMapper
.
selectList
(
whereIn
(
RoutingHeader:
:
getMaterialId
,
nextMaterials
));
for
(
RoutingHeader
child
:
children
)
{
Integer
childId
=
child
.
getId
();
if
(
childId
!=
null
&&
!
headers
.
containsKey
(
childId
))
{
headers
.
put
(
childId
,
child
);
nextFrontier
.
add
(
childId
);
}
}
}
log
.
info
(
"BOM展开: 层={}, 本层工艺={}, 投料={}, 新工艺={}, 累计工艺={}"
,
++
depth
,
frontier
.
size
(),
inputs
.
size
(),
nextFrontier
.
size
(),
headers
.
size
());
frontier
=
nextFrontier
;
}
ctx
.
routingHeaders
=
new
ArrayList
<>(
headers
.
values
());
return
new
ArrayList
<>(
headers
.
keySet
());
}
private
ConvertContext
loadRawData
(
String
sceneId
)
{
ConvertContext
ctx
=
new
ConvertContext
();
...
...
@@ -225,8 +280,7 @@ public class MacroPlannerDataConverter {
// 3. 工艺路线头表
if
(!
materialIds
.
isEmpty
())
{
ctx
.
routingHeaders
=
routingHeaderMapper
.
selectList
(
new
LambdaQueryWrapper
<
RoutingHeader
>()
.
in
(
RoutingHeader:
:
getMaterialId
,
materialIds
));
whereIn
(
RoutingHeader:
:
getMaterialId
,
materialIds
));
routingIds
=
ctx
.
routingHeaders
.
stream
()
.
map
(
RoutingHeader:
:
getId
)
...
...
@@ -238,40 +292,7 @@ public class MacroPlannerDataConverter {
// 4. 工序 (通过 LanuchService 批量查询)
if
(!
routingIds
.
isEmpty
())
{
List
<
Integer
>
routingIds1
=
routingIds
;
// while (routingIds1!=null&&routingIds1.size()>0) {
// List<Routingsupporting> rss = routingsupportingMapper.selectList(
// new LambdaQueryWrapper<Routingsupporting>()
// .in(Routingsupporting::getRoutingHeaderId, routingIds)
// .eq(Routingsupporting::getIsdeleted, 0));
// if (rss != null && rss.size() > 0) {
// Set<String> materialIdrss = rss.stream()
// .map(Routingsupporting::getMaterialId)
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toSet());
// materialIds.addAll(materialIdrss);
// ctx.routingsupportings.addAll(rss);
//
// List<RoutingHeader> rhs = routingHeaderMapper.selectList(
// new LambdaQueryWrapper<RoutingHeader>()
// .in(RoutingHeader::getMaterialId, materialIdrss));
//
// if (rhs != null && rhs.size() > 0) {
// routingIds1 = rhs.stream()
// .map(RoutingHeader::getId)
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toList());
// routingIds.addAll(routingIds1);
// ctx.routingHeaders.addAll(rhs);
// } else {
// routingIds1 = null;
// }
// } else {
// routingIds1 = null;
// }
// }
routingIds
=
expandRoutingHeaders
(
ctx
,
materialIds
);
List
<
Long
>
routingIdsLong
=
routingIds
.
stream
()
.
map
(
Long:
:
valueOf
)
.
collect
(
Collectors
.
toList
());
...
...
@@ -285,8 +306,7 @@ public class MacroPlannerDataConverter {
// 5. 工艺物料消耗 (BOM)
if
(!
routingIds
.
isEmpty
())
{
ctx
.
routingsupportings
=
routingsupportingMapper
.
selectList
(
new
LambdaQueryWrapper
<
Routingsupporting
>()
.
in
(
Routingsupporting:
:
getRoutingHeaderId
,
routingIds
)
whereIn
(
Routingsupporting:
:
getRoutingHeaderId
,
routingIds
)
.
eq
(
Routingsupporting:
:
getIsdeleted
,
0
));
}
log
.
info
(
"加载工艺物料消耗: {} 条"
,
ctx
.
routingsupportings
.
size
());
...
...
@@ -305,32 +325,26 @@ public class MacroPlannerDataConverter {
// 7. 物料主数据
if
(!
materialIds
.
isEmpty
())
{
ctx
.
materialInfos
=
materialInfoMapper
.
selectList
(
new
LambdaQueryWrapper
<
MaterialInfo
>()
.
in
(
MaterialInfo:
:
getId
,
materialIds
));
whereIn
(
MaterialInfo:
:
getId
,
materialIds
));
}
log
.
info
(
"加载物料主数据: {} 条"
,
ctx
.
materialInfos
.
size
());
// 8. 库存、采购、在途 (按 materialId 过滤, 避免全表扫描)
if
(!
materialIds
.
isEmpty
())
{
ctx
.
stocks
=
stockMapper
.
selectList
(
new
LambdaQueryWrapper
<
Stock
>()
.
in
(
Stock:
:
getMaterialId
,
materialIds
)
whereIn
(
Stock:
:
getMaterialId
,
materialIds
)
.
eq
(
Stock:
:
getIsdeleted
,
0
));
ctx
.
materialPurchases
=
materialPurchaseMapper
.
selectList
(
new
LambdaQueryWrapper
<
MaterialPurchase
>()
.
in
(
MaterialPurchase:
:
getMaterialId
,
materialIds
)
whereIn
(
MaterialPurchase:
:
getMaterialId
,
materialIds
)
.
eq
(
MaterialPurchase:
:
getIsdeleted
,
0
));
ctx
.
erpPurchaseOrders
=
erpPurchaseOrderMapper
.
selectList
(
new
LambdaQueryWrapper
<
ErpPurchaseOrder
>()
.
in
(
ErpPurchaseOrder:
:
getMaterialId
,
materialIds
)
whereIn
(
ErpPurchaseOrder:
:
getMaterialId
,
materialIds
)
.
eq
(
ErpPurchaseOrder:
:
getIsdeleted
,
0
));
ctx
.
purchaseReceipts
=
purchaseReceiptMapper
.
selectList
(
new
LambdaQueryWrapper
<
PurchaseReceipt
>()
.
in
(
PurchaseReceipt:
:
getMaterialid
,
materialIds
)
whereIn
(
PurchaseReceipt:
:
getMaterialid
,
materialIds
)
.
eq
(
PurchaseReceipt:
:
getIsdeleted
,
0
));
ctx
.
sjzPfWhStocks
=
sjzPfWhStockMapper
.
selectList
(
new
LambdaQueryWrapper
<
SjzPfWhStock
>()
.
in
(
SjzPfWhStock:
:
getMaterialid
,
materialIds
)
whereIn
(
SjzPfWhStock:
:
getMaterialid
,
materialIds
)
.
eq
(
SjzPfWhStock:
:
getIsdeleted
,
0
));
}
log
.
info
(
"加载库存: {}, 采购: {}, ERP采购订单: {}, 待验: {}, 半成品在途: {}"
,
...
...
@@ -346,8 +360,7 @@ public class MacroPlannerDataConverter {
.
collect
(
Collectors
.
toList
());
if
(!
detailIds
.
isEmpty
())
{
ctx
.
routingDetailEquips
=
routingDetailEquipMapper
.
selectList
(
new
LambdaQueryWrapper
<
RoutingDetailEquip
>()
.
in
(
RoutingDetailEquip:
:
getRoutingDetailId
,
detailIds
)
whereIn
(
RoutingDetailEquip:
:
getRoutingDetailId
,
detailIds
)
.
eq
(
RoutingDetailEquip:
:
getIsdeleted
,
0
));
}
}
...
...
@@ -360,8 +373,7 @@ public class MacroPlannerDataConverter {
.
collect
(
Collectors
.
toSet
());
if
(!
equipIds
.
isEmpty
())
{
ctx
.
planResources
=
planResourceMapper
.
selectList
(
new
LambdaQueryWrapper
<
PlanResource
>()
.
in
(
PlanResource:
:
getId
,
equipIds
)
whereIn
(
PlanResource:
:
getId
,
equipIds
)
.
eq
(
PlanResource:
:
getIsdeleted
,
false
));
}
// 通过 PlanResource.referenceId 查询 Equipinfo
...
...
@@ -371,8 +383,7 @@ public class MacroPlannerDataConverter {
.
collect
(
Collectors
.
toSet
());
if
(!
equipinfoIds
.
isEmpty
())
{
ctx
.
equipinfos
=
equipinfoMapper
.
selectList
(
new
LambdaQueryWrapper
<
Equipinfo
>()
.
in
(
Equipinfo:
:
getId
,
equipinfoIds
)
whereIn
(
Equipinfo:
:
getId
,
equipinfoIds
)
.
eq
(
Equipinfo:
:
getIsdeleted
,
false
));
}
log
.
info
(
"加载设备资源: {}, 设备信息: {}"
,
ctx
.
planResources
.
size
(),
ctx
.
equipinfos
.
size
());
...
...
@@ -385,8 +396,7 @@ public class MacroPlannerDataConverter {
.
collect
(
Collectors
.
toSet
());
if
(!
planResourceIds
.
isEmpty
())
{
ctx
.
equipShiftCapacities
=
equipShiftCapacityMapper
.
selectList
(
new
LambdaQueryWrapper
<
EquipShiftCapacity
>()
.
in
(
EquipShiftCapacity:
:
getPlanResourceId
,
planResourceIds
)
whereIn
(
EquipShiftCapacity:
:
getPlanResourceId
,
planResourceIds
)
.
ge
(
EquipShiftCapacity:
:
getCapacityDate
,
ctx
.
baseTime
)
.
lt
(
EquipShiftCapacity:
:
getCapacityDate
,
horizonEndDateTime
)
.
eq
(
EquipShiftCapacity:
:
getIsDeleted
,
0
));
...
...
src/test/java/com/aps/macroplanner/data/MacroPlannerBomLoadingTest.java
0 → 100644
View file @
a6cb038c
package
com
.
aps
.
macroplanner
.
data
;
import
com.aps.entity.RoutingHeader
;
import
com.aps.entity.Routingsupporting
;
import
com.aps.mapper.RoutingHeaderMapper
;
import
com.aps.mapper.RoutingsupportingMapper
;
import
com.baomidou.mybatisplus.core.MybatisConfiguration
;
import
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper
;
import
com.baomidou.mybatisplus.core.metadata.TableInfoHelper
;
import
org.apache.ibatis.builder.MapperBuilderAssistant
;
import
org.junit.jupiter.api.BeforeEach
;
import
org.junit.jupiter.api.Test
;
import
org.junit.jupiter.api.Timeout
;
import
org.springframework.test.util.ReflectionTestUtils
;
import
java.lang.reflect.Constructor
;
import
java.util.*
;
import
java.util.stream.Collectors
;
import
java.util.stream.IntStream
;
import
static
org
.
junit
.
jupiter
.
api
.
Assertions
.*;
import
static
org
.
mockito
.
ArgumentMatchers
.
any
;
import
static
org
.
mockito
.
Mockito
.*;
class
MacroPlannerBomLoadingTest
{
private
MacroPlannerDataConverter
converter
;
private
RoutingHeaderMapper
headers
;
private
RoutingsupportingMapper
inputs
;
private
Object
context
;
@BeforeEach
void
setUp
()
throws
Exception
{
TableInfoHelper
.
initTableInfo
(
new
MapperBuilderAssistant
(
new
MybatisConfiguration
(),
"bom-test"
),
RoutingHeader
.
class
);
TableInfoHelper
.
initTableInfo
(
new
MapperBuilderAssistant
(
new
MybatisConfiguration
(),
"bom-test"
),
Routingsupporting
.
class
);
converter
=
new
MacroPlannerDataConverter
();
headers
=
mock
(
RoutingHeaderMapper
.
class
);
inputs
=
mock
(
RoutingsupportingMapper
.
class
);
ReflectionTestUtils
.
setField
(
converter
,
"routingHeaderMapper"
,
headers
);
ReflectionTestUtils
.
setField
(
converter
,
"routingsupportingMapper"
,
inputs
);
Class
<?>
contextType
=
Class
.
forName
(
MacroPlannerDataConverter
.
class
.
getName
()
+
"$ConvertContext"
);
Constructor
<?>
constructor
=
contextType
.
getDeclaredConstructor
();
constructor
.
setAccessible
(
true
);
context
=
constructor
.
newInstance
();
ReflectionTestUtils
.
setField
(
context
,
"routingHeaders"
,
new
ArrayList
<>(
Collections
.
singletonList
(
header
(
1
,
"A"
))));
}
@Test
@Timeout
(
5
)
void
eachLevelLoadsOnlyNewRoutingIds
()
{
List
<
Set
<
Object
>>
queriedRoutes
=
new
ArrayList
<>();
when
(
inputs
.
selectList
(
any
())).
thenAnswer
(
call
->
{
Set
<
Object
>
ids
=
values
(
call
.
getArgument
(
0
));
ids
.
remove
(
0
);
queriedRoutes
.
add
(
ids
);
if
(
ids
.
equals
(
Collections
.
singleton
(
1
)))
return
Collections
.
singletonList
(
input
(
1
,
"B"
));
if
(
ids
.
equals
(
Collections
.
singleton
(
2
)))
return
Collections
.
singletonList
(
input
(
2
,
"C"
));
return
Collections
.
emptyList
();
});
when
(
headers
.
selectList
(
any
())).
thenAnswer
(
call
->
values
(
call
.
getArgument
(
0
)).
contains
(
"B"
)
?
Collections
.
singletonList
(
header
(
2
,
"B"
))
:
Collections
.
emptyList
());
Set
<
String
>
materials
=
new
HashSet
<>(
Collections
.
singleton
(
"A"
));
List
<
Integer
>
result
=
ReflectionTestUtils
.
invokeMethod
(
converter
,
"expandRoutingHeaders"
,
context
,
materials
);
assertEquals
(
Arrays
.
asList
(
1
,
2
),
result
);
assertEquals
(
Arrays
.
asList
(
Collections
.
singleton
(
1
),
Collections
.
singleton
(
2
)),
queriedRoutes
);
assertEquals
(
new
HashSet
<>(
Arrays
.
asList
(
"A"
,
"B"
,
"C"
)),
materials
);
verify
(
headers
,
times
(
2
)).
selectList
(
any
());
}
@Test
@Timeout
(
5
)
void
cyclicBomTerminatesWithoutDiscardingReachableHeaders
()
{
when
(
inputs
.
selectList
(
any
())).
thenAnswer
(
call
->
values
(
call
.
getArgument
(
0
)).
contains
(
1
)
?
Collections
.
singletonList
(
input
(
1
,
"B"
))
:
Collections
.
singletonList
(
input
(
2
,
"A"
)));
when
(
headers
.
selectList
(
any
())).
thenReturn
(
Collections
.
singletonList
(
header
(
2
,
"B"
)));
List
<
Integer
>
result
=
ReflectionTestUtils
.
invokeMethod
(
converter
,
"expandRoutingHeaders"
,
context
,
new
HashSet
<>(
Collections
.
singleton
(
"A"
)));
assertEquals
(
Arrays
.
asList
(
1
,
2
),
result
);
verify
(
inputs
,
times
(
2
)).
selectList
(
any
());
verify
(
headers
,
times
(
1
)).
selectList
(
any
());
}
@Test
void
duplicateChildHeadersAreLoadedOnlyOnce
()
{
when
(
inputs
.
selectList
(
any
())).
thenReturn
(
Arrays
.
asList
(
input
(
1
,
"B"
),
input
(
1
,
"B"
)),
Collections
.
emptyList
());
when
(
headers
.
selectList
(
any
())).
thenReturn
(
Arrays
.
asList
(
header
(
2
,
"B"
),
header
(
2
,
"B"
),
header
(
3
,
"B"
)));
List
<
Integer
>
result
=
ReflectionTestUtils
.
invokeMethod
(
converter
,
"expandRoutingHeaders"
,
context
,
new
HashSet
<>(
Collections
.
singleton
(
"A"
)));
assertEquals
(
Arrays
.
asList
(
1
,
2
,
3
),
result
);
verify
(
headers
,
times
(
1
)).
selectList
(
any
());
verify
(
inputs
,
times
(
2
)).
selectList
(
any
());
}
@Test
void
nullInputMaterialDoesNotProduceUnboundedHeaderQuery
()
{
when
(
inputs
.
selectList
(
any
())).
thenReturn
(
Collections
.
singletonList
(
input
(
1
,
null
)));
List
<
Integer
>
result
=
ReflectionTestUtils
.
invokeMethod
(
converter
,
"expandRoutingHeaders"
,
context
,
new
HashSet
<>(
Collections
.
singleton
(
"A"
)));
assertEquals
(
Collections
.
singletonList
(
1
),
result
);
verifyNoInteractions
(
headers
);
}
@Test
void
oracleInGroupsAreBoundedDeduplicatedAndParenthesized
()
{
List
<
Integer
>
ids
=
IntStream
.
range
(
1
,
2002
).
boxed
().
collect
(
Collectors
.
toList
());
ids
.
add
(
1
);
LambdaQueryWrapper
<
RoutingHeader
>
wrapper
=
MacroPlannerDataConverter
.
whereIn
(
RoutingHeader:
:
getId
,
ids
);
String
predicate
=
wrapper
.
getSqlSegment
();
assertEquals
(
2001
,
wrapper
.
getParamNameValuePairs
().
size
());
assertEquals
(
3
,
predicate
.
split
(
" IN "
,
-
1
).
length
-
1
);
assertEquals
(
2
,
predicate
.
split
(
" OR "
,
-
1
).
length
-
1
);
java
.
util
.
regex
.
Matcher
groups
=
java
.
util
.
regex
.
Pattern
.
compile
(
" IN [(]([^)]+)[)]"
).
matcher
(
predicate
);
List
<
Integer
>
sizes
=
new
ArrayList
<>();
while
(
groups
.
find
())
sizes
.
add
(
groups
.
group
(
1
).
split
(
","
).
length
);
assertEquals
(
Arrays
.
asList
(
1000
,
1000
,
1
),
sizes
);
wrapper
.
eq
(
RoutingHeader:
:
getIsDeleted
,
false
);
assertTrue
(
wrapper
.
getSqlSegment
().
contains
(
") AND is_deleted ="
));
assertTrue
(
MacroPlannerDataConverter
.
whereIn
(
RoutingHeader:
:
getId
,
Collections
.
emptyList
())
.
getSqlSegment
().
contains
(
"1 = 0"
));
}
private
Set
<
Object
>
values
(
LambdaQueryWrapper
<?>
wrapper
)
{
wrapper
.
getSqlSegment
();
return
new
HashSet
<>(
wrapper
.
getParamNameValuePairs
().
values
());
}
private
RoutingHeader
header
(
int
id
,
String
material
)
{
RoutingHeader
header
=
new
RoutingHeader
();
header
.
setId
(
id
);
header
.
setMaterialId
(
material
);
return
header
;
}
private
Routingsupporting
input
(
int
routingId
,
String
material
)
{
Routingsupporting
input
=
new
Routingsupporting
();
input
.
setRoutingHeaderId
(
routingId
);
input
.
setMaterialId
(
material
);
return
input
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment