Commit 7d322d24 authored by Tong Li's avatar Tong Li

Merge remote-tracking branch 'origin/master' into tl

# Conflicts:
#	src/test/java/com/aps/demo/PlanResultServiceTest.java
parents c6275d54 8cd195f9
package com.aps.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationFeature;
......@@ -51,6 +52,9 @@ public class RedisConfiguration {
// 设置可见性
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 忽略空值(包括空数组、空字符串、null)
om.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
// 注册 JavaTimeModule 支持 Java 8 日期时间
om.registerModule(new JavaTimeModule());
......
......@@ -599,4 +599,4 @@ public class SwaggerMapParamConfig {
return "请求参数";
}
}
}
\ No newline at end of file
}
......@@ -88,6 +88,9 @@ public class LanuchController {
// 这些参数前端可以不传;不传时后端会根据 sceneId 找场景创建人,再查这个人的策略。
Long userId = getLongParam(params, "userId");
Long baseRuleId = getLongParam(params, "baseRuleId");
if (baseRuleId == null) {
baseRuleId = getLongParam(params, "ruleId");
}
if (baseRuleId == null) {
baseRuleId = getLongParam(params, "referenceId");
}
......
......@@ -215,7 +215,8 @@ public class ChromosomeDataController {
*/
private boolean isFileEntity(String entityName) {
// 这里列出所有文件实体的名称
String[] fileEntities = {"order", "entry", "machine", "globaloperationinfo", "groupresult", "prodprocessexec", "machineoption"};
String[] fileEntities = {"order", "entry", "machine", "globaloperationinfo", "groupresult", "prodprocessexec", "machineoption",
"task"};
for (String fileEntity : fileEntities) {
if (fileEntity.equalsIgnoreCase(entityName)) {
return true;
......
......@@ -421,6 +421,73 @@ public class ResourceGanttController {
@PostMapping("/orderInsertAuto")
@Operation(
summary = "自动插单",
description = "按neworder数据结构自动插入新订单",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "订单插单参数",
required = true,
content = @io.swagger.v3.oas.annotations.media.Content(
mediaType = "application/json",
examples = @io.swagger.v3.oas.annotations.media.ExampleObject(
name = "neworder示例",
value = "{\n" +
" \"sceneId\": \"C4EF4B6DC74E43F78D2EA4102B12066D\",\n" +
" \"neworder\": {\n" +
" \"zoneid\": \"36\",\n" +
" \"isconsume\": 0,\n" +
" \"settle\": 0,\n" +
" \"delay\": 0,\n" +
" \"part\": 0,\n" +
" \"islock\": 0,\n" +
" \"isinsert\": 0,\n" +
" \"mmid\": \"fff9efd4-8fc3-495f-acfe-dbefee3dc9cc\",\n" +
" \"mmname\": \"0.9%氯化钠注射液\",\n" +
" \"mmcode\": \"153004_ZHJ24046\",\n" +
" \"unit\": \"万袋\",\n" +
" \"unitId\": 12,\n" +
" \"series\": \"153004_ZHJ24046\",\n" +
" \"seriesId\": 648,\n" +
" \"seriesName\": \"153004_ZHJ24046\",\n" +
" \"code\": \"DDBH_20260522_2\",\n" +
" \"zone\": \"33331\",\n" +
" \"customerid\": 41,\n" +
" \"customer\": \"远景\",\n" +
" \"deliverytime\": \"2026-05-05T00:00:00.00+08:00\",\n" +
" \"prioritry\": \"5\",\n" +
" \"stockid\": 617,\n" +
" \"stock\": \"测试\",\n" +
" \"price\": 5,\n" +
" \"quantity\": 55,\n" +
" \"begintime\": \"2026-05-01T00:00:00.00+08:00\"\n" +
" }\n" +
"}"
)
)
)
)
public R<String> insertOrderAuto(@RequestBody Map<String, Object> params) {
log.info("insertOrderAuto 请求参数: {}", params);
String sceneId = ParamValidator.getString(params, "sceneId", "场景ID");
@SuppressWarnings("unchecked")
Map<String, Object> newOrderData = (Map<String, Object>) params.get("neworder");
if (newOrderData == null) {
@SuppressWarnings("unchecked")
Map<String, Object> fallbackNewOrderData = (Map<String, Object>) params.get("newOrder");
newOrderData = fallbackNewOrderData;
}
if (newOrderData == null) {
throw new IllegalArgumentException("neworder 不能为空");
}
ParamValidator.validateSceneExists(sceneService, sceneId);
planResultService.InsertOrderAuto(sceneId, convertNewOrderPayload(newOrderData));
return R.ok("插单成功");
}
@PostMapping("/orderInsertAutoOld")
@Operation(
summary = "自动插单旧接口",
description = "创建新工单并按基准时间+冻结期自动排程。若锚点被占用,则占位工单及后续工单后移;若前面有空挡,新工单自动前移到设备最早可用时间",
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(
description = "自动插单参数",
......@@ -456,8 +523,8 @@ public class ResourceGanttController {
)
)
)
public R<String> insertOrderAuto(@RequestBody Map<String, Object> params) {
log.info("insertOrderAuto 请求参数: {}", params);
public R<String> insertOrderAutoOld(@RequestBody Map<String, Object> params) {
log.info("insertOrderAutoOld 请求参数: {}", params);
String sceneId = ParamValidator.getString(params, "sceneId", "场景ID");
@SuppressWarnings("unchecked")
......@@ -782,8 +849,8 @@ public class ResourceGanttController {
}
// 获取分页参数
Integer pageindex = paged.getPageIndex();
Integer pagesize = paged.getPageSize();
Integer pageindex = paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1;
Integer pagesize = paged.getPageSize() != null ? paged.getPageSize() : 1000;
// 校验能否获取对应的文件
Chromosome schedule = sceneService.loadChromosomeFromFile(sceneId);
......@@ -886,8 +953,8 @@ public class ResourceGanttController {
}
// 获取分页参数
Integer pageindex = paged.getPageIndex();
Integer pagesize = paged.getPageSize();
Integer pageindex = paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1;
Integer pagesize = paged.getPageSize() != null ? paged.getPageSize() : 1000;
// 校验能否获取对应的文件
Chromosome schedule = sceneService.loadChromosomeFromFile(sceneId);
......@@ -1108,5 +1175,49 @@ public class ResourceGanttController {
return R.ok(data);
}
private Map<String, Object> convertNewOrderPayload(Map<String, Object> source) {
Map<String, Object> target = new HashMap<>(source);
target.put("orderCode", requireValue(source, "订单编号", "orderCode", "code"));
target.put("materialId", requireValue(source, "物料ID", "materialId", "mmid"));
target.put("quantity", requireValue(source, "数量", "quantity"));
Object startDate = firstValue(source, "startDate", "begintime");
if (startDate != null) {
target.put("startDate", startDate);
}
Object endDate = firstValue(source, "endDate", "deliverytime");
if (endDate != null) {
target.put("endDate", endDate);
}
Object priority = firstValue(source, "priority", "prioritry");
if (priority != null) {
target.put("priority", priority);
}
return target;
}
private Object requireValue(Map<String, Object> source, String fieldName, String... keys) {
Object value = firstValue(source, keys);
if (value == null || String.valueOf(value).trim().isEmpty()) {
throw new IllegalArgumentException(fieldName + "不能为空");
}
return value;
}
private Object firstValue(Map<String, Object> source, String... keys) {
if (source == null || keys == null) {
return null;
}
for (String key : keys) {
Object value = source.get(key);
if (value != null && !String.valueOf(value).trim().isEmpty()) {
return value;
}
}
return null;
}
}
......@@ -1407,8 +1407,11 @@ public class GeneticDecoder {
GAScheduleResult lastGeneOnMachine = getLastMachineTask(machineTasks);
int bomtime = 0;
if (needMaterialCheck&&islockMachineTime) {
bomtime = getOperationBOMTime(operation, chromosome, 2);
earliestStartTime = Math.max(earliestStartTime, bomtime);
if(!isJit) {
bomtime = getOperationBOMTime(operation, chromosome, earliestStartTime, 2);
earliestStartTime = Math.max(earliestStartTime, bomtime);
}
}
......@@ -1521,7 +1524,16 @@ public class GeneticDecoder {
// }
//扣库存
if (needMaterialCheck && islockMachineTime) {
EditOperationBOMTime(operation, chromosome, startTime, machineTasksCache, entryIndexById, scheduleIndexById);
if(!isJit) {
EditOperationBOMTime(operation, chromosome, startTime, machineTasksCache, entryIndexById, scheduleIndexById);
}else {
int bomtime1 = getOperationBOMTime(operation, chromosome, startTime, 2);
if(bomtime1<=startTime)
{
EditOperationBOMTime(operation, chromosome, startTime, machineTasksCache, entryIndexById, scheduleIndexById);
}
}
}
//换型时间是否占用设备加工时间
//10:00 开始上班 前面的任务:24:00 结束 开始换型 休息时间 10小时
......@@ -1959,7 +1971,7 @@ if(geneDetails!=null&&geneDetails.size()>0)
* 汇总当前工序的物料约束,返回所有原料/半成品中最晚满足的那个时刻。
* 返回值含义是“这道工序最早允许因物料而开工的秒数”。
*/
private int getOperationBOMTime(Entry currentOp, Chromosome chromosome,int bomtype) {
private int getOperationBOMTime(Entry currentOp, Chromosome chromosome,int estimatedStartTime,int bomtype) {
List<OrderMaterialRequirement> opboms= currentOp.getMaterialRequirements();
if(opboms==null||!_globalParam.isIsCheckMp())
......@@ -2012,8 +2024,42 @@ if(geneDetails!=null&&geneDetails.size()>0)
sfTime = Math.max(sfTime, sfTime1);
}
}
}else {
if ( bomtype == 2) {
Map<String, OrderMaterialRequirement> totalNeededByMaterial = new HashMap<>();
for (OrderMaterialRequirement req : opboms) {
OrderMaterialRequirement reqq = totalNeededByMaterial.get(req.getMaterialId());
if (reqq != null) {
double allneeded = req.getSpentQty() / req.getMainQty() * currentOp.getQuantity();
allneeded = reqq.getRequiredQuantity() + allneeded;
reqq.setRequiredQuantity(allneeded);
reqq.setQjQty(allneeded);
} else {
OrderMaterialRequirement reqq1 = ProductionDeepCopyUtil.deepCopy(req);
double allneeded = req.getSpentQty() / req.getMainQty() * currentOp.getQuantity();
reqq1.setRequiredQuantity(allneeded);
reqq1.setQjQty(allneeded);
totalNeededByMaterial.put(req.getMaterialId(), reqq1);
}
}
List<OrderMaterialRequirement> remove=new ArrayList<>();
materialRequirementService.CalBom(chromosome,0, totalNeededByMaterial,chromosome.getMaterials(),baseTime.plusSeconds(estimatedStartTime) ,false,remove,null,false);
Optional<LocalDateTime> rawDateOpt = totalNeededByMaterial.values().stream()
.map(OrderMaterialRequirement::getUseTime)
.filter(Objects::nonNull)
.max(LocalDateTime::compareTo);
if (rawDateOpt.isPresent()) {
rawTime = (int) Duration.between(baseTime, rawDateOpt.get()).getSeconds();
}
}
}
}
if (bomtype == 0 || bomtype == 1) {
Optional<LocalDateTime> sfDateOpt = opboms.stream()
.filter(t -> !"MP".equals(t.getMaterialTypeName()) && (t.getProductOrderID() == null || t.getProductOrderID().isEmpty()))
......@@ -2936,6 +2982,7 @@ if(geneDetails!=null&&geneDetails.size()>0)
double totalSetupTime = calculateTotalSetupTime(chromosome);
chromosome.setTotalChangeoverTime(totalSetupTime);
Objectives[i] = totalSetupTime;
}
if (GlobalParam.OBJECTIVE_FLOW_TIME.equals(config.getName())) {
// 4. 最小化总流程时间 所有工序加工时间的总和
......
......@@ -337,9 +337,10 @@ if(isJit)
}
}
}
if(totalNeededByMaterial!=null&&totalNeededByMaterial.size()>0) {
//结合库存计算半成品生产数量,因为可能会修改生产数量所以和原材料分开
materialRequirementService.CalBom(chromosome,0, totalNeededByMaterial,materials,baseTime.plusSeconds(estimatedStartTime) ,false,remove,entrysBygroupId,true);
materialRequirementService.CalBom(chromosome, 0, totalNeededByMaterial, materials, baseTime.plusSeconds(estimatedStartTime), false, remove, entrysBygroupId, true);
}
totalNeededByMaterial.clear();
for (int groupId : chainGroupIds) {
List<Entry> ops = entrysBygroupId.get(groupId);
......
......@@ -444,6 +444,24 @@ public class HillClimbing {
}
FileHelper.writeLogFile(String.format("爬山法%s - kpi:%s",chromosome.getGeneStr(), fitness));
if(chromosome.getMakespan()!=0) {
FileHelper.writeLogFile(String.format("爬山法%s - kpi-Makespan: %f", chromosome.getGeneStr(), chromosome.getMakespan()));
}
if(chromosome.getDelayTime()!=0) {
FileHelper.writeLogFile(String.format("爬山法%s - kpi-DelayTime: %f", chromosome.getGeneStr(), chromosome.getDelayTime()));
}
if(chromosome.getTotalChangeoverTime()!=0) {
FileHelper.writeLogFile(String.format("爬山法%s - kpi-ChangeoverTime: %f",chromosome.getGeneStr(), chromosome.getTotalChangeoverTime()));
}
if(chromosome.getMachineLoadStd()!=0) {
FileHelper.writeLogFile(String.format("爬山法%s - kpi-MachineLoad: %f",chromosome.getGeneStr(), chromosome.getMachineLoadStd()));
}
if(chromosome.getTotalFlowTime()!=0) {
FileHelper.writeLogFile(String.format("爬山法%s - kpi-FlowTime: %f",chromosome.getGeneStr(), chromosome.getTotalFlowTime()));
}
}
/**
......@@ -613,7 +631,20 @@ public class HillClimbing {
// 假设Machine类有拷贝方法,或使用MapStruct等工具进行映射
chromosome.setMachines(ProductionDeepCopyUtil.deepCopyList(machines,Machine.class)); // 简单拷贝,实际可能需要深拷贝
chromosome.setOrders(ProductionDeepCopyUtil.deepCopyList(new CopyOnWriteArrayList<>(orders), Order.class) ); // 简单拷贝,实际可能需要深拷贝
chromosome.setOperatRel(ProductionDeepCopyUtil.deepCopyList(new CopyOnWriteArrayList<>(_entryRel), GroupResult.class) ); // 简单拷贝,实际可能需要深拷贝
chromosome.setMaterials(ProductionDeepCopyUtil.deepCopyTreeMap(materials,String.class,Material.class)); // 简单拷贝,实际可能需要深拷贝
chromosome.setAllOperations(ProductionDeepCopyUtil.deepCopyList(new CopyOnWriteArrayList<>(allOperations), Entry.class) ); // 简单拷贝,实际可能需要深拷贝
// 加载锁定工单到ResultOld
List<GAScheduleResult> lockedOrders = GlobalCacheUtil.get("locked_orders_" + chromosome.getScenarioID());
if (lockedOrders != null && !lockedOrders.isEmpty()) {
chromosome.setResultOld(ProductionDeepCopyUtil.deepCopyList(lockedOrders, GAScheduleResult.class));
FileHelper.writeLogFile("将 " + lockedOrders.size() + " 个锁定工单加载到初始种群中");
} else {
chromosome.setResultOld(new CopyOnWriteArrayList<>());
}
}
// 并行解码所有候选方案
......
......@@ -490,6 +490,7 @@ public class HybridAlgorithm {
chromosome.setMachines(ProductionDeepCopyUtil.deepCopyList(machines,Machine.class)); // 简单拷贝,实际可能需要深拷贝
chromosome.setOrders(ProductionDeepCopyUtil.deepCopyList(new CopyOnWriteArrayList<>(orders), Order.class) ); // 简单拷贝,实际可能需要深拷贝
chromosome.setOperatRel(ProductionDeepCopyUtil.deepCopyList(new CopyOnWriteArrayList<>(_entryRel), GroupResult.class) ); // 简单拷贝,实际可能需要深拷贝
chromosome.setMaterials(ProductionDeepCopyUtil.deepCopyTreeMap(materials, String.class, Material.class)); // 简单拷贝,实际可能需要深拷贝
if (chromosome.getAllOperations() == null || chromosome.getAllOperations().isEmpty()) {
......
......@@ -365,31 +365,32 @@ public class Initialization {
chromo.setOrders(new CopyOnWriteArrayList<>(orders));
if (i < sptCount) {
// SPT规则
// SPT规则 // 1 SPT(最短加工时间)
chromo.setGenerateType("SPT");
generateChromosomeByType(chromo, 1);
} else if (i < eddsptCount) {
// EDD+SPT混合策略
// EDD+SPT混合策略 先按截止日期,再按加工时间
chromo.setGenerateType("EDD+SPT");
generateEDDSPTChromosome(chromo);
} else if (i < eddCount) {
// EDD规则
// EDD规则 截止时间
chromo.setGenerateType("EDD");
generateChromosomeByType(chromo, 3);
} else if (i < bottleneckCount) {
// 瓶颈优先策略
chromo.setGenerateType("BottleneckFirst");
generateBottleneckFirstChromosome(chromo);
} else if (i < sstCount) {
// SST规则(已有物料排序)
// SST规则(已有物料排序):优先选择准备时间最短的工序
chromo.setGenerateType("SST");
generateSSTChromosome(chromo);
} else if (i < crCount) {
// CR规则
// CR规则 CR(关键比率)规则:关键比率 = (截止日期 - 当前日期) / 剩余加工时间
chromo.setGenerateType("CR");
generateCRChromosome(chromo);
} else if (i < lptCount) {
// LPT规则
// LPT规则 2 LPT(最长加工时间)
chromo.setGenerateType("LPT");
generateChromosomeByType(chromo, 2);
} else {
......@@ -461,6 +462,7 @@ public class Initialization {
}
} else if (sortType == 3) {
//截止时间
if (considerSequence) {
sortedOps.sort(Comparator.comparing((Entry op) -> op.getPriority())
.thenComparing(Entry::getSequence)
......
......@@ -1254,6 +1254,9 @@ if(demand==null)
if (needed <= 0) {
orderMaterial.setYpQty(allneeded - needed);
orderMaterial.setQjQty(needed);
orderMaterial.setUseTime(baseTime);
orderMaterial.setPurchaseStartTime(baseTime);
orderMaterial.setPurchaseEndTime(baseTime);
if (commitChanges) {
remove.add(orderMaterial);
}
......@@ -1286,13 +1289,15 @@ if(demand==null)
if (material1 == null) {
break;
}
FileHelper.writeLogFile("RoutingSupportingReplace: "+(commitChanges?"1":"0")+" " +material1.getCode()+": "+useStock);
OrderMaterialRequirement orderMaterial1 = MaterialStock(material1, rsr.getTargetmaterialid(), orderMaterial.getOrderId(), orderMaterial.getChildOrderId(), operationId, allneeded, needed, earliestStartTime, commitChanges);
if (orderMaterial1 != null) {
useStock = orderMaterial1.getUseStock();
needed -= useStock;
FileHelper.writeLogFile("RoutingSupportingReplace: "+material1.getCode()+": "+useStock);
FileHelper.writeLogFile("RoutingSupportingReplace: "+(commitChanges?"1":"0")+" " +material1.getCode()+": "+useStock);
orderMaterial.setUseStock(orderMaterial.getUseStock() + useStock);
orderMaterial.getReplaceMaterial().add(orderMaterial1);
if (needed <= 0) {
......@@ -1307,6 +1312,9 @@ if(demand==null)
if (needed <= 0) {
orderMaterial.setYpQty(allneeded - needed);
orderMaterial.setQjQty(needed);
orderMaterial.setUseTime(baseTime);
orderMaterial.setPurchaseStartTime(baseTime);
orderMaterial.setPurchaseEndTime(baseTime);
if (commitChanges) {
remove.add(orderMaterial);
}
......@@ -1376,6 +1384,9 @@ if(demand==null)
if (needed <= 0) {
orderMaterial.setYpQty(allneeded - needed);
orderMaterial.setQjQty(needed);
orderMaterial.setUseTime(orderMaterial.getArrivalTime());
orderMaterial.setPurchaseStartTime(baseTime);
orderMaterial.setPurchaseEndTime(baseTime);
if (commitChanges) {
remove.add(orderMaterial);
}
......@@ -1537,24 +1548,25 @@ if(demand==null)
.sorted(Comparator.comparing(MaterialSupply::getArrivalTime, Comparator.nullsLast(LocalDateTime::compareTo)))
.collect(Collectors.toList());
for (MaterialSupply supply : sortedInTransit) {
double useq = Math.min(needed, supply.getQuantity());
if (useq <= 0) {
continue;
}
useTransit += useq;
needed -= useq;
if(commitChanges) {
supply.setQuantity(supply.getQuantity() - useq);
}
if (earliestTime == null || (supply.getArrivalTime() != null && supply.getArrivalTime().isAfter(earliestTime))) {
earliestTime = supply.getArrivalTime();
}
if (needed <= 0) {
break;
if(sortedInTransit!=null&&sortedInTransit.size()>0) {
for (MaterialSupply supply : sortedInTransit) {
double useq = Math.min(needed, supply.getQuantity());
if (useq <= 0) {
continue;
}
useTransit += useq;
needed -= useq;
if (commitChanges) {
supply.setQuantity(supply.getQuantity() - useq);
}
if (earliestTime == null || (supply.getArrivalTime() != null && supply.getArrivalTime().isAfter(earliestTime))) {
earliestTime = supply.getArrivalTime();
}
if (needed <= 0) {
break;
}
}
}
orderMaterial.setUseTransit(useTransit);
orderMaterial.setArrivalTime(earliestTime);
......
......@@ -1936,6 +1936,18 @@ if(targetOp.getSequence()>1) {
}
resetDecodeMaterials(chromosome, baseMaterialsSnapshot);
// 重解码前重新初始化物料需求缓存,同时给MaterialRequirementService设置本次排产基准时间。
List<Material> decodeMaterials = chromosome.getMaterials() == null
? new ArrayList<>()
: new ArrayList<>(chromosome.getMaterials().values());
materialRequirementService.preloadRoutingCache(
chromosome.getScenarioID(),
baseTime,
chromosome.getOrders(),
decodeMaterials,
chromosome.getAllOperations(),
globalParam != null && globalParam.isIsCheckSf()
);
MachineSchedulerService machineScheduler = new MachineSchedulerService(baseTime);
chromosome.setMachines(ProductionDeepCopyUtil.deepCopyList(chromosome.getInitMachines(),Machine.class) );
......@@ -1943,8 +1955,9 @@ if(targetOp.getSequence()>1) {
chromosome.getResult().clear();
chromosome.setResult(new CopyOnWriteArrayList<>());
// BOM计算会使用decoder里的materials,自动插单重排也必须传入当前物料快照。
return new GeneticDecoder(globalParam,baseTime, chromosome.getMachines(),
chromosome.getOrders(), null, machineScheduler,materialRequirementService,chromosome.getScenarioID());
chromosome.getOrders(), chromosome.getMaterials(), machineScheduler,materialRequirementService,chromosome.getScenarioID());
}
private TreeMap<String, Material> resolveDecodeMaterials(Chromosome chromosome) {
......
......@@ -5,6 +5,8 @@ import com.aps.common.util.R;
import com.aps.entity.Algorithm.Chromosome;
import com.aps.entity.Algorithm.GAScheduleResult;
import com.aps.entity.Algorithm.OrderMaterialRequirement;
import com.aps.entity.Gantt.ResourceGanttVO;
import com.aps.entity.Gantt.TaskVO;
import com.aps.entity.ProdProcessExec;
import com.aps.entity.Algorithm.GlobalOperationInfo;
import com.aps.entity.Algorithm.IDAndChildID.GroupResult;
......@@ -307,6 +309,7 @@ public class ChromosomeDataService {
entityClassMap.put("prodprocessexec", ProdProcessExec.class);
entityClassMap.put("globaloperationinfo", GlobalOperationInfo.class);
entityClassMap.put("groupresult", GroupResult.class);
entityClassMap.put("task", TaskVO.class);
return entityClassMap.get(entityName.toLowerCase());
}
......@@ -418,7 +421,7 @@ public class ChromosomeDataService {
Map<String, Object> emptyResult = new HashMap<>();
emptyResult.put("records", Collections.emptyList());
emptyResult.put("totalCount", 0);
emptyResult.put("pageIndex", paged.getPageIndex() != null ? paged.getPageIndex() : 1);
emptyResult.put("pageIndex", paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1);
emptyResult.put("size", paged.getPageSize() != null ? paged.getPageSize() : 10);
return emptyResult;
}
......@@ -817,7 +820,7 @@ public class ChromosomeDataService {
}
// 应用分页
if (paged.getPageIndex() != null && paged.getPageSize() != null && paged.getPageSize() > 0) {
int pageIndex = paged.getPageIndex();
int pageIndex = Math.max(1, paged.getPageIndex());
int pageSize = paged.getPageSize();
int start = (pageIndex - 1) * pageSize;
int end = Math.min(start + pageSize, result.size());
......@@ -1180,6 +1183,13 @@ public class ChromosomeDataService {
config.setEntityName(entityName);
config.setDataSource(DataSourceType.FILE);
config.setFieldName("orders");
}
// 特殊处理:当实体是Task时,取资源甘特图中的TaskVO列表。
else if ("task".equalsIgnoreCase(key)) {
config = new EntityConfig();
config.setEntityName(entityName);
config.setDataSource(DataSourceType.FILE);
config.setFieldName("task");
} else {
// 自动创建数据库配置(默认行为)
config = createDefaultDbConfig(entityName);
......@@ -1277,6 +1287,12 @@ public class ChromosomeDataService {
}
try {
if ("task".equalsIgnoreCase(config.getEntityName())) {
Object result = extractResourceGanttTasks(chromosome);
fileDataCache.put(cacheKey, result);
return result;
}
String fieldName = config.getFieldName();
// 特殊处理:当实体是MachineOption时,使用allOperations字段
......@@ -1303,6 +1319,22 @@ public class ChromosomeDataService {
throw new RuntimeException("访问Chromosome字段失败: " + e.getMessage(), e);
}
}
private List<TaskVO> extractResourceGanttTasks(Chromosome chromosome) {
if (chromosome == null) {
return new ArrayList<>();
}
List<Machine> machineList = planResultService.InitCalendarToAllMachines3(chromosome);
List<ResourceGanttVO> resourceGanttVOs = planResultService.convertToResourceGanttVO1(chromosome, machineList);
if (resourceGanttVOs == null || resourceGanttVOs.isEmpty()) {
return new ArrayList<>();
}
return resourceGanttVOs.stream()
.filter(Objects::nonNull)
.filter(vo -> vo.getList() != null)
.flatMap(vo -> vo.getList().stream())
.collect(Collectors.toList());
}
/**
* 从缓存中获取反射字段,避免重复反射操作
......@@ -1573,7 +1605,7 @@ public class ChromosomeDataService {
}
Map<String, Object> result = new HashMap<>();
int page = paged.getPageIndex() != null ? paged.getPageIndex() : 1;
int page = paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1;
int size = paged.getPageSize() != null ? paged.getPageSize() : 10;
if (data instanceof List) {
......
......@@ -65,7 +65,7 @@ public class DatabaseQueryService {
String orderBy = buildOrderBy(paged);
// 分页参数
int page = paged.getPageIndex() != null ? paged.getPageIndex() : 1;
int page = paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1;
int size = paged.getPageSize() != null ? paged.getPageSize() : 10;
// 返回列:fields 有值时只查指定列,否则 SELECT *
......@@ -157,7 +157,7 @@ public class DatabaseQueryService {
Map<String, Object> empty = new HashMap<>();
empty.put("records", Collections.emptyList());
empty.put("totalCount", 0);
empty.put("pageIndex", paged.getPageIndex() != null ? paged.getPageIndex() : 1);
empty.put("pageIndex", paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1);
empty.put("size", paged.getPageSize() != null ? paged.getPageSize() : 10);
return empty;
}
......@@ -165,7 +165,7 @@ public class DatabaseQueryService {
String groupColsStr = String.join(", ", groupCols);
// 修复分组查询时的ORDER BY语法错误
String orderBy = buildOrderByForGroupBy(paged, groupCols);
int page = paged.getPageIndex() != null ? paged.getPageIndex() : 1;
int page = paged.getPageIndex() != null ? Math.max(1, paged.getPageIndex()) : 1;
int size = paged.getPageSize() != null ? paged.getPageSize() : 10;
String countSql = "SELECT COUNT(*) FROM (SELECT " + groupColsStr + " FROM " + tableName + whereClause + " GROUP BY " + groupColsStr + ")";
......@@ -218,8 +218,10 @@ public class DatabaseQueryService {
}
private String buildOraclePaginationSqlForGroupBy(String innerSql, int page, int size) {
int startRow = (page - 1) * size + 1;
int endRow = page * size;
int safePage = Math.max(1, page);
int offsetPage = safePage - 1;
int startRow = offsetPage * size + 1;
int endRow = safePage * size;
return "SELECT * FROM (SELECT a.*, ROWNUM rn FROM (" + innerSql + ") a WHERE ROWNUM <= " + endRow + ") WHERE rn >= " + startRow;
}
......@@ -441,8 +443,10 @@ public class DatabaseQueryService {
* 构建Oracle分页SQL;selectClause 为 null 或空时使用 *。
*/
private String buildOraclePaginationSql(String tableName, String whereClause, String orderBy, int page, int size, String selectClause) {
int startRow = (page - 1) * size + 1;
int endRow = page * size;
int safePage = Math.max(1, page);
int offsetPage = safePage - 1;
int startRow = offsetPage * size + 1;
int endRow = safePage * size;
String cols = (selectClause != null && !selectClause.trim().isEmpty()) ? selectClause : "*";
StringBuilder sql = new StringBuilder();
......@@ -661,4 +665,4 @@ public class DatabaseQueryService {
}
return result.toString();
}
}
\ No newline at end of file
}
......@@ -2000,13 +2000,8 @@ public class LanuchServiceImpl implements LanuchService {
throw new RuntimeException("物料不存在");
}
// 4. 查询工艺路线(按创建时间倒序,获取最新的)
RoutingHeader routingHeader = routingHeaderService.lambdaQuery()
.eq(RoutingHeader::getMaterialId, materialId)
.eq(RoutingHeader::getIsDeleted, 0)
.orderByDesc(RoutingHeader::getCreationTime)
.last("FETCH FIRST 1 ROWS ONLY")
.one();
// 4. 查询工艺路线:插入已有场景时优先沿用场景中同物料已使用的路线,避免误取到最新但未维护完整的路线。
RoutingHeader routingHeader = resolveRoutingHeaderForInsert(sceneId, materialId);
if (routingHeader == null) {
throw new RuntimeException("物料没有配置工艺路线");
}
......@@ -2050,4 +2045,87 @@ public class LanuchServiceImpl implements LanuchService {
log.info("插单成功,场景ID: {}, 订单编码: {}", sceneId, orderCode);
return R.ok("插单成功,订单ID: " + orderId);
}
private RoutingHeader resolveRoutingHeaderForInsert(String sceneId, String materialId) {
List<RoutingHeader> candidates = routingHeaderService.lambdaQuery()
.eq(RoutingHeader::getMaterialId, materialId)
.eq(RoutingHeader::getIsDeleted, 0)
.orderByDesc(RoutingHeader::getCreationTime)
.list();
if (CollectionUtils.isEmpty(candidates)) {
return null;
}
Set<Integer> sceneRoutingIds = prodLaunchOrderService.lambdaQuery()
.eq(ProdLaunchOrder::getSceneId, sceneId)
.eq(ProdLaunchOrder::getMaterialId, materialId)
.isNotNull(ProdLaunchOrder::getRoutingId)
.list()
.stream()
.map(ProdLaunchOrder::getRoutingId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Optional<RoutingHeader> sceneRouting = candidates.stream()
.filter(header -> sceneRoutingIds.contains(header.getId()))
.filter(this::hasResolvableProcessResources)
.findFirst();
if (sceneRouting.isPresent()) {
return sceneRouting.get();
}
Optional<RoutingHeader> namedValidRouting = candidates.stream()
.filter(header -> !isUndefinedRoutingCode(header))
.filter(this::hasResolvableProcessResources)
.findFirst();
if (namedValidRouting.isPresent()) {
return namedValidRouting.get();
}
Optional<RoutingHeader> anyValidRouting = candidates.stream()
.filter(this::hasResolvableProcessResources)
.findFirst();
return anyValidRouting.orElse(candidates.get(0));
}
private boolean hasResolvableProcessResources(RoutingHeader routingHeader) {
if (routingHeader == null || routingHeader.getId() == null) {
return false;
}
Long routingHeaderId = routingHeader.getId().longValue();
List<RoutingDetail> details = routingDetailMapper.selectList(new LambdaQueryWrapper<RoutingDetail>()
.eq(RoutingDetail::getRoutingHeaderId, routingHeaderId)
.eq(RoutingDetail::getIsDeleted, 0));
if (CollectionUtils.isEmpty(details)) {
return false;
}
Map<Long, List<RoutingDetailEquip>> equipsByDetailId = routingDetailEquipService.lambdaQuery()
.eq(RoutingDetailEquip::getRoutingHeaderId, routingHeaderId)
.eq(RoutingDetailEquip::getIsdeleted, 0)
.list()
.stream()
.filter(Objects::nonNull)
.filter(equip -> equip.getRoutingDetailId() != null)
.collect(Collectors.groupingBy(RoutingDetailEquip::getRoutingDetailId));
for (RoutingDetail detail : details) {
if (detail.getEquipTypeId() != null) {
continue;
}
boolean hasDetailEquipType = equipsByDetailId.getOrDefault(detail.getId(), Collections.emptyList())
.stream()
.anyMatch(equip -> equip.getType1() != null);
if (!hasDetailEquipType) {
return false;
}
}
return true;
}
private boolean isUndefinedRoutingCode(RoutingHeader routingHeader) {
String code = routingHeader == null ? null : routingHeader.getCode();
return code != null && code.trim().toLowerCase(Locale.ROOT).startsWith("undefined");
}
}
......@@ -27,6 +27,7 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.imageio.ImageIO;
import java.awt.Color;
......@@ -39,7 +40,9 @@ import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
......@@ -173,7 +176,7 @@ public class PlanResultService {
* 后续会按场景创建人自动回退到可用的策略配置。
*/
public Chromosome execute2(String SceneId) {
return execute2(SceneId, null, null, null);
return execute2(SceneId, 2361l, 241l, null);
}
/**
......@@ -1393,13 +1396,84 @@ public class PlanResultService {
return releasedCount;
}
private String getStringValue(Map<String, Object> data, String key) {
Object value = data == null ? null : data.get(key);
if (value == null) {
return null;
}
String text = String.valueOf(value).trim();
return text.isEmpty() ? null : text;
}
private String getFirstStringValue(Map<String, Object> data, String... keys) {
if (keys == null) {
return null;
}
for (String key : keys) {
String value = getStringValue(data, key);
if (value != null) {
return value;
}
}
return null;
}
private LocalDateTime parseOrderDateTime(Object value) {
if (value == null) {
return null;
}
if (value instanceof LocalDateTime) {
return (LocalDateTime) value;
}
String text = String.valueOf(value).trim();
if (text.isEmpty()) {
return null;
}
try {
return LocalDateTime.parse(text);
} catch (DateTimeParseException ignored) {
return OffsetDateTime.parse(text).toLocalDateTime();
}
}
private Integer parseOrderPriority(Object value) {
if (value == null || String.valueOf(value).trim().isEmpty()) {
return 1;
}
return Integer.valueOf(String.valueOf(value).trim());
}
private void applyNewOrderPayload(ProdLaunchOrder launchOrder, Map<String, Object> newOrderData) {
if (launchOrder == null || newOrderData == null) {
return;
}
String materialCode = getFirstStringValue(newOrderData, "mmcode", "materialCode");
if (materialCode != null) {
launchOrder.setMaterialCode(materialCode);
}
String materialName = getFirstStringValue(newOrderData, "mmname", "materialName");
if (materialName != null) {
launchOrder.setMaterialName(materialName);
}
String serie = getFirstStringValue(newOrderData, "series", "seriesName", "serie");
if (serie != null) {
launchOrder.setSerie(serie);
}
String groupCode = getFirstStringValue(newOrderData, "zone", "zoneid", "groupCode");
if (groupCode != null) {
launchOrder.setGroupCode(groupCode);
}
}
@Transactional(rollbackFor = Exception.class)
public Chromosome InsertOrderAuto(String sceneId, Map<String, Object> newOrderData) {
if (newOrderData == null) {
throw new RuntimeException("newOrder 不能为空");
}
String orderCode = String.valueOf(newOrderData.get("orderCode"));
String materialId = String.valueOf(newOrderData.get("materialId"));
String orderCode = getStringValue(newOrderData, "orderCode");
String materialId = getStringValue(newOrderData, "materialId");
Object qtyObj = newOrderData.get("quantity");
if (orderCode == null || orderCode.trim().isEmpty()) {
throw new RuntimeException("orderCode 不能为空");
......@@ -1411,9 +1485,13 @@ public class PlanResultService {
throw new RuntimeException("quantity 不能为空");
}
Double quantity = Double.valueOf(String.valueOf(qtyObj));
LocalDateTime startDate = parseOrderDateTime(newOrderData.get("startDate"));
LocalDateTime endDate = parseOrderDateTime(newOrderData.get("endDate"));
Integer priority = parseOrderPriority(newOrderData.get("priority"));
// 1. 创建新订单(沿用现有创建逻辑)
R<String> insertResp = lanuchService.insertOrder(sceneId, orderCode, materialId, null, null, 1, quantity);
R<String> insertResp = lanuchService.insertOrder(sceneId, orderCode, materialId,
startDate, endDate, priority, quantity);
String insertMsg = insertResp != null ? insertResp.getData() : null;
if (insertMsg == null || insertMsg.trim().isEmpty()) {
throw new RuntimeException("创建订单失败:未返回订单ID");
......@@ -1438,6 +1516,8 @@ public class PlanResultService {
if (newLaunchOrder == null) {
throw new RuntimeException("新订单不存在:" + newOrderId);
}
applyNewOrderPayload(newLaunchOrder, newOrderData);
_prodLaunchOrderService.updateById(newLaunchOrder);
List<ProdProcessExec> newProcessExecs = _prodProcessExecService.lambdaQuery()
.eq(ProdProcessExec::getSceneId, sceneId)
......@@ -1477,6 +1557,11 @@ public class PlanResultService {
if (apsTimeConfig != null && apsTimeConfig.getBaseTime() != null) {
baseTime = apsTimeConfig.getBaseTime();
}
if (baseTime == null) {
throw new RuntimeException("自动插单失败:排产基准时间为空");
}
// 自动插单后会重解码,染色体里的基准时间也要同步,避免后续按空基准时间计算。
chromosome.setBaseTime(baseTime);
LocalDateTime anchorTime = baseTime.plusSeconds(Math.max(freezeSeconds, 0L));
// 5. 自动插单(占位后推 + 空挡前移)
......@@ -2179,6 +2264,7 @@ if(job.getGeneDetails()!=null)
GlobalParam globalParam = InitGlobalParam();
Object kpiConfig = scheduleStrategyService.loadEffectiveKpiConfig(userId, baseRuleId, sceneId, userRuleId);
globalParam.applyKpiConfig(kpiConfig);
globalParam.setJit(scheduleStrategyService.loadEffectiveIsJit(userId, baseRuleId, sceneId, userRuleId));
return globalParam;
}
......
......@@ -90,6 +90,29 @@ public class ScheduleStrategyService {
}
}
public boolean loadEffectiveIsJit(Long userId, Long baseRuleId, String sceneId, String userRuleId) {
try {
Map<String, Object> params = new HashMap<>();
Long effectiveUserId = resolveScheduleUserId(sceneId, userId);
if (effectiveUserId != null) {
params.put("userId", effectiveUserId);
}
if (baseRuleId != null) {
params.put("baseRuleId", baseRuleId);
}
if (userRuleId != null && !userRuleId.trim().isEmpty()) {
params.put("userRuleId", userRuleId);
}
Map<String, Object> effectiveRule = userStrategyRuleService.getEffectiveRule(params);
return effectiveRule != null && asBoolean(effectiveRule.get("isjit"), false);
} catch (Exception e) {
log.warn("Load JIT strategy failed, use GlobalParam default isJit=false. sceneId={}, userId={}, baseRuleId={}, userRuleId={}",
sceneId, userId, baseRuleId, userRuleId, e);
return false;
}
}
public OrderSortRule createMultiConditionRule(List<Order> orders) {
return createMultiConditionRule(orders, Collections.emptyList());
}
......@@ -241,4 +264,24 @@ public class ScheduleStrategyService {
condition.setReverse(reverse);
return condition;
}
private boolean asBoolean(Object value, boolean defaultValue) {
if (value == null) {
return defaultValue;
}
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof Number) {
return ((Number) value).intValue() != 0;
}
String text = String.valueOf(value).trim();
if ("1".equals(text)) {
return true;
}
if ("0".equals(text)) {
return false;
}
return text.isEmpty() ? defaultValue : Boolean.parseBoolean(text);
}
}
......@@ -41,7 +41,8 @@ public class PlanResultServiceTest {
// sortService.test1();
// nsgaiiUtils.Test();
planResultService.execute2("72744D094BAB45948F5172E84C78B260");//2000
// planResultService.execute2("64E64F6B68094AF38CEDC418630C3CC2");//2000
planResultService.execute2("E1448B3C9C8743DEAB39708F2CFE348A");//2000
// planResultService.execute2("15210B13B88A453F8B84AAC7F16C7541");//2000
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment