Browse Source

退火调度优化

fangpy 3 months ago
parent
commit
d939cc4c9c

+ 4 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/taskassigning/service/DdApsTaService.java

@@ -3,8 +3,10 @@ package com.rongwei.rwapsserver.aps.taskassigning.service;
 import com.rongwei.rwapsserver.aps.domain.ApsFurnaceInstallationDo;
 import com.rongwei.rwapsserver.aps.domain.ApsSolution;
 import com.rongwei.rwapsserver.aps.domain.Equipment;
+import com.rongwei.rwapsserver.aps.taskassigning.tado.ApsSolutionTa;
 import com.rongwei.rwapsserver.aps.taskassigning.tado.EquipmentTa;
 import com.rongwei.rwapsserver.aps.taskassigning.tado.ProductionProcessesTa;
+import com.rongwei.rwapsserver.aps.taskassigning.vo.ProductionScheduleTaVo;
 import com.rongwei.rwapsserver.aps.vo.ProductionScheduleVo;
 
 import java.util.List;
@@ -20,4 +22,6 @@ public interface DdApsTaService {
 
     void equipmentRunTimeMerge(EquipmentTa equipment, List<ApsFurnaceInstallationDo> furnaceInstallations);
 
+    List<ProductionProcessesTa> thProcessMerge(ProductionScheduleTaVo productionScheduleVo, ApsSolutionTa apsSolution, List<ProductionProcessesTa> otherThproces);
+
 }

+ 2 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/taskassigning/service/ProductionScheduleTaService.java

@@ -7,4 +7,6 @@ public interface ProductionScheduleTaService {
 
     ProductionScheduleRetTaVo productionSchedule(ProductionScheduleTaVo productionScheduleVo) throws Exception;
 
+    public ProductionScheduleRetTaVo tuiHuoSchedule(ProductionScheduleTaVo productionScheduleVo) throws Exception;
+
 }

+ 262 - 3
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/taskassigning/service/impl/DdApsTaServiceImpl.java

@@ -2,16 +2,19 @@ package com.rongwei.rwapsserver.aps.taskassigning.service.impl;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
-import com.rongwei.rwapsserver.aps.domain.ApsFurnaceInstallationDo;
-import com.rongwei.rwapsserver.aps.domain.EquipmentRunTime;
-import com.rongwei.rwapsserver.aps.domain.ProductionProcesses;
+import com.rongwei.rwapsserver.aps.domain.*;
 import com.rongwei.rwapsserver.aps.taskassigning.service.DdApsTaService;
+import com.rongwei.rwapsserver.aps.taskassigning.tado.ApsSolutionTa;
 import com.rongwei.rwapsserver.aps.taskassigning.tado.EquipmentTa;
 import com.rongwei.rwapsserver.aps.taskassigning.tado.ProductionProcessesTa;
+import com.rongwei.rwapsserver.aps.taskassigning.vo.ProductionScheduleTaVo;
+import com.rongwei.rwapsserver.aps.util.ApsException;
+import com.rongwei.rwapsserver.aps.vo.ProductionScheduleVo;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.math.BigDecimal;
+import java.math.RoundingMode;
 import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -1070,4 +1073,260 @@ public class DdApsTaServiceImpl implements DdApsTaService {
         }
     }
 
+    /**
+     * 调度退火提前合并
+     * @param productionScheduleVo
+     * @param apsSolution
+     * @param otherThproces
+     * @return
+     */
+    public List<ProductionProcessesTa> thProcessMerge(ProductionScheduleTaVo productionScheduleVo, ApsSolutionTa apsSolution, List<ProductionProcessesTa> otherThproces){
+        List<ProductionProcessesTa> processes = apsSolution.getProcessesList();
+        // 设备集合
+        List<EquipmentTa> equipmentList = apsSolution.getEquipmentList();
+        List<ProductionProcessesTa> mergeprocesses = new ArrayList<>();
+        // 所有作业集合
+        Map<String,ProductionProcessesTa> allProMap = new HashMap<>();
+
+        // 其它作业集合
+        Map<String,List<ProductionProcessesTa>> notthproMap = new HashMap<>();
+        // 按订单分组
+        Map<String, List<ProductionProcessesTa>> orderProcess = processes.stream().collect(Collectors.groupingBy(ProductionProcessesTa::getOrderId));
+        orderProcess.forEach((orderId,orderpss)->{
+            if("6639310eb2204f77ac9d36a2f289039c".equals(orderId)){
+                int a = 1;
+            }
+            // 退火作业集合
+            Map<String,List<ProductionProcessesTa>> thproMap = new HashMap<>();
+            for (ProductionProcessesTa process : orderpss) {
+                if("成退".equals(process.getProcessType()) || "中退".equals(process.getProcessType()) || "小卷成退".equals(process.getProcessType())){
+                    // 锁定的不参与合并
+                    /*if(process.getIfLock()){
+                        mergeprocesses.add(process);
+                    }else{*/
+                    String bsproid = process.getBsProcessesId().get(0);
+                    List<ProductionProcessesTa> bsprocess = thproMap.get(bsproid);
+                    if(bsprocess == null){
+                        bsprocess = new ArrayList<>();
+                    }
+                    bsprocess.add(process);
+                    thproMap.put(bsproid,bsprocess);
+//                    }
+                }else{
+                    mergeprocesses.add(process);
+
+                    String bsproid = process.getBsProcessesId().get(0);
+                    List<ProductionProcessesTa> bsprocess = thproMap.get(bsproid);
+                    if(bsprocess == null){
+                        bsprocess = new ArrayList<>();
+                    }
+                    bsprocess.add(process);
+                    notthproMap.put(bsproid,bsprocess);
+                }
+                allProMap.put(process.getId(),process);
+            }
+            // 退火合并
+            if(thproMap != null && thproMap.size()>0){
+                thproMap.forEach((k,v)->{
+                    // 未锁定的退火合并
+                    if(v.size()>1){
+                        // 作业卷数大于1,合并退火
+                        List<EquipmentTa> equipments = equipmentList.stream().filter(eq -> eq.getId().equals(v.get(0).getOptionalEquipments().get(0))).collect(Collectors.toList());
+                        if(equipments != null && equipments.size()>0){
+                            EquipmentTa equipment = equipments.get(0);
+                            // 根据承重计算最大几卷
+                            int a = equipment.getEquipmentParameter().getEquipmentBearing().divide(v.get(0).getSinglerollweight(), 2, RoundingMode.HALF_UP).intValue();
+                            // 根据宽度计算最大几卷
+                            if("成退".equals(v.get(0).getProcessType()) || "中退".equals(v.get(0).getProcessType())){
+                                int b = equipment.getEquipmentParameter().getEquipmentWidth().divide(v.get(0).getVolumeWidth().add(equipment.getEquipmentParameter().getFurnace()), 2, RoundingMode.HALF_UP).intValue();
+                                if(b<a){
+                                    a = b;
+                                }
+
+                                // 最大装炉卷数
+                                Integer maxheatroll = v.get(0).getProduceOrder().get(0).getMaxheatroll();
+                                if(maxheatroll != null && maxheatroll < a){
+                                    a = maxheatroll;
+                                }
+                                if(equipment.getEquipmentParameter() != null && equipment.getEquipmentParameter().getMaxfurance() != null && equipment.getEquipmentParameter().getMaxfurance()>0){
+                                    if(equipment.getEquipmentParameter().getMaxfurance() < a){
+                                        a = equipment.getEquipmentParameter().getMaxfurance();
+                                    }
+                                }
+                            }
+
+                            // 作业计划加工卷数是否大于一炉最大卷数
+                            if(v.get(0).getPreviousProcesses() != null && v.get(0).getPreviousProcesses().size()>0){
+                                Collections.sort(v,(p1,p2)->{
+                                    return p1.getPreviousProcesses().get(0).getEndTime().compareTo(p2.getPreviousProcesses().get(0).getEndTime());
+                                });
+                            }else{
+                                if(v.get(0).getRooprocess() != null && v.get(0).getRooprocess().getStartTime() != null){
+                                    Collections.sort(v,(p1,p2)->{
+                                        if(p1.getRooprocess() == null || p1.getRooprocess().getStartTime() == null){
+                                            return 1;
+                                        } else if (p2.getRooprocess() == null || p2.getRooprocess().getStartTime() == null) {
+                                            return -1;
+                                        }else{
+                                            return p1.getRooprocess().getStartTime().compareTo(p2.getRooprocess().getStartTime());
+                                        }
+                                    });
+                                }
+                            }
+
+                            if(a == 0){
+                                throw new ApsException(v.get(0).getUniqueBsProcessesId()+"作业单卷重超过设备承重或最大装炉卷数设置为0");
+                            }
+
+                            List<List<ProductionProcessesTa>> chunks = new ArrayList<>();
+
+                            List<ProductionProcessesTa> lockpps = v.stream().filter(m -> m.getIfLock()).collect(Collectors.toList());
+                            List<ProductionProcessesTa> notlockpps = v.stream().filter(m -> !m.getIfLock()).collect(Collectors.toList());
+                            if(lockpps != null && lockpps.size()>0){
+                                mergeprocesses.addAll(lockpps);
+                            }
+                            if(notlockpps != null && notlockpps.size()>0) {
+                                int listSize = notlockpps.size();
+                                /*for (int i = 0; i < listSize; i += a) {
+                                    chunks.add(notlockpps.subList(i, Math.min(i + a, listSize)));
+                                }*/
+                                // 根据最大装炉卷数以及最大等待时间来判断是否合并
+                                LocalDateTime minpreendTime = null;
+                                for (int i = 0; i < listSize; i++) {
+                                    ProductionProcessesTa processes1 = notlockpps.get(i);
+                                    // 是否新组炉
+                                    boolean newList = true;
+                                    if(!chunks.isEmpty()){
+                                        int lastsize = chunks.get(chunks.size() - 1).size();
+                                        // 最大装炉卷数限制
+                                        if(lastsize<a){
+                                            // 有最大等待时间则判断前道工序时间差是否小于最大等待时间
+                                            if(processes1.getMaxWaitTime() != null && processes1.getMaxWaitTime()>0){
+                                                if(processes1.getPreviousProcesses() != null && processes1.getPreviousProcesses().size()>0){
+                                                    // 待合并的退火前道工序结束时间最大差不能大于最大等待时间
+                                                    if(processes1.getPreviousProcesses().get(0).getEndTime().compareTo(minpreendTime.plusMinutes(processes1.getMaxWaitTime()))<0){
+                                                        newList = false;
+                                                    }
+                                                }else {
+                                                    newList = false;
+                                                }
+                                            }else{
+                                                newList = false;
+                                            }
+                                        }
+                                    }
+                                    if(newList){
+                                        minpreendTime = processes1.getPreviousProcesses().get(0).getEndTime();
+                                        List<ProductionProcessesTa> thps = new ArrayList<>();
+                                        thps.add(processes1);
+                                        chunks.add(thps);
+                                    }else{
+                                        chunks.get(chunks.size() - 1).add(processes1);
+                                    }
+                                }
+                            }
+                            // 合并退火作业
+                            for(int j = 0;j < chunks.size();j++){
+                                List<ProductionProcessesTa> thps = chunks.get(j);
+                                for (int i = 0; i < thps.size(); i++) {
+                                    if(i>0){
+                                        // 设置待合并退火的主ID
+                                        thps.get(i).setMergeThMainId(thps.get(0).getId());
+                                        // 设置合并作业的根节点
+                                        thps.get(0).getMergeRooprocess().add(thps.get(i).getRooprocess());
+                                        otherThproces.add(thps.get(i));
+                                        // 退火前一道作业设置下一作业ID
+                                        if(thps.get(i).getPreviousProcessesIds() == null){
+                                            int a12 = 111;
+                                        }
+                                        if(thps.get(i).getPreviousProcessesIds() != null && thps.get(i).getPreviousProcessesIds().size()>0){
+                                            ProductionProcessesTa prepro = allProMap.get(thps.get(i).getPreviousProcessesIds().get(0));
+                                            // 历史关联关系备份
+                                            List<String> oldNextProcessesIds = new ArrayList<>();
+                                            oldNextProcessesIds.addAll(prepro.getNextProcessesIds());
+//                                        prepro.setOldNextProcessesIds(oldNextProcessesIds);
+                                            // 合并后关联关系重置
+                                            if(!prepro.getNextProcessesIds().contains(thps.get(0).getId())){
+                                                int i1 = prepro.getNextProcessesIds().indexOf(thps.get(i).getId());
+                                                prepro.getNextProcessesIds().set(i1,thps.get(0).getId());
+                                            }
+                                            List<String> list = new ArrayList<>();
+                                            Set<String> set = new LinkedHashSet<>();
+                                            for (String nextProcessesId : prepro.getNextProcessesIds()) {
+                                                set.add(nextProcessesId);
+                                            }
+                                            list.addAll(set);
+                                            prepro.setNextProcessesIds(list);
+                                            List<ProductionProcessesTa> nextpros = new ArrayList<>();
+                                            for (String nextProcessesId : prepro.getNextProcessesIds()) {
+                                                nextpros.add(allProMap.get(nextProcessesId));
+                                            }
+                                            prepro.setNextProcesses(nextpros);
+                                            // 当前合并退火作业设置前一道作业
+                                            if(!thps.get(0).getPreviousProcessesIds().contains(prepro.getId())){
+                                                thps.get(0).getPreviousProcessesIds().add(prepro.getId());
+                                            }
+                                            List<ProductionProcessesTa> previousProces = new ArrayList<>();
+                                            for (String pid : thps.get(0).getPreviousProcessesIds()) {
+                                                previousProces.add(allProMap.get(pid));
+                                            }
+                                            thps.get(0).setPreviousProcesses(previousProces);
+                                        }
+                                        // 退火后一道作业设置上一道作业ID
+                                        if(thps.get(i).getNextProcessesIds() != null && thps.get(i).getNextProcessesIds().size()>0){
+                                            for (String nextProcessesId : thps.get(i).getNextProcessesIds()) {
+                                                ProductionProcessesTa nextpro = allProMap.get(nextProcessesId);
+                                                // 历史关联关系备份
+                                                List<String> oldpreids = new ArrayList<>();
+                                                if(nextpro == null){
+                                                    int aaa = 11;
+                                                }
+                                                oldpreids.addAll(nextpro.getPreviousProcessesIds());
+//                                                nextpro.setOldPreviousProcessesIds(oldpreids);
+                                                // 合并后关联关系重置
+                                                List<String> preids = new ArrayList<>();
+                                                preids.add(thps.get(0).getId());
+                                                nextpro.setPreviousProcessesIds(preids);
+
+                                                List<ProductionProcessesTa> nextprepros = new ArrayList<>();
+                                                for (String pid : nextpro.getPreviousProcessesIds()) {
+                                                    nextprepros.add(allProMap.get(pid));
+                                                }
+                                                nextpro.setPreviousProcesses(nextprepros);
+                                            }
+                                            // 设置合并退火作业下一道工序
+                                            thps.get(0).getNextProcessesIds().addAll(thps.get(i).getNextProcessesIds());
+                                            List<ProductionProcessesTa> thnexts = new ArrayList<>();
+                                            for (String pid : thps.get(0).getNextProcessesIds()) {
+                                                thnexts.add(allProMap.get(pid));
+                                            }
+                                            thps.get(0).setNextProcesses(thnexts);
+                                        }
+                                        // 小卷退火卷数合并
+                                        if(thps.get(i).getProcessType().equals("小卷成退")){
+                                            thps.get(0).setMinThPcNum(thps.get(0).getMinThPcNum()+thps.get(i).getMinThPcNum());
+                                        }
+                                    }
+                                }
+                                // 取第一个作业作为合并作业
+                                ProductionProcessesTa mergePro = thps.get(0);
+                                mergePro.setTotalVolumeWidth(mergePro.getVolumeWidth().multiply(new BigDecimal(thps.size())));
+                                mergePro.setTotalSinglerollweight(mergePro.getTotalSinglerollweight().multiply(new BigDecimal(thps.size())));
+                                mergePro.setOpeProducePcNum(thps.size());
+                                // 设置是否满炉
+                                if(thps.size() == a){
+                                    mergePro.setIffullth("y");
+                                }
+                                mergeprocesses.add(mergePro);
+                            }
+                        }
+                    }else{
+                        mergeprocesses.addAll(v);
+                    }
+                });
+            }
+        });
+        return mergeprocesses;
+    }
+
 }

