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
a1a0557e
Commit
a1a0557e
authored
Aug 12, 2026
by
Tong Li
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MP
parent
5a208354
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
234 additions
and
231 deletions
+234
-231
ApsTimeConfig.java
src/main/java/com/aps/entity/ApsTimeConfig.java
+3
-0
MacroPlannerDataConverter.java
.../com/aps/macroplanner/data/MacroPlannerDataConverter.java
+231
-231
No files found.
src/main/java/com/aps/entity/ApsTimeConfig.java
View file @
a1a0557e
...
@@ -17,4 +17,7 @@ public class ApsTimeConfig {
...
@@ -17,4 +17,7 @@ public class ApsTimeConfig {
private
BigDecimal
startCount
;
private
BigDecimal
startCount
;
private
BigDecimal
endCount
;
private
BigDecimal
endCount
;
private
LocalDateTime
deadlineTime
;
private
LocalDateTime
deadlineTime
;
/** 周期维度: DAY(天) / WEEK(周) / MONTH(月), 默认 DAY */
private
String
periodDimension
;
}
}
\ No newline at end of file
src/main/java/com/aps/macroplanner/data/MacroPlannerDataConverter.java
View file @
a1a0557e
...
@@ -4,14 +4,14 @@ import com.aps.common.util.ParamValidator;
...
@@ -4,14 +4,14 @@ import com.aps.common.util.ParamValidator;
import
com.aps.entity.*
;
import
com.aps.entity.*
;
import
com.aps.entity.basic.Material
;
import
com.aps.entity.basic.Material
;
import
com.aps.entity.basic.MaterialSupply
;
import
com.aps.entity.basic.MaterialSupply
;
import
com.aps.mapper.EquipCapacityDefMapper
;
import
com.aps.mapper.EquipShiftCapacityMapper
;
import
com.fasterxml.jackson.core.type.TypeReference
;
import
com.fasterxml.jackson.databind.ObjectMapper
;
import
com.aps.mapper.EquipinfoMapper
;
import
com.aps.mapper.EquipinfoMapper
;
import
com.aps.mapper.ErpPurchaseOrderMapper
;
import
com.aps.mapper.ErpPurchaseOrderMapper
;
import
com.aps.mapper.MaterialInfoMapper
;
import
com.aps.mapper.MaterialInfoMapper
;
import
com.aps.mapper.MaterialPurchaseMapper
;
import
com.aps.mapper.MaterialPurchaseMapper
;
import
com.aps.mapper.MesShiftWorkSchedMapper
;
import
com.aps.mapper.PlanResourceMapper
;
import
com.aps.mapper.PlanResourceMapper
;
import
com.aps.mapper.ProdEquipSpecialCalMapper
;
import
com.aps.mapper.ProdLaunchOrderMapper
;
import
com.aps.mapper.ProdLaunchOrderMapper
;
import
com.aps.mapper.PurchaseReceiptMapper
;
import
com.aps.mapper.PurchaseReceiptMapper
;
import
com.aps.mapper.RoutingDetailEquipMapper
;
import
com.aps.mapper.RoutingDetailEquipMapper
;
...
@@ -30,19 +30,7 @@ import java.math.BigDecimal;
...
@@ -30,19 +30,7 @@ import java.math.BigDecimal;
import
java.time.Duration
;
import
java.time.Duration
;
import
java.time.LocalDate
;
import
java.time.LocalDate
;
import
java.time.LocalDateTime
;
import
java.time.LocalDateTime
;
import
java.time.LocalTime
;
import
java.util.*
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.LinkedHashSet
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Objects
;
import
java.util.Optional
;
import
java.util.Set
;
import
java.util.stream.Collectors
;
import
java.util.stream.Collectors
;
/**
/**
...
@@ -114,11 +102,7 @@ public class MacroPlannerDataConverter {
...
@@ -114,11 +102,7 @@ public class MacroPlannerDataConverter {
@Autowired
@Autowired
private
PlanResourceMapper
planResourceMapper
;
private
PlanResourceMapper
planResourceMapper
;
@Autowired
@Autowired
private
ProdEquipSpecialCalMapper
prodEquipSpecialCalMapper
;
private
EquipShiftCapacityMapper
equipShiftCapacityMapper
;
@Autowired
private
MesShiftWorkSchedMapper
mesShiftWorkSchedMapper
;
@Autowired
private
EquipCapacityDefMapper
equipCapacityDefMapper
;
// ==================== 注入的 Service ====================
// ==================== 注入的 Service ====================
@Autowired
@Autowired
...
@@ -290,22 +274,19 @@ public class MacroPlannerDataConverter {
...
@@ -290,22 +274,19 @@ public class MacroPlannerDataConverter {
}
}
log
.
info
(
"加载设备资源: {}, 设备信息: {}"
,
ctx
.
planResources
.
size
(),
ctx
.
equipinfos
.
size
());
log
.
info
(
"加载设备资源: {}, 设备信息: {}"
,
ctx
.
planResources
.
size
(),
ctx
.
equipinfos
.
size
());
// 11. 设备日历: ProdEquipSpecialCal + MesShiftWorkSched + EquipCapacityDef
// 11. 设备产能日历: EquipShiftCapacity (每天一条, validTimePeriods=有效时间段JSON)
ctx
.
prodEquipSpecialCals
=
prodEquipSpecialCalMapper
.
selectList
(
Set
<
Long
>
planResourceIds
=
ctx
.
planResources
.
stream
()
new
LambdaQueryWrapper
<
ProdEquipSpecialCal
>()
.
map
(
PlanResource:
:
getId
)
.
eq
(
ProdEquipSpecialCal:
:
getSceneId
,
sceneId
));
.
filter
(
Objects:
:
nonNull
)
ctx
.
mesShiftWorkScheds
=
mesShiftWorkSchedMapper
.
selectList
(
.
map
(
Long:
:
valueOf
)
new
LambdaQueryWrapper
<
MesShiftWorkSched
>()
.
collect
(
Collectors
.
toSet
());
.
eq
(
MesShiftWorkSched:
:
getIsdeleted
,
0
));
if
(!
planResourceIds
.
isEmpty
())
{
// EquipCapacityDef 按 PlanResource.referenceId 筛选
ctx
.
equipShiftCapacities
=
equipShiftCapacityMapper
.
selectList
(
if
(!
equipinfoIds
.
isEmpty
())
{
new
LambdaQueryWrapper
<
EquipShiftCapacity
>()
ctx
.
equipCapacityDefs
=
equipCapacityDefMapper
.
selectList
(
.
in
(
EquipShiftCapacity:
:
getPlanResourceId
,
planResourceIds
)
new
LambdaQueryWrapper
<
EquipCapacityDef
>()
.
eq
(
EquipShiftCapacity:
:
getIsDeleted
,
0
));
.
in
(
EquipCapacityDef:
:
getReferenceId
,
equipinfoIds
)
.
eq
(
EquipCapacityDef:
:
getIsDeleted
,
0
));
}
}
log
.
info
(
"加载设备日历: 特殊日历={}, 班次模板={}, 产能定义={}"
,
log
.
info
(
"加载设备产能日历(EquipShiftCapacity): {} 条"
,
ctx
.
equipShiftCapacities
.
size
());
ctx
.
prodEquipSpecialCals
.
size
(),
ctx
.
mesShiftWorkScheds
.
size
(),
ctx
.
equipCapacityDefs
.
size
());
// 12. baseTime
// 12. baseTime
ApsTimeConfig
timeConfig
=
apsTimeConfigService
.
getOne
(
new
LambdaQueryWrapper
<>());
ApsTimeConfig
timeConfig
=
apsTimeConfigService
.
getOne
(
new
LambdaQueryWrapper
<>());
...
@@ -460,212 +441,164 @@ public class MacroPlannerDataConverter {
...
@@ -460,212 +441,164 @@ public class MacroPlannerDataConverter {
}
}
}
}
// ==================== 步骤3:
结合设备日历计算每个 PlanResource (设备) 的
日产能 ====================
// ==================== 步骤3:
从 EQUIP_SHIFT_CAPACITY 读取设备
日产能 ====================
/**
/**
*
按 PlanResource.id 计算每台设备的日产能 (小时), 结合班次+节假日+效率系数
。
*
从 EquipShiftCapacity.validTimePeriods 解析每台设备每天的可用时间段, 计算日产能
。
*
*
* <p>映射链路:</p>
* <p>validTimePeriods 格式: [{"StartTime":"2026-04-09 08:00:00","EndTime":"2026-04-09 18:00:00"}]
* <pre>
* 每天一条记录, 直接求和得到日可用小时数。</p>
* RoutingDetailEquip.equipId → PlanResource.id → unitId = "EQUIP_" + id
* PlanResource.referenceId → Equipinfo.id → Equipinfo.xxx
* ProdEquipSpecialCal(referenceType=1) → MesShiftWorkSched → 班次时间
* ProdEquipSpecialCal(referenceType=2) → 节假日日期
* EquipCapacityDef → 效率系数
* </pre>
*
*
* <p>结果存储到 {@code ctx.dailyCapacityByUnitId} 和节假日映射。</p>
* <p>结果:
* ctx.dailyCapacityByUnitId: unitId → 平均日产能 (小时, 含效率系数)
* ctx.holidayDatesByUnitId: unitId → 日产能为0的日期 (视为节假日)
* </p>
*/
*/
private
void
buildDailyCapacityByUnitId
(
ConvertContext
ctx
)
{
private
void
buildDailyCapacityByUnitId
(
ConvertContext
ctx
)
{
//
计算排产周期数 (从 ApsTimeConfig)
//
读取时间配置, 获取维度 + 计划周期
ApsTimeConfig
timeConfig
=
apsTimeConfigService
.
getOne
(
new
LambdaQueryWrapper
<>());
ApsTimeConfig
timeConfig
=
apsTimeConfigService
.
getOne
(
new
LambdaQueryWrapper
<>());
LocalDate
baseDate
=
ctx
.
baseTime
.
toLocalDate
();
// 维度: 默认 DAY
ctx
.
periodDimension
=
"DAY"
;
if
(
timeConfig
!=
null
&&
timeConfig
.
getPeriodDimension
()
!=
null
&&
!
timeConfig
.
getPeriodDimension
().
trim
().
isEmpty
())
{
ctx
.
periodDimension
=
timeConfig
.
getPeriodDimension
().
trim
().
toUpperCase
();
}
// 时间范围: horizonSeconds = endCount - startCount (秒)
long
horizonSeconds
=
86400
*
DEFAULT_PERIOD_COUNT
;
if
(
timeConfig
!=
null
&&
timeConfig
.
getStartCount
()
!=
null
&&
timeConfig
.
getEndCount
()
!=
null
)
{
if
(
timeConfig
!=
null
&&
timeConfig
.
getStartCount
()
!=
null
&&
timeConfig
.
getEndCount
()
!=
null
)
{
long
horizonSeconds
=
timeConfig
.
getEndCount
().
longValue
()
long
diff
=
timeConfig
.
getEndCount
().
longValue
()
-
timeConfig
.
getStartCount
().
longValue
();
-
timeConfig
.
getStartCount
().
longValue
();
if
(
diff
>
0
)
{
if
(
horizonSeconds
>
0
)
{
horizonSeconds
=
diff
;
ctx
.
periodCount
=
(
int
)
Math
.
max
(
1
,
horizonSeconds
/
86400
);
}
}
}
}
log
.
info
(
"排产周期数: {}"
,
ctx
.
periodCount
);
int
totalDays
=
(
int
)
Math
.
max
(
1
,
horizonSeconds
/
86400
);
LocalDate
horizonEnd
=
baseDate
.
plusDays
(
totalDays
);
// 构建索引
Map
<
Integer
,
PlanResource
>
planResourceById
=
ctx
.
planResources
.
stream
()
.
collect
(
Collectors
.
toMap
(
PlanResource:
:
getId
,
pr
->
pr
,
(
a
,
b
)
->
a
));
Map
<
Integer
,
Equipinfo
>
equipinfoById
=
ctx
.
equipinfos
.
stream
()
.
collect
(
Collectors
.
toMap
(
Equipinfo:
:
getId
,
e
->
e
,
(
a
,
b
)
->
a
));
// MesShiftWorkSched: weekWorkSchedId → List
Map
<
Integer
,
List
<
MesShiftWorkSched
>>
shiftSchedByWeekId
=
ctx
.
mesShiftWorkScheds
.
stream
()
.
filter
(
s
->
s
.
getWeekWorkSchedId
()
!=
null
)
.
collect
(
Collectors
.
groupingBy
(
MesShiftWorkSched:
:
getWeekWorkSchedId
));
// ProdEquipSpecialCal: planResourceId → List (referenceType=1 班次)
Map
<
Long
,
List
<
ProdEquipSpecialCal
>>
shiftCalsByPrId
=
ctx
.
prodEquipSpecialCals
.
stream
()
.
filter
(
c
->
c
.
getPlanResourceId
()
!=
null
&&
c
.
getReferenceType
()
!=
null
&&
c
.
getReferenceType
()
==
1
)
.
collect
(
Collectors
.
groupingBy
(
ProdEquipSpecialCal:
:
getPlanResourceId
));
// ProdEquipSpecialCal: planResourceId → List (referenceType=2 节假日)
Map
<
Long
,
List
<
ProdEquipSpecialCal
>>
holidayCalsByPrId
=
ctx
.
prodEquipSpecialCals
.
stream
()
.
filter
(
c
->
c
.
getPlanResourceId
()
!=
null
&&
c
.
getReferenceType
()
!=
null
&&
c
.
getReferenceType
()
==
2
)
.
collect
(
Collectors
.
groupingBy
(
ProdEquipSpecialCal:
:
getPlanResourceId
));
// EquipCapacityDef: equipinfo.id → EquipCapacityDef
Map
<
Long
,
EquipCapacityDef
>
capDefByRefId
=
ctx
.
equipCapacityDefs
.
stream
()
.
filter
(
d
->
d
.
getReferenceId
()
!=
null
)
.
collect
(
Collectors
.
toMap
(
EquipCapacityDef:
:
getReferenceId
,
d
->
d
,
(
a
,
b
)
->
a
));
double
DEFAULT_DAILY_HOURS
=
16.0
;
// 按维度计算 periodCount
ctx
.
periodCount
=
calculatePeriodCount
(
ctx
.
periodDimension
,
baseDate
,
horizonEnd
);
log
.
info
(
"排产周期: dimension={}, periodCount={}, 总天数={}"
,
ctx
.
periodDimension
,
ctx
.
periodCount
,
totalDays
);
// 收集所有 unitId (从 RoutingDetailEquip)
// planResourceId → PlanResource.id
Set
<
String
>
allUnitIds
=
new
HashSet
<>();
Map
<
Long
,
PlanResource
>
prById
=
ctx
.
planResources
.
stream
()
for
(
RoutingDetailEquip
rde
:
ctx
.
routingDetailEquips
)
{
.
collect
(
Collectors
.
toMap
(
p
->
Long
.
valueOf
(
p
.
getId
()),
p
->
p
,
(
a
,
b
)
->
a
));
if
(
rde
.
getEquipId
()
!=
null
)
{
allUnitIds
.
add
(
"EQUIP_"
+
rde
.
getEquipId
());
}
}
for
(
String
unitId
:
allUnitIds
)
{
ObjectMapper
mapper
=
new
ObjectMapper
();
String
idStr
=
unitId
.
substring
(
6
);
// 去掉 "EQUIP_" 前缀
for
(
EquipShiftCapacity
esc
:
ctx
.
equipShiftCapacities
)
{
long
planResourceId
;
Long
prId
=
esc
.
getPlanResourceId
();
try
{
if
(
prId
==
null
||
esc
.
getCapacityDate
()
==
null
)
{
planResourceId
=
Long
.
parseLong
(
idStr
);
}
catch
(
NumberFormatException
e
)
{
ctx
.
dailyCapacityByUnitId
.
put
(
unitId
,
DEFAULT_DAILY_HOURS
);
continue
;
continue
;
}
}
PlanResource
pr
=
prById
.
get
(
prId
);
PlanResource
pr
=
planResourceById
.
get
(
planResourceId
);
if
(
pr
==
null
)
{
if
(
pr
==
null
)
{
ctx
.
dailyCapacityByUnitId
.
put
(
unitId
,
DEFAULT_DAILY_HOURS
);
continue
;
continue
;
}
}
String
unitId
=
"EQUIP_"
+
pr
.
getId
();
Equipinfo
eq
=
(
pr
.
getReferenceId
()
!=
null
)
?
equipinfoById
.
get
(
pr
.
getReferenceId
())
:
null
;
double
dailyHours
=
0.0
;
if
(
esc
.
getValidTimePeriods
()
!=
null
&&
!
esc
.
getValidTimePeriods
().
trim
().
isEmpty
())
{
// 效率系数: EquipCapacityDef > ProdEquipSpecialCal > 默认 1.0
try
{
double
efficiency
=
1.0
;
List
<
Map
<
String
,
String
>>
periods
=
mapper
.
readValue
(
if
(
eq
!=
null
)
{
esc
.
getValidTimePeriods
(),
EquipCapacityDef
capDef
=
capDefByRefId
.
get
(
eq
.
getId
().
longValue
());
new
TypeReference
<
List
<
Map
<
String
,
String
>>>()
{});
if
(
capDef
!=
null
&&
capDef
.
getEfficiencyCoeff
()
!=
null
)
{
for
(
Map
<
String
,
String
>
tp
:
periods
)
{
efficiency
=
capDef
.
getEfficiencyCoeff
();
String
startStr
=
tp
.
get
(
"StartTime"
);
}
String
endStr
=
tp
.
get
(
"EndTime"
);
}
if
(
startStr
!=
null
&&
endStr
!=
null
)
{
if
(
efficiency
==
1.0
)
{
LocalDateTime
start
=
LocalDateTime
.
parse
(
startStr
.
replace
(
" "
,
"T"
));
List
<
ProdEquipSpecialCal
>
shiftCals
=
shiftCalsByPrId
.
getOrDefault
(
pr
.
getId
(),
Collections
.
emptyList
());
LocalDateTime
end
=
LocalDateTime
.
parse
(
endStr
.
replace
(
" "
,
"T"
));
for
(
ProdEquipSpecialCal
cal
:
shiftCals
)
{
double
hours
=
Duration
.
between
(
start
,
end
).
toMinutes
()
/
60.0
;
if
(
cal
.
getEfficiencyCoeff
()
>
0
)
{
dailyHours
+=
Math
.
max
(
0
,
hours
);
efficiency
=
cal
.
getEfficiencyCoeff
();
break
;
}
}
}
// 收集班次时间
List
<
MesShiftWorkSched
>
allShifts
=
new
ArrayList
<>();
List
<
ProdEquipSpecialCal
>
shiftCals
=
shiftCalsByPrId
.
getOrDefault
(
pr
.
getId
(),
Collections
.
emptyList
());
for
(
ProdEquipSpecialCal
cal
:
shiftCals
)
{
if
(
cal
.
getReferenceId
()
!=
null
)
{
List
<
MesShiftWorkSched
>
scheds
=
shiftSchedByWeekId
.
get
(
cal
.
getReferenceId
().
intValue
());
if
(
scheds
!=
null
)
{
allShifts
.
addAll
(
scheds
);
}
}
}
}
}
// 备选: 通过 PlanResource.workSchedId
}
catch
(
Exception
e
)
{
if
(
allShifts
.
isEmpty
()
&&
pr
.
getWorkSchedId
()
!=
null
)
{
log
.
warn
(
"解析 validTimePeriods 失败: unitId={}, date={}, json={}"
,
List
<
MesShiftWorkSched
>
scheds
=
shiftSchedByWeekId
.
get
(
pr
.
getWorkSchedId
().
intValue
());
unitId
,
esc
.
getCapacityDate
(),
esc
.
getValidTimePeriods
(),
e
);
if
(
scheds
!=
null
)
{
allShifts
.
addAll
(
scheds
);
}
}
}
}
double
dailyHours
;
// 应用效率系数
if
(!
allShifts
.
isEmpty
())
{
double
efficiency
=
1.0
;
dailyHours
=
computeDailyHoursFromShifts
(
allShifts
);
if
(
esc
.
getEfficiencyCoeff
()
!=
null
&&
esc
.
getEfficiencyCoeff
()
>
0
)
{
}
else
{
efficiency
=
esc
.
getEfficiencyCoeff
();
// 无班次: 使用 capabilityValue 回退
if
(
pr
.
getCapabilityValue
()
!=
null
)
{
dailyHours
=
pr
.
getCapabilityValue
().
doubleValue
();
}
else
if
(
eq
!=
null
&&
eq
.
getCapabilityValue
()
!=
null
)
{
dailyHours
=
eq
.
getCapabilityValue
().
doubleValue
();
}
else
{
dailyHours
=
DEFAULT_DAILY_HOURS
;
}
}
}
dailyHours
*=
efficiency
;
dailyHours
*=
efficiency
;
ctx
.
dailyCapacityByUnitId
.
put
(
unitId
,
Math
.
max
(
0
,
dailyHours
));
ctx
.
dailyHoursByUnitId
.
computeIfAbsent
(
unitId
,
k
->
new
LinkedHashMap
<>())
.
put
(
esc
.
getCapacityDate
().
toLocalDate
(),
dailyHours
);
}
}
// 构建节假日映射: unitId → Set<LocalDate>
// 汇总: 平均日产能 + 节假日
for
(
Map
.
Entry
<
Long
,
List
<
ProdEquipSpecialCal
>>
entry
:
holidayCalsByPrId
.
entrySet
())
{
for
(
Map
.
Entry
<
String
,
Map
<
LocalDate
,
Double
>>
entry
:
ctx
.
dailyHoursByUnitId
.
entrySet
())
{
String
unitId
=
"EQUIP_"
+
entry
.
getKey
();
String
unitId
=
entry
.
getKey
();
Set
<
LocalDate
>
dates
=
ctx
.
holidayDatesByUnitId
.
computeIfAbsent
(
unitId
,
k
->
new
HashSet
<>());
double
total
=
0
;
for
(
ProdEquipSpecialCal
cal
:
entry
.
getValue
())
{
int
nonZeroDays
=
0
;
if
(
cal
.
getStartDate
()
==
null
||
cal
.
getEndDate
()
==
null
)
{
Set
<
LocalDate
>
holidayDates
=
new
HashSet
<>();
continue
;
for
(
Map
.
Entry
<
LocalDate
,
Double
>
de
:
entry
.
getValue
().
entrySet
())
{
if
(
de
.
getValue
()
<=
0
)
{
holidayDates
.
add
(
de
.
getKey
());
}
else
{
total
+=
de
.
getValue
();
nonZeroDays
++;
}
}
LocalDate
start
=
cal
.
getStartDate
().
toLocalDate
();
LocalDate
end
=
cal
.
getEndDate
().
toLocalDate
();
for
(
LocalDate
d
=
start
;
!
d
.
isAfter
(
end
);
d
=
d
.
plusDays
(
1
))
{
dates
.
add
(
d
);
}
}
double
avgDaily
=
nonZeroDays
>
0
?
total
/
nonZeroDays
:
16.0
;
ctx
.
dailyCapacityByUnitId
.
put
(
unitId
,
avgDaily
);
if
(!
holidayDates
.
isEmpty
())
{
ctx
.
holidayDatesByUnitId
.
put
(
unitId
,
holidayDates
);
}
}
}
}
log
.
info
(
"
构建日产能(含日历+节假日
): {} 台设备, {} 台有节假日, 默认={}h"
,
log
.
info
(
"
设备日产能(EquipShiftCapacity
): {} 台设备, {} 台有节假日, 默认={}h"
,
ctx
.
dailyCapacityByUnitId
.
size
(),
ctx
.
holidayDatesByUnitId
.
size
(),
DEFAULT_DAILY_HOURS
);
ctx
.
dailyCapacityByUnitId
.
size
(),
ctx
.
holidayDatesByUnitId
.
size
(),
16.0
);
}
}
/**
/**
* 从 MesShiftWorkSched 列表计算平均日工作小时数。
* 按维度计算周期数。
* 遍历每周 7 天, 累加匹配的班次时间, 除以 7 得到日均值。
*
* @param dimension 维度: DAY / WEEK / MONTH
* @param baseDate 起始日期
* @param horizonEnd 计划截止日期 (不包含)
* @return 周期数
*/
*/
private
double
computeDailyHoursFromShifts
(
List
<
MesShiftWorkSched
>
shifts
)
{
private
static
int
calculatePeriodCount
(
String
dimension
,
LocalDate
baseDate
,
LocalDate
horizonEnd
)
{
double
[]
hoursByDay
=
new
double
[
7
];
// 0=Sunday, 1=Monday, ..., 6=Saturday
switch
(
dimension
)
{
for
(
MesShiftWorkSched
s
:
shifts
)
{
case
"WEEK"
:
if
(
s
.
getShiftStart
()
==
null
||
s
.
getShiftEnd
()
==
null
)
{
// 按周: 从 baseDate 开始, 每7天一周, 不足7天也算一周
continue
;
long
totalDays
=
java
.
time
.
temporal
.
ChronoUnit
.
DAYS
.
between
(
baseDate
,
horizonEnd
);
}
return
(
int
)
Math
.
max
(
1
,
Math
.
ceil
(
totalDays
/
7.0
));
LocalTime
startTime
=
s
.
getShiftStart
().
toLocalTime
();
case
"MONTH"
:
LocalTime
endTime
=
s
.
getShiftEnd
().
toLocalTime
();
// 按月: 从 baseDate 所在月开始, 跨越的自然月数
double
shiftHours
=
calculateShiftHours
(
startTime
,
endTime
);
int
months
=
0
;
LocalDate
cursor
=
baseDate
.
withDayOfMonth
(
1
);
int
startDay
=
s
.
getStartWeekDay
()
!=
null
?
s
.
getStartWeekDay
()
:
1
;
while
(
cursor
.
isBefore
(
horizonEnd
))
{
int
endDay
=
s
.
getEndWeekDay
()
!=
null
?
s
.
getEndWeekDay
()
:
startDay
;
cursor
=
cursor
.
plusMonths
(
1
);
int
mappedStart
=
(
startDay
%
7
);
// 数据库: 1=Mon→1, 7=Sun→0
months
++;
int
mappedEnd
=
(
endDay
%
7
);
}
if
(
mappedStart
==
mappedEnd
)
{
return
Math
.
max
(
1
,
months
);
hoursByDay
[
mappedStart
]
+=
shiftHours
;
case
"DAY"
:
}
else
{
default
:
int
day
=
mappedStart
;
long
days
=
java
.
time
.
temporal
.
ChronoUnit
.
DAYS
.
between
(
baseDate
,
horizonEnd
);
while
(
day
!=
mappedEnd
)
{
return
(
int
)
Math
.
max
(
1
,
days
);
hoursByDay
[
day
]
+=
shiftHours
;
day
=
(
day
+
1
)
%
7
;
}
}
hoursByDay
[
mappedEnd
]
+=
shiftHours
;
}
}
double
totalWeekHours
=
0
;
for
(
double
h
:
hoursByDay
)
{
totalWeekHours
+=
h
;
}
return
totalWeekHours
/
7.0
;
}
}
/**
/**
* 计算
班次时长 (小时), 处理跨天 (如 22:00-06:00
)。
* 计算
给定日期范围内的有效工作日数 (不含周日
)。
*/
*/
private
double
calculateShiftHours
(
LocalTime
start
,
LocalTime
end
)
{
private
static
int
calculateWorkDays
(
LocalDate
start
,
LocalDate
endExclusive
)
{
long
minutes
;
int
count
=
0
;
if
(
end
.
isAfter
(
start
))
{
LocalDate
d
=
start
;
minutes
=
Duration
.
between
(
start
,
end
).
toMinutes
();
while
(
d
.
isBefore
(
endExclusive
))
{
}
else
{
if
(
d
.
getDayOfWeek
().
getValue
()
!=
7
)
{
// 非周日
minutes
=
Duration
.
between
(
start
,
LocalTime
.
MAX
).
toMinutes
()
count
++;
+
Duration
.
between
(
LocalTime
.
MIN
,
end
).
toMinutes
();
}
d
=
d
.
plusDays
(
1
);
}
}
return
minutes
/
60.0
;
return
count
;
}
}
// ==================== Sink: 继承 TestDataBuilder, 直接操作 protected 字段 ====================
// ==================== Sink: 继承 TestDataBuilder, 直接操作 protected 字段 ====================
...
@@ -916,7 +849,6 @@ public class MacroPlannerDataConverter {
...
@@ -916,7 +849,6 @@ public class MacroPlannerDataConverter {
// 根据 MATERIAL_PURCHASE 创建供应商 unit:
// 根据 MATERIAL_PURCHASE 创建供应商 unit:
// - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle
// - 有 MATERIAL_PURCHASE → 每供应商一个 Operation, leadTimeDays = purchaseCycle
// - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限
// - 无 MATERIAL_PURCHASE → 通用供应商 unit, relativeDuration=1, leadTimeDays=0, UnitPeriod 无限
for
(
Material
m
:
ctx
.
materialByMaterialId
.
values
())
{
for
(
Material
m
:
ctx
.
materialByMaterialId
.
values
())
{
if
(!
"MP"
.
equals
(
m
.
getMaterialTypeName
()))
{
if
(!
"MP"
.
equals
(
m
.
getMaterialTypeName
()))
{
continue
;
continue
;
...
@@ -969,7 +901,6 @@ public class MacroPlannerDataConverter {
...
@@ -969,7 +901,6 @@ public class MacroPlannerDataConverter {
operations
.
add
(
procureOp
);
operations
.
add
(
procureOp
);
log
.
info
(
"为 MP 原材料 {} 创建通用采购 Operation (无供应商, 无限产能)"
,
m
.
getId
());
log
.
info
(
"为 MP 原材料 {} 创建通用采购 Operation (无供应商, 无限产能)"
,
m
.
getId
());
}
}
}
}
}
}
log
.
info
(
"构建 OperationInput: {}"
,
operationInputs
.
size
());
log
.
info
(
"构建 OperationInput: {}"
,
operationInputs
.
size
());
...
@@ -1054,15 +985,37 @@ public class MacroPlannerDataConverter {
...
@@ -1054,15 +985,37 @@ public class MacroPlannerDataConverter {
initialInventories
.
size
(),
inTransitSupplies
.
size
());
initialInventories
.
size
(),
inTransitSupplies
.
size
());
}
}
// ---------- 步骤7: Period + UnitPeriod (
结合设备日历和周期长度
) ----------
// ---------- 步骤7: Period + UnitPeriod (
按维度创建周期, 聚合日产能
) ----------
private
void
fillPeriodsAndUnitPeriods
(
ConvertContext
ctx
)
{
private
void
fillPeriodsAndUnitPeriods
(
ConvertContext
ctx
)
{
LocalDate
baseDate
=
ctx
.
baseTime
.
toLocalDate
();
LocalDate
baseDate
=
ctx
.
baseTime
.
toLocalDate
();
int
periodCount
=
ctx
.
periodCount
;
int
periodCount
=
ctx
.
periodCount
;
String
dimension
=
ctx
.
periodDimension
;
// 创建周期 (1天/周期)
// 计算 horizonEnd (与 buildDailyCapacityByUnitId 中一致)
for
(
int
i
=
0
;
i
<
periodCount
;
i
++)
{
ApsTimeConfig
timeConfig
=
apsTimeConfigService
.
getOne
(
new
LambdaQueryWrapper
<>());
periods
.
add
(
new
Period
(
i
,
"P"
+
(
i
+
1
),
1.0
,
baseDate
.
plusDays
(
i
)));
long
horizonSeconds
=
86400
*
DEFAULT_PERIOD_COUNT
;
if
(
timeConfig
!=
null
&&
timeConfig
.
getStartCount
()
!=
null
&&
timeConfig
.
getEndCount
()
!=
null
)
{
long
diff
=
timeConfig
.
getEndCount
().
longValue
()
-
timeConfig
.
getStartCount
().
longValue
();
if
(
diff
>
0
)
{
horizonSeconds
=
diff
;
}
}
int
totalDays
=
(
int
)
Math
.
max
(
1
,
horizonSeconds
/
86400
);
LocalDate
horizonEnd
=
baseDate
.
plusDays
(
totalDays
);
// 按维度创建 Period
switch
(
dimension
)
{
case
"WEEK"
:
createWeekPeriods
(
baseDate
,
periodCount
,
horizonEnd
);
break
;
case
"MONTH"
:
createMonthPeriods
(
baseDate
,
periodCount
,
horizonEnd
);
break
;
case
"DAY"
:
default
:
createDayPeriods
(
baseDate
,
periodCount
);
break
;
}
}
// 收集所有 unitId (遍历每个 Operation 的每个 UnitOperation)
// 收集所有 unitId (遍历每个 Operation 的每个 UnitOperation)
...
@@ -1080,35 +1033,82 @@ public class MacroPlannerDataConverter {
...
@@ -1080,35 +1033,82 @@ public class MacroPlannerDataConverter {
LocalDate
periodEnd
=
periodStart
.
plusDays
((
long
)
p
.
getDurationInDays
());
LocalDate
periodEnd
=
periodStart
.
plusDays
((
long
)
p
.
getDurationInDays
());
for
(
String
unitId
:
unitIds
)
{
for
(
String
unitId
:
unitIds
)
{
// 无限产能设备 (通用供应商): 直接创建 unlimited UnitPeriod
// 无限产能设备 (通用供应商): 无需计算, 直接创建 unlimited UnitPeriod
if
(
ctx
.
unlimitedUnitIds
.
contains
(
unitId
))
{
if
(
ctx
.
unlimitedUnitIds
.
contains
(
unitId
))
{
unitPeriods
.
add
(
UnitPeriod
.
unlimited
(
unitId
,
p
));
unitPeriods
.
add
(
UnitPeriod
.
unlimited
(
unitId
,
p
));
continue
;
continue
;
}
}
double
dailyCapacity
=
ctx
.
dailyCapacityByUnitId
.
getOrDefault
(
unitId
,
DEFAULT_DAILY_HOURS
)
;
double
periodMaxCapacity
;
Map
<
LocalDate
,
Double
>
hourlyData
=
ctx
.
dailyHoursByUnitId
.
get
(
unitId
);
// 节假日扣减
if
(
hourlyData
!=
null
&&
!
hourlyData
.
isEmpty
())
{
Set
<
LocalDate
>
holidays
=
ctx
.
holidayDatesByUnitId
.
getOrDefault
(
unitId
,
Collections
.
emptySet
());
// 有 EquipShiftCapacity 数据: 按实际日产能加和
long
holidaysInPeriod
=
0
;
double
totalHours
=
0
;
for
(
LocalDate
h
:
holidays
)
{
for
(
Map
.
Entry
<
LocalDate
,
Double
>
e
:
hourlyData
.
entrySet
()
)
{
if
(!
h
.
isBefore
(
periodStart
)
&&
h
.
isBefore
(
periodEnd
))
{
if
(!
e
.
getKey
().
isBefore
(
periodStart
)
&&
e
.
getKey
()
.
isBefore
(
periodEnd
))
{
holidaysInPeriod
++
;
totalHours
+=
e
.
getValue
()
;
}
}
}
}
long
periodDays
=
(
long
)
p
.
getDurationInDays
();
periodMaxCapacity
=
totalHours
;
long
effectiveDays
=
Math
.
max
(
0
,
periodDays
-
holidaysInPeriod
);
}
else
{
double
periodMaxCapacity
=
dailyCapacity
*
effectiveDays
;
// 无 EquipShiftCapacity 数据: 使用平均日产能 × 工作日天数
double
dailyCapacity
=
ctx
.
dailyCapacityByUnitId
.
getOrDefault
(
unitId
,
DEFAULT_DAILY_HOURS
);
int
workDays
=
calculateWorkDays
(
periodStart
,
periodEnd
);
periodMaxCapacity
=
dailyCapacity
*
workDays
;
}
if
(
periodMaxCapacity
<=
0
)
{
if
(
periodMaxCapacity
<=
0
)
{
periodMaxCapacity
=
DEFAULT_DAILY_HOURS
*
effectiveDays
;
int
workDays
=
calculateWorkDays
(
periodStart
,
periodEnd
);
periodMaxCapacity
=
DEFAULT_DAILY_HOURS
*
Math
.
max
(
1
,
workDays
);
}
}
unitPeriods
.
add
(
new
UnitPeriod
(
unitId
,
p
,
0.0
,
periodMaxCapacity
,
false
));
unitPeriods
.
add
(
new
UnitPeriod
(
unitId
,
p
,
0.0
,
periodMaxCapacity
,
false
));
}
}
}
}
log
.
info
(
"构建 Period: {} (共{}天), UnitPeriod: {} (基于设备日历+节假日)"
,
log
.
info
(
"构建 Period: {} (dimension={}, 共{}天), UnitPeriod: {}"
,
periods
.
size
(),
periodCount
,
unitPeriods
.
size
());
periods
.
size
(),
dimension
,
totalDays
,
unitPeriods
.
size
());
}
/** DAY 维度: 每天一个 Period */
private
void
createDayPeriods
(
LocalDate
baseDate
,
int
periodCount
)
{
for
(
int
i
=
0
;
i
<
periodCount
;
i
++)
{
periods
.
add
(
new
Period
(
i
,
formatDayName
(
baseDate
.
plusDays
(
i
)),
1.0
,
baseDate
.
plusDays
(
i
)));
}
}
/** WEEK 维度: 每周一个 Period (周一起始, 7天, 最后一周可能不足7天) */
private
void
createWeekPeriods
(
LocalDate
baseDate
,
int
periodCount
,
LocalDate
horizonEnd
)
{
for
(
int
i
=
0
;
i
<
periodCount
;
i
++)
{
LocalDate
weekStart
=
baseDate
.
plusDays
(
i
*
7L
);
LocalDate
weekEnd
=
weekStart
.
plusDays
(
7
);
if
(
weekEnd
.
isAfter
(
horizonEnd
))
{
weekEnd
=
horizonEnd
;
}
long
durationDays
=
java
.
time
.
temporal
.
ChronoUnit
.
DAYS
.
between
(
weekStart
,
weekEnd
);
periods
.
add
(
new
Period
(
i
,
"W"
+
(
i
+
1
)
+
"("
+
weekStart
+
")"
,
(
double
)
durationDays
,
weekStart
));
}
}
/** MONTH 维度: 每月一个 Period (按自然月, 首尾月可能不足月) */
private
void
createMonthPeriods
(
LocalDate
baseDate
,
int
periodCount
,
LocalDate
horizonEnd
)
{
LocalDate
cursor
=
baseDate
.
withDayOfMonth
(
1
);
for
(
int
i
=
0
;
i
<
periodCount
;
i
++)
{
LocalDate
monthStart
=
cursor
;
LocalDate
monthEnd
=
cursor
.
plusMonths
(
1
);
if
(
monthEnd
.
isAfter
(
horizonEnd
))
{
monthEnd
=
horizonEnd
;
}
long
durationDays
=
java
.
time
.
temporal
.
ChronoUnit
.
DAYS
.
between
(
monthStart
,
monthEnd
);
periods
.
add
(
new
Period
(
i
,
formatMonthName
(
monthStart
),
(
double
)
durationDays
,
monthStart
));
cursor
=
cursor
.
plusMonths
(
1
);
}
}
private
static
String
formatDayName
(
LocalDate
date
)
{
return
String
.
format
(
"D%02d(%02d-%02d)"
,
date
.
getDayOfYear
(),
date
.
getMonthValue
(),
date
.
getDayOfMonth
());
}
private
static
String
formatMonthName
(
LocalDate
date
)
{
return
String
.
format
(
"M%d-%02d"
,
date
.
getYear
(),
date
.
getMonthValue
());
}
}
// ---------- 步骤8: SalesDemand (从订单推导) ----------
// ---------- 步骤8: SalesDemand (从订单推导) ----------
...
@@ -1236,9 +1236,7 @@ public class MacroPlannerDataConverter {
...
@@ -1236,9 +1236,7 @@ public class MacroPlannerDataConverter {
List
<
RoutingDetailEquip
>
routingDetailEquips
=
new
ArrayList
<>();
List
<
RoutingDetailEquip
>
routingDetailEquips
=
new
ArrayList
<>();
List
<
Equipinfo
>
equipinfos
=
new
ArrayList
<>();
List
<
Equipinfo
>
equipinfos
=
new
ArrayList
<>();
List
<
PlanResource
>
planResources
=
new
ArrayList
<>();
List
<
PlanResource
>
planResources
=
new
ArrayList
<>();
List
<
ProdEquipSpecialCal
>
prodEquipSpecialCals
=
new
ArrayList
<>();
List
<
EquipShiftCapacity
>
equipShiftCapacities
=
new
ArrayList
<>();
List
<
MesShiftWorkSched
>
mesShiftWorkScheds
=
new
ArrayList
<>();
List
<
EquipCapacityDef
>
equipCapacityDefs
=
new
ArrayList
<>();
LocalDateTime
baseTime
;
LocalDateTime
baseTime
;
// 转换过程的中间映射
// 转换过程的中间映射
...
@@ -1254,15 +1252,17 @@ public class MacroPlannerDataConverter {
...
@@ -1254,15 +1252,17 @@ public class MacroPlannerDataConverter {
Map
<
Long
,
Operation
>
operationByRoutingDetailId
=
new
HashMap
<>();
Map
<
Long
,
Operation
>
operationByRoutingDetailId
=
new
HashMap
<>();
/** materialId → 成品/半成品库 StockingPoint (首个库存点) */
/** materialId → 成品/半成品库 StockingPoint (首个库存点) */
Map
<
String
,
StockingPoint
>
finalSpByMaterialId
=
new
HashMap
<>();
Map
<
String
,
StockingPoint
>
finalSpByMaterialId
=
new
HashMap
<>();
/** 周期维度: DAY/WEEK/MONTH, 默认 DAY */
String
periodDimension
=
"DAY"
;
/** unitId ("EQUIP_" + PlanResource.id) → 日产能 (小时), 含日历+效率系数 */
/** unitId ("EQUIP_" + PlanResource.id) → 日产能 (小时), 含日历+效率系数 */
Map
<
String
,
Double
>
dailyCapacityByUnitId
=
new
HashMap
<>();
Map
<
String
,
Double
>
dailyCapacityByUnitId
=
new
HashMap
<>();
/** unitId → 每天实际产能 (LocalDate → 小时), 从 EquipShiftCapacity 解析 */
Map
<
String
,
Map
<
LocalDate
,
Double
>>
dailyHoursByUnitId
=
new
HashMap
<>();
/** unitId → 节假日日期集合 */
/** unitId → 节假日日期集合 */
Map
<
String
,
Set
<
LocalDate
>>
holidayDatesByUnitId
=
new
HashMap
<>();
Map
<
String
,
Set
<
LocalDate
>>
holidayDatesByUnitId
=
new
HashMap
<>();
/** unitId 集合: 产能无上限的设备 (如通用供应商, 无 MATERIAL_PURCHASE 时) */
/** unitId 集合: 产能无上限的设备 (如通用供应商, 无 MATERIAL_PURCHASE 时) */
Set
<
String
>
unlimitedUnitIds
=
new
HashSet
<>();
Set
<
String
>
unlimitedUnitIds
=
new
HashSet
<>();
/** 排产周期数, 从 ApsTimeConfig 计算, 维度为 DAY 时是天数, WEEK 时是周数, MONTH 时是月数 */
/** 排产周期数 (天), 从 ApsTimeConfig 计算 */
int
periodCount
=
7
;
int
periodCount
=
7
;
}
}
}
}
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