Commit 1c20209d authored by Tong Li's avatar Tong Li

遗传算法

parent edfba6c2
package com.aps.entity.Algorithm;
/**
* 作者:佟礼
* 时间:2025-12-04
*/
public enum DependencyType {
FinishToStart(0), // 完成-开始(FS):串行 下料→加工→组装
StartToStart(1), // 开始-开始(SS): 加工 A 和加工 B 可同时进行 重叠 重叠生产:涂装开始后 30 分钟,烘烤可开始
FinishToFinish(2), // 完成-完成(FF):前置工序完成后,后继工序才能完成 所有并行加工工序需同时完成
StartToFinish(3); // 开始-完成(SF):前置工序开始后,后继工序才能完成
private final int value;
DependencyType(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
package com.aps.entity.Algorithm;
import lombok.Data;
/**
* 作者:佟礼
* 时间:2025-12-04
*/
@Data
public class OperationDependency {
private int Id ;
private int prevOperationId ; // 前置工序ID
private int nextOperationId ; // 后继工序ID
private DependencyType dependencyType=DependencyType.FinishToStart ; // 依赖类型
}
...@@ -9,6 +9,40 @@ import lombok.Data; ...@@ -9,6 +9,40 @@ import lombok.Data;
@Data @Data
public class ScheduleResultDetail { public class ScheduleResultDetail {
private String Key; private String Key;
private int StartTime; // 相对开始时间(分钟) private int StartTime; // 相对开始时间(秒)
private int EndTime; // 相对结束时间(分钟) private int EndTime; // 相对结束时间(秒)
private double OneTime; // 单件工时
private double Quantity; // 时间段
// Key 的 getter/setter
public String getKey() {
return Key;
}
public void setKey(String key) {
this.Key = key;
}
// StartTime 的 getter/setter
public int getStartTime() {
return StartTime;
}
public void setStartTime(int startTime) {
this.StartTime = startTime;
}
// EndTime 的 getter/setter
public int getEndTime() {
return EndTime;
}
public void setEndTime(int endTime) {
this.EndTime = endTime;
}
// 对应C#的计算属性 processingTime(通过方法实现)
public int getProcessingTime() {
return EndTime - StartTime; // 绝对处理时间(分钟)
}
} }
package com.aps.entity.basic; package com.aps.entity.basic;
import com.aps.entity.Algorithm.OperationDependency;
import lombok.Data; import lombok.Data;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -12,7 +13,7 @@ import java.util.List; ...@@ -12,7 +13,7 @@ import java.util.List;
@Data @Data
public class Entry { public class Entry {
public Entry() { public Entry() {
PrevEntryIds=new ArrayList<Integer>(); PrevEntryIds=new ArrayList<OperationDependency>();
} }
/** /**
...@@ -74,12 +75,12 @@ public class Entry { ...@@ -74,12 +75,12 @@ public class Entry {
/** /**
* 前工单ID * 前工单ID
*/ */
public List<Integer> PrevEntryIds ;//前工序 public List<OperationDependency> PrevEntryIds ;//前工序
/** /**
* 工单ID * 工单ID
*/ */
public List<Integer> NextEntryIds ;//后工序 public List<OperationDependency> NextEntryIds ;//后工序
/** /**
* 数据状态 1 拆分 2 新建 * 数据状态 1 拆分 2 新建
......
...@@ -5,6 +5,7 @@ import com.aps.entity.Algorithm.*; ...@@ -5,6 +5,7 @@ import com.aps.entity.Algorithm.*;
import com.aps.entity.ProdEquipment; import com.aps.entity.ProdEquipment;
import com.aps.entity.basic.*; import com.aps.entity.basic.*;
import com.aps.service.plan.MachineSchedulerService; import com.aps.service.plan.MachineSchedulerService;
import org.springframework.http.server.DelegatingServerHttpResponse;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
...@@ -31,6 +32,7 @@ public class GeneticDecoder { ...@@ -31,6 +32,7 @@ public class GeneticDecoder {
private final List<Material> materials; private final List<Material> materials;
private List<Entry> _allOperations; private List<Entry> _allOperations;
private GlobalParam _globalParam; private GlobalParam _globalParam;
private MachineCalculator machineCalculator;
public GeneticDecoder(GlobalParam globalParam,LocalDateTime baseTime, List<Machine> machines, List<Order> orders, public GeneticDecoder(GlobalParam globalParam,LocalDateTime baseTime, List<Machine> machines, List<Order> orders,
List<Material> materials, MachineSchedulerService machineScheduler) { List<Material> materials, MachineSchedulerService machineScheduler) {
this.baseTime = baseTime; this.baseTime = baseTime;
...@@ -40,6 +42,7 @@ public class GeneticDecoder { ...@@ -40,6 +42,7 @@ public class GeneticDecoder {
this.materials = materials; this.materials = materials;
this.machineScheduler = machineScheduler; this.machineScheduler = machineScheduler;
_globalParam=globalParam; _globalParam=globalParam;
machineCalculator=new MachineCalculator(baseTime,machines,machineScheduler);
// this.orderMaxID = orders.stream().mapToInt(Order::getId).max().orElse(0); // this.orderMaxID = orders.stream().mapToInt(Order::getId).max().orElse(0);
} }
public Chromosome decodeChromosomeWithCache(Chromosome chromosome) { public Chromosome decodeChromosomeWithCache(Chromosome chromosome) {
...@@ -195,15 +198,7 @@ public class GeneticDecoder { ...@@ -195,15 +198,7 @@ public class GeneticDecoder {
if (!currentOp.getPrevEntryIds().isEmpty()) { if (!currentOp.getPrevEntryIds().isEmpty()) {
// 处理多个前工序 // 处理多个前工序
for (int opid : currentOp.getPrevEntryIds()) { prevtime= CalPrevtime( prevtime, currentOp, chromosome, processTime, targetMachine);
List<GAScheduleResult> prevOperations = chromosome.getResult().stream()
.filter(t -> t.getGroupId() == groupId && t.getOperationId() == opid)
.collect(Collectors.toList());
for (GAScheduleResult prevOp : prevOperations) {
prevtime = Math.max(prevtime, prevOp.getEndTime()+prevOp.getTeardownTime());
}
}
} }
// 上个离散参数 // 上个离散参数
...@@ -303,9 +298,10 @@ public class GeneticDecoder { ...@@ -303,9 +298,10 @@ public class GeneticDecoder {
} }
MachineCalculator machineCalculator=new MachineCalculator(baseTime,machines,machineScheduler);
List<ScheduleResultDetail> geneDetails = machineCalculator.getNextAvailableTime(machine, earliestStartTime, -1, List<ScheduleResultDetail> geneDetails = machineCalculator.getNextAvailableTime(machine, earliestStartTime, -1,
processingTimeTotal, chromosome.getResult(), false, true, true); processingTimeTotal, chromosome.getResult(), false, true,processingTime, operation.getQuantity(), true);
...@@ -337,7 +333,7 @@ public class GeneticDecoder { ...@@ -337,7 +333,7 @@ public class GeneticDecoder {
machineCalculator.AddMachineAvailable(machine,geneDetails); machineCalculator.AddMachineAvailable(machine,geneDetails);
geneDetails = machineCalculator.getNextAvailableTime(machine, conflictEarliestStartTime, -1, geneDetails = machineCalculator.getNextAvailableTime(machine, conflictEarliestStartTime, -1,
processingTimeTotal, chromosome.getResult(), false, true, true); processingTimeTotal, chromosome.getResult(), false, true,processingTime, operation.getQuantity(), true);
if (!geneDetails.isEmpty()) { if (!geneDetails.isEmpty()) {
startTime = geneDetails.stream() startTime = geneDetails.stream()
...@@ -361,9 +357,11 @@ public class GeneticDecoder { ...@@ -361,9 +357,11 @@ public class GeneticDecoder {
result.setProductId(operation.getProductId()); result.setProductId(operation.getProductId());
result.setMachineId(machine.getId()); result.setMachineId(machine.getId());
operation.setSelectMachineID(machine.getId()); operation.setSelectMachineID(machine.getId());
result.setQuantity(operation.getQuantity());
result.setStartTime(startTime); result.setStartTime(startTime);
result.setEndTime(endTime); result.setEndTime(endTime);
result.setOneTime(processingTime);
result.setQuantity(operation.getQuantity());
result.setTeardownTime(teardownTime); result.setTeardownTime(teardownTime);
if(existingResult!=null) { if(existingResult!=null) {
result.setDesignatedStartTime(existingResult.getDesignatedStartTime()); result.setDesignatedStartTime(existingResult.getDesignatedStartTime());
...@@ -376,6 +374,256 @@ public class GeneticDecoder { ...@@ -376,6 +374,256 @@ public class GeneticDecoder {
chromosome.getResult().add(result); chromosome.getResult().add(result);
return endTime; return endTime;
} }
/**
* 计算前序结束时间
* @param prevtime
* @param currentOp
* @param chromosome
* @return
*/
private int CalPrevtime(int prevtime,Entry currentOp,Chromosome chromosome,double processTime,Machine machine) {
List<OperationDependency> FsOperations = currentOp.getPrevEntryIds().stream()
.filter(t -> t.getDependencyType() == DependencyType.FinishToStart)
.collect(Collectors.toList());//串行
if (FsOperations != null && FsOperations.size() > 0) {
for (OperationDependency opid : FsOperations) {
List<GAScheduleResult> prevOperations = chromosome.getResult().stream()
.filter(t -> t.getGroupId() == currentOp.GroupId && t.getOperationId() == opid.getPrevOperationId())
.collect(Collectors.toList());//多台
for (GAScheduleResult prevOp : prevOperations) {
prevtime = Math.max(prevtime, prevOp.getEndTime() + prevOp.getTeardownTime());
}
}
}
List<OperationDependency> SSOperations = currentOp.getPrevEntryIds().stream()
.filter(t -> t.getDependencyType() == DependencyType.StartToStart)
.collect(Collectors.toList());//重叠
if (FsOperations != null && FsOperations.size() > 0) {
List<GAScheduleResult> newScheduleResult = new ArrayList<>();
List<ScheduleResultDetail> newScheduleResultDetails = new ArrayList<>();
for (OperationDependency opid : SSOperations) {
List<GAScheduleResult> prevOperations = chromosome.getResult().stream()
.filter(t -> t.getGroupId() == currentOp.GroupId && t.getOperationId() == opid.getPrevOperationId())
.collect(Collectors.toList());//多台
for (GAScheduleResult prevOp : prevOperations) {
newScheduleResult.add(prevOp);
newScheduleResultDetails.addAll(prevOp.getGeneDetails());
}
}
int prevperationEndTime = newScheduleResult.stream()
.mapToInt(GAScheduleResult::getEndTime)
.max()
.orElse(0);
prevtime = calculateNextOperationStartTime(newScheduleResult, newScheduleResultDetails, 0, processTime);
// 下一工序结束时间需晚于上一工序结束时间一个产品加工时间
double nextopplanend = prevperationEndTime + processTime;
List<ScheduleResultDetail> geneDetails1 = machineCalculator.checkMachineStartTime(machine, (int) (processTime * currentOp.getQuantity()),
baseTime.plusSeconds((int) nextopplanend),
baseTime.plusSeconds(prevtime),
chromosome.getResult());
if (geneDetails1 != null && !geneDetails1.isEmpty()) {
prevtime = Math.max(prevtime,
geneDetails1.stream()
.mapToInt(ScheduleResultDetail::getStartTime)
.min()
.orElse(prevtime)
);
}
}
return prevtime;
}
/**
* 计算在上一工序生产后什么时间可以开工
* @param details 工序详情列表
* @param startindex 起始索引
* @param time2 时间参数2
* @return 可开工时间
*/
private int calculateNextOperationStartTime(List<GAScheduleResult> results,List<ScheduleResultDetail> details, int startindex, double time2) {
double qty= results.stream().mapToDouble(GAScheduleResult::getQuantity) // 替换为实际字段名
.sum();
double time1 = 0.0;
for (GAScheduleResult r : results) {
time1 += 1.0 / r.getOneTime();
}
int leadCount =(int) calculateLeadProductionItems(qty, time1, time2);
if (leadCount == qty) {
return details.stream()
.mapToInt(ScheduleResultDetail::getEndTime)
.max()
.orElse(0);
}
if (startindex > leadCount) {
leadCount = startindex;
}
if(results.size()>1) {//多前置工序
calculateWithMoreMachine(details, leadCount);
}
// 单设备时使用原逻辑
return (int)calculateWithSingleMachine(details, leadCount, time1);
}
/**
* 多设备并行时计算满足提前量的最早时间
*/
private int calculateWithParallelMachines(List<ScheduleResultDetail> details, int leadCount, int time1) {
// 收集所有设备的时间点(开始和结束)并去重排序
Set<Integer> timePoints = new TreeSet<>();
for (ScheduleResultDetail detail : details) {
timePoints.add(detail.getStartTime());
timePoints.add(detail.getEndTime());
}
List<Integer> sortedTimes = new ArrayList<>(timePoints);
int accumulatedQty = 0;
int prevTime = 0;
for (int currTime : sortedTimes) {
if (prevTime >= currTime) {
prevTime = currTime;
continue;
}
// 计算当前时间区间[prevTime, currTime)内的有效产能
int duration = currTime - prevTime;
int machinesWorking = 0;
for (ScheduleResultDetail detail : details) {
// 设备在当前区间内是否处于工作状态
if (detail.getStartTime() <= prevTime && detail.getEndTime() >= currTime) {
machinesWorking++;
}
}
// 区间内的产量(每台设备单位时间产能为1/time1,多设备累加)
int qtyInInterval = (duration / time1) * machinesWorking;
accumulatedQty += qtyInInterval;
// 检查是否满足提前量
if (accumulatedQty >= leadCount) {
// 反推精确满足提前量的时间点
int excess = accumulatedQty - leadCount;
int timeNeeded = leadCount * time1 - (accumulatedQty - qtyInInterval) * time1;
return prevTime + timeNeeded;
}
prevTime = currTime;
}
// 若所有时间点仍未满足,返回最后结束时间
return details.stream().mapToInt(ScheduleResultDetail::getEndTime).max().orElse(0);
}
/**
* 前置多工序多台,合并时间片段并计算满足前置产量的时间点
* @param details 排产详情列表(包含开始时间、结束时间、单件工时)
* @param leadCount 需要提前生产的数量
* @return 满足前置产量的时间点
*/
private int calculateWithMoreMachine(List<ScheduleResultDetail> details, int leadCount) {
if (details == null || details.isEmpty() || leadCount <= 0) {
return 0; // 边界条件处理
}
// 1. 收集所有时间点(开始和结束)并去重排序
Set<Integer> timePoints = new TreeSet<>();
for (ScheduleResultDetail detail : details) {
timePoints.add(detail.getStartTime());
timePoints.add(detail.getEndTime());
}
List<Integer> sortedTimes = new ArrayList<>(timePoints);
if (sortedTimes.size() < 2) {
return details.get(0).getEndTime(); // 无有效时间片段
}
// 2. 按时间片段计算累计产量
double accumulatedQty = 0.0;
for (int i = 0; i < sortedTimes.size() - 1; i++) {
int startTime = sortedTimes.get(i);
int endTime = sortedTimes.get(i + 1);
int duration = endTime - startTime;
if (duration <= 0) {
continue; // 跳过无效时间段
}
// 计算当前时间段内所有运行的设备总效率(件/单位时间)
//1秒生产的数量
double totalEfficiency = details.stream()
.filter(detail -> detail.getStartTime() <= startTime
&& detail.getEndTime() >= endTime)
.mapToDouble(detail -> 1.0 / detail.getOneTime()) // 单件工时的倒数即效率
.sum();
// 当前时间段可生产的数量
double segmentQty = totalEfficiency * duration;
// 检查是否在当前时间段内满足前置产量
if (accumulatedQty + segmentQty >= leadCount) {
// 计算精确时间点(向上取整避免小数精度问题)
double remainingQty = leadCount - accumulatedQty;
int requiredTime = (int) Math.ceil(remainingQty / totalEfficiency);//数量除以每秒的数量, 每秒生产多少个,生产X用几秒
return startTime + requiredTime;
}
accumulatedQty += segmentQty;
}
// 3. 所有片段累计仍未满足(返回最后结束时间)
return details.stream()
.mapToInt(ScheduleResultDetail::getEndTime)
.max()
.orElse(0);
}
/**
* 单设备时使用原逻辑(保持兼容)
*/
private double calculateWithSingleMachine(List<ScheduleResultDetail> details, int leadCount, double time1) {
//上一工序的排产详情 - 需要提前生产出来的数量花费的时间
double alltime = leadCount * time1;
for (ScheduleResultDetail detail : details) {
double time3 = alltime - detail.getProcessingTime();
if (time3 < 0) {
return detail.getEndTime() + time3;
} else if (time3 == 0) {
return detail.getEndTime();
} else {
alltime = time3;
}
}
return details.get(details.size() - 1).getEndTime();
}
/**
* 计算工序1需要提前生产的件数
* @param qty 总订单量
* @param time1 工序1单件工时
* @param time2 工序2单件工时
* @return 工序1需要提前生产的件数
*/
private double calculateLeadProductionItems(double qty, double time1, double time2) {
if (time1 < time2) {
return 1;
}
if (qty <= 0 || time1 <= 0 || time2 <= 0) {
throw new IllegalArgumentException("参数必须为正整数");
}
double totalTime1 = qty * time1;
double totalTime2 = qty * time2;
double timeDifference = totalTime2 - totalTime1;
return (timeDifference / time1) > qty ? qty : (timeDifference / time1);
}
private String ConvertTime(int minute) { private String ConvertTime(int minute) {
return baseTime.plusSeconds(minute).format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm")); return baseTime.plusSeconds(minute).format(java.time.format.DateTimeFormatter.ofPattern("MM-dd HH:mm"));
} }
......
...@@ -9,6 +9,7 @@ import com.aps.service.plan.MachineSchedulerService; ...@@ -9,6 +9,7 @@ import com.aps.service.plan.MachineSchedulerService;
import com.baomidou.mybatisplus.core.toolkit.StringUtils; import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
...@@ -41,6 +42,7 @@ public class MachineCalculator { ...@@ -41,6 +42,7 @@ public class MachineCalculator {
int prevtime, int processingTime, int prevtime, int processingTime,
List<GAScheduleResult> existingTasks, List<GAScheduleResult> existingTasks,
boolean isInterrupt, boolean istask, boolean isInterrupt, boolean istask,
double oneTime,double quantity,
boolean islockMachineTime) { boolean islockMachineTime) {
LocalDateTime startTime = baseTime.plus(proposedStartTime, ChronoUnit.SECONDS); LocalDateTime startTime = baseTime.plus(proposedStartTime, ChronoUnit.SECONDS);
...@@ -52,7 +54,7 @@ public class MachineCalculator { ...@@ -52,7 +54,7 @@ public class MachineCalculator {
// 查找合适的班次窗口 // 查找合适的班次窗口
return findEarliestStart(machine, processingTime, startTime, prevtimestr, return findEarliestStart(machine, processingTime, startTime, prevtimestr,
existingTasks, istask, islockMachineTime); existingTasks, oneTime, quantity, istask, islockMachineTime);
} }
...@@ -60,7 +62,7 @@ public class MachineCalculator { ...@@ -60,7 +62,7 @@ public class MachineCalculator {
private List<ScheduleResultDetail> findEarliestStart( private List<ScheduleResultDetail> findEarliestStart(
Machine machine, int processingTime, LocalDateTime currentTime, Machine machine, int processingTime, LocalDateTime currentTime,
String prevtime, List<GAScheduleResult> existingTasks, boolean checkprevtime, boolean islockMachineTime String prevtime, List<GAScheduleResult> existingTasks,double oneTime,double quantity, boolean checkprevtime, boolean islockMachineTime
) { ) {
List<GAScheduleResult> machineTasks = existingTasks.stream() List<GAScheduleResult> machineTasks = existingTasks.stream()
.filter(t -> t.getMachineId() == machine.getId()) .filter(t -> t.getMachineId() == machine.getId())
...@@ -79,12 +81,14 @@ public class MachineCalculator { ...@@ -79,12 +81,14 @@ public class MachineCalculator {
LocalDateTime endCandidate = startCandidate.plusSeconds(processingTime); LocalDateTime endCandidate = startCandidate.plusSeconds(processingTime);
if (endCandidate.isAfter(slot.getEnd())) { if (endCandidate.isAfter(slot.getEnd())) {
return CaldEarliestStart(machine, processingTime, currentTime, prevtime, machineTasks, checkprevtime,islockMachineTime); return CaldEarliestStart(machine, processingTime, currentTime, prevtime, machineTasks,oneTime,quantity, checkprevtime,islockMachineTime);
} else { } else {
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));
time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, endCandidate)); time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, endCandidate));
time.setOneTime(oneTime);
time.setQuantity(quantity);
times.add(time); times.add(time);
if(islockMachineTime) { if(islockMachineTime) {
RemoveMachineAvailable(machine, time); RemoveMachineAvailable(machine, time);
...@@ -96,7 +100,7 @@ public class MachineCalculator { ...@@ -96,7 +100,7 @@ public class MachineCalculator {
private List<ScheduleResultDetail> CaldEarliestStart( private List<ScheduleResultDetail> CaldEarliestStart(
Machine machine, int processingTime, LocalDateTime currentTime, Machine machine, int processingTime, LocalDateTime currentTime,
String prevtime, List<GAScheduleResult> machineTasks, boolean checkprevtime, boolean islockMachineTime String prevtime, List<GAScheduleResult> machineTasks,double oneTime,double quantity, boolean checkprevtime, boolean islockMachineTime
) { ) {
int remainingTime = processingTime; int remainingTime = processingTime;
LocalDateTime st = StringUtils.isEmpty(prevtime) ? currentTime : LocalDateTime.parse(prevtime); LocalDateTime st = StringUtils.isEmpty(prevtime) ? currentTime : LocalDateTime.parse(prevtime);
...@@ -166,6 +170,8 @@ public class MachineCalculator { ...@@ -166,6 +170,8 @@ public class MachineCalculator {
time.setKey(shift.getKey()); time.setKey(shift.getKey());
time.setStartTime((int) ChronoUnit.SECONDS.between(baseTime, effectiveStart)); time.setStartTime((int) ChronoUnit.SECONDS.between(baseTime, effectiveStart));
time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, currentTime)); time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, currentTime));
time.setQuantity((int)(processable/oneTime));
time.setOneTime(oneTime);
times.add(time); times.add(time);
if (islockMachineTime) { if (islockMachineTime) {
// 还原未使用的时间段 // 还原未使用的时间段
...@@ -216,7 +222,209 @@ public class MachineCalculator { ...@@ -216,7 +222,209 @@ public class MachineCalculator {
return start; return start;
} }
/**
* 检查设备的可用开工时间
* @param machine 目标设备
* @param processingTime 加工时长(分钟)
* @param currentTime 当前时间
* @param startTime 开始时间
* @param existingTasks 已有任务列表
* @return 可用时间段列表,无可用时间返回null
*/
public List<ScheduleResultDetail> checkMachineStartTime(Machine machine, int processingTime,
LocalDateTime currentTime, LocalDateTime startTime,
List<GAScheduleResult> existingTasks) {
// 获取设备上已有任务并按开始时间排序
List<GAScheduleResult> machineTasks = existingTasks.stream()
.filter(t -> t.getMachineId() == machine.getId())
.sorted(Comparator.comparing(GAScheduleResult::getStartTime))
.collect(Collectors.toList());
List<ScheduleResultDetail> times = new ArrayList<>();
// 获取当前/上一个班次
TimeSegment slot = getCurrentOrPrevShift(machine, currentTime);
if (slot == null) {
return null;
}
// 计算候选开始/结束时间(当前时间往前推加工时长)
LocalDateTime startCandidate = currentTime.minusSeconds(processingTime);
LocalDateTime endCandidate = currentTime;
// 检查是否在可用时间段内
if (startCandidate.isBefore(slot.getStart())) {
// 放不下,寻找后续可用时间
return checkMachineShift(machine, processingTime, currentTime, startTime, machineTasks);
} else {
// 生成可用时间段
ScheduleResultDetail time = new ScheduleResultDetail();
time.setKey(slot.getKey());
// 计算相对基准时间的分钟数(对应原代码TotalMinutes
time.setStartTime((int) ChronoUnit.SECONDS.between(baseTime, startCandidate));
time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, endCandidate));
times.add(time);
return times;
}
}
/**
* 获取设备当前或上一个有效班次
* @param machine 目标设备
* @param time 目标时间
* @return 有效班次,无则递归生成后返回
*/
private TimeSegment getCurrentOrPrevShift(Machine machine, LocalDateTime time) {
TimeSegment start = null;
// 找到最后一个可用班次:未使用、非维护类型、开始时间早于目标时间
// 倒序遍历找最后一个符合条件的班次
List<TimeSegment> availability = machine.getAvailability();
for (int i = availability.size() - 1; i >= 0; i--) {
TimeSegment slot = availability.get(i);
if (!slot.isUsed()
&& slot.getType() != SegmentType.MAINTENANCE
&& slot.getStart().isBefore(time)) {
start = slot;
break;
}
}
// 若未找到班次,且目标时间晚于所有班次的结束时间 → 生成新班次并递归
if (start == null) {
// 获取所有班次的最大结束时间
Optional<LocalDateTime> maxEndTime = availability.stream()
.map(TimeSegment::getEnd)
.max(LocalDateTime::compareTo);
// 检查是否目标时间晚于最大结束时间(处理空列表情况)
boolean isTimeAfterMaxEnd = maxEndTime.map(end -> time.isAfter(end)).orElse(true);
if (isTimeAfterMaxEnd) {
// 生成新的时间段并添加到设备可用班次中
LocalDateTime generateStartTime = maxEndTime.orElse(LocalDateTime.now());
List<TimeSegment> timeSegments = machineScheduler.generateTimeSegment(machine, generateStartTime);
machine.getAvailability().addAll(timeSegments);
// 更新机器列表中的对应机器
Machine m = machines.stream()
.filter(t -> t.getId() == machine.getId())
.findFirst()
.orElse(null);
if (m != null) {
MachineTimeline machineTimeline = machineScheduler.getOrCreateTimeline(m);
// 原代码注释:m.Availability = machineTimeline.Segments;
// 如需启用请补充:m.setAvailability(machineTimeline.getSegments());
}
// 递归调用,重新获取有效班次
return getCurrentOrPrevShift(machine, time);
}
}
return start;
}
/**
* 检查设备班次,计算满足加工时长的可用时间段
* @param machine 目标设备
* @param processingTime 总加工时长(分钟)
* @param currentTime 当前时间
* @param startTime 开始时间阈值
* @param machineTasks 设备已有任务列表
* @return 分段的可用时间段列表,无可用时间返回null
*/
private List<ScheduleResultDetail> checkMachineShift(Machine machine, int processingTime,
LocalDateTime currentTime, LocalDateTime startTime,
List<GAScheduleResult> machineTasks) {
int remainingTime = processingTime;
// 初始化上一班次结束时间(对应原2000-01-01的初始值)
LocalDateTime prevEnd = LocalDateTime.of(2000, 1, 1, 0, 0);
List<ScheduleResultDetail> times = new ArrayList<>();
while (remainingTime > 0) {
// 开始时间阈值超过当前时间,无可用时间
if (startTime.isAfter(currentTime)) {
return null;
}
// 获取当前/上一个有效班次
TimeSegment shift = getCurrentOrPrevShift(machine, currentTime);
if (shift == null) {
return null;
}
LocalDateTime shiftStart = shift.getStart();
LocalDateTime shiftEnd = shift.getEnd();
// 非初始状态(上一班次已存在),检查班次连续性
if (prevEnd.getYear()!=2000) {
// 检查上一班次和当前班次之间是否有任务
if (prevEnd.isAfter(shiftEnd)) {
// 查找任务:任务开始时间在当前班次开始 ~ 上一班次结束之间
LocalDateTime finalPrevEnd = prevEnd;
Optional<GAScheduleResult> task = machineTasks.stream()
.filter(t -> baseTime.plusSeconds(t.getStartTime()).isAfter(shiftStart)
&& baseTime.plusSeconds(t.getStartTime()).isBefore(finalPrevEnd))
.findFirst();
if (task.isPresent()) {
// 班次间有任务,重置状态重新查找
currentTime = shiftStart;
remainingTime = processingTime;
prevEnd = LocalDateTime.of(2000, 1, 1, 0, 0);
times.clear();
continue;
}
}
// 检查维护窗口
if (machine.getMaintenanceWindows() != null && !machine.getMaintenanceWindows().isEmpty()) {
LocalDateTime finalPrevEnd = prevEnd;
Optional<MaintenanceWindow> maintenanceTask = machine.getMaintenanceWindows().stream()
.filter(t -> t.getStartTime().isAfter(shiftStart)
&& t.getStartTime().isBefore(finalPrevEnd))
.findFirst();
if (maintenanceTask.isPresent()) {
// 班次间有维护任务,重置状态重新查找
currentTime = shiftStart;
remainingTime = processingTime;
prevEnd = LocalDateTime.of(2000, 1, 1, 0, 0);
times.clear();
continue;
}
}
}
// 调整班次有效结束时间(不超过当前时间)
LocalDateTime effectiveEnd = shiftEnd.isAfter(currentTime) ? currentTime : shiftEnd;
// 计算班次可用时长
Duration availableDuration = Duration.between(shiftStart, effectiveEnd);
long availableMinutes = availableDuration.getSeconds();
// 当前班次可处理的时长(不超过剩余需要的时长)
int processableMinutes = (int) Math.min(remainingTime, availableMinutes);
// 计算当前班次的有效开始时间
LocalDateTime effectiveStart = effectiveEnd.minusSeconds(processableMinutes);
prevEnd = effectiveStart; // 更新上一班次结束时间
remainingTime -= processableMinutes; // 剩余时长递减
currentTime = effectiveStart; // 更新当前时间,继续循环
// 生成当前班次的时间片段
ScheduleResultDetail time = new ScheduleResultDetail();
time.setKey(shift.getKey());
time.setStartTime((int) ChronoUnit.SECONDS.between(baseTime, effectiveStart));
time.setEndTime((int) ChronoUnit.SECONDS.between(baseTime, effectiveEnd));
times.add(time);
}
return times;
}
private void RemoveMachineAvailable(Machine machine, ScheduleResultDetail geneDetails) { private void RemoveMachineAvailable(Machine machine, ScheduleResultDetail geneDetails) {
List<TimeSegment> timeSegments = new ArrayList<>(); List<TimeSegment> timeSegments = new ArrayList<>();
......
package com.aps.service.Algorithm; package com.aps.service.Algorithm;
import com.aps.common.util.ProductionDeepCopyUtil; import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.entity.Algorithm.Chromosome; import com.aps.entity.Algorithm.*;
import com.aps.entity.Algorithm.GAScheduleResult;
import com.aps.entity.Algorithm.GlobalOperationInfo;
import com.aps.entity.Algorithm.IDAndChildID.GroupResult; import com.aps.entity.Algorithm.IDAndChildID.GroupResult;
import com.aps.entity.Algorithm.IDAndChildID.NodeInfo; import com.aps.entity.Algorithm.IDAndChildID.NodeInfo;
import com.aps.entity.Algorithm.ScheduleResultDetail;
import com.aps.entity.basic.Entry; import com.aps.entity.basic.Entry;
import com.aps.entity.basic.GlobalParam; import com.aps.entity.basic.GlobalParam;
import com.aps.entity.basic.MachineOption; import com.aps.entity.basic.MachineOption;
...@@ -173,9 +170,28 @@ public class ScheduleOperationService { ...@@ -173,9 +170,28 @@ public class ScheduleOperationService {
{ {
//存在则修改顺和前后序 //存在则修改顺和前后序
entry.setSequence(nodeInfo.getGroupSerial()); entry.setSequence(nodeInfo.getGroupSerial());
entry.setPrevEntryIds(nodeInfo.getNewParentIds()); // entry.setPrevEntryIds(nodeInfo.getNewParentIds());
entry.setNextEntryIds(nodeInfo.getNewChildIds()); // entry.setNextEntryIds(nodeInfo.getNewChildIds());
if(nodeInfo.getNewParentIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewParentIds()) {
OperationDependency od=new OperationDependency();
od.setPrevOperationId(id);
OperationDependency.add(od);
}
entry.setPrevEntryIds(OperationDependency);
}
if(nodeInfo.getNewChildIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewChildIds()) {
OperationDependency od=new OperationDependency();
od.setNextOperationId(id);
OperationDependency.add(od);
}
entry.setNextEntryIds(OperationDependency);
}
GlobalOperationInfo info= chromosome.getGlobalOpList().stream() GlobalOperationInfo info= chromosome.getGlobalOpList().stream()
.filter(t->t.getOp().getId()==entry.getId()) .filter(t->t.getOp().getId()==entry.getId())
.findFirst() .findFirst()
...@@ -196,8 +212,28 @@ public class ScheduleOperationService { ...@@ -196,8 +212,28 @@ public class ScheduleOperationService {
newOp.setId(nodeInfo.getGlobalSerial()); newOp.setId(nodeInfo.getGlobalSerial());
newOp.setSequence(nodeInfo.getGroupSerial()); newOp.setSequence(nodeInfo.getGroupSerial());
newOp.setExecId(nodeInfo.getOriginalId()); newOp.setExecId(nodeInfo.getOriginalId());
newOp.setPrevEntryIds(nodeInfo.getNewParentIds()); // newOp.setPrevEntryIds(nodeInfo.getNewParentIds());
newOp.setNextEntryIds(nodeInfo.getNewChildIds()); // newOp.setNextEntryIds(nodeInfo.getNewChildIds());
if(nodeInfo.getNewParentIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewParentIds()) {
OperationDependency od=new OperationDependency();
od.setPrevOperationId(id);
OperationDependency.add(od);
}
entry.setPrevEntryIds(OperationDependency);
}
if(nodeInfo.getNewChildIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewChildIds()) {
OperationDependency od=new OperationDependency();
od.setNextOperationId(id);
OperationDependency.add(od);
}
entry.setNextEntryIds(OperationDependency);
}
newOp.setQuantity(newids.get(nodeInfo.getOriginalId())); newOp.setQuantity(newids.get(nodeInfo.getOriginalId()));
newOp.setMainId(MainId); newOp.setMainId(MainId);
newOp.setState(2); newOp.setState(2);
...@@ -281,10 +317,10 @@ public class ScheduleOperationService { ...@@ -281,10 +317,10 @@ public class ScheduleOperationService {
for (Entry entry : newEntrys) { for (Entry entry : newEntrys) {
if(entry.getNextEntryIds().size()>0) if(entry.getNextEntryIds().size()>0)
{ {
for (Integer id : entry.getNextEntryIds()) { for (OperationDependency od : entry.getNextEntryIds()) {
Entry nextentry= newEntrys.stream() Entry nextentry= newEntrys.stream()
.filter(t->t.getGroupId()==entry.getGroupId()&&t.getId()==id) .filter(t->t.getGroupId()==entry.getGroupId()&&t.getId()==od.getNextOperationId())
.findFirst() .findFirst()
.orElse(null); .orElse(null);
if(nextentry!=null) { if(nextentry!=null) {
...@@ -325,8 +361,28 @@ public class ScheduleOperationService { ...@@ -325,8 +361,28 @@ public class ScheduleOperationService {
entry.setGroupId(i+1); entry.setGroupId(i+1);
entry.setSequence(nodeInfo.getGroupSerial()); entry.setSequence(nodeInfo.getGroupSerial());
entry.setExecId(nodeInfo.getOriginalId()); entry.setExecId(nodeInfo.getOriginalId());
entry.setPrevEntryIds(nodeInfo.getNewParentIds()); // entry.setPrevEntryIds(nodeInfo.getNewParentIds());
entry.setNextEntryIds(nodeInfo.getNewChildIds()); // entry.setNextEntryIds(nodeInfo.getNewChildIds());
if(nodeInfo.getNewParentIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewParentIds()) {
OperationDependency od=new OperationDependency();
od.setPrevOperationId(id);
OperationDependency.add(od);
}
entry.setPrevEntryIds(OperationDependency);
}
if(nodeInfo.getNewChildIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewChildIds()) {
OperationDependency od=new OperationDependency();
od.setNextOperationId(id);
OperationDependency.add(od);
}
entry.setNextEntryIds(OperationDependency);
}
entry.setMainId(""); entry.setMainId("");
entry.setState(2); entry.setState(2);
//工序基本信息 //工序基本信息
...@@ -487,8 +543,28 @@ public class ScheduleOperationService { ...@@ -487,8 +543,28 @@ public class ScheduleOperationService {
if(node!=null) if(node!=null)
{ {
entry.setSequence(node.getGroupSerial()); entry.setSequence(node.getGroupSerial());
entry.setPrevEntryIds(node.getNewParentIds()); // entry.setPrevEntryIds(node.getNewParentIds());
entry.setNextEntryIds(node.getNewChildIds()); // entry.setNextEntryIds(node.getNewChildIds());
if(node.getNewParentIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : node.getNewParentIds()) {
OperationDependency od=new OperationDependency();
od.setPrevOperationId(id);
OperationDependency.add(od);
}
entry.setPrevEntryIds(OperationDependency);
}
if(node.getNewChildIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : node.getNewChildIds()) {
OperationDependency od=new OperationDependency();
od.setNextOperationId(id);
OperationDependency.add(od);
}
entry.setNextEntryIds(OperationDependency);
}
GlobalOperationInfo info= chromosome.getGlobalOpList().stream() GlobalOperationInfo info= chromosome.getGlobalOpList().stream()
.filter(t->t.getOp().getId()==entry.getId()) .filter(t->t.getOp().getId()==entry.getId())
.findFirst() .findFirst()
......
...@@ -5,12 +5,9 @@ import com.aps.common.util.JsonFileReader; ...@@ -5,12 +5,9 @@ import com.aps.common.util.JsonFileReader;
import com.aps.common.util.ProductionDeepCopyUtil; import com.aps.common.util.ProductionDeepCopyUtil;
import com.aps.controller.gantt.FileUploadController; import com.aps.controller.gantt.FileUploadController;
import com.aps.entity.*; import com.aps.entity.*;
import com.aps.entity.Algorithm.Chromosome; import com.aps.entity.Algorithm.*;
import com.aps.entity.Algorithm.GAScheduleResult;
import com.aps.entity.Algorithm.IDAndChildID.GroupResult; import com.aps.entity.Algorithm.IDAndChildID.GroupResult;
import com.aps.entity.Algorithm.IDAndChildID.NodeInfo; import com.aps.entity.Algorithm.IDAndChildID.NodeInfo;
import com.aps.entity.Algorithm.ScheduleParams;
import com.aps.entity.Algorithm.ScheduleResultDetail;
import com.aps.entity.basic.ScheduleChromosome; import com.aps.entity.basic.ScheduleChromosome;
import com.aps.entity.Schedule.GenVO; import com.aps.entity.Schedule.GenVO;
import com.aps.entity.Schedule.MachineVO; import com.aps.entity.Schedule.MachineVO;
...@@ -135,10 +132,25 @@ public class PlanResultService { ...@@ -135,10 +132,25 @@ public class PlanResultService {
} }
} }
public Chromosome execute1() {
public Chromosome execute1() {
try { try {
//List<ScheduleResultDetail> details=new ArrayList<>();
// ScheduleResultDetail detail1=new ScheduleResultDetail();
//
// detail1.setOneTime(100);//单件工时
//
//
// ScheduleResultDetail detail2=new ScheduleResultDetail();
//
// detail2.setOneTime(200);//单件工时
// details.add(detail1);
// details.add(detail2);
// mergeSegmentsWithDifferentOneTime(details, 50);
// 1. 读取数据 // 1. 读取数据
List<Machine> machines = loadData("machines.json", Machine.class); List<Machine> machines = loadData("machines.json", Machine.class);
List<Product> products = loadData("products.json", Product.class); List<Product> products = loadData("products.json", Product.class);
...@@ -166,23 +178,20 @@ public class PlanResultService { ...@@ -166,23 +178,20 @@ public class PlanResultService {
} }
ScheduleParams param = new ScheduleParams(); ScheduleParams param = new ScheduleParams();
param.setBaseTime(LocalDateTime.of(2025, 11, 1, 0, 0, 0)); param.setBaseTime(LocalDateTime.of(2025, 11, 1, 0, 0, 0));
param.setPopulationSize(50); param.setPopulationSize(1);
param.setMaxIterations(100); param.setMaxIterations(2);
// List<MesHoliday> holidays= _MesHolidayService.list(); // List<MesHoliday> holidays= _MesHolidayService.list();
// 创建节假日 // 创建节假日
List<Holiday> holidays = Arrays.asList(
new Holiday(LocalDateTime.of(2025, 10, 1, 0, 0),
LocalDateTime.of(2025, 10, 7, 23, 59))
);
// 将节假日添加到所有设备中 // 将节假日添加到所有设备中
addHolidaysToAllMachines(machines, holidays); // addHolidaysToAllMachines(machines);
// 3. 创建调度服务 // 3. 创建调度服务
MachineSchedulerService machineScheduler = new MachineSchedulerService( MachineSchedulerService machineScheduler = new MachineSchedulerService(
holidays, param.getBaseTime()); param.getBaseTime());
// 4. 初始化机器时间线 // 4. 初始化机器时间线
for (Machine machine : machines) { for (Machine machine : machines) {
...@@ -214,7 +223,10 @@ order.setDueDate(LocalDateTime.of(2025, 12, 1,0,0,0)); ...@@ -214,7 +223,10 @@ order.setDueDate(LocalDateTime.of(2025, 12, 1,0,0,0));
// entry.setMaterialRequirements(o.getMaterialRequirements()); // 假设Operation类有获取物料需求的方法 // entry.setMaterialRequirements(o.getMaterialRequirements()); // 假设Operation类有获取物料需求的方法
if (sequence != 1) { if (sequence != 1) {
entry.getPrevEntryIds().add(id - 1); // 假设Entry类有getPrevEntryIds()返回List<Integer> OperationDependency od=new OperationDependency();
od.setPrevOperationId(id - 1);
entry.getPrevEntryIds().add(od); // 假设Entry类有getPrevEntryIds()返回List<Integer>
} }
allOperations.add(entry); allOperations.add(entry);
...@@ -794,8 +806,27 @@ order.setDueDate(LocalDateTime.of(2025, 12, 1,0,0,0)); ...@@ -794,8 +806,27 @@ order.setDueDate(LocalDateTime.of(2025, 12, 1,0,0,0));
entry.setGroupId(i + 1); entry.setGroupId(i + 1);
entry.setSequence(nodeInfo.getGroupSerial()); entry.setSequence(nodeInfo.getGroupSerial());
entry.setExecId(nodeInfo.getOriginalId()); entry.setExecId(nodeInfo.getOriginalId());
entry.setPrevEntryIds(nodeInfo.getNewParentIds());
entry.setNextEntryIds(nodeInfo.getNewChildIds()); if(nodeInfo.getNewParentIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewParentIds()) {
OperationDependency od=new OperationDependency();
od.setPrevOperationId(id);
OperationDependency.add(od);
}
entry.setPrevEntryIds(OperationDependency);
}
if(nodeInfo.getNewChildIds()!=null)
{
List<OperationDependency> OperationDependency=new ArrayList<>();
for (int id : nodeInfo.getNewChildIds()) {
OperationDependency od=new OperationDependency();
od.setNextOperationId(id);
OperationDependency.add(od);
}
entry.setNextEntryIds(OperationDependency);
}
ProdProcessExec op= ProdProcessExecs.stream() ProdProcessExec op= ProdProcessExecs.stream()
.filter(t->t.getExecId().equals(entry.getExecId())) .filter(t->t.getExecId().equals(entry.getExecId()))
.findFirst().orElse(null); .findFirst().orElse(null);
......
...@@ -5,36 +5,12 @@ ...@@ -5,36 +5,12 @@
"startTime": "08:00:00", "startTime": "08:00:00",
"endTime": "18:00:00", "endTime": "18:00:00",
"days": [1, 2, 3, 4, 5], "days": [1, 2, 3, 4, 5],
"startDate": "2025-10-10T00:00:00", "startDate": "2000-01-01T00:00:00",
"endDate": "2025-11-10T00:00:00", "endDate": "2000-01-01T00:00:00",
"shiftDate": "0001-01-01T00:00:00", "shiftDate": "0001-01-01T00:00:00",
"temporaryShift": false, "temporaryShift": false,
"priority": 0, "priority": 0,
"status":0 "status":0
},{
"startTime": "18:00:00",
"endTime": "20:00:00",
"days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": true,
"priority": 0,
"status":1
},{
"startTime": "08:00:00",
"endTime": "18:00:00",
"days": [0],
"shiftDate": "0001-01-01T00:00:00",
"temporaryShift": true,
"priority": 0,
"status":1
},{
"startTime": "08:00:00",
"endTime": "18:00:00",
"days": null,
"shiftDate": "2025-10-01T00:00:00",
"temporaryShift": true,
"priority": 0,
"status":1
}], }],
"maintenanceWindows": [{ "maintenanceWindows": [{
"startTime": "2025-10-08T10:00:00", "startTime": "2025-10-08T10:00:00",
...@@ -49,6 +25,8 @@ ...@@ -49,6 +25,8 @@
"endTime": "18:00:00", "endTime": "18:00:00",
"days": [1, 2, 3,4,5], "days": [1, 2, 3,4,5],
"shiftDate": "0001-01-01T00:00:00", "shiftDate": "0001-01-01T00:00:00",
"startDate": "2000-01-01T00:00:00",
"endDate": "2000-01-01T00:00:00",
"temporaryShift": false, "temporaryShift": false,
"priority": 0, "priority": 0,
"status":0 "status":0
...@@ -62,6 +40,8 @@ ...@@ -62,6 +40,8 @@
"endTime": "18:00:00", "endTime": "18:00:00",
"days": [1, 2, 3, 4, 5], "days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00", "shiftDate": "0001-01-01T00:00:00",
"startDate": "2000-01-01T00:00:00",
"endDate": "2000-01-01T00:00:00",
"temporaryShift": false, "temporaryShift": false,
"priority": 0, "priority": 0,
"status":0 "status":0
...@@ -75,6 +55,8 @@ ...@@ -75,6 +55,8 @@
"endTime": "18:00:00", "endTime": "18:00:00",
"days": [1, 2, 3, 4, 5], "days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00", "shiftDate": "0001-01-01T00:00:00",
"startDate": "2000-01-01T00:00:00",
"endDate": "2000-01-01T00:00:00",
"temporaryShift": false, "temporaryShift": false,
"priority": 0, "priority": 0,
"status":0 "status":0
...@@ -88,6 +70,8 @@ ...@@ -88,6 +70,8 @@
"endTime": "18:00:00", "endTime": "18:00:00",
"days": [1, 2, 3, 4, 5], "days": [1, 2, 3, 4, 5],
"shiftDate": "0001-01-01T00:00:00", "shiftDate": "0001-01-01T00:00:00",
"startDate": "2000-01-01T00:00:00",
"endDate": "2000-01-01T00:00:00",
"temporaryShift": false, "temporaryShift": false,
"priority": 0, "priority": 0,
"status":0 "status":0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment