Преглед изворни кода

APS平台生产排程平台规划算法

fangpy пре 1 година
родитељ
комит
e4e477e3ff
17 измењених фајлова са 880 додато и 162 уклоњено
  1. 86 31
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/ApsBalancingApplication.java
  2. 15 1
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/controller/ApsSchedulingController.java
  3. 22 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/AluminumCoil.java
  4. 4 4
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ApsAbstractPersistable.java
  5. 23 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ApsOverallConfig.java
  6. 10 3
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ApsSolution.java
  7. 17 1
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/Equipment.java
  8. 16 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/EquipmentParameter.java
  9. 26 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/EquipmentRunTime.java
  10. 2 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ProduceOrder.java
  11. 155 21
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ProductionProcesses.java
  12. 356 12
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/score/ApsConstraintProvider.java
  13. 84 56
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/service/impl/ProductionScheduleServiceImpl.java
  14. 7 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/util/ApsConstants.java
  15. 39 0
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/util/ApsException.java
  16. 15 33
      rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/vo/ProductionScheduleVo.java
  17. 3 0
      rw-aps-server/src/main/resources/bootstrap.yml

+ 86 - 31
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/ApsBalancingApplication.java

@@ -1,26 +1,31 @@
 package com.rongwei.rwapsserver.aps;
 
 import cn.hutool.core.date.DateUtil;
+import cn.hutool.json.JSONUtil;
 import com.rongwei.rwapsserver.aps.domain.ApsSolution;
 import com.rongwei.rwapsserver.aps.domain.Equipment;
 import com.rongwei.rwapsserver.aps.domain.ProduceOrder;
 import com.rongwei.rwapsserver.aps.domain.ProductionProcesses;
 import com.rongwei.rwapsserver.aps.score.ApsConstraintProvider;
 import lombok.extern.slf4j.Slf4j;
+import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
+import org.optaplanner.core.api.solver.SolutionManager;
 import org.optaplanner.core.api.solver.Solver;
 import org.optaplanner.core.api.solver.SolverFactory;
 import org.optaplanner.core.config.solver.SolverConfig;
 import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
 
 @Slf4j
 public class ApsBalancingApplication {
 
     public static void main(String[] args) {
-        testAps();
-
+//        testAps();
+        /*String a = "1";
+        String b = "1";
+        System.out.println(a == b);*/
         /*ProductionProcesses p1 = new ProductionProcesses();
         ProductionProcesses p2 = new ProductionProcesses();
         p1.setId(1111);
@@ -33,41 +38,83 @@ public class ApsBalancingApplication {
         LocalDateTime time2 = LocalDateTime.now();
         long a = DateUtil.between(Date.from(time2.atZone(ZoneId.systemDefault()).toInstant()),date1, DateUnit.DAY);
         System.out.println("a:"+a);*/
+
+//        System.out.println(JSONUtil.toJsonStr(getTestApsSolution()));
+
+        /*Boolean a = null;
+        if(a){
+            System.out.println("1111");
+        }
+        System.out.println("2222");*/
+
+        /*Map<String,Integer> map = new HashMap();
+        Integer a = 3;
+        map.put("1",a);
+
+        ApsBalancingApplication application = new ApsBalancingApplication();
+        application.testOne(map);
+        System.out.println(map.get("1"));*/
+
+        /*List<Map<String,Integer>> list = new ArrayList<>();
+        Map<String,Integer> map = new HashMap();
+        map.put("1",1);map.put("2",2);map.put("3",3);*/
+
+//        System.out.println((int)Math.ceil((double) 3/2));
+
+        Boolean a = true;
+        while (a){
+            for (int i=0;i<10;i++){
+                if(i > 6){
+                    a = false;
+                    break;
+                }
+            }
+        }
+        System.out.println("1111111");
     }
 
-    private static void testAps(){
+    public static void testAps(){
         log.info("app start...");
+        Date startDate = DateUtil.parseDateTime("2024-03-06 17:00:12");
+        LocalDateTime startTime = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+//        ApsConstraintProvider.startTimeLong = startDate.getTime();
+//        ApsSolution.startTime = startTime;
         SolverFactory<ApsSolution> solverFactory = SolverFactory.create(new SolverConfig()
                 .withSolutionClass(ApsSolution.class)
                 .withEntityClasses(ProductionProcesses.class)
                 .withConstraintProviderClass(ApsConstraintProvider.class)
-                .withTerminationSpentLimit(Duration.ofSeconds(5)));
+                .withTerminationSpentLimit(Duration.ofSeconds(20)));
         Solver<ApsSolution> solver = solverFactory.buildSolver();
 
 //        System.out.println("\nProcesses:\n" + unsolvedCloudBalance.toStringProcessList());
 
         // Display the computer list
 //        System.out.println("\nComputers:\n" + unsolvedCloudBalance.toStringComputerList());
-
+        ApsSolution apsSolution = getTestApsSolution();
         log.info("start to solve...");
         ApsSolution solvedBalance = solver.solve(getTestApsSolution());
+        System.out.println("*****************************");
+        System.out.println(solvedBalance);
+        SolutionManager<ApsSolution, HardSoftScore> scoreManager = SolutionManager.create(solverFactory);
+        System.out.println(scoreManager.explain(solvedBalance));
+        System.out.println("*****************************");
         System.out.println(solvedBalance);
         // Display the result
 //        System.out.println("\nSolved cloudBalance with 3 computers and 12 processes:\n" + toDisplayString(solvedCloudBalance));
     }
 
-    private static ApsSolution getTestApsSolution(){
+    public static ApsSolution getTestApsSolution(){
         ApsSolution unsolvedCloudBalance = new ApsSolution();
         // 设备列表初始化
         List<Equipment> equipmentList = new ArrayList<>();
-        Equipment eq1 = new Equipment(1,"均热炉");
-        Equipment eq2 = new Equipment(2,"均热炉");
-        Equipment eq3 = new Equipment(3,"卧式分切机");
-        Equipment eq4 = new Equipment(4,"卧式分切机");
-        Equipment eq5 = new Equipment(5,"卧式分切机");
-        Equipment eq6 = new Equipment(6,"厚纵剪");
-        Equipment eq7 = new Equipment(7,"厚纵剪");
-        Equipment eq8 = new Equipment(8,"氮气保护退火炉");
+        Equipment eq1 = new Equipment("1","均热炉");
+        Equipment eq2 = new Equipment("2","均热炉");
+        Equipment eq3 = new Equipment("3","卧式分切机");
+        Equipment eq4 = new Equipment("4","卧式分切机");
+        Equipment eq5 = new Equipment("5","卧式分切机");
+        Equipment eq6 = new Equipment("6","厚纵剪");
+        Equipment eq7 = new Equipment("7","厚纵剪");
+        Equipment eq8 = new Equipment("8","氮气保护退火炉");
         equipmentList.add(eq1);
         equipmentList.add(eq2);
         equipmentList.add(eq3);
@@ -80,24 +127,29 @@ public class ApsBalancingApplication {
         // 工步初始化
         List<ProductionProcesses> processes = new ArrayList<>();
         // 订单初始化
-        ProduceOrder produceOrder1 = new ProduceOrder("order1","订单1", DateUtil.parseDateTime("2024-03-01 21:52:12"));
-        ProduceOrder produceOrder2 = new ProduceOrder("order2","订单2", DateUtil.parseDateTime("2024-03-01 19:20:12"));
-        ProductionProcesses p1 = new ProductionProcesses(1,"均热炉",Arrays.asList(new ProduceOrder[]{produceOrder1}),10);
-        p1.setOptionalEquipments(Arrays.asList(new Long[]{1L,2L}));
-        ProductionProcesses p2 = new ProductionProcesses(2,"卧式分切机",Arrays.asList(new ProduceOrder[]{produceOrder1}),12);
-        p2.setOptionalEquipments(Arrays.asList(new Long[]{3L,4L,5L}));
-        ProductionProcesses p6 = new ProductionProcesses(6,"氮气保护退火炉",Arrays.asList(new ProduceOrder[]{produceOrder1,produceOrder2}),72);
-        p6.setOptionalEquipments(Arrays.asList(new Long[]{8L}));
+        ProduceOrder produceOrder1 = new ProduceOrder("order1","订单1", DateUtil.parseDateTime("2024-03-06 19:20:12"));
+        ProduceOrder produceOrder2 = new ProduceOrder("order2","订单2", DateUtil.parseDateTime("2024-03-06 19:20:12"));
+        ProductionProcesses p1 = new ProductionProcesses("1","均热炉",Arrays.asList(new ProduceOrder[]{produceOrder1}),10);
+        p1.setOptionalEquipments(Arrays.asList(new String[]{"1","2"}));
+        ProductionProcesses p2 = new ProductionProcesses("2","卧式分切机",Arrays.asList(new ProduceOrder[]{produceOrder1}),12);
+        p2.setOptionalEquipments(Arrays.asList(new String[]{"3","4","5"}));
+        ProductionProcesses p6 = new ProductionProcesses("6","氮气保护退火炉",Arrays.asList(new ProduceOrder[]{produceOrder1,produceOrder2}),72);
+        p6.setOptionalEquipments(Arrays.asList(new String[]{"8"}));
         p6.setMergeProcessMark("tuihuolu");
+//        p1.setNextProcessesIds(Arrays.asList(new String[]{p2.getId()}));p2.setPreviousProcessesIds(Arrays.asList(new String[]{p1.getId()}));
+//        p2.setNextProcessesIds(Arrays.asList(new String[]{p6.getId()}));
         p1.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p2}));p2.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p1}));
         p2.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p6}));
         processes.add(p1);processes.add(p2);
-        ProductionProcesses p3 = new ProductionProcesses(3,"均热炉",Arrays.asList(new ProduceOrder[]{produceOrder2}),10);
-        p3.setOptionalEquipments(Arrays.asList(new Long[]{1L,2L}));
-        ProductionProcesses p4 = new ProductionProcesses(4,"卧式分切机",Arrays.asList(new ProduceOrder[]{produceOrder2}),12);
-        p4.setOptionalEquipments(Arrays.asList(new Long[]{3L,4L,5L}));
-        ProductionProcesses p5 = new ProductionProcesses(5,"厚纵剪",Arrays.asList(new ProduceOrder[]{produceOrder2}),23);
-        p5.setOptionalEquipments(Arrays.asList(new Long[]{6L,7L}));
+        ProductionProcesses p3 = new ProductionProcesses("3","均热炉",Arrays.asList(new ProduceOrder[]{produceOrder2}),10);
+        p3.setOptionalEquipments(Arrays.asList(new String[]{"1","2"}));
+        ProductionProcesses p4 = new ProductionProcesses("4","卧式分切机",Arrays.asList(new ProduceOrder[]{produceOrder2}),12);
+        p4.setOptionalEquipments(Arrays.asList(new String[]{"3","4","5"}));
+        ProductionProcesses p5 = new ProductionProcesses("5","厚纵剪",Arrays.asList(new ProduceOrder[]{produceOrder2}),23);
+        p5.setOptionalEquipments(Arrays.asList(new String[]{"6","7"}));
+//        p3.setNextProcessesIds(Arrays.asList(new String[]{p4.getId()}));p4.setPreviousProcessesIds(Arrays.asList(new String[]{p3.getId()}));
+//        p4.setNextProcessesIds(Arrays.asList(new String[]{p5.getId()}));p5.setPreviousProcessesIds(Arrays.asList(new String[]{p4.getId()}));
+//        p5.setNextProcessesIds(Arrays.asList(new String[]{p6.getId()}));p6.setPreviousProcessesIds(Arrays.asList(new String[]{p2.getId(),p5.getId()}));
         p3.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p4}));p4.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p3}));
         p4.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p5}));p5.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p4}));
         p5.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p6}));p6.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p2,p5}));
@@ -106,4 +158,7 @@ public class ApsBalancingApplication {
         return unsolvedCloudBalance;
     }
 
+    private void testOne(Map<String,Integer> map){
+        map.put("1",map.get("1")+2);
+    }
 }

+ 15 - 1
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/controller/ApsSchedulingController.java

@@ -1,10 +1,12 @@
 package com.rongwei.rwapsserver.aps.controller;
 
+import com.rongwei.rwapsserver.aps.domain.EquipmentRunTime;
 import com.rongwei.rwapsserver.aps.service.ProductionScheduleService;
 import com.rongwei.rwapsserver.aps.vo.ProductionScheduleRetVo;
 import com.rongwei.rwapsserver.aps.vo.ProductionScheduleVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
@@ -25,9 +27,21 @@ public class ApsSchedulingController {
      * @param productionScheduleVo
      * @return
      */
-    @RequestMapping("/productionSchedule")
+    @PostMapping("/productionSchedule")
     public ProductionScheduleRetVo productionSchedule(@RequestBody ProductionScheduleVo productionScheduleVo) throws Exception{
         return productionScheduleService.productionSchedule(productionScheduleVo);
     }
 
+    /**
+     * test
+     * @param equipmentRunTime
+     * @return
+     */
+    @PostMapping("/test")
+    public ProductionScheduleRetVo test(@RequestBody EquipmentRunTime equipmentRunTime) throws Exception{
+        System.out.println(equipmentRunTime.getStartRunTime());
+        System.out.println(equipmentRunTime.getEndRunTime());
+        return new ProductionScheduleRetVo();
+    }
+
 }

+ 22 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/AluminumCoil.java

@@ -0,0 +1,22 @@
+package com.rongwei.rwapsserver.aps.domain;
+
+import lombok.Data;
+import java.math.BigDecimal;
+
+@Data
+public class AluminumCoil {
+
+    // 业务作业ID
+    private String bsId;
+    // 单卷宽
+    private BigDecimal volumeWidth;
+    // 单卷厚度
+    private BigDecimal volumeThickness;
+    // 单卷重
+    private BigDecimal volumeWeight;
+    // 合金
+    private String volumeMetal;
+    // 合金状态
+    private String volumeMetalstate;
+
+}

+ 4 - 4
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ApsAbstractPersistable.java

@@ -4,21 +4,21 @@ import org.optaplanner.core.api.domain.lookup.PlanningId;
 
 public abstract class ApsAbstractPersistable {
 
-    protected Long id;
+    protected String id;
 
     protected ApsAbstractPersistable() {
     }
 
-    protected ApsAbstractPersistable(long id) {
+    protected ApsAbstractPersistable(String id) {
         this.id = id;
     }
 
     @PlanningId
-    public long getId() {
+    public String getId() {
         return id;
     }
 
-    public void setId(long id) {
+    public void setId(String id) {
         this.id = id;
     }
 

+ 23 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ApsOverallConfig.java

@@ -0,0 +1,23 @@
+package com.rongwei.rwapsserver.aps.domain;
+
+import lombok.Data;
+
+import java.util.Map;
+
+/**
+ * APS全局配置类
+ */
+@Data
+public class ApsOverallConfig {
+
+    // 排程计划指定开始时间
+    private Long startTimeLong;
+
+    /**
+     * 工序之间流转时间全局配置(暂时不考虑跨工厂)
+     * 车间内流转时间(分钟):WORKSHOP_IN
+     * 跨车间流转时间(分钟):WORKSHOP_CROSS
+     */
+    private Map<String,Integer> roamTime;
+
+}

+ 10 - 3
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ApsSolution.java

@@ -29,7 +29,9 @@ public class ApsSolution extends ApsAbstractPersistable{
     @Getter(value = AccessLevel.NONE)
     private HardSoftScore score;
 
-    public ApsSolution(long id, List<ProductionProcesses> processesList, List<Equipment> equipmentList) {
+    public LocalDateTime startTime;
+
+    public ApsSolution(String id, List<ProductionProcesses> processesList, List<Equipment> equipmentList) {
         super(id);
         this.processesList = processesList;
         this.equipmentList = equipmentList;
@@ -53,12 +55,17 @@ public class ApsSolution extends ApsAbstractPersistable{
         for (ProductionProcesses productionProcesses : processesList) {
             produceTimeTotal = produceTimeTotal + productionProcesses.getProduceTime();
         }
-        LocalDateTime now = LocalDateTime.now();
-        return ValueRangeFactory.createLocalDateTimeValueRange(now,now.plusMinutes(produceTimeTotal),1, ChronoUnit.MINUTES);
+        LocalDateTime fromTime = LocalDateTime.now();
+        if(startTime != null){
+            fromTime = startTime;
+        }
+        System.out.println("startTime:"+startTime);
+        return ValueRangeFactory.createLocalDateTimeValueRange(fromTime,fromTime.plusMinutes(produceTimeTotal),1, ChronoUnit.MINUTES);
     }
 
     @PlanningScore
     public HardSoftScore getScore() {
         return score;
     }
+
 }

+ 17 - 1
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/Equipment.java

@@ -5,6 +5,7 @@ import lombok.EqualsAndHashCode;
 import lombok.NoArgsConstructor;
 
 import java.math.BigDecimal;
+import java.util.List;
 
 /**
  * 设备
@@ -54,7 +55,22 @@ public class Equipment extends ApsAbstractPersistable{
      */
     private Integer unitProductTime;
 
-    public Equipment(long id, String equipmentType) {
+    /**
+     * 设备运行时间段
+     */
+    private List<EquipmentRunTime> equipmentRunTimes;
+
+    /**
+     * 设备参数实体类
+     */
+    private EquipmentParameter equipmentParameter;
+
+    /**
+     * 设备上排产的工序
+     */
+    private List<ProductionProcesses> processesList;
+
+    public Equipment(String id, String equipmentType) {
         super(id);
         this.equipmentType = equipmentType;
     }

+ 16 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/EquipmentParameter.java

@@ -0,0 +1,16 @@
+package com.rongwei.rwapsserver.aps.domain;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * 设备参数实体类
+ */
+@Data
+public class EquipmentParameter {
+
+    // 设备宽度
+    private BigDecimal equipmentWidth;
+
+}

+ 26 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/EquipmentRunTime.java

@@ -0,0 +1,26 @@
+package com.rongwei.rwapsserver.aps.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+/**
+ * 设备运行中时间,表示排程锁定后设备运行时间段
+ */
+@Data
+public class EquipmentRunTime {
+
+    /**
+     * 设备运行开始时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime startRunTime;
+
+    /**
+     * 设备运行结束时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime endRunTime;
+
+}

+ 2 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ProduceOrder.java

@@ -1,5 +1,6 @@
 package com.rongwei.rwapsserver.aps.domain;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.util.Date;
@@ -30,6 +31,7 @@ public class ProduceOrder {
     /**
      * 交货日期
      */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date deliveryDate;
 
     /**

+ 155 - 21
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/domain/ProductionProcesses.java

@@ -2,7 +2,9 @@ package com.rongwei.rwapsserver.aps.domain;
 
 import org.optaplanner.core.api.domain.entity.PlanningEntity;
 import org.optaplanner.core.api.domain.variable.PlanningVariable;
+import org.springframework.beans.BeanUtils;
 
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
@@ -17,7 +19,7 @@ public class ProductionProcesses extends ApsAbstractPersistable{
 
     }
 
-    public ProductionProcesses(long id, String equipmentType, List<ProduceOrder> produceOrder, Integer produceTime) {
+    public ProductionProcesses(String id, String equipmentType, List<ProduceOrder> produceOrder, Integer produceTime) {
         super(id);
         this.equipmentType = equipmentType;
         this.produceOrder = produceOrder;
@@ -37,18 +39,23 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     /**
      * 可选设备ID
      */
-    private List<Long> optionalEquipments;
+    private List<String> optionalEquipments;
 
     /**
      * 工步生产时的设备
      */
-    private Long equipmentId;
+    private String equipmentId;
 
     /**
      * 工步生产时的设备
      */
     private Equipment equipment;
 
+    /**
+     * 工序类型
+     */
+    private String processType;
+
     /**
      * 所属的生产订单
      */
@@ -69,6 +76,31 @@ public class ProductionProcesses extends ApsAbstractPersistable{
      */
     private Integer circulationTime;
 
+    /**
+     * 加工数量
+     */
+    private BigDecimal produceNum;
+
+    /**
+     * 单批次生产时间
+     */
+    private Integer unitProduceTime;
+
+    /**
+     * 生产批次数量
+     */
+    private Integer producePcNum;
+
+    /**
+     * 当前工序一批次生产的是否拆分为下一工序批次数量
+     */
+    private Integer unitBatchNum;
+
+    /**
+     * 连续生产标识
+     */
+    private String seriesProduceMark;
+
     /**
      * 下一步工序最小等待生产时间
      */
@@ -84,6 +116,11 @@ public class ProductionProcesses extends ApsAbstractPersistable{
      */
     private Integer produceTime;
 
+    /**
+     * 所有下道工序汇总时间
+     */
+    private Integer nextAllProduceTime;
+
     /**
      * 开始时间
      */
@@ -101,7 +138,7 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     /**
      * 上一个工步ID
      */
-    private List<Long> previousProcessesIds;
+    private List<String> previousProcessesIds;
 
     /**
      * 下一个工步
@@ -110,7 +147,22 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     /**
      * 下一个工步ID
      */
-    private List<Long> nextProcessesIds;
+    private List<String> nextProcessesIds;
+
+    /**
+     * APS排程全局配置
+     */
+    private ApsOverallConfig apsOverallConfig;
+
+    /**
+     * 加工铝卷的集合
+     */
+    private List<AluminumCoil> aluminumCoils;
+
+    /**
+     * 是否锁定,锁定的工序开始时间、结束时间需要初始化,参与排程但开始时间、结束时间、以及设备占用不会变化
+     */
+    private Boolean ifLock;
 
     public String getEquipmentType() {
         return equipmentType;
@@ -126,17 +178,33 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     }
 
     public void setEquipment(Equipment equipment) {
-        this.equipment = equipment;
-        if(equipment != null){
-            this.equipmentId = equipment.getId();
+        if(this.ifLock == null || !this.ifLock){
+            this.equipment = equipment;
+            if(equipment != null){
+                this.equipmentId = equipment.getId();
+            }
+        }
+        if(this.equipment != null){
+            // 设置设备的工序占用
+            if(this.equipment.getProcessesList() == null){
+                this.equipment.setProcessesList(new ArrayList<>());
+            }
+            ProductionProcesses pp = new ProductionProcesses();
+            pp.setId(this.getId());
+            pp.setEquipmentId(this.getEquipmentId());
+//            pp.setEquipment(this.getEquipment());
+            pp.setProduceTime(this.getProduceTime());
+            pp.setStartTime(this.getStartTime());
+            pp.setEndTime(this.getEndTime());
+            this.equipment.getProcessesList().add(pp);
         }
     }
 
-    public Long getEquipmentId() {
+    public String getEquipmentId() {
         return equipmentId;
     }
 
-    public void setEquipmentId(Long equipmentId) {
+    public void setEquipmentId(String equipmentId) {
         this.equipmentId = equipmentId;
     }
 
@@ -154,9 +222,11 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     }
 
     public void setStartTime(LocalDateTime startTime) {
-        this.startTime = startTime;
-        if(startTime != null){
-            this.endTime = startTime.plusMinutes(produceTime);
+        if(this.ifLock == null || !this.ifLock){
+            this.startTime = startTime;
+            if(startTime != null){
+                this.endTime = startTime.plusMinutes(produceTime);
+            }
         }
     }
 
@@ -175,7 +245,7 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     public void setPreviousProcesses(List<ProductionProcesses> previousProcesses) {
         this.previousProcesses = previousProcesses;
         if(previousProcesses != null){
-            List<Long> ids = new ArrayList<>();
+            List<String> ids = new ArrayList<>();
             for (ProductionProcesses previousProcess : previousProcesses) {
                 ids.add(previousProcess.getId());
             }
@@ -190,7 +260,7 @@ public class ProductionProcesses extends ApsAbstractPersistable{
     public void setNextProcesses(List<ProductionProcesses> nextProcesses) {
         this.nextProcesses = nextProcesses;
         if(nextProcesses != null){
-            List<Long> ids = new ArrayList<>();
+            List<String> ids = new ArrayList<>();
             for (ProductionProcesses nextProcesse : nextProcesses) {
                 ids.add(nextProcesse.getId());
             }
@@ -246,19 +316,19 @@ public class ProductionProcesses extends ApsAbstractPersistable{
         this.mergeProcessMark = mergeProcessMark;
     }
 
-    public List<Long> getPreviousProcessesIds() {
+    public List<String> getPreviousProcessesIds() {
         return previousProcessesIds;
     }
 
-    public void setPreviousProcessesIds(List<Long> previousProcessesIds) {
+    public void setPreviousProcessesIds(List<String> previousProcessesIds) {
         this.previousProcessesIds = previousProcessesIds;
     }
 
-    public List<Long> getNextProcessesIds() {
+    public List<String> getNextProcessesIds() {
         return nextProcessesIds;
     }
 
-    public void setNextProcessesIds(List<Long> nextProcessesIds) {
+    public void setNextProcessesIds(List<String> nextProcessesIds) {
         this.nextProcessesIds = nextProcessesIds;
     }
 
@@ -270,11 +340,75 @@ public class ProductionProcesses extends ApsAbstractPersistable{
         this.bsProcessesId = bsProcessesId;
     }
 
-    public List<Long> getOptionalEquipments() {
+    public List<String> getOptionalEquipments() {
         return optionalEquipments;
     }
 
-    public void setOptionalEquipments(List<Long> optionalEquipments) {
+    public void setOptionalEquipments(List<String> optionalEquipments) {
         this.optionalEquipments = optionalEquipments;
     }
+
+    public BigDecimal getProduceNum() {
+        return produceNum;
+    }
+
+    public void setProduceNum(BigDecimal produceNum) {
+        this.produceNum = produceNum;
+    }
+
+    public Integer getUnitProduceTime() {
+        return unitProduceTime;
+    }
+
+    public void setUnitProduceTime(Integer unitProduceTime) {
+        this.unitProduceTime = unitProduceTime;
+    }
+
+    public String getSeriesProduceMark() {
+        return seriesProduceMark;
+    }
+
+    public void setSeriesProduceMark(String seriesProduceMark) {
+        this.seriesProduceMark = seriesProduceMark;
+    }
+
+    public ApsOverallConfig getApsOverallConfig() {
+        return apsOverallConfig;
+    }
+
+    public void setApsOverallConfig(ApsOverallConfig apsOverallConfig) {
+        this.apsOverallConfig = apsOverallConfig;
+    }
+
+    public Integer getProducePcNum() {
+        return producePcNum;
+    }
+
+    public void setProducePcNum(Integer producePcNum) {
+        this.producePcNum = producePcNum;
+    }
+
+    public Integer getNextAllProduceTime() {
+        return nextAllProduceTime;
+    }
+
+    public void setNextAllProduceTime(Integer nextAllProduceTime) {
+        this.nextAllProduceTime = nextAllProduceTime;
+    }
+
+    public List<AluminumCoil> getAluminumCoils() {
+        return aluminumCoils;
+    }
+
+    public void setAluminumCoils(List<AluminumCoil> aluminumCoils) {
+        this.aluminumCoils = aluminumCoils;
+    }
+
+    public String getProcessType() {
+        return processType;
+    }
+
+    public void setProcessType(String processType) {
+        this.processType = processType;
+    }
 }

+ 356 - 12
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/score/ApsConstraintProvider.java

@@ -2,17 +2,16 @@ package com.rongwei.rwapsserver.aps.score;
 
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.StrUtil;
+import com.rongwei.rwapsserver.aps.domain.Equipment;
+import com.rongwei.rwapsserver.aps.domain.EquipmentRunTime;
 import com.rongwei.rwapsserver.aps.domain.ProduceOrder;
 import com.rongwei.rwapsserver.aps.domain.ProductionProcesses;
+import com.rongwei.rwapsserver.aps.util.ApsConstants;
 import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
-import org.optaplanner.core.api.score.stream.Constraint;
-import org.optaplanner.core.api.score.stream.ConstraintFactory;
-import org.optaplanner.core.api.score.stream.ConstraintProvider;
-import org.optaplanner.core.api.score.stream.Joiners;
-
+import org.optaplanner.core.api.score.stream.*;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
-import java.util.Date;
+import java.util.*;
 
 import static org.optaplanner.core.api.score.stream.ConstraintCollectors.count;
 
@@ -25,16 +24,20 @@ public class ApsConstraintProvider implements ConstraintProvider {
         return new Constraint[]{
                 eqTypeSame(constraintFactory),
                 noPreGbAfterNow(constraintFactory),
-                hasPreGbAfterNow(constraintFactory),
+//                hasPreGbAfterNow(constraintFactory),
+                hasOnePreGbAfterNow(constraintFactory),
                 eqTimeCross(constraintFactory),
                 deliveryDate(constraintFactory),
-                balancedEqUse(constraintFactory),
+                seriesProduce(constraintFactory),
+                lzTimeLessMaxWait(constraintFactory),
+                equipmentRunTime(constraintFactory),
+//                balancedEqUse(constraintFactory),
 //                mergeProcess(constraintFactory)
         };
     }
 
     /**
-     * 硬约束:工步所需的设备类型和工步所用的设备类型必须一致
+     * 硬约束:工步所需的设备必须在工序的可选设备之内
      * @param constraintFactory
      * @return
      */
@@ -55,7 +58,11 @@ public class ApsConstraintProvider implements ConstraintProvider {
     private Constraint noPreGbAfterNow(ConstraintFactory constraintFactory) {
         return constraintFactory.forEach(ProductionProcesses.class)
                 .filter(productionProcesses -> {
-                    return productionProcesses.getPreviousProcesses() == null && productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli()< DateUtil.date().getTime();
+                    if(productionProcesses.getApsOverallConfig().getStartTimeLong() != null && productionProcesses.getApsOverallConfig().getStartTimeLong()>0){
+                        return productionProcesses.getPreviousProcesses() == null && productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli() < productionProcesses.getApsOverallConfig().getStartTimeLong();
+                    }else{
+                        return productionProcesses.getPreviousProcesses() == null && productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli() < DateUtil.date().getTime();
+                    }
                 })
                 .penalize(HardSoftScore.ONE_HARD,(productionProcesses) -> 80)
                 .asConstraint("noPreGbAfterNow");
@@ -73,12 +80,39 @@ public class ApsConstraintProvider implements ConstraintProvider {
                     // 获取上一步最晚结束时间
                     LocalDateTime preLastMinTime = null;
                     LocalDateTime preLastMaxTime = null;
+                    // 最大单批次生产时间
+                    Integer maxUnitProduceTime = 0;
                     if(productionProcesses.getPreviousProcesses() != null){
                         for (ProductionProcesses previousProcess : productionProcesses.getPreviousProcesses()) {
+                            // 流转时间
+                            Integer lzTimes = 0;
+                            if(previousProcess.getEquipment().getWorkshop() != null && previousProcess.getEquipment().getWorkshop().equals(productionProcesses.getEquipment().getWorkshop())){
+                                lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_IN");
+                            }else{
+                                lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_CROSS");
+                            }
+                            // 单批次生产最大时间
+                            if(previousProcess.getUnitProduceTime() != null && maxUnitProduceTime<previousProcess.getUnitProduceTime()){
+                                maxUnitProduceTime = previousProcess.getUnitProduceTime();
+                            }
+
+                            // 计算最小、最大等待时间
                             LocalDateTime thisMinEndTime = previousProcess.getEndTime();
                             LocalDateTime thisMaxEndTime = null;
                             if(previousProcess.getMinWaitTime() != null){
-                                thisMinEndTime = thisMinEndTime.plusMinutes(previousProcess.getMinWaitTime());
+                                /**
+                                 * 根据前后工序单批次生产时间比较来排序
+                                 * 1、前道工序单批次生产时间小于后道工序单批次生产时间,则最小开始时间等于单批次生产时间
+                                 * 2、前道工序单批次生产时间小于后道工序单批次生产时间,则从最后倒排时间
+                                 */
+
+
+                                // 最小等待时间对比流转时间
+                                if(lzTimes>previousProcess.getMinWaitTime()){
+                                    thisMinEndTime = thisMinEndTime.plusMinutes(lzTimes);
+                                }else{
+                                    thisMinEndTime = thisMinEndTime.plusMinutes(previousProcess.getMinWaitTime());
+                                }
                             }
                             if(previousProcess.getMaxWaitTime() != null){
                                 thisMaxEndTime = thisMaxEndTime.plusMinutes(previousProcess.getMaxWaitTime());
@@ -102,6 +136,7 @@ public class ApsConstraintProvider implements ConstraintProvider {
                                 }
                             }
                         }
+
                         boolean bln = productionProcesses.getStartTime().atZone(zoneId).toInstant().toEpochMilli() < preLastMinTime.atZone(zoneId).toInstant().toEpochMilli();
                         if(preLastMaxTime != null){
                             System.out.println("preLastMaxTime != null");
@@ -116,6 +151,272 @@ public class ApsConstraintProvider implements ConstraintProvider {
                 .asConstraint("hasPreGbAfterNow");
     }
 
+    /**
+     * 前提条件:前道工序只有一个
+     * 硬约束:有上一步工步的开始时间要大于上一步工步最早结束时间,小于最晚结束时间
+     * 根据上一步的最小等待时间和最大等待时间来判断
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint hasOnePreGbAfterNow(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    boolean bln = false;
+                    // 最大单批次生产时间
+                    Integer maxUnitProduceTime = 0;
+                    // 最大流转时间
+                    Integer lzMaxTimes = 0;
+                    // 前道工序最小首批加工最大等待时间
+                    LocalDateTime lastFirstMaxWaitTime = null;
+                    if(productionProcesses.getPreviousProcesses() != null && productionProcesses.getPreviousProcesses().size()>0){
+                        // 瓶颈工序,简化为等待前道工序全部完成才加工后道工序
+                        if((productionProcesses.getBottleneck() != null && productionProcesses.getBottleneck())
+                                || (productionProcesses.getPreviousProcesses().get(0).getBottleneck() != null && productionProcesses.getPreviousProcesses().get(0).getBottleneck())){
+                            LocalDateTime lastMaxStartTime = null;
+                            LocalDateTime lastMinMaxWaitTime = null;
+                            for (ProductionProcesses previousProcess : productionProcesses.getPreviousProcesses()) {
+                                // 流转时间
+                                Integer lzTimes = 0;
+                                if(previousProcess.getEquipment().getWorkshop() != null && previousProcess.getEquipment().getWorkshop().equals(productionProcesses.getEquipment().getWorkshop())){
+                                    lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_IN");
+                                }else{
+                                    lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_CROSS");
+                                }
+                                if(lzMaxTimes<lzTimes){
+                                    lzMaxTimes = lzTimes;
+                                }
+                                // 最小等待时间
+                                Integer minWaitTime = previousProcess.getMinWaitTime() == null ? 0 : previousProcess.getMinWaitTime();
+                                if(lzTimes>minWaitTime){
+                                    minWaitTime = lzTimes;
+                                }
+                                if(lastMaxStartTime == null){
+                                    lastMaxStartTime = previousProcess.getEndTime().plusMinutes(minWaitTime);
+                                }else {
+                                    if(lastMaxStartTime.compareTo(previousProcess.getEndTime().plusMinutes(minWaitTime))<0){
+                                        lastMaxStartTime = previousProcess.getEndTime().plusMinutes(minWaitTime);
+                                    }
+                                }
+                                // 如有最大等待时间,计算首批次加工等待时间不能超过最大等待时间
+                                if(previousProcess.getMaxWaitTime() != null){
+                                    if(lastMinMaxWaitTime == null){
+                                        lastMinMaxWaitTime = previousProcess.getEndTime().plusMinutes(previousProcess.getMaxWaitTime());
+                                    }else{
+                                        if(previousProcess.getEndTime().plusMinutes(previousProcess.getMaxWaitTime()).compareTo(lastFirstMaxWaitTime)<0){
+                                            lastMinMaxWaitTime = previousProcess.getEndTime().plusMinutes(previousProcess.getMaxWaitTime());
+                                        }
+                                    }
+                                }
+                            }
+                            // 开始时间不小于最大的最小等待时间、不大于最小的最大等待时间
+                            if(productionProcesses.getStartTime().compareTo(lastMaxStartTime)<0){
+                                bln = true;
+                            }else if(productionProcesses.getStartTime().compareTo(lastMinMaxWaitTime)>0){
+                                bln = true;
+                            }
+                        }
+                        // 非瓶颈工序,非瓶颈工序,默认前道工序只有一个
+                        else{
+                            // 此种情况简化为前道工序只有一个
+                            ProductionProcesses preProcess = productionProcesses.getPreviousProcesses().get(0);
+                            maxUnitProduceTime = preProcess.getUnitProduceTime();
+                            // 前道工序的所有下道工序
+                            List<ProductionProcesses> nextProcesses = preProcess.getNextProcesses();
+                            Map<String,Integer> map = new HashMap<>();
+                            for (ProductionProcesses nextProcess : nextProcesses) {
+                                map.put(nextProcess.getId(),0);
+                                allNextProduceTime(nextProcess,map,nextProcess.getId());
+                                nextProcess.setNextAllProduceTime(map.get(nextProcess.getId()));
+                            }
+                            nextProcesses.sort((o1, o2) -> o1.getNextAllProduceTime().compareTo(o2.getNextAllProduceTime()));
+                            // 前道工序
+                            LocalDateTime startTime = preProcess.getStartTime();
+                            LocalDateTime endTime = preProcess.getStartTime();
+                            for (ProductionProcesses nextProcess : nextProcesses) {
+                                int i = preProcess.getUnitProduceTime() * nextProcess.getProducePcNum();
+                                if(!nextProcess.getId().equals(productionProcesses.getId())){
+                                    startTime = startTime.plusMinutes(i);
+                                }else{
+                                    endTime = startTime.plusMinutes(i);
+                                }
+                            }
+
+                            // 流转时间
+                            Integer lzTimes = 0;
+                            if(preProcess.getEquipment().getWorkshop() != null && preProcess.getEquipment().getWorkshop().equals(productionProcesses.getEquipment().getWorkshop())){
+                                lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_IN");
+                            }else{
+                                lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_CROSS");
+                            }
+                            // 最小等待时间对比流转时间
+                            if(preProcess.getMinWaitTime() != null && lzTimes<preProcess.getMinWaitTime()){
+                                lzTimes = preProcess.getMinWaitTime();
+                            }
+
+                            /**
+                             * 后道工序批次生产时间大于等于前道工序批次生产时间
+                             * 从第一批次开始往后排程
+                             */
+                            System.out.println("log-开始时间:"+productionProcesses.getStartTime());
+                            System.out.println("log-正确的开始时间:"+startTime.plusMinutes(preProcess.getUnitProduceTime()).plusMinutes(lzTimes));
+                            if(preProcess.getMaxWaitTime() != null){
+                                lastFirstMaxWaitTime = startTime.plusMinutes(preProcess.getMaxWaitTime());
+                            }
+                            if(productionProcesses.getUnitProduceTime()>=maxUnitProduceTime){
+                                if(productionProcesses.getStartTime().compareTo(startTime.plusMinutes(preProcess.getUnitProduceTime()).plusMinutes(lzTimes))<0){
+                                    bln = true;
+                                }else{
+                                    if(lastFirstMaxWaitTime != null){
+                                        if(productionProcesses.getStartTime().compareTo(lastFirstMaxWaitTime)>0){
+                                            bln = true;
+                                        }
+                                    }
+                                }
+                            }
+                            /**
+                             * 后道工序批次生产时间小于前道工序批次生产时间
+                             * 从最后批次开始往前排程
+                             */
+                            else{
+                                // 开始时间:最后一批次结束时间加流转时间,再往前倒排批次数减一乘以单批次生产时间
+                                LocalDateTime startTime1 = endTime.plusMinutes(lzTimes).minusMinutes(productionProcesses.getUnitProduceTime() * (productionProcesses.getProducePcNum() - 1));
+                                // 结束时间重新赋值
+                                productionProcesses.setEndTime(endTime.plusMinutes(lzTimes).plusMinutes(productionProcesses.getUnitProduceTime()));
+                                if(productionProcesses.getStartTime().compareTo(startTime1)<0){
+                                    bln = true;
+                                }
+                                // 存在最大等待时间时
+                                if(preProcess.getMaxWaitTime() != null){
+                                    // 超过最大等待时间
+                                    if(productionProcesses.getStartTime().compareTo(startTime1.plusMinutes(preProcess.getMaxWaitTime()))>0){
+                                        bln = true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    return bln;
+                })
+                .penalize(HardSoftScore.ONE_HARD,(productionProcesses) -> 80)
+                .asConstraint("hasOnePreGbAfterNow");
+    }
+
+    /**
+     * 汇总所有下道工序的加工时间
+     * @param nextProcess
+     * @param map
+     */
+    private void allNextProduceTime(ProductionProcesses nextProcess,Map<String,Integer> map,String proId){
+        map.put(proId,map.get(proId)+nextProcess.getProduceTime());
+        if(nextProcess.getNextProcesses() != null && nextProcess.getNextProcesses().size()>0){
+            for (ProductionProcesses process : nextProcess.getNextProcesses()) {
+                allNextProduceTime(process,map,proId);
+            }
+        }
+    }
+
+    /**
+     * 设备运行时间段,不可再排产
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint equipmentRunTime(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    boolean bol = false;
+                    // 瓶颈工序合并生产规则,总宽度和总承重不超过最大值即可
+                    if(productionProcesses.getBottleneck() != null && productionProcesses.getBottleneck()){
+
+                    }
+                    // 非瓶颈工序设备运行时间段,不可再排产
+                    else{
+                        if(productionProcesses.getEquipment() != null && productionProcesses.getEquipment().getEquipmentRunTimes() != null &&
+                                productionProcesses.getEquipment().getEquipmentRunTimes().size()>0){
+                            for (EquipmentRunTime equipmentRunTime : productionProcesses.getEquipment().getEquipmentRunTimes()) {
+                                if(equipmentRunTime.getStartRunTime() != null && equipmentRunTime.getEndRunTime() != null){
+                                    LocalDateTime startTime = equipmentRunTime.getStartRunTime();
+                                    LocalDateTime endRunTime = equipmentRunTime.getEndRunTime();
+                                    if((productionProcesses.getStartTime().compareTo(startTime)>=0 && productionProcesses.getStartTime().compareTo(endRunTime)<=0)
+                                            || (productionProcesses.getEndTime().compareTo(startTime)>=0 && productionProcesses.getEndTime().compareTo(endRunTime)<=0)){
+                                        bol = true;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                    return bol;
+                })
+                .penalize(HardSoftScore.ONE_HARD,(proc1)->80)
+                .asConstraint("equipmentRunTime");
+    }
+
+    /**
+     * 退火炉等合并工序生产约束
+     * @param constraintFactory
+     * @return
+     */
+    /*private Constraint mergeProcess(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    boolean bol = false;
+                    // 退火炉特殊合并生产规则
+                    if(ApsConstants.EQUIPMENTTYPE_TUIHUOLU.equals(productionProcesses.getEquipmentType())){
+                        if(productionProcesses.getEquipment() != null && productionProcesses.getEquipment().getProcessesList() != null){
+                            // 当前工序所选设备生产中的或待生产的工序集合
+                            List<ProductionProcesses> processesList = productionProcesses.getEquipment().getProcessesList();
+                            for (ProductionProcesses processes : processesList) {
+                                // 开始时间和结束时间要么和已排产的工序一样,要么在这个范围之外
+                                if((productionProcesses.getStartTime().compareTo(processes.getStartTime())>0 && productionProcesses.getStartTime().compareTo(processes.getEndTime())<0)
+                                        || (productionProcesses.getEndTime().compareTo(processes.getStartTime())>0 && productionProcesses.getEndTime().compareTo(processes.getEndTime())<0)){
+                                    bol = true;
+                                    break;
+                                }
+                                // 开始时间结束时间在已排产的某个时间段内
+                                if(productionProcesses.getStartTime().compareTo(processes.getStartTime())>=0 && productionProcesses.getEndTime().compareTo(processes.getEndTime())<=0){
+
+                                }
+
+                            }
+                        }
+                    }
+                    return bol;
+                })
+                .penalize(HardSoftScore.ONE_HARD,(proc1)->80)
+                .asConstraint("mergeProcess");
+    }*/
+
+
+    /**
+     * 最大等待时间,不能小于流转时间
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint lzTimeLessMaxWait(ConstraintFactory constraintFactory) {
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .filter(productionProcesses -> {
+                    // 对比前后工序设备所属工厂、获取最大流转时间
+                    boolean bol = false;
+                    if(productionProcesses.getPreviousProcesses() != null && productionProcesses.getPreviousProcesses().size()>0){
+                        for (ProductionProcesses previousProcess : productionProcesses.getPreviousProcesses()) {
+                            Integer lzTimes = 0;
+                            if(previousProcess.getEquipment().getWorkshop() != null && previousProcess.getEquipment().getWorkshop().equals(productionProcesses.getEquipment().getWorkshop())){
+                                lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_IN");
+                            }else{
+                                lzTimes = productionProcesses.getApsOverallConfig().getRoamTime().get("WORKSHOP_CROSS");
+                            }
+                            if(previousProcess.getMaxWaitTime() != null && previousProcess.getMaxWaitTime()>0 && previousProcess.getMaxWaitTime()<lzTimes){
+                                bol = true;
+                                break;
+                            }
+                        }
+                    }
+                    return bol;
+                })
+                .penalize(HardSoftScore.ONE_HARD,(proc1)->90)
+                .asConstraint("lzTimeLessMaxWait");
+    }
+
     /**
      * 硬约束:同一个设备的不同的工步运行时间不能有交叉
      * @param constraintFactory
@@ -197,7 +498,7 @@ public class ApsConstraintProvider implements ConstraintProvider {
      * @param constraintFactory
      * @return
      */
-    private Constraint mergeProcess(ConstraintFactory constraintFactory){
+    /*private Constraint mergeProcess(ConstraintFactory constraintFactory){
         return constraintFactory.forEach(ProductionProcesses.class)
                 .join(ProductionProcesses.class,Joiners.equal(ProductionProcesses::getEquipmentId))
                 .filter((proc1,proc2)->{
@@ -214,5 +515,48 @@ public class ApsConstraintProvider implements ConstraintProvider {
                 })
                 .penalize(HardSoftScore.ONE_SOFT,(proc1,proc2)->50)
                 .asConstraint("mergeProcess");
+    }*/
+
+    /**
+     * 连续生产约束条件,同一设备的连续生产标识一样的工序、尽量排在一起
+     * @param constraintFactory
+     * @return
+     */
+    private Constraint seriesProduce(ConstraintFactory constraintFactory){
+        return constraintFactory.forEach(ProductionProcesses.class)
+                .groupBy(ProductionProcesses::getEquipmentId, ConstraintCollectors.toList())
+                .filter((equipmentId,processes) -> {
+                    if(processes != null && processes.size()>1){
+                        // 安装开始时间排序
+                        Collections.sort(processes, Comparator.comparing(ProductionProcesses::getStartTime));
+                        // 临时标记
+                        Boolean a = false;
+                        for(int i=0;i<processes.size()-1;i++){
+                            if(processes.get(i).getSeriesProduceMark() != null && processes.get(i+1).getSeriesProduceMark() != null
+                                    && !processes.get(i).getSeriesProduceMark().equals(processes.get(i+1).getSeriesProduceMark())){
+                                a = true;
+                            }
+                        }
+                        return a;
+                    }else{
+                        return false;
+                    }
+                })
+                .penalize(HardSoftScore.ONE_SOFT,(proc1,proc2)->{
+                    int b = 0;
+                    for(int i=0;i<proc2.size()-1;i++){
+                        if(proc2.get(i).getSeriesProduceMark() != null && proc2.get(i+1).getSeriesProduceMark() != null
+                                && !proc2.get(i).getSeriesProduceMark().equals(proc2.get(i+1).getSeriesProduceMark())){
+                            b++;
+                        }
+                    }
+                    if(b>0){
+//                        System.out.println("扣分:"+b);
+                        return b;
+                    }
+                    return 1;
+                })
+                .asConstraint("seriesProduce");
+
     }
 }

+ 84 - 56
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/service/impl/ProductionScheduleServiceImpl.java

@@ -1,41 +1,58 @@
 package com.rongwei.rwapsserver.aps.service.impl;
 
-import cn.hutool.core.date.DateUtil;
+import com.rongwei.rwapsserver.aps.domain.ApsOverallConfig;
 import com.rongwei.rwapsserver.aps.domain.ApsSolution;
-import com.rongwei.rwapsserver.aps.domain.Equipment;
-import com.rongwei.rwapsserver.aps.domain.ProduceOrder;
 import com.rongwei.rwapsserver.aps.domain.ProductionProcesses;
 import com.rongwei.rwapsserver.aps.score.ApsConstraintProvider;
 import com.rongwei.rwapsserver.aps.service.ProductionScheduleService;
+import com.rongwei.rwapsserver.aps.util.ApsException;
 import com.rongwei.rwapsserver.aps.vo.ProductionScheduleRetVo;
 import com.rongwei.rwapsserver.aps.vo.ProductionScheduleVo;
-import org.optaplanner.core.api.score.ScoreExplanation;
-import org.optaplanner.core.api.score.ScoreManager;
 import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
 import org.optaplanner.core.api.solver.SolutionManager;
 import org.optaplanner.core.api.solver.Solver;
 import org.optaplanner.core.api.solver.SolverFactory;
 import org.optaplanner.core.config.solver.SolverConfig;
 import org.springframework.stereotype.Service;
-
 import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.*;
 
 @Service
 public class ProductionScheduleServiceImpl implements ProductionScheduleService {
 
+    /**
+     * APS生产计划排程
+     * @param productionScheduleVo
+     * @return
+     */
     @Override
     public ProductionScheduleRetVo productionSchedule(ProductionScheduleVo productionScheduleVo) {
+        // 排程结果对象
         ProductionScheduleRetVo productionScheduleRetVo = new ProductionScheduleRetVo();
+        // 排程校验
+        if(productionScheduleVo.getRoamTime() == null || productionScheduleVo.getRoamTime().size() == 0){
+            throw new ApsException("缺少全局流转时间配置");
+        }
+        // 排程运行时长
+        Integer planSeconds = productionScheduleVo.getPlanSeconds();
+        if(planSeconds == null || planSeconds <= 0){
+            planSeconds = 15;
+        }
+
+        // optaplanner 求解器配置实例化
         SolverFactory<ApsSolution> solverFactory = SolverFactory.create(new SolverConfig()
                 .withSolutionClass(ApsSolution.class)
                 .withEntityClasses(ProductionProcesses.class)
                 .withConstraintProviderClass(ApsConstraintProvider.class)
-                .withTerminationSpentLimit(Duration.ofSeconds(5)));
+                .withTerminationSpentLimit(Duration.ofSeconds(planSeconds)));
         Solver<ApsSolution> solver = solverFactory.buildSolver();
-        ApsSolution solvedBalance = solver.solve(getTestApsSolution());
+        // optaplanner 求解器数据装配
+        ApsSolution apsSolution = getPreApsSolution(productionScheduleVo);
+//        ApsSolution apsSolutionData = solver.solve(apsSolution);
+        // optaplanner 求解器运行
+        ApsSolution solvedBalance = solver.solve(apsSolution);
         System.out.println("*****************************");
         System.out.println(solvedBalance);
         SolutionManager<ApsSolution, HardSoftScore> scoreManager = SolutionManager.create(solverFactory);
@@ -52,53 +69,64 @@ public class ProductionScheduleServiceImpl implements ProductionScheduleService
         return productionScheduleRetVo;
     }
 
-    private static ApsSolution getTestApsSolution(){
+    /**
+     * 业务数据模型转换前置处理
+     * @param productionScheduleVo
+     * @return
+     */
+    private static ApsSolution getPreApsSolution(ProductionScheduleVo productionScheduleVo){
         ApsSolution unsolvedCloudBalance = new ApsSolution();
+        // 排程全局配置
+        ApsOverallConfig apsOverallConfig = new ApsOverallConfig();
+        // 排程计划开始时间
+        Date startDate = productionScheduleVo.getApsPlanStartDate();
+        LocalDateTime startTime = startDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
+        apsOverallConfig.setStartTimeLong(startDate.getTime());
+        // 排程全局配置
+        apsOverallConfig.setRoamTime(productionScheduleVo.getRoamTime());
+        unsolvedCloudBalance.setStartTime(startTime);
+
+        // 根据前道、后道工序ID,转换为前道、后道工序对象
+        Map<String,ProductionProcesses> idMaps = new HashMap<>();
+        for (ProductionProcesses process : productionScheduleVo.getProcesses()) {
+            idMaps.put(process.getId(),process);
+        }
+        for (ProductionProcesses process : productionScheduleVo.getProcesses()) {
+            // 全局配置设置
+            process.setApsOverallConfig(apsOverallConfig);
+            // 前道工序
+            if(process.getPreviousProcessesIds() != null && process.getPreviousProcessesIds().size()>0){
+                List<ProductionProcesses> pres = new ArrayList<>();
+                for (String previousProcessesId : process.getPreviousProcessesIds()) {
+                    pres.add(idMaps.get(previousProcessesId));
+                }
+                if(pres != null && pres.size()>0){
+                    process.setPreviousProcesses(pres);
+                }
+            }
+            // 后道工序
+            if(process.getNextProcessesIds() != null && process.getNextProcessesIds().size()>0){
+                List<ProductionProcesses> nexts = new ArrayList<>();
+                for (String nextId : process.getNextProcessesIds()) {
+                    nexts.add(idMaps.get(nextId));
+                }
+                if(nexts != null && nexts.size()>0){
+                    process.setNextProcesses(nexts);
+                }
+            }
+            // 默认批次为1
+            if(process.getProducePcNum() == null){
+                process.setProducePcNum(1);
+            }
+            // 单位批次生产时间
+            if(process.getUnitProduceTime() == null){
+                process.setUnitProduceTime((int)(Math.ceil((double)process.getProduceTime()/process.getProducePcNum())));
+            }
+        }
         // 设备列表初始化
-        List<Equipment> equipmentList = new ArrayList<>();
-        Equipment eq1 = new Equipment(1,"均热炉");
-        Equipment eq2 = new Equipment(2,"均热炉");
-        Equipment eq3 = new Equipment(3,"卧式分切机");
-        Equipment eq4 = new Equipment(4,"卧式分切机");
-        Equipment eq5 = new Equipment(5,"卧式分切机");
-        Equipment eq6 = new Equipment(6,"厚纵剪");
-        Equipment eq7 = new Equipment(7,"厚纵剪");
-        Equipment eq8 = new Equipment(8,"氮气保护退火炉");
-        equipmentList.add(eq1);
-        equipmentList.add(eq2);
-        equipmentList.add(eq3);
-        equipmentList.add(eq4);
-        equipmentList.add(eq5);
-        equipmentList.add(eq6);
-        equipmentList.add(eq7);
-        equipmentList.add(eq8);
-        unsolvedCloudBalance.setEquipmentList(equipmentList);
-        // 工步初始化
-        List<ProductionProcesses> processes = new ArrayList<>();
-        // 订单初始化
-        ProduceOrder produceOrder1 = new ProduceOrder("order1","订单1", DateUtil.parseDateTime("2024-03-01 21:52:12"));
-        ProduceOrder produceOrder2 = new ProduceOrder("order2","订单2", DateUtil.parseDateTime("2024-03-01 19:20:12"));
-        ProductionProcesses p1 = new ProductionProcesses(1,"均热炉",Arrays.asList(new ProduceOrder[]{produceOrder1}),10);
-        p1.setOptionalEquipments(Arrays.asList(new Long[]{1L,2L}));
-        ProductionProcesses p2 = new ProductionProcesses(2,"卧式分切机",Arrays.asList(new ProduceOrder[]{produceOrder1}),12);
-        p2.setOptionalEquipments(Arrays.asList(new Long[]{3L,4L,5L}));
-        ProductionProcesses p6 = new ProductionProcesses(6,"氮气保护退火炉",Arrays.asList(new ProduceOrder[]{produceOrder1,produceOrder2}),72);
-        p6.setOptionalEquipments(Arrays.asList(new Long[]{8L}));
-        p6.setMergeProcessMark("tuihuolu");
-        p1.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p2}));p2.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p1}));
-        p2.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p6}));
-        processes.add(p1);processes.add(p2);
-        ProductionProcesses p3 = new ProductionProcesses(3,"均热炉",Arrays.asList(new ProduceOrder[]{produceOrder2}),10);
-        p3.setOptionalEquipments(Arrays.asList(new Long[]{1L,2L}));
-        ProductionProcesses p4 = new ProductionProcesses(4,"卧式分切机",Arrays.asList(new ProduceOrder[]{produceOrder2}),12);
-        p4.setOptionalEquipments(Arrays.asList(new Long[]{3L,4L,5L}));
-        ProductionProcesses p5 = new ProductionProcesses(5,"厚纵剪",Arrays.asList(new ProduceOrder[]{produceOrder2}),23);
-        p5.setOptionalEquipments(Arrays.asList(new Long[]{6L,7L}));
-        p3.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p4}));p4.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p3}));
-        p4.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p5}));p5.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p4}));
-        p5.setNextProcesses(Arrays.asList(new ProductionProcesses[]{p6}));p6.setPreviousProcesses(Arrays.asList(new ProductionProcesses[]{p2,p5}));
-        processes.add(p3);processes.add(p4);processes.add(p5);processes.add(p6);
-        unsolvedCloudBalance.setProcessesList(processes);
+        unsolvedCloudBalance.setEquipmentList(productionScheduleVo.getEquipmentList());
+        // 工序任务初始化
+        unsolvedCloudBalance.setProcessesList(productionScheduleVo.getProcesses());
         return unsolvedCloudBalance;
     }
 }

+ 7 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/util/ApsConstants.java

@@ -0,0 +1,7 @@
+package com.rongwei.rwapsserver.aps.util;
+
+public class ApsConstants {
+
+    public final static String EQUIPMENTTYPE_TUIHUOLU = "退火炉";
+
+}

+ 39 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/util/ApsException.java

@@ -0,0 +1,39 @@
+package com.rongwei.rwapsserver.aps.util;
+
+import lombok.Data;
+
+@Data
+public class ApsException extends RuntimeException{
+
+    /**
+     * 异常编码
+     */
+    private String code;
+
+    /**
+     * 附加数据
+     */
+    private Object data;
+
+    public ApsException(String errorMsg) {
+        super(errorMsg);
+        this.code= "500";
+    }
+
+    public ApsException(String code, String errorMsg) {
+        super(errorMsg);
+        this.code = code;
+    }
+
+    public ApsException(String code, String errorMsg, Throwable errorCourse) {
+        super(errorMsg,errorCourse);
+        this.code = code;
+    }
+
+    public ApsException(String code, String message, Object data) {
+        super(message);
+        this.code = code;
+        this.data = data;
+    }
+
+}

+ 15 - 33
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/vo/ProductionScheduleVo.java

@@ -1,13 +1,14 @@
 package com.rongwei.rwapsserver.aps.vo;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.rongwei.rwapsserver.aps.domain.Equipment;
 import com.rongwei.rwapsserver.aps.domain.ProductionProcesses;
 import lombok.Data;
-
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
-//@Data
+@Data
 public class ProductionScheduleVo {
 
     private String productionScheduleId;
@@ -15,8 +16,14 @@ public class ProductionScheduleVo {
     /**
      * APS 排程计划开始时间
      */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date apsPlanStartDate;
 
+    /**
+     * 指定运行秒数
+     */
+    private Integer planSeconds;
+
     /**
      * 生产工序集合
      */
@@ -27,36 +34,11 @@ public class ProductionScheduleVo {
      */
     private List<Equipment> equipmentList;
 
-    public String getProductionScheduleId() {
-        return productionScheduleId;
-    }
-
-    public void setProductionScheduleId(String productionScheduleId) {
-        System.out.println("productionScheduleId:"+productionScheduleId);
-        this.productionScheduleId = productionScheduleId;
-    }
-
-    public List<ProductionProcesses> getProcesses() {
-        return processes;
-    }
-
-    public void setProcesses(List<ProductionProcesses> processes) {
-        this.processes = processes;
-    }
-
-    public List<Equipment> getEquipmentList() {
-        return equipmentList;
-    }
-
-    public void setEquipmentList(List<Equipment> equipmentList) {
-        this.equipmentList = equipmentList;
-    }
-
-    public Date getApsPlanStartDate() {
-        return apsPlanStartDate;
-    }
+    /**
+     * 工序之间流转时间设置(暂时不考虑跨工厂)
+     * 车间内流转时间:WORKSHOP_IN
+     * 跨车间流转时间:WORKSHOP_CROSS
+     */
+    private Map<String,Integer> roamTime;
 
-    public void setApsPlanStartDate(Date apsPlanStartDate) {
-        this.apsPlanStartDate = apsPlanStartDate;
-    }
 }

+ 3 - 0
rw-aps-server/src/main/resources/bootstrap.yml

@@ -7,6 +7,9 @@ spring:
     multipart:
       max-file-size: 100MB
       max-request-size: 1000MB
+  jackson:
+    time-zone: GMT+8
+    date-format: yyyy-MM-dd HH:mm:ss
 server:
   port: 9358
 management: