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
2f7a699b
Commit
2f7a699b
authored
May 29, 2026
by
DESKTOP-VKRD9QF\Administration
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
http://39.100.78.207:1213/tongli/hyh.apsj
parents
f3011df8
813959ca
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
654 additions
and
81 deletions
+654
-81
DraftScheduleResult.java
...in/java/com/aps/entity/Algorithm/DraftScheduleResult.java
+35
-0
Entry.java
src/main/java/com/aps/entity/basic/Entry.java
+6
-0
Material.java
src/main/java/com/aps/entity/basic/Material.java
+10
-0
GeneticDecoder.java
src/main/java/com/aps/service/Algorithm/GeneticDecoder.java
+164
-33
GeneticDecoderBom.java
...ain/java/com/aps/service/Algorithm/GeneticDecoderBom.java
+109
-0
HybridAlgorithm.java
src/main/java/com/aps/service/Algorithm/HybridAlgorithm.java
+7
-3
Initialization.java
src/main/java/com/aps/service/Algorithm/Initialization.java
+24
-5
MachineCalculator.java
...ain/java/com/aps/service/Algorithm/MachineCalculator.java
+11
-4
MaterialRequirementService.java
...com/aps/service/Algorithm/MaterialRequirementService.java
+4
-4
OperationSplitService.java
...java/com/aps/service/Algorithm/OperationSplitService.java
+272
-31
PlanResultService.java
src/main/java/com/aps/service/plan/PlanResultService.java
+12
-1
No files found.
src/main/java/com/aps/entity/Algorithm/DraftScheduleResult.java
0 → 100644
View file @
2f7a699b
package
com
.
aps
.
entity
.
Algorithm
;
/**
* 作者:佟礼
* 时间:2026-05-27
*/
public
class
DraftScheduleResult
{
private
int
operationId
;
private
int
idealStartTime
;
private
int
idealEndTime
;
private
Long
selectedMachineId
;
private
double
processTime
;
private
int
groupId
;
private
int
sequence
;
public
DraftScheduleResult
(
int
operationId
,
int
idealStartTime
,
int
idealEndTime
,
Long
selectedMachineId
,
double
processTime
,
int
groupId
,
int
sequence
)
{
this
.
operationId
=
operationId
;
this
.
idealStartTime
=
idealStartTime
;
this
.
idealEndTime
=
idealEndTime
;
this
.
selectedMachineId
=
selectedMachineId
;
this
.
processTime
=
processTime
;
this
.
groupId
=
groupId
;
this
.
sequence
=
sequence
;
}
public
int
getOperationId
()
{
return
operationId
;
}
public
int
getIdealStartTime
()
{
return
idealStartTime
;
}
public
int
getIdealEndTime
()
{
return
idealEndTime
;
}
public
Long
getSelectedMachineId
()
{
return
selectedMachineId
;
}
public
double
getProcessTime
()
{
return
processTime
;
}
public
int
getGroupId
()
{
return
groupId
;
}
public
int
getSequence
()
{
return
sequence
;
}
}
src/main/java/com/aps/entity/basic/Entry.java
View file @
2f7a699b
...
@@ -175,6 +175,12 @@ private Long isInterrupt = 1l;
...
@@ -175,6 +175,12 @@ private Long isInterrupt = 1l;
private
int
anchorTimeSecond
=
0
;
private
int
anchorTimeSecond
=
0
;
private
transient
boolean
backwardFailed
=
false
;
private
transient
boolean
scheduled
=
false
;
private
transient
Integer
actualFinishTime
;
public
enum
SchedulingMode
{
public
enum
SchedulingMode
{
FORWARD
,
BACKWARD
FORWARD
,
BACKWARD
}
}
...
...
src/main/java/com/aps/entity/basic/Material.java
View file @
2f7a699b
...
@@ -67,6 +67,16 @@ public class Material {
...
@@ -67,6 +67,16 @@ public class Material {
*/
*/
private
Long
CkeckLeadTime
;
private
Long
CkeckLeadTime
;
/**
* 最小生产量
*/
private
BigDecimal
minProduction
;
/**
* 最大生产量
*/
private
BigDecimal
maxProduction
;
@Override
@Override
public
String
toString
()
{
public
String
toString
()
{
return
"Material{"
+
return
"Material{"
+
...
...
src/main/java/com/aps/service/Algorithm/GeneticDecoder.java
View file @
2f7a699b
...
@@ -290,7 +290,7 @@ public class GeneticDecoder {
...
@@ -290,7 +290,7 @@ public class GeneticDecoder {
return
1
;
return
1
;
}
}
private
void
CreateNewOpSequence
(
Chromosome
chromosome
)
private
void
CreateNewOpSequence
(
Chromosome
chromosome
,
boolean
isJit
)
{
{
//成品订单
//成品订单
...
@@ -342,10 +342,16 @@ public class GeneticDecoder {
...
@@ -342,10 +342,16 @@ public class GeneticDecoder {
if
(
entry
==
null
)
{
if
(
entry
==
null
)
{
continue
;
continue
;
}
}
if
(
isJit
)
{
entry
.
setSchedulingMode
(
Entry
.
SchedulingMode
.
BACKWARD
.
name
());
}
else
{
entry
.
setSchedulingMode
(
Entry
.
SchedulingMode
.
FORWARD
.
name
());
entry
.
setSchedulingMode
(
Entry
.
SchedulingMode
.
FORWARD
.
name
());
}
if
(
entry
!=
null
&&
entry
.
getDependentOnOrderIds
().
size
()>
0
)
if
(
entry
!=
null
&&
entry
.
getDependentOnOrderIds
().
size
()>
0
)
{
{
if
(
isJit
)
{
finalSequence
.
add
(
num
);
}
for
(
int
order
:
entry
.
getDependentOnOrderIds
())
{
for
(
int
order
:
entry
.
getDependentOnOrderIds
())
{
// 依赖的半成品订单可能不在旧排序里,缺失时跳过,避免空指针。
// 依赖的半成品订单可能不在旧排序里,缺失时跳过,避免空指针。
...
@@ -357,8 +363,9 @@ public class GeneticDecoder {
...
@@ -357,8 +363,9 @@ public class GeneticDecoder {
InsertSimSequence
(
orderProcessCounter
,
num1
,
finalSequence
,
sfSequence
,
allOperations
);
InsertSimSequence
(
orderProcessCounter
,
num1
,
finalSequence
,
sfSequence
,
allOperations
);
}
}
}
}
if
(!
isJit
)
{
finalSequence
.
add
(
num
);
finalSequence
.
add
(
num
);
}
}
else
{
}
else
{
finalSequence
.
add
(
num
);
finalSequence
.
add
(
num
);
...
@@ -503,7 +510,7 @@ public class GeneticDecoder {
...
@@ -503,7 +510,7 @@ public class GeneticDecoder {
if
(
isnew
==
1
)
{
if
(
isnew
==
1
)
{
long
t2
=
System
.
nanoTime
();
long
t2
=
System
.
nanoTime
();
CreateNewOpSequence
(
chromosome
);
CreateNewOpSequence
(
chromosome
,
isJit
);
// CreateNewOpSequence 会重建半成品插入顺序,生成后再规整一次,避免重复调度同一订单。
// CreateNewOpSequence 会重建半成品插入顺序,生成后再规整一次,避免重复调度同一订单。
normalizeOperationSequencing
(
chromosome
);
normalizeOperationSequencing
(
chromosome
);
// FileHelper.writeLogFile("[PERF] serialDecode CreateNewOpSequence 耗时=" + fmtMs(System.nanoTime() - t2));
// FileHelper.writeLogFile("[PERF] serialDecode CreateNewOpSequence 耗时=" + fmtMs(System.nanoTime() - t2));
...
@@ -604,18 +611,27 @@ public class GeneticDecoder {
...
@@ -604,18 +611,27 @@ public class GeneticDecoder {
}
}
}
Set
<
Integer
>
semiFinishedOrderIds
=
new
HashSet
<>();
if
(
isJit
)
{
// 半成品订单ID集合
semiFinishedOrderIds
=
chromosome
.
getOrders
().
stream
()
.
filter
(
Order:
:
isNewSfCreate
).
map
(
Order:
:
getId
).
collect
(
Collectors
.
toSet
());
}
}
for
(
Map
.
Entry
<
Integer
,
List
<
Entry
>>
entry
:
entrysBygroupId
.
entrySet
())
{
for
(
Map
.
Entry
<
Integer
,
List
<
Entry
>>
entry
:
entrysBygroupId
.
entrySet
())
{
int
groupId
=
entry
.
getKey
();
int
groupId
=
entry
.
getKey
();
Entry
firstOp
=
entry
.
getValue
().
get
(
0
);
Entry
firstOp
=
entry
.
getValue
().
get
(
0
);
if
(
Entry
.
SchedulingMode
.
BACKWARD
.
name
().
equals
(
if
(
Entry
.
SchedulingMode
.
BACKWARD
.
name
().
equals
(
firstOp
.
getSchedulingMode
()
))
{
firstOp
.
getSchedulingMode
())&&
semiFinishedOrderIds
.
contains
(
groupId
))
{
orderSchedulingInfo
.
put
(
groupId
,
orderSchedulingInfo
.
put
(
groupId
,
new
AbstractMap
.
SimpleEntry
<>(
true
,
0
));
new
AbstractMap
.
SimpleEntry
<>(
true
,
0
));
}
}
}
}
// Map<Long, String> machineState = chromosome.getMachines().stream()
// Map<Long, String> machineState = chromosome.getMachines().stream()
// .collect(Collectors.toMap(Machine::getId, m -> ""));
// .collect(Collectors.toMap(Machine::getId, m -> ""));
...
@@ -624,36 +640,85 @@ public class GeneticDecoder {
...
@@ -624,36 +640,85 @@ public class GeneticDecoder {
Map
<
Long
,
CopyOnWriteArrayList
<
GAScheduleResult
>>
machineTasksCache
=
new
HashMap
<>();
Map
<
Long
,
CopyOnWriteArrayList
<
GAScheduleResult
>>
machineTasksCache
=
new
HashMap
<>();
// 记录已经整单转正排的订单,避免倒排序列后续再次进入同一个 groupId。
// 记录已经整单转正排的订单,避免倒排序列后续再次进入同一个 groupId。
Set
<
Integer
>
forwardFallbackOrderIds
=
new
HashSet
<>();
Set
<
Integer
>
forwardFallbackOrderIds
=
new
HashSet
<>();
// 混合排程:半成品正排后需要重排的成品订单
Set
<
Integer
>
reForwardFinishedOrderIds
=
new
HashSet
<>();
// 延迟重排映射:半成品ID → 关联成品ID集合(等半成品全部排完后再处理)
Map
<
Integer
,
Set
<
Integer
>>
pendingReForwardMap
=
new
HashMap
<>();
Map
<
Integer
,
Order
>
orders
=
chromosome
.
getOrders
().
stream
()
.
collect
(
Collectors
.
toMap
(
Order:
:
getId
,
// 2. Value映射:Material → Material
order
->
order
));
int
opCount
=
0
;
int
opCount
=
0
;
long
slowOpThresholdNs
=
500_000_000L
;
// 500ms
long
slowOpThresholdNs
=
500_000_000L
;
// 500ms
for
(
int
groupId
:
chromosome
.
getOperationSequencing
())
{
for
(
int
groupId
:
chromosome
.
getOperationSequencing
())
{
int
scheduledCount
=
orderProcessCounter
.
get
(
groupId
);
int
scheduledCount
=
orderProcessCounter
.
get
(
groupId
);
if
(
groupId
==
7
)
{
int
i
=
0
;
}
List
<
Entry
>
orderOps
=
new
ArrayList
<>();
List
<
Entry
>
orderOps
=
new
ArrayList
<>();
boolean
orderIsJit
=
orderDueDate
.
get
(
groupId
)>
0
;
boolean
orderIsJit
=
orderDueDate
.
get
(
groupId
)>
0
;
AbstractMap
.
SimpleEntry
<
Boolean
,
Integer
>
schedInfo
=
orderSchedulingInfo
.
get
(
groupId
);
AbstractMap
.
SimpleEntry
<
Boolean
,
Integer
>
schedInfo
=
orderSchedulingInfo
.
get
(
groupId
);
orderIsJit
=
schedInfo
!=
null
&&
schedInfo
.
getKey
();
orderIsJit
=
schedInfo
!=
null
&&
schedInfo
.
getKey
();
// 半成品倒排失败后,关联成品强制正排
if
(
reForwardFinishedOrderIds
.
contains
(
groupId
))
{
orderIsJit
=
false
;
schedInfo
=
new
AbstractMap
.
SimpleEntry
<>(
false
,
0
);
}
int
orderAnchor
=
schedInfo
!=
null
?
schedInfo
.
getValue
()
:
0
;
int
orderAnchor
=
schedInfo
!=
null
?
schedInfo
.
getValue
()
:
0
;
// 半成品订单首次处理时,延迟计算锚点:沿依赖链找到根成品工序并正推
// 半成品订单首次处理时,延迟计算锚点:沿依赖链找到根成品工序并正推
if
(
orderIsJit
&&
orderAnchor
==
0
)
{
if
(
orderIsJit
&&
orderAnchor
==
0
)
{
orderAnchor
=
bom
.
computeSemiFinishedAnchor
(
this
,
groupId
,
entrysBygroupId
,
if
(
semiFinishedOrderIds
.
contains
(
groupId
))
{
Order
order
=
orders
.
get
(
groupId
);
orderAnchor
=
bom
.
computeHybridSemiAnchor
(
this
,
groupId
,
entrysBygroupId
,
opMachineKeyMap
,
chromosome
,
scheduleIndexById
,
machineTasksCache
,
machineIdMap
,
entryIndexById
,
reForwardFinishedOrderIds
,
_globalParam
,
order
);
if
(
orderAnchor
<
0
)
{
orderIsJit
=
false
;
orderSchedulingInfo
.
put
(
groupId
,
new
AbstractMap
.
SimpleEntry
<>(
false
,
-
1
));
if
(!
reForwardFinishedOrderIds
.
isEmpty
())
{
pendingReForwardMap
.
put
(
groupId
,
new
HashSet
<>(
reForwardFinishedOrderIds
));
reForwardFinishedOrderIds
.
clear
();
}
}
else
{
orderSchedulingInfo
.
put
(
groupId
,
new
AbstractMap
.
SimpleEntry
<>(
true
,
orderAnchor
));
}
}
else
{
orderAnchor
=
bom
.
computeSemiFinishedAnchor
(
this
,
groupId
,
entrysBygroupId
,
opMachineKeyMap
,
chromosome
,
opMachineKeyMap
,
chromosome
,
scheduleIndexById
,
machineTasksCache
,
machineIdMap
,
entryIndexById
);
scheduleIndexById
,
machineTasksCache
,
machineIdMap
,
entryIndexById
);
if
(
orderAnchor
<
0
)
{
if
(
orderAnchor
<
0
)
{
orderIsJit
=
false
;
orderIsJit
=
false
;
orderSchedulingInfo
.
put
(
groupId
,
orderSchedulingInfo
.
put
(
groupId
,
new
AbstractMap
.
SimpleEntry
<>(
false
,
-
1
));
new
AbstractMap
.
SimpleEntry
<>(
false
,
-
1
));
}
else
{
}
else
{
orderSchedulingInfo
.
put
(
groupId
,
orderSchedulingInfo
.
put
(
groupId
,
new
AbstractMap
.
SimpleEntry
<>(
true
,
orderAnchor
));
new
AbstractMap
.
SimpleEntry
<>(
true
,
orderAnchor
));
}
}
}
}
}
if
(
orderIsJit
)
if
(
orderIsJit
)
...
@@ -718,6 +783,23 @@ public class GeneticDecoder {
...
@@ -718,6 +783,23 @@ public class GeneticDecoder {
orderProcessCounter
.
put
(
groupId
,
entrysBygroupId
.
get
(
groupId
).
size
());
orderProcessCounter
.
put
(
groupId
,
entrysBygroupId
.
get
(
groupId
).
size
());
orderLastEndTime
.
put
(
groupId
,
forwardEndTime
);
orderLastEndTime
.
put
(
groupId
,
forwardEndTime
);
forwardFallbackOrderIds
.
add
(
groupId
);
forwardFallbackOrderIds
.
add
(
groupId
);
if
(
semiFinishedOrderIds
.
contains
(
groupId
))
{
Set
<
Integer
>
fallbackFinishedIds
=
new
HashSet
<>();
List
<
Integer
>
targetFinishedOpIds
=
currentOp
.
getTargetFinishedOperationId
();
if
(
targetFinishedOpIds
!=
null
&&
!
targetFinishedOpIds
.
isEmpty
())
{
for
(
Integer
targetOpId
:
targetFinishedOpIds
)
{
Entry
finishedOp
=
entryIndexById
.
get
(
targetOpId
);
if
(
finishedOp
!=
null
)
{
fallbackFinishedIds
.
add
(
finishedOp
.
getGroupId
());
}
}
}
reForwardFinishedOrders
(
groupId
,
fallbackFinishedIds
,
chromosome
,
machineTasksCache
,
scheduleIndexById
,
machineIdMap
,
entryIndexById
,
opMachineKeyMap
,
entrysBygroupId
,
orderProcessCounter
,
orderLastEndTime
,
forwardFallbackOrderIds
,
orderSchedulingInfo
);
}
opCount
+=
entrysBygroupId
.
get
(
groupId
).
size
();
opCount
+=
entrysBygroupId
.
get
(
groupId
).
size
();
continue
;
continue
;
}
}
...
@@ -728,6 +810,22 @@ public class GeneticDecoder {
...
@@ -728,6 +810,22 @@ public class GeneticDecoder {
orderProcessCounter
.
put
(
groupId
,
orderProcessCounter
.
get
(
groupId
)
+
1
);
orderProcessCounter
.
put
(
groupId
,
orderProcessCounter
.
get
(
groupId
)
+
1
);
orderLastEndTime
.
put
(
groupId
,
actualEndTime
);
orderLastEndTime
.
put
(
groupId
,
actualEndTime
);
if
(
semiFinishedOrderIds
.
contains
(
groupId
)
&&
orderProcessCounter
.
get
(
groupId
)
>=
entrysBygroupId
.
get
(
groupId
).
size
())
{
Set
<
Integer
>
pendingFinishedIds
=
pendingReForwardMap
.
remove
(
groupId
);
if
(
pendingFinishedIds
!=
null
&&
!
pendingFinishedIds
.
isEmpty
())
{
int
semiEndTime
=
orderLastEndTime
.
get
(
groupId
);
FileHelper
.
writeLogFile
(
"[ReForward] 半成品"
+
groupId
+
"全部排完(结束时间="
+
semiEndTime
+
"),重新正排关联成品"
+
pendingFinishedIds
);
reForwardFinishedOrders
(
groupId
,
pendingFinishedIds
,
chromosome
,
machineTasksCache
,
scheduleIndexById
,
machineIdMap
,
entryIndexById
,
opMachineKeyMap
,
entrysBygroupId
,
orderProcessCounter
,
orderLastEndTime
,
forwardFallbackOrderIds
,
orderSchedulingInfo
);
}
}
}
}
if
(
chromosome
.
getReOrderids
()!=
null
&&
chromosome
.
getReOrderids
().
size
()>
0
)
{
if
(
chromosome
.
getReOrderids
()!=
null
&&
chromosome
.
getReOrderids
().
size
()>
0
)
{
chromosome
.
getOperationSequencing
().
removeIf
(
t
->
chromosome
.
getReOrderids
().
contains
(
t
));
chromosome
.
getOperationSequencing
().
removeIf
(
t
->
chromosome
.
getReOrderids
().
contains
(
t
));
...
@@ -750,7 +848,7 @@ public class GeneticDecoder {
...
@@ -750,7 +848,7 @@ public class GeneticDecoder {
int
isnew
=
generateGlobalOpList
(
chromosome
);
int
isnew
=
generateGlobalOpList
(
chromosome
);
if
(
isnew
==
1
)
{
if
(
isnew
==
1
)
{
CreateNewOpSequence
(
chromosome
);
CreateNewOpSequence
(
chromosome
,
_globalParam
.
isJit
()
);
// CreateNewOpSequence 会重建半成品插入顺序,生成后再规整一次,避免重复调度同一订单。
// CreateNewOpSequence 会重建半成品插入顺序,生成后再规整一次,避免重复调度同一订单。
normalizeOperationSequencing
(
chromosome
);
normalizeOperationSequencing
(
chromosome
);
}
}
...
@@ -1427,7 +1525,7 @@ public class GeneticDecoder {
...
@@ -1427,7 +1525,7 @@ public class GeneticDecoder {
// 下面开始生成正式的 geneDetails。
// 下面开始生成正式的 geneDetails。
if
(
_globalParam
.
is_smoothChangeOver
())
{
if
(
_globalParam
.
is_smoothChangeOver
())
{
if
(
isJit
&&
islockMachineTime
)
{
if
(
isJit
&&
islockMachineTime
&&
machineTasks
!=
null
&&
machineTasks
.
size
()>
0
)
{
// JIT 倒排 + 换型:同时校验前置换型、当前加工、后置换型能否插入相邻任务窗口。
// JIT 倒排 + 换型:同时校验前置换型、当前加工、后置换型能否插入相邻任务窗口。
BackwardChangeoverHelper
.
JitBackwardScheduleResult
backwardResult
=
BackwardChangeoverHelper
.
JitBackwardScheduleResult
backwardResult
=
backwardChangeoverHelper
.
scheduleJitBackwardWithChangeover
(
machine
,
operation
,
earliestStartTime
,
backwardChangeoverHelper
.
scheduleJitBackwardWithChangeover
(
machine
,
operation
,
earliestStartTime
,
...
@@ -1504,11 +1602,8 @@ public class GeneticDecoder {
...
@@ -1504,11 +1602,8 @@ public class GeneticDecoder {
.
orElse
(
0
);
.
orElse
(
0
);
if
(
startTime
<
0
)
{
if
(
startTime
<
0
)
{
if
(
islockMachineTime
)
if
(
isJit
&&
islockMachineTime
)
{
{
return
OperationScheduleResult
.
forwardFallback
();
// FileHelper.writeLogFile(" 半成品222222 " + operation.getGroupId() + " - " + operation.getSequence() + ",开始时间: " + startTime + ",结束时间: " + 0 + ",处理时间: " + processingTime + ", 后处理: " + teardownTime +
// ", 前处理: " + preTime + ", 换型: " + setupTime + ", 数量: " + operation.getQuantity() + ", 设备: " + machine.getId() + ", 是否可中断: " + operation.getIsInterrupt());
}
}
return
OperationScheduleResult
.
success
(
null
);
return
OperationScheduleResult
.
success
(
null
);
...
@@ -3117,5 +3212,41 @@ if(geneDetails!=null&&geneDetails.size()>0)
...
@@ -3117,5 +3212,41 @@ if(geneDetails!=null&&geneDetails.size()>0)
orders
.
removeIf
(
t
->
newoorderids
.
contains
(
t
.
getId
()));
orders
.
removeIf
(
t
->
newoorderids
.
contains
(
t
.
getId
()));
}
}
}
}
private
void
reForwardFinishedOrders
(
int
semiOrderId
,
Set
<
Integer
>
reForwardFinishedOrderIds
,
Chromosome
chromosome
,
Map
<
Long
,
CopyOnWriteArrayList
<
GAScheduleResult
>>
machineTasksCache
,
Map
<
Integer
,
GAScheduleResult
>
scheduleIndexById
,
Map
<
Long
,
Machine
>
machineIdMap
,
Map
<
Integer
,
Entry
>
entryIndexById
,
Map
<
String
,
OpMachine
>
opMachineKeyMap
,
Map
<
Integer
,
List
<
Entry
>>
entrysBygroupId
,
Map
<
Integer
,
Integer
>
orderProcessCounter
,
Map
<
Integer
,
Integer
>
orderLastEndTime
,
Set
<
Integer
>
forwardFallbackOrderIds
,
Map
<
Integer
,
AbstractMap
.
SimpleEntry
<
Boolean
,
Integer
>>
orderSchedulingInfo
)
{
for
(
int
finishedId
:
reForwardFinishedOrderIds
)
{
int
scheduledCount
=
orderProcessCounter
.
getOrDefault
(
finishedId
,
0
);
if
(
scheduledCount
==
0
)
{
continue
;
}
clearOrderSchedulingForForwardFallback
(
chromosome
,
finishedId
,
machineTasksCache
,
scheduleIndexById
);
int
forwardEndTime
=
scheduleOrderForwardFallback
(
finishedId
,
chromosome
,
machineIdMap
,
machineTasksCache
,
entryIndexById
,
scheduleIndexById
,
opMachineKeyMap
);
orderProcessCounter
.
put
(
finishedId
,
entrysBygroupId
.
get
(
finishedId
).
size
());
orderLastEndTime
.
put
(
finishedId
,
forwardEndTime
);
forwardFallbackOrderIds
.
add
(
finishedId
);
orderSchedulingInfo
.
put
(
finishedId
,
new
AbstractMap
.
SimpleEntry
<>(
false
,
0
));
FileHelper
.
writeLogFile
(
"[ReForward] 半成品"
+
semiOrderId
+
"倒排早于基准时间,成品"
+
finishedId
+
"重新正排,结束时间="
+
forwardEndTime
);
}
reForwardFinishedOrderIds
.
clear
();
}
}
}
src/main/java/com/aps/service/Algorithm/GeneticDecoderBom.java
View file @
2f7a699b
...
@@ -385,4 +385,113 @@ if(isJit)
...
@@ -385,4 +385,113 @@ if(isJit)
return
Math
.
max
(
estimatedStartTime
,
rawTime
);
return
Math
.
max
(
estimatedStartTime
,
rawTime
);
}
}
/**
* 半成品锚点计算(JIT倒排模式:成品先于半成品,直接取成品已排的开始时间)
* 返回 -1 触发正排+标记成品重排,>=0 作为半成品倒排锚点(半成品最晚完成时间)
*/
public
int
computeHybridSemiAnchor
(
GeneticDecoder
decoder
,
int
semiOrderId
,
Map
<
Integer
,
List
<
Entry
>>
entrysBygroupId
,
Map
<
String
,
OpMachine
>
opMachineKeyMap
,
Chromosome
chromosome
,
Map
<
Integer
,
GAScheduleResult
>
scheduleIndexById
,
Map
<
Long
,
CopyOnWriteArrayList
<
GAScheduleResult
>>
machineTasksCache
,
Map
<
Long
,
Machine
>
machineIdMap
,
Map
<
Integer
,
Entry
>
entryIndexById
,
Set
<
Integer
>
reForwardFinishedOrderIds
,
GlobalParam
_globalParam
,
Order
order
)
{
List
<
Entry
>
sfOps
=
entrysBygroupId
.
get
(
semiOrderId
);
if
(
sfOps
==
null
||
sfOps
.
isEmpty
())
return
0
;
Entry
firstSfOp
=
sfOps
.
stream
().
min
(
Comparator
.
comparing
(
Entry:
:
getSequence
)).
orElse
(
null
);
if
(
firstSfOp
==
null
)
return
0
;
List
<
Integer
>
targetFinishedOpIds
=
firstSfOp
.
getTargetFinishedOperationId
();
if
(
targetFinishedOpIds
==
null
||
targetFinishedOpIds
.
isEmpty
())
{
return
0
;
}
// 找关联的成品订单
int
finishedOrderId
=
-
1
;
Entry
targetFinishedOp
=
null
;
for
(
Integer
targetOpId
:
targetFinishedOpIds
)
{
Entry
op
=
entryIndexById
.
get
(
targetOpId
);
if
(
op
!=
null
)
{
targetFinishedOp
=
op
;
finishedOrderId
=
op
.
getGroupId
();
break
;
}
}
if
(
finishedOrderId
<=
0
)
return
0
;
List
<
Entry
>
finishedOps
=
entrysBygroupId
.
get
(
finishedOrderId
).
stream
()
.
sorted
(
Comparator
.
comparing
(
Entry:
:
getSequence
))
.
collect
(
Collectors
.
toList
());
if
(
finishedOps
.
isEmpty
())
return
0
;
// JIT倒排模式成品已排完,直接从scheduleIndexById取成品第一道工序的开始时间
Entry
finishedFirstOp
=
finishedOps
.
get
(
0
);
GAScheduleResult
finishedFirstResult
=
scheduleIndexById
.
get
(
finishedFirstOp
.
getId
());
if
(
finishedFirstResult
==
null
)
{
// 成品还没排(不应该发生在JIT模式),降级为原有逻辑
return
computeSemiFinishedAnchor
(
decoder
,
semiOrderId
,
entrysBygroupId
,
opMachineKeyMap
,
chromosome
,
scheduleIndexById
,
machineTasksCache
,
machineIdMap
,
entryIndexById
);
}
String
schedulingMode
=
finishedFirstOp
.
getSchedulingMode
();
if
(
Entry
.
SchedulingMode
.
BACKWARD
.
name
().
equals
(
schedulingMode
))
{
// 成品倒排成功 → 半成品倒排:锚点 = 成品开始时间 - 缓冲
int
semiLatestEndTime
=
finishedFirstResult
.
getStartTime
();
int
semiTotalTime
=
0
;
for
(
Entry
op
:
sfOps
)
{
String
key
=
semiOrderId
+
"_"
+
op
.
getSequence
();
OpMachine
opt
=
opMachineKeyMap
.
get
(
key
);
if
(
opt
!=
null
)
semiTotalTime
+=
calculateOperationProcessingTime
(
op
,
opt
,
_globalParam
);
}
Material
material
=
chromosome
.
getMaterials
().
get
(
order
.
getMaterialId
());
int
ckeckLeadTime
=
0
;
if
(
material
.
getCkeckLeadTime
()!=
null
)
{
ckeckLeadTime
=(
material
.
getCkeckLeadTime
().
intValue
()*
24
*
60
*
60
);
}
semiTotalTime
+=
ckeckLeadTime
;
if
(
semiLatestEndTime
-
semiTotalTime
<
0
)
{
FileHelper
.
writeLogFile
(
"[HybridSemi] 半成品"
+
semiOrderId
+
" 倒排开工<0,正排+成品"
+
finishedOrderId
+
"重排"
);
reForwardFinishedOrderIds
.
add
(
finishedOrderId
);
return
-
1
;
}
FileHelper
.
writeLogFile
(
"[HybridSemi] 半成品"
+
semiOrderId
+
" 锚点="
+
semiLatestEndTime
+
" (成品"
+
finishedOrderId
+
"开始="
+
finishedFirstResult
.
getStartTime
()
+
")"
);
return
semiLatestEndTime
-
ckeckLeadTime
;
}
else
{
// 成品正排了 → 半成品也正排
FileHelper
.
writeLogFile
(
"[HybridSemi] 成品"
+
finishedOrderId
+
"已正排,半成品"
+
semiOrderId
+
"也正排"
);
reForwardFinishedOrderIds
.
add
(
finishedOrderId
);
return
-
1
;
}
}
private
int
calculateOperationProcessingTime
(
Entry
op
,
OpMachine
machineOption
,
GlobalParam
_globalParam
)
{
int
total
=
0
;
if
(
op
.
getConstTime
()
==
1
)
//常数时间
{
total
=
(
int
)
Math
.
ceil
(
machineOption
.
getRuntime
().
doubleValue
());
}
else
{
double
t
=
machineOption
.
getRuntime
().
doubleValue
()
/
machineOption
.
getSingleOut
().
doubleValue
()
*
(
op
.
getQuantity
());
total
=
(
int
)
Math
.
ceil
(
t
);
}
if
(
op
.
getSetupTime
()
!=
null
)
total
+=
op
.
getSetupTime
().
intValue
();
total
+=
op
.
getTeardownTime
();
if
(
!
_globalParam
.
is_smoothSetup
())
{
total
+=
op
.
getPreTime
();
}
return
total
;
}
}
}
src/main/java/com/aps/service/Algorithm/HybridAlgorithm.java
View file @
2f7a699b
...
@@ -85,7 +85,9 @@ public class HybridAlgorithm {
...
@@ -85,7 +85,9 @@ public class HybridAlgorithm {
{
{
throw
new
RuntimeException
(
"没有待排产工单"
);
throw
new
RuntimeException
(
"没有待排产工单"
);
}
}
if
(
_GlobalParam
.
isIsMultipleMachine
())
{
allOperations
=
OperationSplitService
.
splitMultiMachineOperations
(
allOperations
,
_GlobalParam
,
machines
,
_entryRel
);
}
// if(materials!=null&&materials.size()>0) {
// if(materials!=null&&materials.size()>0) {
//
//
// materialRequirementService.init(materials, orders, allOperations, _entryRel, machineScheduler, machines,_GlobalParam);
// materialRequirementService.init(materials, orders, allOperations, _entryRel, machineScheduler, machines,_GlobalParam);
...
@@ -433,6 +435,7 @@ public class HybridAlgorithm {
...
@@ -433,6 +435,7 @@ public class HybridAlgorithm {
chromosome
.
setMaterials
(
ProductionDeepCopyUtil
.
deepCopyTreeMap
(
materials
,
String
.
class
,
Material
.
class
));
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setMaterials
(
ProductionDeepCopyUtil
.
deepCopyTreeMap
(
materials
,
String
.
class
,
Material
.
class
));
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setAllOperations
(
ProductionDeepCopyUtil
.
deepCopyList
(
new
CopyOnWriteArrayList
<>(
allOperations
),
Entry
.
class
)
);
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setAllOperations
(
ProductionDeepCopyUtil
.
deepCopyList
(
new
CopyOnWriteArrayList
<>(
allOperations
),
Entry
.
class
)
);
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setGlobalOpList
(
ProductionDeepCopyUtil
.
deepCopyList
(
globalOpList
,
GlobalOperationInfo
.
class
)
);
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setGlobalOpList
(
ProductionDeepCopyUtil
.
deepCopyList
(
globalOpList
,
GlobalOperationInfo
.
class
)
);
// 简单拷贝,实际可能需要深拷贝
//chromosome.setObjectiveWeights(_objectiveWeights);
//chromosome.setObjectiveWeights(_objectiveWeights);
chromosome
.
setBaseTime
(
param
.
getBaseTime
());
chromosome
.
setBaseTime
(
param
.
getBaseTime
());
...
@@ -490,8 +493,9 @@ public class HybridAlgorithm {
...
@@ -490,8 +493,9 @@ public class HybridAlgorithm {
chromosome
.
setMaterials
(
ProductionDeepCopyUtil
.
deepCopyTreeMap
(
materials
,
String
.
class
,
Material
.
class
));
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setMaterials
(
ProductionDeepCopyUtil
.
deepCopyTreeMap
(
materials
,
String
.
class
,
Material
.
class
));
// 简单拷贝,实际可能需要深拷贝
chromosome
.
setAllOperations
(
ProductionDeepCopyUtil
.
deepCopyList
(
new
CopyOnWriteArrayList
<>(
allOperations
),
Entry
.
class
)
);
// 简单拷贝,实际可能需要深拷贝
if
(
chromosome
.
getAllOperations
()
==
null
||
chromosome
.
getAllOperations
().
isEmpty
())
{
// chromosome.setGlobalOpList(ProductionDeepCopyUtil.deepCopyList(globalOpList, GlobalOperationInfo.class) ); // 简单拷贝,实际可能需要深拷贝
chromosome
.
setAllOperations
(
ProductionDeepCopyUtil
.
deepCopyList
(
new
CopyOnWriteArrayList
<>(
allOperations
),
Entry
.
class
)
);
}
// chromosome.setGlobalOpList(ProductionDeepCopyUtil.deepCopyList(globalOpList, GlobalOperationInfo.class) ); // 简单拷贝,实际可能需要深拷贝
//chromosome.setObjectiveWeights(_objectiveWeights);
//chromosome.setObjectiveWeights(_objectiveWeights);
chromosome
.
setBaseTime
(
param
.
getBaseTime
());
chromosome
.
setBaseTime
(
param
.
getBaseTime
());
// chromosome.setInitMachines(ProductionDeepCopyUtil.deepCopyList(machines,Machine.class)); // 简单拷贝,实际可能需要深拷贝
// chromosome.setInitMachines(ProductionDeepCopyUtil.deepCopyList(machines,Machine.class)); // 简单拷贝,实际可能需要深拷贝
...
...
src/main/java/com/aps/service/Algorithm/Initialization.java
View file @
2f7a699b
...
@@ -478,7 +478,10 @@ public class Initialization {
...
@@ -478,7 +478,10 @@ public class Initialization {
.
thenComparing
(
randomIds:
:
get
));
.
thenComparing
(
randomIds:
:
get
));
}
}
}
}
if
(
_globalParam
.
isIsMultipleMachine
())
{
sortedOps
=
OperationSplitService
.
splitOpsForHeuristic
(
sortedOps
,
rnd
);
chromosome
.
setAllOperations
(
new
CopyOnWriteArrayList
<>(
sortedOps
));
}
int
globalOpId
=
0
;
int
globalOpId
=
0
;
// 为每个工序选择机器
// 为每个工序选择机器
...
@@ -583,7 +586,10 @@ public class Initialization {
...
@@ -583,7 +586,10 @@ public class Initialization {
.
thenComparing
(
randomIds:
:
get
));
.
thenComparing
(
randomIds:
:
get
));
}
}
if
(
_globalParam
.
isIsMultipleMachine
())
{
sortedOps
=
OperationSplitService
.
splitOpsForHeuristic
(
sortedOps
,
rnd
);
chromosome
.
setAllOperations
(
new
CopyOnWriteArrayList
<>(
sortedOps
));
}
int
globalOpId
=
0
;
int
globalOpId
=
0
;
// 为每个工序选择机器
// 为每个工序选择机器
for
(
Entry
op
:
sortedOps
)
{
for
(
Entry
op
:
sortedOps
)
{
...
@@ -672,6 +678,10 @@ public class Initialization {
...
@@ -672,6 +678,10 @@ public class Initialization {
.
thenComparing
(
op
->
op
.
getMachineOptions
().
get
(
0
).
getProcessingTime
()
*
op
.
getQuantity
())
.
thenComparing
(
op
->
op
.
getMachineOptions
().
get
(
0
).
getProcessingTime
()
*
op
.
getQuantity
())
.
thenComparing
(
randomIds:
:
get
));
.
thenComparing
(
randomIds:
:
get
));
if
(
_globalParam
.
isIsMultipleMachine
())
{
sortedOps
=
OperationSplitService
.
splitOpsForHeuristic
(
sortedOps
,
rnd
);
chromosome
.
setAllOperations
(
new
CopyOnWriteArrayList
<>(
sortedOps
));
}
int
globalOpId
=
0
;
int
globalOpId
=
0
;
for
(
Entry
op
:
sortedOps
)
{
for
(
Entry
op
:
sortedOps
)
{
...
@@ -817,7 +827,10 @@ public class Initialization {
...
@@ -817,7 +827,10 @@ public class Initialization {
})
})
.
thenComparing
(
randomIds:
:
get
));
.
thenComparing
(
randomIds:
:
get
));
}
}
if
(
_globalParam
.
isIsMultipleMachine
())
{
sortedOps
=
OperationSplitService
.
splitOpsForHeuristic
(
sortedOps
,
rnd
);
chromosome
.
setAllOperations
(
new
CopyOnWriteArrayList
<>(
sortedOps
));
}
int
globalOpId
=
0
;
int
globalOpId
=
0
;
for
(
Entry
op
:
sortedOps
)
{
for
(
Entry
op
:
sortedOps
)
{
int
groupId
=
op
.
getGroupId
();
int
groupId
=
op
.
getGroupId
();
...
@@ -907,7 +920,10 @@ public class Initialization {
...
@@ -907,7 +920,10 @@ public class Initialization {
})
})
.
thenComparing
(
op
->
op
.
getProductId
()
!=
null
?
op
.
getProductId
()
:
""
)
.
thenComparing
(
op
->
op
.
getProductId
()
!=
null
?
op
.
getProductId
()
:
""
)
.
thenComparing
(
randomIds:
:
get
));
.
thenComparing
(
randomIds:
:
get
));
if
(
_globalParam
.
isIsMultipleMachine
())
{
sortedOps
=
OperationSplitService
.
splitOpsForHeuristic
(
sortedOps
,
rnd
);
chromosome
.
setAllOperations
(
new
CopyOnWriteArrayList
<>(
sortedOps
));
}
int
globalOpId
=
0
;
int
globalOpId
=
0
;
for
(
Entry
op
:
sortedOps
)
{
for
(
Entry
op
:
sortedOps
)
{
int
groupId
=
op
.
getGroupId
();
int
groupId
=
op
.
getGroupId
();
...
@@ -971,7 +987,10 @@ public class Initialization {
...
@@ -971,7 +987,10 @@ public class Initialization {
.
thenComparing
(
op
->
op
.
getProductId
()
!=
null
?
op
.
getProductId
()
:
""
)
// 相同物料排在一起,减少换型
.
thenComparing
(
op
->
op
.
getProductId
()
!=
null
?
op
.
getProductId
()
:
""
)
// 相同物料排在一起,减少换型
.
thenComparing
(
randomIds:
:
get
));
.
thenComparing
(
randomIds:
:
get
));
if
(
_globalParam
.
isIsMultipleMachine
())
{
sortedOps
=
OperationSplitService
.
splitOpsForHeuristic
(
sortedOps
,
rnd
);
chromosome
.
setAllOperations
(
new
CopyOnWriteArrayList
<>(
sortedOps
));
}
int
globalOpId
=
0
;
int
globalOpId
=
0
;
...
...
src/main/java/com/aps/service/Algorithm/MachineCalculator.java
View file @
2f7a699b
...
@@ -540,13 +540,20 @@ public class MachineCalculator {
...
@@ -540,13 +540,20 @@ public class MachineCalculator {
double
e
=
(
double
)
processingTime
/
slot
.
getEfficiency
();
double
e
=
(
double
)
processingTime
/
slot
.
getEfficiency
();
LocalDateTime
endCandidate
=
null
;
LocalDateTime
endCandidate
=
startCandidate
.
plusSeconds
((
int
)
Math
.
ceil
(
e
));
LocalDateTime
startCandidate1
=
null
;
if
(
isJit
)
{
endCandidate
=
endCandidate1
;
startCandidate1
=
endCandidate
.
plusSeconds
(-(
int
)
Math
.
ceil
(
e
));;
}
else
{
endCandidate
=
startCandidate
.
plusSeconds
((
int
)
Math
.
ceil
(
e
));
startCandidate1
=
startCandidate
;
}
ScheduleResultDetail
time
=
new
ScheduleResultDetail
();
ScheduleResultDetail
time
=
new
ScheduleResultDetail
();
time
.
setKey
(
slot
.
getKey
());
time
.
setKey
(
slot
.
getKey
());
time
.
setStartTime
((
int
)
ChronoUnit
.
SECONDS
.
between
(
baseTime
,
startCandidate
));
time
.
setStartTime
((
int
)
ChronoUnit
.
SECONDS
.
between
(
baseTime
,
startCandidate
1
));
time
.
setEndTime
((
int
)
ChronoUnit
.
SECONDS
.
between
(
baseTime
,
endCandidate
));
time
.
setEndTime
((
int
)
ChronoUnit
.
SECONDS
.
between
(
baseTime
,
endCandidate
));
time
.
setOneTime
(
oneTime
);
time
.
setOneTime
(
oneTime
);
time
.
setQuantity
(
quantity
);
time
.
setQuantity
(
quantity
);
...
...
src/main/java/com/aps/service/Algorithm/MaterialRequirementService.java
View file @
2f7a699b
...
@@ -1250,7 +1250,7 @@ if(demand==null)
...
@@ -1250,7 +1250,7 @@ if(demand==null)
}
}
}
}
}
}
FileHelper
.
writeLogFile
(
"BOM:"
+
material
.
getCode
());
//
FileHelper.writeLogFile("BOM:"+material.getCode());
if
(
needed
<=
0
)
{
if
(
needed
<=
0
)
{
orderMaterial
.
setYpQty
(
allneeded
-
needed
);
orderMaterial
.
setYpQty
(
allneeded
-
needed
);
orderMaterial
.
setQjQty
(
needed
);
orderMaterial
.
setQjQty
(
needed
);
...
@@ -1285,11 +1285,11 @@ if(demand==null)
...
@@ -1285,11 +1285,11 @@ if(demand==null)
for
(
RoutingSupportingReplace
rsr
:
routingsupportingreplaces2
)
{
for
(
RoutingSupportingReplace
rsr
:
routingsupportingreplaces2
)
{
Material
material1
=
materials
.
get
(
rsr
.
getTargetmaterialid
());
Material
material1
=
materials
.
get
(
rsr
.
getTargetmaterialid
());
FileHelper
.
writeLogFile
(
"RoutingSupportingReplace:"
+
material1
.
getCode
());
//
FileHelper.writeLogFile("RoutingSupportingReplace:"+material1.getCode());
if
(
material1
==
null
)
{
if
(
material1
==
null
)
{
break
;
break
;
}
}
FileHelper
.
writeLogFile
(
"RoutingSupportingReplace: "
+(
commitChanges
?
"1"
:
"0"
)+
" "
+
material1
.
getCode
()+
": "
+
useStock
);
//
FileHelper.writeLogFile("RoutingSupportingReplace: "+(commitChanges?"1":"0")+" " +material1.getCode()+": "+useStock);
OrderMaterialRequirement
orderMaterial1
=
MaterialStock
(
material1
,
rsr
.
getTargetmaterialid
(),
orderMaterial
.
getOrderId
(),
orderMaterial
.
getChildOrderId
(),
operationId
,
allneeded
,
needed
,
earliestStartTime
,
commitChanges
);
OrderMaterialRequirement
orderMaterial1
=
MaterialStock
(
material1
,
rsr
.
getTargetmaterialid
(),
orderMaterial
.
getOrderId
(),
orderMaterial
.
getChildOrderId
(),
operationId
,
allneeded
,
needed
,
earliestStartTime
,
commitChanges
);
if
(
orderMaterial1
!=
null
)
{
if
(
orderMaterial1
!=
null
)
{
...
@@ -1297,7 +1297,7 @@ if(demand==null)
...
@@ -1297,7 +1297,7 @@ if(demand==null)
needed
-=
useStock
;
needed
-=
useStock
;
FileHelper
.
writeLogFile
(
"RoutingSupportingReplace: "
+(
commitChanges
?
"1"
:
"0"
)+
" "
+
material1
.
getCode
()+
": "
+
useStock
);
//
FileHelper.writeLogFile("RoutingSupportingReplace: "+(commitChanges?"1":"0")+" " +material1.getCode()+": "+useStock);
orderMaterial
.
setUseStock
(
orderMaterial
.
getUseStock
()
+
useStock
);
orderMaterial
.
setUseStock
(
orderMaterial
.
getUseStock
()
+
useStock
);
orderMaterial
.
getReplaceMaterial
().
add
(
orderMaterial1
);
orderMaterial
.
getReplaceMaterial
().
add
(
orderMaterial1
);
if
(
needed
<=
0
)
{
if
(
needed
<=
0
)
{
...
...
src/main/java/com/aps/service/Algorithm/OperationSplitService.java
View file @
2f7a699b
...
@@ -2,6 +2,8 @@ package com.aps.service.Algorithm;
...
@@ -2,6 +2,8 @@ package com.aps.service.Algorithm;
import
com.aps.common.util.FileHelper
;
import
com.aps.common.util.FileHelper
;
import
com.aps.common.util.ProductionDeepCopyUtil
;
import
com.aps.common.util.ProductionDeepCopyUtil
;
import
com.aps.entity.Algorithm.IDAndChildID.GroupResult
;
import
com.aps.entity.Algorithm.IDAndChildID.NodeInfo
;
import
com.aps.entity.Algorithm.OperationDependency
;
import
com.aps.entity.Algorithm.OperationDependency
;
import
com.aps.entity.basic.Entry
;
import
com.aps.entity.basic.Entry
;
import
com.aps.entity.basic.GlobalParam
;
import
com.aps.entity.basic.GlobalParam
;
...
@@ -30,14 +32,20 @@ public class OperationSplitService {
...
@@ -30,14 +32,20 @@ public class OperationSplitService {
/**
/**
* 对工序列表进行多设备拆分(预处理入口,结合设备负载)
* 对工序列表进行多设备拆分(预处理入口,结合设备负载)
*
*
* 对齐 SpiltOperation 的处理顺序:
* 1. 先通过 IdGroupingWithDualSerial.addNode 更新 OperatRel,生成新的 GlobalSerial、GroupSerial、NewParentIds、NewChildIds
* 2. 再从 OperatRel 的 NodeInfo 生成 Entry,设置 NextOperationId 和 PrevOperationId
*
* @param allOperations 原始工序列表
* @param allOperations 原始工序列表
* @param globalParam 全局参数
* @param globalParam 全局参数
* @param machines 设备列表(用于计算各设备总预期负载)
* @param machines 设备列表(用于计算各设备总预期负载)
* @param operatRel 工序关系列表(可变参数,拆分后会被原地更新)
* @return 拆分后的工序列表
* @return 拆分后的工序列表
*/
*/
public
static
List
<
Entry
>
splitMultiMachineOperations
(
List
<
Entry
>
allOperations
,
public
static
List
<
Entry
>
splitMultiMachineOperations
(
List
<
Entry
>
allOperations
,
GlobalParam
globalParam
,
GlobalParam
globalParam
,
List
<
Machine
>
machines
)
{
List
<
Machine
>
machines
,
List
<
GroupResult
>
operatRel
)
{
if
(
allOperations
==
null
||
allOperations
.
isEmpty
())
{
if
(
allOperations
==
null
||
allOperations
.
isEmpty
())
{
return
allOperations
;
return
allOperations
;
}
}
...
@@ -55,50 +63,140 @@ public class OperationSplitService {
...
@@ -55,50 +63,140 @@ public class OperationSplitService {
FileHelper
.
writeLogFile
(
"[工序拆分] 开始多设备工序拆分(负载感知),原始工序数="
+
allOperations
.
size
());
FileHelper
.
writeLogFile
(
"[工序拆分] 开始多设备工序拆分(负载感知),原始工序数="
+
allOperations
.
size
());
// 旧 Entry 索引(id → Entry)
Map
<
Integer
,
Entry
>
oldEntryMap
=
new
LinkedHashMap
<>();
for
(
Entry
op
:
allOperations
)
{
oldEntryMap
.
put
(
op
.
getId
(),
op
);
}
Map
<
Long
,
Double
>
machineTotalLoad
=
calcMachineTotalLoad
(
allOperations
);
Map
<
Long
,
Double
>
machineTotalLoad
=
calcMachineTotalLoad
(
allOperations
);
int
maxId
=
allOperations
.
stream
().
mapToInt
(
Entry:
:
getId
).
max
().
orElse
(
0
);
// ===== Phase 1: 先更新 OperatRel(结构权威)=====
int
nextId
=
maxId
+
1
;
List
<
GroupResult
>
currentRel
=
(
operatRel
!=
null
&&
!
operatRel
.
isEmpty
())
?
new
ArrayList
<>(
operatRel
)
:
rebuildOperatRel
(
allOperations
);
Map
<
String
,
SplitOpMeta
>
splitMetas
=
new
LinkedHashMap
<>();
Map
<
Integer
,
List
<
Integer
>>
splitMapping
=
new
LinkedHashMap
<>()
;
int
totalSplitOps
=
0
;
List
<
Entry
>
result
=
new
ArrayList
<>()
;
int
totalSplitCount
=
0
;
Map
<
Integer
,
List
<
Entry
>>
groupMap
=
new
LinkedHashMap
<>();
Set
<
Entry
>
splitOriginals
=
new
LinkedHashSet
<>();
for
(
Entry
op
:
allOperations
)
{
for
(
Entry
op
:
allOperations
)
{
groupMap
.
computeIfAbsent
(
op
.
getGroupId
(),
k
->
new
ArrayList
<>()).
add
(
op
);
List
<
MachineOption
>
options
=
op
.
getMachineOptions
();
if
(
options
==
null
||
options
.
size
()
<=
1
)
{
continue
;
}
splitOriginals
.
add
(
op
);
}
}
int
totalSplitCount
=
0
;
for
(
Entry
op
:
splitOriginals
)
{
int
totalSplitOps
=
0
;
List
<
MachineOption
>
options
=
op
.
getMachineOptions
();
int
groupIdx
=
op
.
getGroupId
()
-
1
;
GroupResult
gr
=
currentRel
.
get
(
groupIdx
);
for
(
Map
.
Entry
<
Integer
,
List
<
Entry
>>
groupEntry
:
groupMap
.
entrySet
())
{
NodeInfo
origNode
=
gr
.
getNodeInfoList
().
stream
()
List
<
Entry
>
groupOps
=
groupEntry
.
getValue
();
.
filter
(
n
->
n
.
getOriginalId
().
equals
(
op
.
getExecId
()))
groupOps
.
sort
(
Comparator
.
comparingInt
(
Entry:
:
getSequence
));
.
findFirst
().
orElse
(
null
);
if
(
origNode
==
null
)
{
continue
;
}
List
<
Entry
>
newGroupOps
=
new
ArrayList
<>();
List
<
Integer
>
parentIds
=
origNode
.
getNewParentIds
()
!=
null
for
(
Entry
op
:
groupOps
)
{
?
new
ArrayList
<>(
origNode
.
getNewParentIds
())
:
new
ArrayList
<>();
List
<
MachineOption
>
options
=
op
.
getMachineOptions
();
List
<
Integer
>
childIds
=
origNode
.
getNewChildIds
()
!=
null
if
(
options
!=
null
&&
options
.
size
()
>
1
)
{
?
new
ArrayList
<>(
origNode
.
getNewChildIds
())
:
new
ArrayList
<>();
List
<
Entry
>
subOps
=
splitEntryByLoad
(
op
,
options
,
nextId
,
machineTotalLoad
);
nextId
+=
subOps
.
size
();
String
mainId
=
UUID
.
randomUUID
().
toString
().
replace
(
"-"
,
""
);
newGroupOps
.
addAll
(
subOps
);
double
[]
qtys
=
calcQuantityDistribution
(
op
,
options
,
machineTotalLoad
);
splitMapping
.
put
(
op
.
getId
(),
subOps
.
stream
().
map
(
Entry:
:
getId
).
collect
(
Collectors
.
toList
()));
SplitOpMeta
meta
=
new
SplitOpMeta
(
mainId
,
op
);
totalSplitOps
++;
meta
.
subExecIds
.
add
(
op
.
getExecId
());
totalSplitCount
+=
subOps
.
size
();
meta
.
subQtys
.
add
(
qtys
[
0
]);
}
else
{
meta
.
subMachineOptions
.
add
(
options
.
get
(
0
));
newGroupOps
.
add
(
op
);
for
(
int
i
=
1
;
i
<
options
.
size
();
i
++)
{
String
newExecId
=
UUID
.
randomUUID
().
toString
().
replace
(
"-"
,
""
);
currentRel
=
IdGroupingWithDualSerial
.
addNode
(
currentRel
,
groupIdx
,
newExecId
,
parentIds
,
childIds
,
op
.
getExecId
());
meta
.
subExecIds
.
add
(
newExecId
);
meta
.
subQtys
.
add
(
qtys
[
i
]);
meta
.
subMachineOptions
.
add
(
options
.
get
(
i
));
}
}
splitMetas
.
put
(
op
.
getExecId
(),
meta
);
totalSplitOps
++;
totalSplitCount
+=
options
.
size
();
}
}
for
(
int
i
=
0
;
i
<
newGroupOps
.
size
();
i
++)
{
// 写回 operatRel
newGroupOps
.
get
(
i
).
setSequence
(
i
+
1
);
if
(
operatRel
!=
null
)
{
operatRel
.
clear
();
operatRel
.
addAll
(
currentRel
);
}
}
result
.
addAll
(
newGroupOps
);
FileHelper
.
writeLogFile
(
String
.
format
(
"[工序拆分] OperatRel已更新,添加了%d个新节点,共%d个分组"
,
totalSplitCount
-
splitOriginals
.
size
(),
currentRel
.
size
()));
// ===== Phase 2: 从更新后的 OperatRel 生成 Entry =====
List
<
Entry
>
result
=
new
ArrayList
<>();
for
(
int
gi
=
0
;
gi
<
currentRel
.
size
();
gi
++)
{
GroupResult
gr
=
currentRel
.
get
(
gi
);
int
groupId
=
gi
+
1
;
for
(
NodeInfo
node
:
gr
.
getNodeInfoList
())
{
int
globalSerial
=
node
.
getGlobalSerial
();
String
execId
=
node
.
getOriginalId
();
Entry
entry
=
oldEntryMap
.
get
(
globalSerial
);
if
(
entry
!=
null
)
{
entry
.
setId
(
globalSerial
);
entry
.
setSequence
(
node
.
getGroupSerial
());
entry
.
setGroupId
(
groupId
);
setEntryDepsFromNodeInfo
(
entry
,
node
);
SplitOpMeta
meta
=
splitMetas
.
get
(
execId
);
if
(
meta
!=
null
)
{
entry
.
setMainId
(
meta
.
mainId
);
entry
.
setSplitSourceId
(
meta
.
originalEntry
.
getId
());
entry
.
setState
(
1
);
entry
.
setNewCreate
(
false
);
entry
.
setQuantity
(
meta
.
subQtys
.
get
(
0
));
MachineOption
mo
=
meta
.
subMachineOptions
.
get
(
0
);
entry
.
setSelectMachineID
(
mo
.
getMachineId
());
entry
.
setMachineOptions
(
Collections
.
singletonList
(
mo
));
}
}
else
{
SplitOpMeta
meta
=
findMetaForExecId
(
splitMetas
,
execId
);
if
(
meta
!=
null
)
{
int
idx
=
meta
.
subExecIds
.
indexOf
(
execId
);
if
(
idx
>
0
)
{
Entry
sub
=
ProductionDeepCopyUtil
.
deepCopy
(
meta
.
originalEntry
,
Entry
.
class
);
sub
.
setId
(
globalSerial
);
sub
.
setExecId
(
execId
);
sub
.
setSequence
(
node
.
getGroupSerial
());
sub
.
setGroupId
(
groupId
);
setEntryDepsFromNodeInfo
(
sub
,
node
);
sub
.
setMainId
(
meta
.
mainId
);
sub
.
setSplitSourceId
(
meta
.
originalEntry
.
getId
());
sub
.
setState
(
2
);
sub
.
setNewCreate
(
true
);
sub
.
setSelectMachineID
(
meta
.
subMachineOptions
.
get
(
idx
).
getMachineId
());
sub
.
setMachineOptions
(
Collections
.
singletonList
(
meta
.
subMachineOptions
.
get
(
idx
)));
sub
.
setQuantity
(
meta
.
subQtys
.
get
(
idx
));
entry
=
sub
;
oldEntryMap
.
put
(
globalSerial
,
entry
);
}
}
}
}
updateAllDependencies
(
result
,
splitMapping
);
if
(
entry
!=
null
)
{
result
.
add
(
entry
);
}
}
}
FileHelper
.
writeLogFile
(
String
.
format
(
FileHelper
.
writeLogFile
(
String
.
format
(
"[工序拆分] 拆分了%d道多设备工序 → 产生%d道子工序,最终总工序数=%d"
,
"[工序拆分] 拆分了%d道多设备工序 → 产生%d道子工序,最终总工序数=%d"
,
...
@@ -228,6 +326,7 @@ public class OperationSplitService {
...
@@ -228,6 +326,7 @@ public class OperationSplitService {
int
maxId
=
ops
.
stream
().
mapToInt
(
Entry:
:
getId
).
max
().
orElse
(
0
);
int
maxId
=
ops
.
stream
().
mapToInt
(
Entry:
:
getId
).
max
().
orElse
(
0
);
int
nextId
=
maxId
+
1
;
int
nextId
=
maxId
+
1
;
Map
<
Integer
,
List
<
Integer
>>
splitMapping
=
new
LinkedHashMap
<>();
List
<
Entry
>
result
=
new
ArrayList
<>();
List
<
Entry
>
result
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
Integer
,
List
<
Entry
>>
groupEntry
:
groupMap
.
entrySet
())
{
for
(
Map
.
Entry
<
Integer
,
List
<
Entry
>>
groupEntry
:
groupMap
.
entrySet
())
{
List
<
Entry
>
expanded
=
new
ArrayList
<>();
List
<
Entry
>
expanded
=
new
ArrayList
<>();
...
@@ -236,6 +335,8 @@ public class OperationSplitService {
...
@@ -236,6 +335,8 @@ public class OperationSplitService {
if
(
op
.
getSplitSourceId
()
==
null
&&
options
!=
null
&&
options
.
size
()
>
1
)
{
if
(
op
.
getSplitSourceId
()
==
null
&&
options
!=
null
&&
options
.
size
()
>
1
)
{
List
<
Entry
>
subOps
=
splitEntryRandomPartial
(
op
,
options
,
nextId
,
rnd
);
List
<
Entry
>
subOps
=
splitEntryRandomPartial
(
op
,
options
,
nextId
,
rnd
);
nextId
+=
subOps
.
size
();
nextId
+=
subOps
.
size
();
splitMapping
.
put
(
op
.
getId
(),
subOps
.
stream
().
map
(
Entry:
:
getId
).
collect
(
Collectors
.
toList
()));
expanded
.
addAll
(
subOps
);
expanded
.
addAll
(
subOps
);
}
else
{
}
else
{
expanded
.
add
(
op
);
expanded
.
add
(
op
);
...
@@ -246,6 +347,8 @@ public class OperationSplitService {
...
@@ -246,6 +347,8 @@ public class OperationSplitService {
}
}
result
.
addAll
(
expanded
);
result
.
addAll
(
expanded
);
}
}
updateAllDependencies
(
result
,
splitMapping
);
return
result
;
return
result
;
}
}
...
@@ -263,6 +366,90 @@ public class OperationSplitService {
...
@@ -263,6 +366,90 @@ public class OperationSplitService {
return
load
;
return
load
;
}
}
private
static
void
setEntryDepsFromNodeInfo
(
Entry
entry
,
NodeInfo
node
)
{
if
(
node
.
getNewParentIds
()
!=
null
&&
!
node
.
getNewParentIds
().
isEmpty
())
{
List
<
OperationDependency
>
prevDeps
=
new
ArrayList
<>();
for
(
int
parentId
:
node
.
getNewParentIds
())
{
if
(
parentId
>
0
)
{
OperationDependency
dep
=
new
OperationDependency
();
dep
.
setPrevOperationId
(
parentId
);
dep
.
setNextOperationId
(
entry
.
getId
());
prevDeps
.
add
(
dep
);
}
}
entry
.
setPrevEntryIds
(
prevDeps
);
}
if
(
node
.
getNewChildIds
()
!=
null
&&
!
node
.
getNewChildIds
().
isEmpty
())
{
List
<
OperationDependency
>
nextDeps
=
new
ArrayList
<>();
for
(
int
childId
:
node
.
getNewChildIds
())
{
if
(
childId
>
0
)
{
OperationDependency
dep
=
new
OperationDependency
();
dep
.
setPrevOperationId
(
entry
.
getId
());
dep
.
setNextOperationId
(
childId
);
nextDeps
.
add
(
dep
);
}
}
entry
.
setNextEntryIds
(
nextDeps
);
}
}
private
static
double
[]
calcQuantityDistribution
(
Entry
original
,
List
<
MachineOption
>
options
,
Map
<
Long
,
Double
>
loadMap
)
{
int
n
=
options
.
size
();
double
[]
weights
=
new
double
[
n
];
double
totalWeight
=
0
;
for
(
int
i
=
0
;
i
<
n
;
i
++)
{
MachineOption
mo
=
options
.
get
(
i
);
if
(
loadMap
!=
null
)
{
double
load
=
loadMap
.
getOrDefault
(
mo
.
getMachineId
(),
0.0
);
weights
[
i
]
=
1.0
/
Math
.
max
(
load
+
mo
.
getProcessingTime
(),
0.001
);
}
else
{
weights
[
i
]
=
1.0
/
Math
.
max
(
mo
.
getProcessingTime
(),
0.001
);
}
totalWeight
+=
weights
[
i
];
}
double
[]
quantities
=
new
double
[
n
];
double
remaining
=
original
.
getQuantity
();
for
(
int
i
=
0
;
i
<
n
;
i
++)
{
double
proportion
=
(
i
==
n
-
1
)
?
1.0
:
weights
[
i
]
/
totalWeight
;
double
qty
=
Math
.
max
(
1
,
Math
.
round
(
original
.
getQuantity
()
*
proportion
*
100.0
)
/
100.0
);
if
(
i
==
n
-
1
)
{
qty
=
remaining
;
}
qty
=
Math
.
min
(
qty
,
remaining
);
remaining
-=
qty
;
quantities
[
i
]
=
qty
;
}
return
quantities
;
}
private
static
SplitOpMeta
findMetaForExecId
(
Map
<
String
,
SplitOpMeta
>
metas
,
String
execId
)
{
for
(
SplitOpMeta
meta
:
metas
.
values
())
{
if
(
meta
.
subExecIds
.
contains
(
execId
))
{
return
meta
;
}
}
return
null
;
}
private
static
class
SplitOpMeta
{
final
String
mainId
;
final
Entry
originalEntry
;
final
List
<
String
>
subExecIds
=
new
ArrayList
<>();
final
List
<
Double
>
subQtys
=
new
ArrayList
<>();
final
List
<
MachineOption
>
subMachineOptions
=
new
ArrayList
<>();
SplitOpMeta
(
String
mainId
,
Entry
originalEntry
)
{
this
.
mainId
=
mainId
;
this
.
originalEntry
=
originalEntry
;
}
}
private
static
void
fixDependencyReferences
(
List
<
OperationDependency
>
deps
,
private
static
void
fixDependencyReferences
(
List
<
OperationDependency
>
deps
,
int
originalId
,
int
newId
,
boolean
isPrev
)
{
int
originalId
,
int
newId
,
boolean
isPrev
)
{
if
(
deps
==
null
)
return
;
if
(
deps
==
null
)
return
;
...
@@ -326,4 +513,58 @@ public class OperationSplitService {
...
@@ -326,4 +513,58 @@ public class OperationSplitService {
FileHelper
.
writeLogFile
(
String
.
format
(
FileHelper
.
writeLogFile
(
String
.
format
(
"[工序拆分] 更新了%d个拆分的依赖引用"
,
splitMapping
.
size
()));
"[工序拆分] 更新了%d个拆分的依赖引用"
,
splitMapping
.
size
()));
}
}
/**
* 从拆分后的 Entry 列表重建 OperatRel (GroupResult 列表)
* 对齐 IdGroupingWithDualSerial 的输出格式:
* originalId = execId, globalSerial = id, groupSerial = sequence
* newParentIds = 从 prevEntryIds 提取的 prevOperationId
* newChildIds = 从 nextEntryIds 提取的 nextOperationId
*/
public
static
List
<
GroupResult
>
rebuildOperatRel
(
List
<
Entry
>
allOperations
)
{
Map
<
Integer
,
List
<
Entry
>>
groupMap
=
new
LinkedHashMap
<>();
for
(
Entry
op
:
allOperations
)
{
groupMap
.
computeIfAbsent
(
op
.
getGroupId
(),
k
->
new
ArrayList
<>()).
add
(
op
);
}
List
<
GroupResult
>
results
=
new
ArrayList
<>();
for
(
Map
.
Entry
<
Integer
,
List
<
Entry
>>
groupEntry
:
groupMap
.
entrySet
())
{
List
<
Entry
>
groupOps
=
groupEntry
.
getValue
();
groupOps
.
sort
(
Comparator
.
comparingInt
(
Entry:
:
getSequence
));
List
<
NodeInfo
>
nodeInfoList
=
new
ArrayList
<>();
Map
<
String
,
Integer
>
originalToGlobalSerial
=
new
HashMap
<>();
for
(
Entry
op
:
groupOps
)
{
List
<
Integer
>
parentIds
=
new
ArrayList
<>();
if
(
op
.
getPrevEntryIds
()
!=
null
)
{
for
(
OperationDependency
dep
:
op
.
getPrevEntryIds
())
{
if
(
dep
.
getPrevOperationId
()
>
0
)
{
parentIds
.
add
(
dep
.
getPrevOperationId
());
}
}
}
List
<
Integer
>
childIds
=
new
ArrayList
<>();
if
(
op
.
getNextEntryIds
()
!=
null
)
{
for
(
OperationDependency
dep
:
op
.
getNextEntryIds
())
{
if
(
dep
.
getNextOperationId
()
>
0
)
{
childIds
.
add
(
dep
.
getNextOperationId
());
}
}
}
String
execId
=
op
.
getExecId
()
!=
null
?
op
.
getExecId
()
:
""
;
int
globalSerial
=
op
.
getId
();
int
groupSerial
=
op
.
getSequence
();
originalToGlobalSerial
.
put
(
execId
,
globalSerial
);
nodeInfoList
.
add
(
new
NodeInfo
(
execId
,
globalSerial
,
groupSerial
,
parentIds
,
childIds
));
}
results
.
add
(
new
GroupResult
(
nodeInfoList
,
originalToGlobalSerial
));
}
return
results
;
}
}
}
\ No newline at end of file
src/main/java/com/aps/service/plan/PlanResultService.java
View file @
2f7a699b
...
@@ -2069,6 +2069,12 @@ public class PlanResultService {
...
@@ -2069,6 +2069,12 @@ public class PlanResultService {
.
sorted
(
Comparator
.
comparingInt
(
GAScheduleResult:
:
getOperationId
))
.
sorted
(
Comparator
.
comparingInt
(
GAScheduleResult:
:
getOperationId
))
.
collect
(
Collectors
.
toList
());
.
collect
(
Collectors
.
toList
());
Map
<
Integer
,
Order
>
orders
=
schedule
.
getOrders
().
stream
()
.
collect
(
Collectors
.
toMap
(
Order:
:
getId
,
// 2. Value映射:Material → Material
order
->
order
));
for
(
GAScheduleResult
job
:
sortedJobs
)
{
for
(
GAScheduleResult
job
:
sortedJobs
)
{
StringBuilder
sb
=
new
StringBuilder
();
StringBuilder
sb
=
new
StringBuilder
();
String
TargetFinishedOperationIds
=
""
;
String
TargetFinishedOperationIds
=
""
;
...
@@ -2078,14 +2084,16 @@ public class PlanResultService {
...
@@ -2078,14 +2084,16 @@ public class PlanResultService {
.
map
(
String:
:
valueOf
)
// 把每个Integer转成String
.
map
(
String:
:
valueOf
)
// 把每个Integer转成String
.
collect
(
Collectors
.
joining
(
","
));
.
collect
(
Collectors
.
joining
(
","
));
}
}
Order
order
=
orders
.
get
(
job
.
getGroupId
());
sb
.
append
(
String
.
format
(
sb
.
append
(
String
.
format
(
"[%d-%d]:[%s-%s] Order %d,OrderID %s, Machine %d, Operation %d, Quantity %.1f, processingTime %.1f, 前处理 %d, 后处理 %d, 离散参数 %d, bomtime %s,TargetOperationId %s"
,
"[%d-%d]:[%s-%s] Order %d,OrderID %s,
OrderDueDate %s,
Machine %d, Operation %d, Quantity %.1f, processingTime %.1f, 前处理 %d, 后处理 %d, 离散参数 %d, bomtime %s,TargetOperationId %s"
,
job
.
getStartTime
(),
job
.
getStartTime
(),
job
.
getEndTime
(),
job
.
getEndTime
(),
ConvertTime
(
job
.
getStartTime
()),
ConvertTime
(
job
.
getStartTime
()),
ConvertTime
(
job
.
getEndTime
()),
ConvertTime
(
job
.
getEndTime
()),
job
.
getGroupId
(),
job
.
getGroupId
(),
job
.
getOrderId
(),
job
.
getOrderId
(),
order
.
getDueDate
().
format
(
DateTimeFormatter
.
ofPattern
(
"YYYY-MM-dd HH:mm:ss"
)),
job
.
getMachineId
(),
job
.
getMachineId
(),
job
.
getOperationId
(),
job
.
getOperationId
(),
job
.
getQuantity
(),
job
.
getQuantity
(),
...
@@ -2469,6 +2477,9 @@ if(job.getGeneDetails()!=null)
...
@@ -2469,6 +2477,9 @@ if(job.getGeneDetails()!=null)
material
.
setMaterialTypeName
(
m
.
getMaterialTypeName
());
material
.
setMaterialTypeName
(
m
.
getMaterialTypeName
());
material
.
setCode
(
m
.
getCode
());
material
.
setCode
(
m
.
getCode
());
material
.
setName
(
m
.
getName
());
material
.
setName
(
m
.
getName
());
material
.
setMaxProduction
(
m
.
getMaxProduction
());
material
.
setMinProduction
(
m
.
getMinProduction
());
// material.setCkeckLeadTime(m.getInspectDuration());
// material.setCkeckLeadTime(m.getInspectDuration());
// material.setPurchaseLeadTime(m.getPurchaseDuration());
// material.setPurchaseLeadTime(m.getPurchaseDuration());
...
...
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