diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttRespVO.java index d7b184e4b..49192aa23 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttRespVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttRespVO.java @@ -34,6 +34,10 @@ public class DevicePlanGanttRespVO { private LocalDateTime latestStartTime; @Schema(description = "计划数量") private Long planNumber; + @Schema(description = "计划编码") + private String planCode; + @Schema(description = "任务单编码") + private String taskCode; @Schema(description = "产品名称") private String productName; @Schema(description = "产品编码") @@ -42,5 +46,20 @@ public class DevicePlanGanttRespVO { private String deviceName; @Schema(description = "设备编码") private String deviceCode; + + @Schema(description = "来源:HISTORY-历史计划,CURRENT-本次排产", example = "CURRENT") + private String sourceType; + + @Schema(description = "计划开始时间(yyyy-MM-dd HH:mm:ss)") + private String planStartTimeStr; + + @Schema(description = "计划结束时间(yyyy-MM-dd HH:mm:ss)") + private String planEndTimeStr; + + @Schema(description = "最晚开工时间(yyyy-MM-dd HH:mm:ss)") + private String latestStartTimeStr; + + @Schema(description = "交货日期(yyyy-MM-dd)") + private String deliveryDateStr; } } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanRespVO.java index 3ab84fd53..752cd8099 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanRespVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanRespVO.java @@ -151,4 +151,10 @@ public class PlanRespVO { @Schema(description = "设备名称") private String deviceName; + + @Schema(description = "最晚开工时间") + private LocalDateTime latestStartTime; + + @Schema(description = "交货日期") + private LocalDateTime deliveryDate; } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/itemrequisition/ItemAnalysisServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/itemrequisition/ItemAnalysisServiceImpl.java index d557d20f9..617c68ac1 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/itemrequisition/ItemAnalysisServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/itemrequisition/ItemAnalysisServiceImpl.java @@ -65,32 +65,53 @@ public class ItemAnalysisServiceImpl implements ItemAnalysisService { public List getItemAnalysis(TaskDO taskDO) { List detailDOList = taskService.getTaskDetailListByTaskId(taskDO.getId()); - Map itemMap = new HashMap<>(); - //算物料需求 - for (TaskDetailDO detail : detailDOList) { - //这里要求销售的单位和物料单位一致等于内置单位 -// BomDO bomDO = bomService.selectByProductId(detail.getProductId()); -// if (!bomDO.getUnitId().equals(ProductUnitEnum.Each.getUnitId()) -// || !detail.getUnitId().equals(ProductUnitEnum.Each.getUnitId())) { -// log.error(UNIT_ERROR); -// throw exception(new ErrorCode(500, UNIT_ERROR)); -// } + List itemList = new ArrayList<>(); - //每个taskDetail计算一次物料需求,汇总到map里面 + // 算物料需求:逐条追加,不合并 + for (TaskDetailDO detail : detailDOList) { List bomDetailDOList = bomService.getBomDetailListByProductId(detail.getProductId(), detail.getNumber()); - buildItemMap(itemMap, bomDetailDOList,detail.getProductId()); + buildItemList(itemList, bomDetailDOList, detail.getProductId()); } - //算库存 - for (Long key : itemMap.keySet()) { - BigDecimal stockCount = erpStockService.getStockCount(key); - itemMap.get(key).setStockNumber(stockCount); - BigDecimal stockWorkshopCount = stockWorkshopService.getStockCount(key); - itemMap.get(key).setStockWorkshopNumber(stockWorkshopCount); + + // 算库存:每条都查并赋值(不合并情况下可能重复物料) + for (ItemRequisitionAndStock item : itemList) { + BigDecimal stockCount = erpStockService.getStockCount(item.getItemId()); + item.setStockNumber(stockCount); + BigDecimal stockWorkshopCount = stockWorkshopService.getStockCount(item.getItemId()); + item.setStockWorkshopNumber(stockWorkshopCount); } - List list = new ArrayList<>(itemMap.values()); - list = buildDetailVOList(list); - return list; + + return buildDetailVOList(itemList); + + // ============ 合并子物料版本 =========== +// List detailDOList = taskService.getTaskDetailListByTaskId(taskDO.getId()); +// Map itemMap = new HashMap<>(); +// //算物料需求 +// for (TaskDetailDO detail : detailDOList) { +// //这里要求销售的单位和物料单位一致等于内置单位 +//// BomDO bomDO = bomService.selectByProductId(detail.getProductId()); +//// if (!bomDO.getUnitId().equals(ProductUnitEnum.Each.getUnitId()) +//// || !detail.getUnitId().equals(ProductUnitEnum.Each.getUnitId())) { +//// log.error(UNIT_ERROR); +//// throw exception(new ErrorCode(500, UNIT_ERROR)); +//// } +// +// //每个taskDetail计算一次物料需求,汇总到map里面 +// List bomDetailDOList = +// bomService.getBomDetailListByProductId(detail.getProductId(), detail.getNumber()); +// buildItemMap(itemMap, bomDetailDOList,detail.getProductId()); +// } +// //算库存 +// for (Long key : itemMap.keySet()) { +// BigDecimal stockCount = erpStockService.getStockCount(key); +// itemMap.get(key).setStockNumber(stockCount); +// BigDecimal stockWorkshopCount = stockWorkshopService.getStockCount(key); +// itemMap.get(key).setStockWorkshopNumber(stockWorkshopCount); +// } +// List list = new ArrayList<>(itemMap.values()); +// list = buildDetailVOList(list); +// return list; } //分析计划的物料需求 @@ -174,6 +195,66 @@ public class ItemAnalysisServiceImpl implements ItemAnalysisService { }); } + private void buildItemList(List itemList, + List bomDetailDOList, + Long currentProductId) { + for (BomDetailDO bomDetail : bomDetailDOList) { + Long itemId = bomDetail.getProductId(); + + // 可选:仅做单位冲突校验(跨产品同子物料) +// for (ItemRequisitionAndStock existed : itemList) { +// if (!Objects.equals(existed.getItemId(), itemId)) { +// continue; +// } +// if (!Objects.equals(existed.getUnitId(), bomDetail.getUnitId())) { +// BomDO currBom = bomService.getBom(bomDetail.getBomId()); +// BomDO prevBom = existed.getSourceBomId() == null ? null : bomService.getBom(existed.getSourceBomId()); +// +// ErpProductDO subItem = productService.getProduct(itemId); +// ErpProductDO currProduct = currentProductId == null ? null : productService.getProduct(currentProductId); +// ErpProductDO prevProduct = existed.getSourceProductId() == null ? null : productService.getProduct(existed.getSourceProductId()); +// +// ErpProductUnitDO currUnit = productUnitService.getProductUnit(bomDetail.getUnitId()); +// ErpProductUnitDO prevUnit = productUnitService.getProductUnit(existed.getUnitId()); +// +// String currProductCode = currProduct == null ? "-" : Objects.toString(currProduct.getBarCode(), "-"); +// String currProductName = currProduct == null ? "-" : Objects.toString(currProduct.getName(), "-"); +// String prevProductCode = prevProduct == null ? "-" : Objects.toString(prevProduct.getBarCode(), "-"); +// String prevProductName = prevProduct == null ? "-" : Objects.toString(prevProduct.getName(), "-"); +// +// String currBomCode = currBom == null ? "-" : Objects.toString(currBom.getCode(), "-"); +// String prevBomCode = prevBom == null ? "-" : Objects.toString(prevBom.getCode(), "-"); +// +// String subItemCode = subItem == null ? "-" : Objects.toString(subItem.getBarCode(), "-"); +// String subItemName = subItem == null ? "-" : Objects.toString(subItem.getName(), "-"); +// +// String currUnitName = currUnit == null ? "-" : Objects.toString(currUnit.getName(), "-"); +// String prevUnitName = prevUnit == null ? "-" : Objects.toString(prevUnit.getName(), "-"); +// +// String msg = String.format( +// "产品[%s-%s] 的 BOM[%s] 中,子物料[%s-%s] 单位[%s] 与 产品[%s-%s] 的 BOM[%s] 中子物料单位[%s] 冲突!应改成一致的单位", +// currProductCode, currProductName, currBomCode, +// subItemCode, subItemName, currUnitName, +// prevProductCode, prevProductName, prevBomCode, prevUnitName +// ); +// log.error(msg); +// throw exception(new ErrorCode(5_002, msg)); +// } +// } + + // 不合并:每条 BOM 明细生成一条记录 + ItemRequisitionAndStock temp = new ItemRequisitionAndStock() + .setItemId(itemId) + .setNumber(bomDetail.getUsageNumber()) + .setUnitId(bomDetail.getUnitId()) + .setSourceBomId(bomDetail.getBomId()) + .setSourceProductId(currentProductId); + + itemList.add(temp); + } + + } + private void buildItemMap(Map itemMap, List bomDetailDOList, Long currentProductId) { diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java index d61efd4e9..5ef9aa5fc 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java @@ -30,6 +30,7 @@ import cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper; import cn.iocoder.yudao.module.mes.dal.mysql.paigongrecord.PaigongRecordMapper; import cn.iocoder.yudao.module.mes.dal.mysql.plan.PlanMapper; import cn.iocoder.yudao.module.mes.dal.mysql.stockindetail.StockInDetailMapper; +import cn.iocoder.yudao.module.mes.dal.mysql.task.TaskMapper; import cn.iocoder.yudao.module.mes.dal.redis.no.MesNoRedisDAO; import cn.iocoder.yudao.module.mes.service.itemrequisition.ItemAnalysisService; import cn.iocoder.yudao.module.mes.service.itemrequisition.ItemRequisitionService; @@ -37,6 +38,7 @@ import cn.iocoder.yudao.module.mes.service.itemrequisition.entity.ItemRequisitio import cn.iocoder.yudao.module.mes.service.organization.OrganizationService; import cn.iocoder.yudao.module.mes.service.paigongrecord.PaigongRecordService; import cn.iocoder.yudao.module.mes.service.task.TaskService; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.service.user.AdminUserService; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -107,6 +109,9 @@ public class PlanServiceImpl implements PlanService { @Lazy private ErpProductService erpProductService; + @Resource + @Lazy + private TaskMapper taskMapper; @Override @@ -286,11 +291,30 @@ public class PlanServiceImpl implements PlanService { } @Override public PlanRespVO getPlanRespVO(PlanDO planDO) { - PlanRespVO planRespVO = BeanUtils.toBean(planDO, PlanRespVO.class); - planRespVO.setProductName(productService.getProduct(planDO.getProductId()).getName()); - planRespVO.setTaskCode(taskService.getTask(planDO.getTaskId()).getCode()); - planRespVO.setWorker(userService.getUser(planDO.getWorkerId()).getUsername()); - planRespVO.setFeedingPipelineName(organizationService.getOrganization(planDO.getFeedingPipeline()).getName()); + PlanRespVO planRespVO = BeanUtils.toBean(planDO, PlanRespVO.class); + + if (planDO.getProductId() != null) { + ErpProductDO product = productService.getProduct(planDO.getProductId()); + planRespVO.setProductName(product == null ? null : product.getName()); + } + + if (planDO.getTaskId() != null) { + TaskDO task = taskService.getTask(planDO.getTaskId()); + planRespVO.setTaskCode(task == null ? null : task.getCode()); + } + + if (planDO.getWorkerId() != null) { + AdminUserDO user = userService.getUser(planDO.getWorkerId()); + planRespVO.setWorker(user == null ? null : user.getUsername()); + } else { + planRespVO.setWorker(null); + } + + if (planDO.getFeedingPipeline() != null) { + OrganizationDO organization = organizationService.getOrganization(planDO.getFeedingPipeline()); + planRespVO.setFeedingPipelineName(organization == null ? null : organization.getName()); + } + return planRespVO; } @@ -538,14 +562,24 @@ public class PlanServiceImpl implements PlanService { Set deviceIds = planList.stream().map(PlanDO::getDeviceId).filter(Objects::nonNull).collect(Collectors.toSet()); Set productIds = planList.stream().map(PlanDO::getProductId).filter(Objects::nonNull).collect(Collectors.toSet()); + Set taskIds = planList.stream().map(PlanDO::getTaskId).filter(Objects::nonNull).collect(Collectors.toSet()); Map deviceMap = deviceLedgerMapper.selectBatchIds(deviceIds).stream() .collect(Collectors.toMap(DeviceLedgerDO::getId, d -> d, (a, b) -> a)); Map productMap = erpProductService.getProductMap(productIds); + Map taskCodeMap = CollUtil.isEmpty(taskIds) + ? Collections.emptyMap() + : taskMapper.selectBatchIds(taskIds).stream() + .collect(Collectors.toMap(TaskDO::getId, TaskDO::getCode, (a, b) -> a)); + + Map> group = planList.stream() .collect(Collectors.groupingBy(PlanDO::getDeviceId, LinkedHashMap::new, Collectors.toList())); + + + List result = new ArrayList<>(); for (Map.Entry> entry : group.entrySet()) { Long deviceId = entry.getKey(); @@ -557,9 +591,13 @@ public class PlanServiceImpl implements PlanService { vo.setDeviceCode(device == null ? null : device.getDeviceCode()); List plans = new ArrayList<>(); + DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + for (PlanDO plan : entry.getValue()) { DevicePlanGanttRespVO.PlanItem item = new DevicePlanGanttRespVO.PlanItem(); item.setPlanId(plan.getId()); + item.setPlanCode(plan.getCode()); + item.setTaskCode(taskCodeMap.get(plan.getTaskId())); item.setPlanStartTime(plan.getPlanStartTime()); item.setPlanEndTime(plan.getPlanEndTime()); item.setLatestStartTime(plan.getLatestStartTime()); // 前提:PlanDO已加该字段 @@ -569,6 +607,11 @@ public class PlanServiceImpl implements PlanService { item.setDeviceName(device == null ? null : device.getDeviceName()); item.setProductCode(product == null ? null : product.getBarCode()); item.setDeviceCode(device == null ? null : device.getDeviceCode()); + item.setSourceType("HISTORY"); + item.setPlanStartTimeStr(plan.getPlanStartTime().format(DATETIME_FMT)); + item.setPlanEndTimeStr(plan.getPlanEndTime().format(DATETIME_FMT)); + item.setLatestStartTimeStr(plan.getLatestStartTime().format(DATETIME_FMT)); + plans.add(item); } vo.setPlans(plans); diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule1NearDueDateStrategy.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule1NearDueDateStrategy.java new file mode 100644 index 000000000..b3b157f58 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule1NearDueDateStrategy.java @@ -0,0 +1,59 @@ +package cn.iocoder.yudao.module.mes.strategy.task; + +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.Comparator; +import java.util.List; + +@Component +public class Rule1NearDueDateStrategy implements ScheduleSortStrategy { + + @Override + public Integer ruleCode() { + return 1; + } + + @Override + public void sort(List plans) { + if (plans == null || plans.size() <= 1) { + return; + } + + plans.sort( + Comparator + // 第一排序:最晚开工时间 ASC + .comparing((PlanSaveReqVO p) -> nvlTime(p.getLatestStartTime())) + // 第二排序:订单交期 ASC + .thenComparing(p -> nvlTime(p.getDeliveryDate())) + // 第三排序:订单优先级 DESC + .thenComparing((PlanSaveReqVO p) -> nvlInt(p.getOrderPriority()), Comparator.reverseOrder()) + // 第四排序:订单明细交期时间 ASC + .thenComparing(p -> nvlTime(p.getOrderDetailDeliveryDate())) + // 第五排序:订单号 ASC(这里用任务单号 workOrderCode 作为订单号) + .thenComparing(p -> nvlStr(p.getWorkOrderCode())) + // 第六排序:明细行号 ASC(这里用订单明细Id代替) + .thenComparing(p -> nvlLong(p.getOrderDetailId())) + ); + } + + private LocalDateTime nvlTime(LocalDateTime value) { + return value != null ? value : LocalDateTime.MAX; + } + + private Integer nvlInt(Integer value) { + // DESC 排序里,null 放最后:给最小值 + return value != null ? value : Integer.MIN_VALUE; + } + + private Long nvlLong(Long value) { + return value != null ? value : Long.MAX_VALUE; + } + + private String nvlStr(String value) { + return value != null ? value : ""; + } + + +}