+ 233 - 0
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/taskassigning/service/impl/ProductionScheduleTaServiceImpl.java

@@ -3,6 +3,7 @@ package com.rongwei.rwapsserver.aps.taskassigning.service.impl;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
 import com.rongwei.rwapsserver.aps.domain.*;
+import com.rongwei.rwapsserver.aps.score.ApsConstraintProvider;
 import com.rongwei.rwapsserver.aps.service.ApsService;
 import com.rongwei.rwapsserver.aps.service.DdApsService;
 import com.rongwei.rwapsserver.aps.taskassigning.service.DdApsTaService;
@@ -17,6 +18,7 @@ import com.rongwei.rwapsserver.aps.util.ApsConstants;
 import com.rongwei.rwapsserver.aps.util.ApsException;
 import com.rongwei.rwapsserver.aps.util.ApsUtils;
 import com.rongwei.rwapsserver.aps.vo.ProductionScheduleRetVo;
+import com.rongwei.rwapsserver.aps.vo.ProductionScheduleVo;
 import lombok.extern.slf4j.Slf4j;
 import org.optaplanner.core.api.score.ScoreExplanation;
 import org.optaplanner.core.api.score.buildin.hardsoft.HardSoftScore;
@@ -1538,4 +1540,235 @@ public class ProductionScheduleTaServiceImpl implements ProductionScheduleTaServ
         }
     }
 
+    /**
+     * 退火调度排程
+     * @param productionScheduleVo
+     * @return
+     */
+    public ProductionScheduleRetTaVo tuiHuoSchedule(ProductionScheduleTaVo productionScheduleVo) throws Exception{
+        log.info("*************** 退火调度排程开始:"+productionScheduleVo.getProductionScheduleId()+" *******************");
+        if(productionScheduleVo.getScheduleType().getScheduleType() == null){
+            productionScheduleVo.getScheduleType().setScheduleType("default");
+        }
+
+        ApsUtils apsUtils = new ApsUtils();
+        // 排程结果对象
+        ProductionScheduleRetTaVo productionScheduleRetVo = new ProductionScheduleRetTaVo();
+        // 排程校验
+        if(productionScheduleVo.getRoamTime() == null || productionScheduleVo.getRoamTime().size() == 0){
+            throw new ApsException("缺少全局流转时间配置");
+        }
+
+        // optaplanner 求解器数据装配
+        List<ProductionProcessesTa> otherThproces = new ArrayList<>();
+        List<ProductionProcessesTa> otherNotThproces = new ArrayList<>();
+        ApsSolutionTa apsSolution = getPreApsSolution(productionScheduleVo,otherThproces);
+        // 判断是否有非锁定的工序
+        boolean hasSchedulePro = false;
+        for (ProductionProcessesTa ProductionProcessesTa : apsSolution.getProcessesList()) {
+            if(!ProductionProcessesTa.getIfLock()){
+                hasSchedulePro = true;
+                break;
+            }
+        }
+        if(!hasSchedulePro){
+            throw new ApsException("没有可排程的工序");
+        }
+
+        // 退火合并
+        List<ProductionProcessesTa> thList = ddApsService.thProcessMerge(productionScheduleVo,apsSolution,otherThproces);
+        apsSolution.setProcessesList(thList);
+
+        // 去掉锁定工序
+        List<ProductionProcessesTa> notLocks = new ArrayList<>();
+        List<ProductionProcessesTa> hasLocks = new ArrayList<>();
+        if(apsSolution.getProcessesList() != null && apsSolution.getProcessesList().size()>0){
+            for (ProductionProcessesTa ProductionProcessesTa : apsSolution.getProcessesList()) {
+                if(!ProductionProcessesTa.getIfLock()){
+                    notLocks.add(ProductionProcessesTa);
+                }else{
+                    hasLocks.add(ProductionProcessesTa);
+                }
+            }
+        }
+        apsSolution.setProcessesList(notLocks);
+
+        ApsSolutionTa apsSolutionTh = null;
+        Map<String,Map<String,List<ProductionProcessesTa>>> relPros = new HashMap<>();
+        // 调度退火排程
+        apsSolutionTh = new ApsSolutionTa();
+        List<ProductionProcessesTa> tuihuos = new ArrayList<>();
+        for (ProductionProcessesTa process : apsSolution.getProcessesList()) {
+            if((process.getProcessType().equals("成退") || process.getProcessType().equals("中退") || process.getProcessType().equals("小卷成退")) && !process.getIfLock()){
+                tuihuos.add(process);
+            }
+        }
+
+        apsSolutionTh.setProcessesList(tuihuos);
+        apsSolutionTh.setEquipmentList(apsSolution.getEquipmentList());
+
+        // 退火工序求解器运行
+        int processNum1 = apsSolutionTh.getProcessesList().size();
+        int runPlanSeconds1 = (processNum1)*30;
+        // CPU核数
+        String cores = Runtime.getRuntime().availableProcessors() + "";
+        /*SolverFactory<ApsSolutionTa> solverFactory1 = SolverFactory.create(new SolverConfig()
+                        .withEnvironmentMode(EnvironmentMode.REPRODUCIBLE)
+                        .withSolutionClass(ApsSolutionTa.class)
+                        .withEntityClasses(ProductionProcessesTa.class)
+                        .withConstraintProviderClass(ApsConstraintProvider.class)
+//                .withTerminationSpentLimit(Duration.ofSeconds(runPlanSeconds1))
+                        .withTerminationConfig(new TerminationConfig().withUnimprovedSecondsSpentLimit(120L))
+                        .withMoveThreadCount(cores)
+        );*/
+        SolverFactory<ApsSolutionTa> solverFactory1 = SolverFactory.create(new SolverConfig()
+                        .withEnvironmentMode(EnvironmentMode.REPRODUCIBLE)
+                        .withSolutionClass(ApsSolutionTa.class)
+                        .withEntityClasses(ProductionProcessesTa.class, EquipmentTa.class)
+                        .withConstraintProviderClass(ApsConstraintListProvider.class)
+                        .withTerminationConfig(new TerminationConfig().withUnimprovedSecondsSpentLimit(60L))
+                        .withMoveThreadCount(cores)
+        );
+        Solver<ApsSolutionTa> solver1 = solverFactory1.buildSolver();
+        // 退火调度排序
+        List<ProductionProcessesTa> hapres = new ArrayList<>();
+        List<ProductionProcessesTa> nothapres = new ArrayList<>();
+        for (ProductionProcessesTa v : apsSolutionTh.getProcessesList()) {
+            if(v.getPreviousProcesses() != null && v.getPreviousProcesses().size() > 0 && v.getPreviousProcesses().get(0).getEndTime() != null){
+                hapres.add(v);
+            }else {
+                nothapres.add(v);
+            }
+        }
+
+        Collections.sort(hapres,(v1,v2)->{
+            int a = 0;
+            if(v1.getPreviousProcesses().get(0).getStartTime().compareTo(v2.getPreviousProcesses().get(0).getStartTime()) == 0){
+                a = v2.getVolumeWidth().compareTo(v1.getVolumeWidth());
+            }else{
+                a = v2.getPreviousProcesses().get(0).getStartTime().compareTo(v1.getPreviousProcesses().get(0).getStartTime());
+            }
+            return a;
+        });
+
+        if(nothapres != null && nothapres.size()>0){
+            hapres.addAll(nothapres);
+        }
+        apsSolutionTh.setProcessesList(hapres);
+
+        ApsSolutionTa solvedBalance = solver1.solve(apsSolutionTh);
+
+        log.info("**************退火排程评分分析***************");
+        SolutionManager<ApsSolutionTa, HardSoftScore> scoreManager1 = SolutionManager.create(solverFactory1);
+        ScoreExplanation<ApsSolutionTa, HardSoftScore> explain1 = scoreManager1.explain(solvedBalance);
+        log.info(explain1.toString());
+        log.info("**************退火排程评分分析***************");
+
+        solvedBalance.getProcessesList().addAll(hasLocks);
+        // 退火合并工序排程完拆分
+        if(otherThproces != null && otherThproces.size()>0){
+            List<ProductionProcessesTa> otherThproce1 = new ArrayList<>();
+            for (ProductionProcessesTa otherThproce : otherThproces) {
+                if(StrUtil.isNotBlank(otherThproce.getMergeThMainId())){
+                    ProductionProcessesTa thpro = null;
+                    for (ProductionProcessesTa productionProcesses : solvedBalance.getProcessesList()) {
+                        if(productionProcesses.getId().equals(otherThproce.getMergeThMainId())){
+                            thpro = productionProcesses;
+                            break;
+                        }
+                    }
+                    if(thpro != null){
+                        otherThproce.setStartTime(thpro.getStartTime());
+                        otherThproce.setEndTime(thpro.getEndTime());
+                        otherThproce.setEquipmentId(thpro.getEquipmentId());
+                        otherThproce.setEquipment(thpro.getEquipment());
+                        otherThproce.setConflictRoptions(thpro.getConflictRoptions());
+
+                        thpro.setOpeProducePcNum(1);
+                        if(thpro.getProcessType().equals("小卷成退")){
+                            thpro.setMinThPcNum(thpro.getMinThPcNum()-otherThproce.getMinThPcNum());
+                        }
+                    }else{
+                        otherThproce1.add(otherThproce);
+                    }
+                }
+            }
+            if(otherThproce1 != null && otherThproce1.size()>0){
+                for (ProductionProcessesTa otherThproce : otherThproce1) {
+                    if(StrUtil.isNotBlank(otherThproce.getMergeThMainId())){
+                        ProductionProcessesTa thpro = null;
+                        for (ProductionProcessesTa productionProcesses : otherThproces) {
+                            if(productionProcesses.getId().equals(otherThproce.getMergeThMainId())){
+                                thpro = productionProcesses;
+                                break;
+                            }
+                        }
+                        if(thpro != null){
+                            otherThproce.setStartTime(thpro.getStartTime());
+                            otherThproce.setEndTime(thpro.getEndTime());
+                            otherThproce.setEquipmentId(thpro.getEquipmentId());
+                            otherThproce.setEquipment(thpro.getEquipment());
+                            otherThproce.setConflictRoptions(thpro.getConflictRoptions());
+
+                            thpro.setOpeProducePcNum(1);
+                            if(thpro.getProcessType().equals("小卷成退")){
+                                thpro.setMinThPcNum(thpro.getMinThPcNum()-otherThproce.getMinThPcNum());
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        for (ProductionProcessesTa productionProcesses : solvedBalance.getProcessesList()) {
+            productionProcesses.setPreviousProcesses(null);
+            productionProcesses.setRooprocess(null);
+            productionProcesses.setNextProcesses(null);
+            productionProcesses.getEquipment().setProcessesList(null);
+            productionProcesses.getEquipment().setEquipmentRunTimes(null);
+            productionProcesses.setPreviousStep(null);
+            productionProcesses.setOptionalProviderEquipments(null);
+            productionProcesses.setEquass(null);
+            productionProcesses.setMergeRooprocess(null);
+            productionProcesses.setOpeProducePcNum(1);
+            if(productionProcesses.getApsOverallConfig() != null){
+                productionProcesses.getApsOverallConfig().setFurnaceInstallations(null);
+                productionProcesses.getApsOverallConfig().setMergeFurnaces(null);
+            }
+            if(productionProcesses.getOldNextProcessesIds() != null){
+                productionProcesses.setNextProcessesIds(productionProcesses.getOldNextProcessesIds());
+            }
+            if(productionProcesses.getOldPreviousProcessesIds() != null){
+                productionProcesses.setPreviousProcessesIds(productionProcesses.getOldPreviousProcessesIds());
+            }
+            // 冲突约束补充处理
+            if(productionProcesses.getConflictRoptions() != null && productionProcesses.getConflictRoptions().size()>0){
+                productionProcesses.getConflictRoptions().forEach((k,v)->{
+                    if(StrUtil.isBlank(productionProcesses.getHasConflict())){
+                        productionProcesses.setHasConflict("y");
+                    }
+                    // 强制约束
+                    if(k.indexOf("hard") == 0){
+                        if(StrUtil.isBlank(productionProcesses.getConflictDes())){
+                            productionProcesses.setConflictDes(v);
+                        }else{
+                            productionProcesses.setConflictDes(productionProcesses.getConflictDes()+";"+v);
+                        }
+                    }
+                    // 非强制约束
+                    else if (k.indexOf("soft") == 0) {
+                        if(StrUtil.isBlank(productionProcesses.getSoftconflictdes())){
+                            productionProcesses.setSoftconflictdes(v);
+                        }else{
+                            productionProcesses.setSoftconflictdes(productionProcesses.getSoftconflictdes()+";"+v);
+                        }
+                    }
+                });
+            }
+        }
+
+        productionScheduleRetVo.setProcesses(solvedBalance.getProcessesList());
+
+        return productionScheduleRetVo;
+    }
 }

+ 282 - 17
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/taskassigning/tado/TaskStartTimeListenerTa.java

@@ -1,10 +1,13 @@
 package com.rongwei.rwapsserver.aps.taskassigning.tado;
 
-import com.rongwei.rwapsserver.aps.domain.EquipmentRunTime;
+import com.rongwei.rwapsserver.aps.domain.*;
+import com.rongwei.rwapsserver.aps.util.ApsUtils;
 import com.rongwei.rwapsserver.aps.util.ObjectNull;
 import lombok.extern.slf4j.Slf4j;
 import org.optaplanner.core.api.domain.variable.ListVariableListener;
 import org.optaplanner.core.api.score.director.ScoreDirector;
+
+import java.math.BigDecimal;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.util.*;
@@ -73,25 +76,61 @@ public class TaskStartTimeListenerTa implements ListVariableListener<ApsSolution
         }
         LocalDateTime previousEndTime = index == 0 ? apsStartTime : tasks.get(index - 1).getEndTime();
 
-        for (int i = index; i < tasks.size(); i++) {
-            ProductionProcessesTa t = tasks.get(i);
-            LocalDateTime previousLastEndTime = getEarlyStartTime(t, equipmentTa,allProcessesList);
-            if(previousLastEndTime != null && previousLastEndTime.compareTo(previousEndTime)>0){
-                previousEndTime = previousLastEndTime;
-            }
-            // 过滤设备占用时间
-            previousEndTime = eqRunTimeDistinct(equipmentTa,t,previousEndTime);
-            if (!Objects.equals(t.getStartTime(), previousEndTime)) {
-                scoreDirector.beforeVariableChanged(t, "startTime");
-                t.setStartTime(previousEndTime);
-                scoreDirector.afterVariableChanged(t, "startTime");
+        // 退火
+        if("成退,中退,小卷成退".contains(tasks.get(0).getProcessType())){
+            for (int i = index; i < tasks.size(); i++) {
+                ProductionProcessesTa t = tasks.get(i);
+                LocalDateTime previousLastEndTime = getEarlyStartTime(t, equipmentTa,allProcessesList);
+                if(i>0){
+                    ProductionProcessesTa pret = tasks.get(i-1);
+                    if(previousLastEndTime.compareTo(pret.getStartTime())<=0){
+                        // 设置当前工序和上道工序开始时间一样(即合并为同一炉)
+                        if(thMerge(tasks,i,equipmentTa)){
+                            previousEndTime = pret.getStartTime();
+                        }
+                    }
+                }
+
+                if(previousLastEndTime != null && previousLastEndTime.compareTo(previousEndTime)>0){
+                    previousEndTime = previousLastEndTime;
+                }
+                // 过滤设备占用时间
+                previousEndTime = eqRunTimeDistinct(equipmentTa,t,previousEndTime);
+                if (!Objects.equals(t.getStartTime(), previousEndTime)) {
+                    scoreDirector.beforeVariableChanged(t, "startTime");
+                    t.setStartTime(previousEndTime);
+                    scoreDirector.afterVariableChanged(t, "startTime");
+                }
+
+                LocalDateTime specificDateTime = LocalDateTime.of(2026, Month.JANUARY, 1, 12, 30);
+                if(previousEndTime.compareTo(specificDateTime)>0){
+                    int aa = 0;
+                }
+                previousEndTime = t.getEndTime();
             }
+        }
+        // 非退火
+        else{
+            for (int i = index; i < tasks.size(); i++) {
+                ProductionProcessesTa t = tasks.get(i);
+                LocalDateTime previousLastEndTime = getEarlyStartTime(t, equipmentTa,allProcessesList);
+                if(previousLastEndTime != null && previousLastEndTime.compareTo(previousEndTime)>0){
+                    previousEndTime = previousLastEndTime;
+                }
+                // 过滤设备占用时间
+                previousEndTime = eqRunTimeDistinct(equipmentTa,t,previousEndTime);
+                if (!Objects.equals(t.getStartTime(), previousEndTime)) {
+                    scoreDirector.beforeVariableChanged(t, "startTime");
+                    t.setStartTime(previousEndTime);
+                    scoreDirector.afterVariableChanged(t, "startTime");
+                }
 
-            LocalDateTime specificDateTime = LocalDateTime.of(2026, Month.JANUARY, 1, 12, 30);
-            if(previousEndTime.compareTo(specificDateTime)>0){
-                int aa = 0;
+                LocalDateTime specificDateTime = LocalDateTime.of(2026, Month.JANUARY, 1, 12, 30);
+                if(previousEndTime.compareTo(specificDateTime)>0){
+                    int aa = 0;
+                }
+                previousEndTime = t.getEndTime();
             }
-            previousEndTime = t.getEndTime();
         }
     }
 
@@ -166,6 +205,232 @@ public class TaskStartTimeListenerTa implements ListVariableListener<ApsSolution
         return previousLastEndTime;
     }
 
+    private Boolean thMerge(List<ProductionProcessesTa> tasks,int i, EquipmentTa equipmentTa){
+        // 前道作业
+        ProductionProcessesTa processbf = tasks.get(i-1);
+        // 当前作业
+        ProductionProcessesTa process = tasks.get(i);
+
+        Boolean hasMergeTh = false;
+        if(process.getProcessType().equals(processbf.getProcessType())){
+            // 递归前道工序获取当前工序的前一炉退火工序
+            List<ProductionProcessesTa> mergePros = new ArrayList<>();
+            for (int j = i-1; j >= 0 ; j--) {
+                if(mergePros.size() == 0){
+                    mergePros.add(tasks.get(j));
+                }else{
+                    if(tasks.get(j).getStartTime().compareTo(mergePros.get(0).getStartTime()) == 0){
+                        mergePros.add(tasks.get(j));
+                    }
+                }
+            }
+            if(process.getProcessType().equals("成退") || process.getProcessType().equals("中退")){
+                if(process.getProcessType().equals(processbf.getProcessType())
+                        && process.getVolumeMetal().equals(processbf.getVolumeMetal())
+                        && process.getVolumeMetalstate() != null && process.getVolumeMetalstate().equals(processbf.getVolumeMetalstate())){
+                    // 获取最大、最小宽度厚度重量等
+                    BigDecimal hasMergeMaxWidth = null;
+                    BigDecimal hasMergeMinWidth = null;
+                    BigDecimal hasMergeMaxThickness = null;
+                    BigDecimal hasMergeMinThickness = null;
+                    BigDecimal hasMergeMaxWeight = null;
+                    BigDecimal hasMergeMinWeight = null;
+                    if(mergePros.size()>0){
+                        for (ProductionProcessesTa mergePro : mergePros) {
+                            // 最大宽度
+                            if(hasMergeMaxWidth == null){
+                                hasMergeMaxWidth = mergePro.getVolumeWidth();
+                            }else{
+                                if(mergePro.getVolumeWidth().compareTo(hasMergeMaxWidth)>0){
+                                    hasMergeMaxWidth = mergePro.getVolumeWidth();
+                                }
+                            }
+                            // 最小宽度
+                            if(hasMergeMinWidth == null){
+                                hasMergeMinWidth = mergePro.getVolumeWidth();
+                            }else{
+                                if(mergePro.getVolumeWidth().compareTo(hasMergeMinWidth)<0){
+                                    hasMergeMinWidth = mergePro.getVolumeWidth();
+                                }
+                            }
+                            // 最大厚度
+                            if(hasMergeMaxThickness == null){
+                                hasMergeMaxThickness = mergePro.getVolumeThickness();
+                            }else{
+                                if(mergePro.getVolumeThickness().compareTo(hasMergeMaxThickness)>0){
+                                    hasMergeMaxThickness = mergePro.getVolumeThickness();
+                                }
+                            }
+                            // 最小厚度
+                            if(hasMergeMinThickness == null){
+                                hasMergeMinThickness = mergePro.getVolumeThickness();
+                            }else{
+                                if(mergePro.getVolumeThickness().compareTo(hasMergeMinThickness)<0){
+                                    hasMergeMinThickness = mergePro.getVolumeThickness();
+                                }
+                            }
+                            // 最大重量
+                            if(hasMergeMaxWeight == null){
+                                hasMergeMaxWeight = mergePro.getSinglerollweight();
+                            }else{
+                                if(mergePro.getSinglerollweight().compareTo(hasMergeMaxWeight)>0){
+                                    hasMergeMaxWeight = mergePro.getSinglerollweight();
+                                }
+                            }
+                            // 最小重量
+                            if(hasMergeMinWeight == null){
+                                hasMergeMinWeight = mergePro.getSinglerollweight();
+                            }else{
+                                if(mergePro.getSinglerollweight().compareTo(hasMergeMinWeight)<0){
+                                    hasMergeMinWeight = mergePro.getSinglerollweight();
+                                }
+                            }
+                        }
+                    }
+
+                    // 最大、最小宽度
+                    BigDecimal maxVolumeWidth = hasMergeMaxWidth;
+                    BigDecimal minVolumeWidth = hasMergeMinWidth;
+                    if(process.getVolumeWidth().compareTo(maxVolumeWidth)>0){
+                        maxVolumeWidth = process.getVolumeWidth();
+                    }
+                    if(process.getVolumeWidth().compareTo(minVolumeWidth)<0){
+                        minVolumeWidth = process.getVolumeWidth();
+                    }
+                    // 最大、最小厚度
+                    BigDecimal maxVolumeThickness = hasMergeMaxThickness;
+                    BigDecimal minVolumeThickness = hasMergeMinThickness;
+                    if(process.getVolumeThickness().compareTo(maxVolumeThickness)>0){
+                        maxVolumeThickness = process.getVolumeThickness();
+                    }
+                    if(process.getVolumeThickness().compareTo(minVolumeThickness)<0){
+                        minVolumeThickness = process.getVolumeThickness();
+                    }
+                    // 最大、最小重量
+                    BigDecimal maxSinglerollweight = hasMergeMaxWeight;
+                    BigDecimal minSinglerollweight = hasMergeMinWeight;
+                    if(process.getSinglerollweight().compareTo(maxSinglerollweight)>0){
+                        maxSinglerollweight = process.getSinglerollweight();
+                    }
+                    if(process.getSinglerollweight().compareTo(minSinglerollweight)<0){
+                        minSinglerollweight = process.getSinglerollweight();
+                    }
+                    // 根据最小厚度查询厚差
+                    BigDecimal ztkc = new BigDecimal("250");
+                    BigDecimal ctkc = new BigDecimal("200");
+                    if(process.getApsOverallConfig().getMiddifference() != null && process.getApsOverallConfig().getMiddifference()>0){
+                        ztkc = new BigDecimal(process.getApsOverallConfig().getMiddifference());
+                    }
+                    if(process.getApsOverallConfig().getFurnacedifference() != null && process.getApsOverallConfig().getFurnacedifference()>0){
+                        ctkc = new BigDecimal(process.getApsOverallConfig().getFurnacedifference());
+                    }
+                    // 读取配置中的卷重差
+                    BigDecimal ztjzc = new BigDecimal("4");
+                    BigDecimal ctjzc = new BigDecimal("2");
+                    if(process.getApsOverallConfig().getWeightdifference() != null){
+                        ztjzc = process.getApsOverallConfig().getWeightdifference();
+                    }
+                    if(process.getApsOverallConfig().getMidweightdifference() != null){
+                        ctjzc = process.getApsOverallConfig().getMidweightdifference();
+                    }
+
+                    BigDecimal zthc = new BigDecimal("0.05");
+                    BigDecimal cthc = new BigDecimal("0.05");
+                    List<ApsAnnealingDifferenceDo> apsAnnealingDifferences = process.getApsOverallConfig().getApsAnnealingDifferences();
+                    if(apsAnnealingDifferences != null && apsAnnealingDifferences.size()>0){
+                        for (ApsAnnealingDifferenceDo apsAnnealingDifference : apsAnnealingDifferences) {
+                            BigDecimal startthickness = apsAnnealingDifference.getStartthickness();
+                            BigDecimal endthickness = apsAnnealingDifference.getEndthickness();
+                            if(startthickness == null && endthickness == null){
+                                continue;
+                            }
+                            boolean fl = true;
+                            if(startthickness != null){
+                                if(minVolumeThickness.compareTo(startthickness)<0){
+                                    fl = false;
+                                }
+                            }
+                            if(endthickness != null){
+                                if(minVolumeThickness.compareTo(endthickness)>0){
+                                    fl = false;
+                                }
+                            }
+                            if(fl){
+                                BigDecimal declinedifference = apsAnnealingDifference.getDeclinedifference();
+                                BigDecimal moderatedifference = apsAnnealingDifference.getModeratedifference();
+                                if(declinedifference != null){
+                                    cthc = declinedifference;
+                                }
+                                if(moderatedifference != null){
+                                    zthc = moderatedifference;
+                                }
+                            }
+                        }
+                    }
+
+                    BigDecimal btVolumeWidth = null;
+                    BigDecimal btVolumeThickness = null;
+                    BigDecimal btSinglerollweight = null;
+                    if("成退".equals(process.getProcessType())){
+                        btVolumeWidth = ctkc;
+                        btVolumeThickness = cthc;
+                        btSinglerollweight = ctjzc;
+                    }else if ("中退".equals(process.getProcessType())){
+                        btVolumeWidth = ztkc;
+                        btVolumeThickness = zthc;
+                        btSinglerollweight = ztjzc;
+                    }
+                    if(maxVolumeWidth.subtract(minVolumeWidth).compareTo(btVolumeWidth)<=0 && maxVolumeThickness.subtract(minVolumeThickness).compareTo(btVolumeThickness)<=0
+                            && maxSinglerollweight.subtract(minSinglerollweight).compareTo(btSinglerollweight)<=0){
+                        hasMergeTh = true;
+                    }
+                }
+
+            }else{
+                // 小卷成退合并工序
+                String groupname = ApsUtils.getGroupNameMinTh(process);
+                String bfgroupname = ApsUtils.getGroupNameMinTh(processbf);
+                Set<String> minThGroupNames = new HashSet<>();
+                minThGroupNames.add(groupname);
+                minThGroupNames.add(bfgroupname);
+                // 获取前一炉合并工序的总重量
+                BigDecimal totalWeight = null;
+                for (ProductionProcessesTa mergePro : mergePros) {
+                    if(totalWeight == null){
+                        totalWeight = mergePro.getSinglerollweight();
+                    }else{
+                        totalWeight = totalWeight.add(mergePro.getSinglerollweight());
+                    }
+                }
+                totalWeight = totalWeight.add(process.getSinglerollweight());
+                if(totalWeight.compareTo(equipmentTa.getEquipmentParameter().getEquipmentBearing())<=0 && process.getProcessType().equals(processbf.getProcessType())){
+                    if(minThGroupNames.size() == 1){
+                        hasMergeTh = true;
+                    }else if(minThGroupNames.size() > 1){
+                        boolean a = false;
+                        for (ApsMergeFurnaceDo mergeFurnace : process.getApsOverallConfig().getMergeFurnaces()) {
+                            boolean ab = true;
+                            for (String groupname1 : minThGroupNames) {
+                                if(!mergeFurnace.getCompatibilitygroup().contains(groupname1)){
+                                    ab = false;
+                                    break;
+                                }
+                            }
+                            if(ab){
+                                a = true;
+                                break;
+                            }
+                        }
+                        if(a){
+                            hasMergeTh = true;
+                        }
+                    }
+                }
+            }
+        }
+        return hasMergeTh;
+    }
+
     /*public void updateTaskStartTime(ScoreDirector<ApsSolutionTa> scoreDirector, EquipmentTa resource, int fromIndex) {
         List<ProductionProcessesTa> allTasks = scoreDirector.getWorkingSolution().getProcessesList();
         LocalDateTime beginTime = allTasks.get(0).getApsOverallConfig().getStartTime();

+ 39 - 4
rw-aps-server/src/main/java/com/rongwei/rwapsserver/aps/util/ApsUtils.java

@@ -2,10 +2,7 @@ package com.rongwei.rwapsserver.aps.util;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.StrUtil;
-import com.rongwei.rwapsserver.aps.domain.ApsSolution;
-import com.rongwei.rwapsserver.aps.domain.Equipment;
-import com.rongwei.rwapsserver.aps.domain.EquipmentRunTime;
-import com.rongwei.rwapsserver.aps.domain.ProductionProcesses;
+import com.rongwei.rwapsserver.aps.domain.*;
 import com.rongwei.rwapsserver.aps.score.ApsConstraintProvider;
 import com.rongwei.rwapsserver.aps.taskassigning.tado.EquipmentTa;
 import com.rongwei.rwapsserver.aps.taskassigning.tado.ProductionProcessesTa;
@@ -545,4 +542,42 @@ public class ApsUtils {
         }
         return isSeriesLz;
     }
+
+    /**
+     * 获取小卷退火所在的组名
+     * @return
+     */
+    public static String getGroupNameMinTh(ProductionProcessesTa process){
+        String groupname = null;
+        for (ApsFurnaceInstallationDo furnaceInstallation : process.getApsOverallConfig().getFurnaceInstallations()) {
+            if(process.getVolumeMetal().equals(furnaceInstallation.getAlloy())){
+                boolean a = true;
+                if(furnaceInstallation.getAlloystatus() != null && !furnaceInstallation.getAlloystatus().contains(process.getVolumeMetalstate())){
+                    a = false;
+                }
+                if(furnaceInstallation.getStartthickness() != null && furnaceInstallation.getStartthickness().compareTo(process.getVolumeThickness())>0){
+                    a = false;
+                }
+                if(furnaceInstallation.getEndthickness() != null && furnaceInstallation.getEndthickness().compareTo(process.getVolumeThickness())<0){
+                    a = false;
+                }
+                if(furnaceInstallation.getStartwidth() != null && new BigDecimal(furnaceInstallation.getStartwidth()).compareTo(process.getVolumeWidth())>0){
+                    a = false;
+                }
+                if(furnaceInstallation.getEndwidth() != null && new BigDecimal(furnaceInstallation.getEndwidth()).compareTo(process.getVolumeWidth())<0){
+                    a = false;
+                }
+                if(a){
+                    groupname = furnaceInstallation.getId();
+                    break;
+                }
+            }
+        }
+        if(groupname == null){
+            groupname = "group-self-" + process.getVolumeMetal() + process.getVolumeMetalstate() + process.getProducttype() + process.getVolumeWidth() + process.getVolumeThickness();
+        }
+
+        return groupname;
+    }
+
 }