diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java index 8d92716b1..188a874b1 100644 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/service/product/ErpProductServiceImpl.java @@ -98,23 +98,26 @@ public class ErpProductServiceImpl implements ErpProductService { @Transactional(rollbackFor = Exception.class) public Long createProduct(ProductSaveReqVO createReqVO) throws UnsupportedEncodingException { + String code = createReqVO.getBarCode(); + boolean autoGeneratedCode = StringUtils.isBlank(code); - String code = createReqVO.getBarCode(); ErpProductDO product = BeanUtils.toBean(createReqVO, ErpProductDO.class); if (productMapper.selectProductExist(product)) { throw exception(PRODUCT_NAME_AND_STANDARD_EXISTS); } - if (StringUtils.isBlank(code)) { + if (autoGeneratedCode) { code = autoCodeUtil.genSerialCode("PRODUCT_CODE_GENERATE", null); } else { - if (productMapper.selectOne(Wrappers.lambdaQuery().eq(ErpProductDO::getBarCode,code)) != null) { + if (productMapper.selectOne(Wrappers.lambdaQuery() + .eq(ErpProductDO::getBarCode, code)) != null) { throw exception(PRODUCT_CODE_EXISTS); } } + // TODO 芋艿:校验分类 // 插入 ErpProductCategoryDO productCategory = productCategoryMapper.selectById(product.getCategoryId()); @@ -127,8 +130,13 @@ public class ErpProductServiceImpl implements ErpProductService { id = productCategory.getParentId(); } + // 自动生成才拼接 分类编码-流水号;手工输入则原样保存 + if (autoGeneratedCode) { + product.setBarCode(productCategory.getCode() + "-" + code); + } else { + product.setBarCode(code); + } - product.setBarCode(productCategory.getCode()+ "-" + code); productMapper.insert(product); diff --git a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java index 1abef087e..473d3f69d 100644 --- a/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java +++ b/yudao-module-mes/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java @@ -171,4 +171,22 @@ public interface ErrorCodeConstants { ErrorCode CAL_HOLIDAY_EXISTS = new ErrorCode(6_001, "已设置成功,请勿重复点击"); ErrorCode FILE_NOT_EXISTS = new ErrorCode(6_002, "esop文件表库不存在"); + + //======================================排产相关 1003010000================================================= + + ErrorCode SORT_RULE_NOT_EXISTS = new ErrorCode(100_301_0001, "排序规则不能为空"); + ErrorCode UNSUPPORTED_SORTING_RULE = new ErrorCode(100_301_0002, "不支持的排序规则"); + ErrorCode SCHEDULE_PRODUCT_ID_EMPTY = new ErrorCode(100_301_0003, "产品ID不能为空"); + ErrorCode SCHEDULE_PLAN_NUMBER_INVALID = new ErrorCode(100_301_0004, "计划数量不能为空且必须大于0"); + ErrorCode SCHEDULE_PRODUCT_DEVICE_NOT_FOUND = new ErrorCode(100_301_0005, "产品未关联设备,productId={}"); + ErrorCode SCHEDULE_PRODUCT_DEVICE_UNAVAILABLE = new ErrorCode(100_301_0006, "产品关联设备均不可用,productId={}"); + ErrorCode SCHEDULE_ORDER_DETAIL_DELIVERY_DATE_EMPTY = new ErrorCode(100_301_0007, "订单明细交期不能为空,taskDetailId={}"); + ErrorCode SCHEDULE_CAPACITY_TYPE_EMPTY = new ErrorCode(100_301_0008, "产能来源不能为空"); + ErrorCode UNSUPPORTED_CAPACITY_TYPE = new ErrorCode(100_301_0009, "不支持的产能来源: {}"); + ErrorCode SCHEDULE_DELIVERY_DATE_EMPTY = new ErrorCode(100_301_0010, "订单交期不能为空,taskDetailId={}"); + ErrorCode SCHEDULE_DEVICE_SELECT_FAILED = new ErrorCode(100_301_0011, "设备分配失败,无可用设备,productId={}"); + + + + } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/enums/CapacityTypeEnum.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/enums/CapacityTypeEnum.java new file mode 100644 index 000000000..66240b285 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/enums/CapacityTypeEnum.java @@ -0,0 +1,41 @@ +package cn.iocoder.yudao.module.mes.controller.admin.deviceledger.enums; + +import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum CapacityTypeEnum { + + RATED(1, "额定产能") { + @Override + public Integer getCapacity(DeviceLedgerDO device) { + return device.getRatedCapacity(); + } + }, + DAILY_AVERAGE(2, "每日报工平均值") { + @Override + public Integer getCapacity(DeviceLedgerDO device) { + return device.getDailyAverageValue(); + } + }, + DATA_COLLECTION(3, "数据采集产能") { + @Override + public Integer getCapacity(DeviceLedgerDO device) { + return device.getDataCollectionCapacity(); + } + }; + + private final Integer type; + private final String desc; + + public abstract Integer getCapacity(DeviceLedgerDO device); + + public static CapacityTypeEnum of(Integer type) { + for (CapacityTypeEnum item : values()) { + if (item.type.equals(type)) return item; + } + return null; + } +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerPageReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerPageReqVO.java index 28bf3d8c0..3a856b28b 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerPageReqVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerPageReqVO.java @@ -86,4 +86,10 @@ public class DeviceLedgerPageReqVO extends PageParam { @Schema(description = "关联采集设备id") private Long dvId; + @Schema(description = "是否排产") + private Integer isScheduled; + + @Schema(description = "产品Id") + private Long productId; + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerRespVO.java index 1b8edaf45..9946a0c19 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerRespVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerRespVO.java @@ -163,4 +163,10 @@ public class DeviceLedgerRespVO extends BaseDO { @Schema(description = "额定产能") private Integer ratedCapacity; + + @Schema(description = "每日报工平均值(根据工单报工计算)") + private Integer dailyAverageValue; + + @Schema(description = "数据采集产能") + private Integer dataCollectionCapacity; } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerSaveReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerSaveReqVO.java index 60c80f25d..b19be7192 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerSaveReqVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/deviceledger/vo/DeviceLedgerSaveReqVO.java @@ -87,4 +87,10 @@ public class DeviceLedgerSaveReqVO { @Schema(description = "额定产能") private Integer ratedCapacity; + + @Schema(description = "每日报工平均值(根据工单报工计算)") + private Integer dailyAverageValue; + + @Schema(description = "数据采集产能") + private Integer dataCollectionCapacity; } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java index 3919ebc80..df488ee8a 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/PlanController.java @@ -15,20 +15,24 @@ import cn.iocoder.yudao.module.erp.dal.mysql.stock.ErpWarehouseMapper; import cn.iocoder.yudao.module.erp.service.stock.ErpStockOutService; import cn.iocoder.yudao.module.erp.service.stock.ErpStockService; import cn.iocoder.yudao.module.erp.service.stock.ErpWarehouseService; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO; import cn.iocoder.yudao.module.iot.framework.mqtt.utils.DateUtils; import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.*; import cn.iocoder.yudao.module.mes.controller.admin.zjproductrecord.vo.ZjProductRecordSaveReqVO; import cn.iocoder.yudao.module.mes.dal.dataobject.bom.BomDO; import cn.iocoder.yudao.module.mes.dal.dataobject.bom.BomDetailDO; +import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.organization.OrganizationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.zjproduct.ZjProductDO; import cn.iocoder.yudao.module.mes.dal.dataobject.zjproductrecord.ZjProductRecordDO; import cn.iocoder.yudao.module.mes.dal.mysql.bom.BomDetailMapper; import cn.iocoder.yudao.module.mes.dal.mysql.bom.BomMapper; +import cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper; import cn.iocoder.yudao.module.mes.dal.mysql.plan.PlanMapper; import cn.iocoder.yudao.module.mes.service.bom.BomService; +import cn.iocoder.yudao.module.mes.service.deviceledger.DeviceLedgerService; import cn.iocoder.yudao.module.mes.service.itemrequisition.ItemAnalysisService; import cn.iocoder.yudao.module.mes.service.itemrequisition.entity.ItemRequisitionAndStock; import cn.iocoder.yudao.module.mes.service.organization.OrganizationService; @@ -40,8 +44,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.context.annotation.Lazy; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -84,6 +90,10 @@ public class PlanController { @Resource private ErpStockOutService erpStockOutService; + @Resource + @Lazy + private DeviceLedgerService deviceLedgerService; + @Resource private BomMapper bomMapper; @@ -118,6 +128,40 @@ public class PlanController { return success(true); } + @PostMapping("/create-batch") + @Operation(summary = "批量创建生产计划") + @Transactional(rollbackFor = Exception.class) + @PreAuthorize("@ss.hasPermission('mes:plan:create')") + public CommonResult> createPlanBatch(@Valid @RequestBody PlanBatchCreateReqVO reqVO) { + List createReqVOList = reqVO.getCreateReqVOList(); + List ids = new ArrayList<>(createReqVOList.size()); + + for (PlanSaveReqVO req : createReqVOList) { + Long planId = planService.createPlan(req); + ids.add(planId); + + if (req.getIsPreProduction() != null + && req.getIsPreProduction().compareTo(BigDecimal.ONE) == 0) { + List zjProductDOList = zjProductService.getZjProductByProductId(req.getProductId()); + for (ZjProductDO source : zjProductDOList) { + ZjProductRecordDO record = new ZjProductRecordDO(); + record.setType(source.getType()); + record.setName(source.getName()); + record.setUnit(source.getUnit()); + record.setUpperVal(source.getUpperVal()); + record.setLowerVal(source.getLowerVal()); + record.setProductId(source.getProductId()); + record.setPlanId(planId); + zjProductRecordService.createZjProductRecord( + BeanUtils.toBean(record, ZjProductRecordSaveReqVO.class)); + } + } + } + + return success(ids); + } + + @PutMapping("/update") @Operation(summary = "更新生产计划") @PreAuthorize("@ss.hasPermission('mes:plan:update')") @@ -156,13 +200,32 @@ public class PlanController { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } + + // 原有:投料管道名称 Map organizationMap = organizationService.getOrganizationVOMap( convertSet(list, PlanRespVO::getFeedingPipeline)); + + // 新增:设备名称映射(按你项目里的设备 service 替换) + Map deviceMap = deviceLedgerService.getDeviceMap( + convertSet(list, PlanRespVO::getDeviceId)); + return BeanUtils.toBean(list, PlanRespVO.class, item -> { - MapUtils.findAndThen(organizationMap, item.getFeedingPipeline(), organization -> item.setFeedingPipelineName(organization.getName())); + MapUtils.findAndThen(organizationMap, item.getFeedingPipeline(), + organization -> item.setFeedingPipelineName(organization.getName())); + + DeviceLedgerDO device = deviceMap.get(item.getDeviceId()); + if (device != null) { + item.setDeviceName(device.getDeviceName()); + } else { + String baseName = StringUtils.hasText(item.getDeviceName()) + ? item.getDeviceName() + : "设备[" + item.getDeviceId() + "]"; + item.setDeviceName(baseName + "(已删除)"); + } }); } + @GetMapping("/export-excel") @Operation(summary = "导出生产计划 Excel") @PreAuthorize("@ss.hasPermission('mes:plan:export')") @@ -523,5 +586,14 @@ public class PlanController { List> dayCounts = planService.getLastSevenDaysCompletedCount(); return success(dayCounts); } + + @GetMapping("/gantt-by-device") + @Operation(summary = "按设备分组查询计划甘特图") + @PreAuthorize("@ss.hasPermission('mes:plan:query')") + public CommonResult> ganttByDevice(@Valid DevicePlanGanttReqVO reqVO) { + return success(planService.getDevicePlanGantt(reqVO)); + } + + } diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttReqVO.java new file mode 100644 index 000000000..e036b1165 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttReqVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.yudao.module.mes.controller.admin.plan.vo; + + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Schema(description = "设备甘特图查询参数") +public class DevicePlanGanttReqVO { + + @Schema(description = "设备ID列表") + private List deviceIds; + + @Schema(description = "查询开始时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + @Schema(description = "查询结束时间") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; +} \ No newline at end of file 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 new file mode 100644 index 000000000..99ab7e3e9 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/DevicePlanGanttRespVO.java @@ -0,0 +1,42 @@ +package cn.iocoder.yudao.module.mes.controller.admin.plan.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Schema(description = "设备分组甘特图响应") +public class DevicePlanGanttRespVO { + + @Schema(description = "设备ID") + private Long deviceId; + + @Schema(description = "设备名称") + private String deviceName; + + @Schema(description = "设备编码") + private String deviceCode; + + @Schema(description = "该设备计划列表") + private List plans; + + @Data + public static class PlanItem { + @Schema(description = "计划ID") + private Long planId; + @Schema(description = "计划开始时间") + private LocalDateTime planStartTime; + @Schema(description = "计划结束时间") + private LocalDateTime planEndTime; + @Schema(description = "最晚开工时间") + private LocalDateTime latestStartTime; + @Schema(description = "计划数量") + private Long planNumber; + @Schema(description = "产品名称") + private String productName; + @Schema(description = "设备名称") + private String deviceName; + } +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanBatchCreateReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanBatchCreateReqVO.java new file mode 100644 index 000000000..8fe93fcc1 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanBatchCreateReqVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.mes.controller.admin.plan.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +@Schema(description = "管理后台 - 生产计划批量创建 Request VO") +public class PlanBatchCreateReqVO { + + @Schema(description = "批量计划列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "批量计划列表不能为空") + @Valid + private List createReqVOList; + + @Schema(description = "排序规则(可选)") + private Integer sortRule; + + @Schema(description = "产能类型(可选)") + private Integer capacityType; +} 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 0bad43b68..3ab84fd53 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 @@ -145,4 +145,10 @@ public class PlanRespVO { @Schema(description = "合格率") @ExcelProperty("合格率") private BigDecimal passRate; + + @Schema(description = "设备id") + private Long deviceId; + + @Schema(description = "设备名称") + private String deviceName; } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanSaveReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanSaveReqVO.java index 56e69f6fa..737349017 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanSaveReqVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/plan/vo/PlanSaveReqVO.java @@ -21,6 +21,9 @@ public class PlanSaveReqVO { @Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "21176") private Long productId; + @Schema(description = "产品编码") + private String productCode; + @Schema(description = "任务单明细ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "18938") private Long taskDetailId; @@ -95,4 +98,26 @@ public class PlanSaveReqVO { @Schema(description = "合格率") private BigDecimal passRate; + + @Schema(description = "设备Id") + private Long deviceId; + + @Schema(description = "订单优先级") + private Integer orderPriority; + + @Schema(description = "任务单") + private String workOrderCode; + + @Schema(description = "交期时间") + private LocalDateTime deliveryDate; + + @Schema(description = "订单明细交期时间") + private LocalDateTime orderDetailDeliveryDate; + + @Schema(description = "订单明细Id") + private Long orderDetailId; + + @Schema(description = "最晚开工时间") + private LocalDateTime latestStartTime; + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java index 03209d756..39d9702d1 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/TaskController.java @@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.task.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDetailDO; @@ -13,6 +14,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.task.ViewTaskProductSummary; import cn.iocoder.yudao.module.mes.service.task.TaskService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import org.apache.commons.lang3.StringUtils; import org.springframework.security.access.prepost.PreAuthorize; @@ -130,6 +132,25 @@ public class TaskController { list); } + @PostMapping("/one-click-schedule") + @Operation( + summary = "一键排产", + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + required = true, + content = @io.swagger.v3.oas.annotations.media.Content( + mediaType = "application/json", + schema = @Schema(implementation = TaskOneClickScheduleReqVO.class) + ) + ) + ) + @PreAuthorize("@ss.hasPermission('mes:task:create')") + public CommonResult> oneClickSchedule(@Valid @RequestBody TaskOneClickScheduleReqVO reqVO) { + return success(taskService.oneClickSchedule(reqVO)); + + } + + + // ==================== 子表(生产任务单明细) ==================== @GetMapping("/task-detail/list-by-task-id") diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/DeviceCandidate.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/DeviceCandidate.java new file mode 100644 index 000000000..6ab716c67 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/DeviceCandidate.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.mes.controller.admin.task.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +@AllArgsConstructor +public class DeviceCandidate { + private Long deviceId; + private String deviceName; + private Integer capacityValue; + private LocalDateTime nextAvailableTime; +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskDetailRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskDetailRespVO.java index 9be3c2c75..28a3df3ea 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskDetailRespVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskDetailRespVO.java @@ -104,4 +104,8 @@ public class TaskDetailRespVO { @Schema(description = "订单单号", example = "你猜") @ExcelProperty("订单单号") private String saleOrderCode; + + @Schema(description = "任务单编码", example = "") + @ExcelProperty("任务单编码") + private String taskCode; } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskOneClickScheduleReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskOneClickScheduleReqVO.java new file mode 100644 index 000000000..b380f850a --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskOneClickScheduleReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.mes.controller.admin.task.vo; + +import cn.iocoder.yudao.module.iot.controller.admin.devicemodelrules.vo.PointRulesRespVO; +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Schema(description = "管理后台 - 一键排产 Request VO") +@Data +public class TaskOneClickScheduleReqVO { + + @ArraySchema( + arraySchema = @Schema(description = "待排产计划列表", requiredMode = Schema.RequiredMode.REQUIRED), + schema = @Schema(implementation = PlanSaveReqVO.class) + ) +// @Schema(description = "待排产计划列表", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "排产计划不能为空") + @Valid + private List createReqVO; + + @Schema(description = "排序规则,2=订单优先级规则") + @NotNull(message = "排序规则不能为空") + private Integer sortRule; + + @Schema(description = "产能来源:1-额定产能 2-每日报工平均值 3-数据采集产能", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "产能来源不能为空") + private Integer capacityType; +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskOneClickScheduleRespVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskOneClickScheduleRespVO.java new file mode 100644 index 000000000..e8c4f5bb7 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskOneClickScheduleRespVO.java @@ -0,0 +1,86 @@ +package cn.iocoder.yudao.module.mes.controller.admin.task.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - 一键排产结果") +@Data +public class TaskOneClickScheduleRespVO { + + @Schema(description = "设备ID") + private Long deviceId; + + @Schema(description = "设备名称") + private String deviceName; + + @Schema(description = "设备额定产能") + private Integer ratedCapacity; + + @Schema(description = "该设备排产计划") + private List plans; + + @Data + @Schema(description = "一键排产-计划项") + public static class PlanRespItem { + + @Schema(description = "计划ID", example = "10001") + private Long planId; + + @Schema(description = "计划编码", example = "PLAN202604090001") + private String code; + + @Schema(description = "产品ID", example = "21176") + private Long productId; + + @Schema(description = "任务单ID", example = "18331") + private Long taskId; + + @Schema(description = "任务单明细ID", example = "18938") + private Long taskDetailId; + + @Schema(description = "计划数量", example = "1200") + private Long planNumber; + +// @Schema(description = "计划开始时间") +// private LocalDateTime planStartTime; +// +// @Schema(description = "计划结束时间") +// private LocalDateTime planEndTime; + + @Schema(description = "排产天数", example = "3") + private Integer scheduleDays; +// +// @Schema(description = "最晚开工时间") +// private LocalDateTime latestStartTime; + + @Schema(description = "设备ID", example = "101") + private Long deviceId; + + @Schema(description = "来源:HISTORY-历史计划,CURRENT-本次排产", example = "CURRENT") + private String sourceType; + + @Schema(description = "任务单编码", example = "TASK202604090001") + private String taskCode; + + @Schema(description = "产品编码", example = "P202604090001") + private String productCode; + + @Schema(description = "产品名称", example = "汽车内饰件A") + private String productName; + + @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/task/vo/TaskPageReqVO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskPageReqVO.java index efa2b00b3..849be1ac6 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskPageReqVO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/task/vo/TaskPageReqVO.java @@ -41,6 +41,13 @@ public class TaskPageReqVO extends PageParam { @Schema(description = "创建时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime[] createTime; + @Schema(description = "任务单类型", example = "你猜") private String taskType; -} \ No newline at end of file + + @Schema(description = "是否急单", example = "") + private Integer isUrgent; + + @Schema(description = "是否排产", example = "") + private Integer isScheduled; +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/deviceledger/DeviceLedgerDO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/deviceledger/DeviceLedgerDO.java index 3878f16bc..4b22a0a6a 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/deviceledger/DeviceLedgerDO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/deviceledger/DeviceLedgerDO.java @@ -195,4 +195,16 @@ public class DeviceLedgerDO extends BaseDO { * 额定产能 */ private Integer ratedCapacity; + /** + * 每日报工平均值(根据工单报工计算) + */ + private Integer dailyAverageValue; + + /** + * 数据采集产能 + */ + private Integer dataCollectionCapacity; + + + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/plan/PlanDO.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/plan/PlanDO.java index afb4ac777..154596b80 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/plan/PlanDO.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/plan/PlanDO.java @@ -143,4 +143,22 @@ public class PlanDO extends BaseDO { private Long workerId; + /** + * 设备id-排产 + */ + private Long deviceId; + + + /** + * 最晚开工时间 + */ + private LocalDateTime latestStartTime; + + /** + * 交货日期 + */ + 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/dal/mysql/deviceledger/DeviceLedgerMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/deviceledger/DeviceLedgerMapper.java index 5c189f07d..62816d4ce 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/deviceledger/DeviceLedgerMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/deviceledger/DeviceLedgerMapper.java @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import com.alibaba.excel.util.StringUtils; import org.apache.ibatis.annotations.Mapper; import cn.iocoder.yudao.module.mes.controller.admin.deviceledger.vo.*; +import org.apache.ibatis.annotations.Param; /** * 设备类型 Mapper @@ -38,6 +39,7 @@ public interface DeviceLedgerMapper extends BaseMapperX { .eqIfPresent(DeviceLedgerDO::getDeviceRemark, reqVO.getDeviceRemark()) .eqIfPresent(DeviceLedgerDO::getRemark, reqVO.getRemark()) .eqIfPresent(DeviceLedgerDO::getSort, reqVO.getSort()) + .eqIfPresent(DeviceLedgerDO::getIsScheduled, reqVO.getIsScheduled()) .betweenIfPresent(DeviceLedgerDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(DeviceLedgerDO::getCreateTime); @@ -53,4 +55,6 @@ public interface DeviceLedgerMapper extends BaseMapperX { return selectPage(reqVO, deviceLedgerDOLambdaQueryWrapperX); } + List selectListByIds(@Param("ids") Collection ids); + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java index 28404347b..f71bc714c 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/plan/PlanMapper.java @@ -43,9 +43,11 @@ public interface PlanMapper extends BaseMapperX { .eqIfPresent(PlanDO::getProductionManagerId, reqVO.getProductionManagerId()) .eqIfPresent(PlanDO::getRemark, reqVO.getRemark()) .betweenIfPresent(PlanDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(PlanDO::getPriorityNum) - .orderByDesc(PlanDO::getPlanStartTime) - .orderByDesc(PlanDO::getPlanEndTime)); + .orderByDesc(PlanDO::getId)); +// +// .orderByDesc(PlanDO::getPriorityNum) +// .orderByDesc(PlanDO::getPlanStartTime) +// .orderByDesc(PlanDO::getPlanEndTime)); } default PlanDO selectByNo(String no) { diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/task/TaskMapper.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/task/TaskMapper.java index cbb557b49..5a89b4fdb 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/task/TaskMapper.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/task/TaskMapper.java @@ -26,6 +26,7 @@ public interface TaskMapper extends BaseMapperX { .eqIfPresent(TaskDO::getStatus, reqVO.getStatus()) .eqIfPresent(TaskDO::getProcessInstanceId, reqVO.getProcessInstanceId()) .eqIfPresent(TaskDO::getRemark, reqVO.getRemark()) + .eqIfPresent(TaskDO::getTaskType, reqVO.getTaskType()) .betweenIfPresent(TaskDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(TaskDO::getId)); } @@ -38,6 +39,9 @@ public interface TaskMapper extends BaseMapperX { .betweenIfPresent(TaskDO::getDeliveryDate, reqVO.getDeliveryDate()) .inIfPresent(TaskDO::getStatus, TaskStatusEnum.getEnablePlanStatus()) .eqIfPresent(TaskDO::getStatus, reqVO.getStatus()) + .eqIfPresent(TaskDO::getIsUrgent, reqVO.getIsUrgent()) + .likeIfPresent(TaskDO::getTaskType, reqVO.getTaskType()) + .eqIfPresent(TaskDO::getIsScheduled, reqVO.getIsScheduled()) .eqIfPresent(TaskDO::getProcessInstanceId, reqVO.getProcessInstanceId()) .eqIfPresent(TaskDO::getRemark, reqVO.getRemark()) .betweenIfPresent(TaskDO::getCreateTime, reqVO.getCreateTime()) diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerService.java index 85867d2ea..4adb90ceb 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerService.java @@ -4,9 +4,6 @@ import java.io.UnsupportedEncodingException; import java.util.*; import javax.validation.*; -import cn.iocoder.yudao.module.erp.controller.admin.mold.vo.MoldRespVO; -import cn.iocoder.yudao.module.common.dal.dataobject.mold.MoldDO; -import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; import cn.iocoder.yudao.module.mes.controller.admin.deviceledger.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -83,4 +80,8 @@ public interface DeviceLedgerService { List exportMold(Long id); void regenerateCode(Long id, String code) throws UnsupportedEncodingException; + + Map getDeviceMap(Set longs); + + } \ 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/deviceledger/DeviceLedgerServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerServiceImpl.java index ed35bd072..9fa05db06 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/deviceledger/DeviceLedgerServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.mes.service.deviceledger; +import cn.hutool.core.collection.CollStreamUtil; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.module.common.enums.CodeTypeEnum; @@ -11,8 +12,11 @@ import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProduc import cn.iocoder.yudao.module.common.dal.dataobject.mold.MoldBrandDO; import cn.iocoder.yudao.module.common.dal.dataobject.mold.MoldDO; import cn.iocoder.yudao.module.common.dal.mysql.mold.MoldMapper; +import cn.iocoder.yudao.module.erp.dal.dataobject.productdevicerel.ProductDeviceRelDO; +import cn.iocoder.yudao.module.erp.dal.mysql.productdevicerel.ProductDeviceRelMapper; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceRespVO; import cn.iocoder.yudao.module.iot.controller.admin.device.vo.DeviceSaveReqVO; +import cn.iocoder.yudao.module.iot.dal.dataobject.device.DeviceDO; import cn.iocoder.yudao.module.iot.service.device.DeviceService; import cn.iocoder.yudao.module.mes.dal.dataobject.devicetype.DeviceTypeDO; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; @@ -44,6 +48,7 @@ import org.springframework.validation.annotation.Validated; import java.io.UnsupportedEncodingException; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import cn.iocoder.yudao.module.mes.controller.admin.deviceledger.vo.*; @@ -90,6 +95,9 @@ public class DeviceLedgerServiceImpl implements DeviceLedgerService { @Resource private MoldMapper moldMapper; + @Resource + private ProductDeviceRelMapper productDeviceRelMapper; + @Resource private ErpProductUnitService productUnitService; @@ -292,6 +300,23 @@ public class DeviceLedgerServiceImpl implements DeviceLedgerService { @Override public PageResult getDeviceLedgerPage(DeviceLedgerPageReqVO pageReqVO) { + + if(pageReqVO.getProductId() != null ){ + List relList = productDeviceRelMapper.selectList( + Wrappers.lambdaQuery() + .eq(ProductDeviceRelDO::getProductId, pageReqVO.getProductId()) + .select(ProductDeviceRelDO::getDeviceId) + ); + + String ids = relList.stream() + .map(ProductDeviceRelDO::getDeviceId) + .filter(Objects::nonNull) + .map(String::valueOf) + .collect(Collectors.joining(",")); + + pageReqVO.setIds(ids); + } + PageResult deviceLedgerDOPageResult = deviceLedgerMapper.selectPage(pageReqVO); for (DeviceLedgerDO deviceLedgerDO : deviceLedgerDOPageResult.getList()) { if (deviceLedgerDO.getDeviceType()!=null){ @@ -398,4 +423,12 @@ public class DeviceLedgerServiceImpl implements DeviceLedgerService { } + @Override + public Map getDeviceMap(Set ids) { + if (ids == null || ids.isEmpty()) { + return Collections.emptyMap(); + } + return deviceLedgerMapper.selectListByIds(ids).stream() + .collect(Collectors.toMap(DeviceLedgerDO::getId, Function.identity(), (a, b) -> a)); + } } \ 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/plan/PlanService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java index e4f0ad0b2..b98b10078 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanService.java @@ -3,10 +3,7 @@ package cn.iocoder.yudao.module.mes.service.plan; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ErpProductRespVO; import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionSaveReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanPageReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanRespVO; -import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanStatusUpdateVO; +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.service.itemrequisition.entity.ItemRequisitionAndStock; @@ -109,4 +106,8 @@ public interface PlanService { List getPlanCapacity(List statusList, LocalDateTime startTime, LocalDateTime endTime); List> getLastSevenDaysCompletedCount(); + + List getDevicePlanGantt(DevicePlanGanttReqVO reqVO); + + } \ 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/plan/PlanServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/plan/PlanServiceImpl.java index 0a7157a59..3d0136905 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 @@ -19,12 +19,14 @@ import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequi import cn.iocoder.yudao.module.mes.controller.admin.itemrequisition.vo.ItemRequisitionStatusEnum; import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.*; import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskStatusEnum; +import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.itemrequisition.ItemRequisitionDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.organization.OrganizationDO; import cn.iocoder.yudao.module.mes.dal.dataobject.paigongrecord.PaigongRecordDO; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDetailDO; +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; @@ -37,11 +39,13 @@ import cn.iocoder.yudao.module.mes.service.paigongrecord.PaigongRecordService; import cn.iocoder.yudao.module.mes.service.task.TaskService; 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; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import io.swagger.v3.oas.annotations.media.Schema; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; @@ -95,6 +99,16 @@ public class PlanServiceImpl implements PlanService { @Resource private ErpWarehouseMapper erpWarehouseMapper; + @Resource + @Lazy + private DeviceLedgerMapper deviceLedgerMapper; + + @Resource + @Lazy + private ErpProductService erpProductService; + + + @Override @Transactional(rollbackFor = Exception.class) public Long createPlan(PlanSaveReqVO createReqVO) { @@ -496,4 +510,71 @@ public class PlanServiceImpl implements PlanService { List planDOList = planMapper.selectBy(pageReqVO); return planDOList; } + + @Override + public List getDevicePlanGantt(DevicePlanGanttReqVO reqVO) { + LambdaQueryWrapper qw = Wrappers.lambdaQuery() + .eq(PlanDO::getDeleted, false) + .eq(PlanDO::getIsEnable, true) + .isNotNull(PlanDO::getDeviceId) + .orderByAsc(PlanDO::getDeviceId) + .orderByAsc(PlanDO::getPlanStartTime); + + if (CollUtil.isNotEmpty(reqVO.getDeviceIds())) { + qw.in(PlanDO::getDeviceId, reqVO.getDeviceIds()); + } + // 与查询区间有交集:start<=endTime && end>=startTime + if (reqVO.getStartTime() != null) { + qw.ge(PlanDO::getPlanEndTime, reqVO.getStartTime()); + } + if (reqVO.getEndTime() != null) { + qw.le(PlanDO::getPlanStartTime, reqVO.getEndTime()); + } + + List planList = planMapper.selectList(qw); + if (CollUtil.isEmpty(planList)) { + return Collections.emptyList(); + } + + 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()); + + Map deviceMap = deviceLedgerMapper.selectBatchIds(deviceIds).stream() + .collect(Collectors.toMap(DeviceLedgerDO::getId, d -> d, (a, b) -> a)); + Map productMap = erpProductService.getProductMap(productIds); + + 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(); + DeviceLedgerDO device = deviceMap.get(deviceId); + + DevicePlanGanttRespVO vo = new DevicePlanGanttRespVO(); + vo.setDeviceId(deviceId); + vo.setDeviceName(device == null ? null : device.getDeviceName()); + vo.setDeviceCode(device == null ? null : device.getDeviceCode()); + + List plans = new ArrayList<>(); + for (PlanDO plan : entry.getValue()) { + DevicePlanGanttRespVO.PlanItem item = new DevicePlanGanttRespVO.PlanItem(); + item.setPlanId(plan.getId()); + item.setPlanStartTime(plan.getPlanStartTime()); + item.setPlanEndTime(plan.getPlanEndTime()); + item.setLatestStartTime(plan.getLatestStartTime()); // 前提:PlanDO已加该字段 + item.setPlanNumber(plan.getPlanNumber()); + ErpProductDO product = productMap.get(plan.getProductId()); + item.setProductName(product == null ? null : product.getName()); + item.setDeviceName(device == null ? null : device.getDeviceName()); + plans.add(item); + } + vo.setPlans(plans); + result.add(vo); + } + return result; + } + + + } \ 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/task/TaskService.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java index 75317bc27..7d46fb31e 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskService.java @@ -4,10 +4,8 @@ import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskDetailRespVO; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskPageReqVO; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskRespVO; -import cn.iocoder.yudao.module.mes.controller.admin.task.vo.TaskSaveReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; +import cn.iocoder.yudao.module.mes.controller.admin.task.vo.*; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDetailDO; @@ -161,8 +159,15 @@ public interface TaskService { */ List generatePlan2(Long taskId, Long productId, Long totalNumber, int productsOfPlan); - /** - * 根据任务单明细id,产品id,产品总数,计划数量,每个计划产品数生成planDO - * */ - List generatePlan3(Long taskId,Long taskDetailId, Long productId, Long totalNumber, int productsOfPlan); + /** + * 根据任务单明细id,产品id,产品总数,计划数量,每个计划产品数生成planDO + */ + List generatePlan3(Long taskId, Long taskDetailId, Long productId, Long totalNumber, int productsOfPlan); + + + /** + * 一件排产 + */ + List oneClickSchedule(TaskOneClickScheduleReqVO reqVO); + } \ 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/task/TaskServiceImpl.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java index 9c9e13b31..2f32ed247 100644 --- a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/service/task/TaskServiceImpl.java @@ -6,28 +6,44 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.erp.controller.admin.autocode.util.AutoCodeUtil; +import cn.iocoder.yudao.module.erp.controller.admin.product.vo.product.ProductRelationRespVO; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductDO; import cn.iocoder.yudao.module.erp.dal.dataobject.product.ErpProductUnitDO; +import cn.iocoder.yudao.module.erp.dal.mysql.product.ErpProductMapper; import cn.iocoder.yudao.module.erp.service.product.ErpProductService; import cn.iocoder.yudao.module.erp.service.product.ErpProductUnitService; +import cn.iocoder.yudao.module.mes.controller.admin.deviceledger.enums.CapacityTypeEnum; +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanStatusEnum; import cn.iocoder.yudao.module.mes.controller.admin.task.vo.*; +import cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO; import cn.iocoder.yudao.module.mes.dal.dataobject.plan.PlanDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.TaskDetailDO; import cn.iocoder.yudao.module.mes.dal.dataobject.task.ViewTaskProductSummary; +import cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper; import cn.iocoder.yudao.module.mes.dal.mysql.plan.PlanMapper; import cn.iocoder.yudao.module.mes.dal.mysql.task.TaskDetailMapper; import cn.iocoder.yudao.module.mes.dal.mysql.task.TaskMapper; import cn.iocoder.yudao.module.mes.dal.mysql.task.ViewTaskProductSummaryMapper; import cn.iocoder.yudao.module.mes.dal.redis.no.MesNoRedisDAO; +import cn.iocoder.yudao.module.mes.strategy.task.ScheduleSortStrategyFactory; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import javax.annotation.Resource; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; import java.util.*; +import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; @@ -40,6 +56,7 @@ import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; */ @Service @Validated +@Slf4j public class TaskServiceImpl implements TaskService { @Resource @@ -49,8 +66,17 @@ public class TaskServiceImpl implements TaskService { @Resource private MesNoRedisDAO noRedisDAO; + @Resource + private ErpProductMapper erpProductMapper; + @Resource + @Lazy + private DeviceLedgerMapper deviceLedgerMapper; + @Resource private AutoCodeUtil autoCodeUtil; + @Resource + private ScheduleSortStrategyFactory scheduleSortStrategyFactory; + @Override @Transactional(rollbackFor = Exception.class) @@ -239,6 +265,9 @@ public class TaskServiceImpl implements TaskService { for (TaskDetailRespVO respVO : resList) { Long number = planMapper.selectSumTaskDetail(respVO.getId(),null); respVO.setPlanNumber(number); + TaskDO taskDO = taskMapper.selectOne(Wrappers.lambdaQuery().eq(TaskDO::getId, respVO.getTaskId())); + respVO.setTaskCode(taskDO !=null && StringUtils.isNotBlank(taskDO.getCode()) ? taskDO.getCode() : ""); + } return resList; @@ -333,4 +362,308 @@ public class TaskServiceImpl implements TaskService { } return list; } + + @Override + @Transactional(rollbackFor = Exception.class) + public List oneClickSchedule(TaskOneClickScheduleReqVO reqVO) { + if (reqVO == null || CollUtil.isEmpty(reqVO.getCreateReqVO())) { + return Collections.emptyList(); + } + Map taskCache = new HashMap<>(); + Map productCache = new HashMap<>(); + + // 1) 先按规则排序 + List sortedPlans = new ArrayList<>(reqVO.getCreateReqVO()); + // 排序前:若明细交期为空,则回填为订单交期 + sortedPlans.forEach(plan -> { + if (plan.getOrderDetailDeliveryDate() == null) { + plan.setOrderDetailDeliveryDate(plan.getDeliveryDate()); + } + }); + scheduleSortStrategyFactory.get(reqVO.getSortRule()).sort(sortedPlans); + for (PlanSaveReqVO p : sortedPlans) { + log.info("sorted: productCode={}, deliveryDate={}, orderDetailDeliveryDate={}, priority={}, workOrderCode={}, detailId={}, planNumber={}", + p.getProductCode(), + p.getDeliveryDate(), + p.getOrderDetailDeliveryDate(), + p.getOrderPriority(), + p.getWorkOrderCode(), + p.getOrderDetailId(), + p.getPlanNumber()); + } + + // 2) 返回结果:按设备分组 + Map deviceResultMap = new LinkedHashMap<>(); + + // 3) 设备下次可开工时间(内存态,边排边更新) + Map deviceNextAvailableTime = new HashMap<>(); + + CapacityTypeEnum capacityType = CapacityTypeEnum.of(reqVO.getCapacityType()); + if (capacityType == null) { + throw exception(UNSUPPORTED_CAPACITY_TYPE, reqVO.getCapacityType()); + } + + DateTimeFormatter DATETIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + + + for (PlanSaveReqVO item : sortedPlans) { + if (item.getProductId() == null) { + throw exception(SCHEDULE_PRODUCT_ID_EMPTY); + } + if (item.getPlanNumber() == null || item.getPlanNumber() <= 0) { + throw exception(SCHEDULE_PLAN_NUMBER_INVALID); + } + + // 4) 找产品关联设备(单设备直接用;多设备按设备可用时间最早优先,再按id升序兜底) + List deviceRels = erpProductMapper.selectDevicesByProductId(item.getProductId()); + if (CollUtil.isEmpty(deviceRels)) { + throw exception(SCHEDULE_PRODUCT_DEVICE_NOT_FOUND, item.getProductId()); + } + + // 构建设备候选(含选中口径的产能 + nextAvailable) + List candidates = new ArrayList<>(); + for (ProductRelationRespVO rel : deviceRels) { + if (rel == null || rel.getId() == null) { + continue; + } + Long deviceId = rel.getId(); + DeviceLedgerDO device = deviceLedgerMapper.selectById(deviceId); + if (device == null) { + continue; + } + + Integer capacityValue = capacityType.getCapacity(device); + if (capacityValue == null || capacityValue <= 0) { + continue; // 当前口径产能无效则跳过 + } + + LocalDateTime nextTime = deviceNextAvailableTime.get(deviceId); + if (nextTime == null) { + nextTime = queryDeviceNextAvailableTime(deviceId); + deviceNextAvailableTime.put(deviceId, nextTime); + } + + candidates.add(new DeviceCandidate(deviceId, device.getDeviceName(), capacityValue, nextTime)); + } + + if (CollUtil.isEmpty(candidates)) { + throw exception(SCHEDULE_PRODUCT_DEVICE_UNAVAILABLE, item.getProductId()); + } + + // 5) 选设备:优先空闲/最早可开工(nextAvailable最小),并列按deviceId升序 + DeviceCandidate chosen = candidates.stream() + .sorted(Comparator + .comparing(DeviceCandidate::getNextAvailableTime) + .thenComparing(DeviceCandidate::getDeviceId, Comparator.reverseOrder())) + .findFirst() + .orElseThrow(() -> exception(SCHEDULE_PRODUCT_DEVICE_UNAVAILABLE, item.getProductId())); + + + // 6) 按 数量/额定产能 计算天数 + int days = (int) Math.ceil((double) item.getPlanNumber() / chosen.getCapacityValue()); + if (days <= 0) { + days = 1; + } + + // 7) 计算开始/结束时间(算法生成) + LocalDate startDate = chosen.getNextAvailableTime().toLocalDate(); + LocalDateTime planStartTime = startDate.atStartOfDay(); + LocalDateTime planEndTime = startDate.plusDays(days - 1).atTime(23, 59, 59); + + // 7.1) 计算最晚开工时间(优先 orderDetailDeliveryDate,缺失则降级 deliveryDate) + LocalDate dueDate; + if (item.getOrderDetailDeliveryDate() != null) { + dueDate = item.getOrderDetailDeliveryDate().toLocalDate(); + } else if (item.getDeliveryDate() != null) { + dueDate = item.getDeliveryDate().toLocalDate();; // 如果你的 deliveryDate 是 LocalDateTime,这里改成 toLocalDate() + } else { + throw exception(SCHEDULE_DELIVERY_DATE_EMPTY, item.getTaskDetailId()); + } + + // 天数A = (截止日期 - 计划开始日期) - scheduleDays + long availableDays = ChronoUnit.DAYS.between(startDate, dueDate) + 1; + // 最晚可向后平移天数 + long dayA = availableDays - days; + LocalDateTime latestStartTime = dayA <= 0 + ? planStartTime + : planStartTime.plusDays(dayA); + + // 8) 更新该设备下次可开工时间(下一天00:00:00) + LocalDateTime nextAvailable = planEndTime.plusSeconds(1); + deviceNextAvailableTime.put(chosen.getDeviceId(), nextAvailable); + + // 9) 组装返回(按设备分组) + TaskOneClickScheduleRespVO deviceResp = deviceResultMap.computeIfAbsent(chosen.getDeviceId(), k -> { + TaskOneClickScheduleRespVO vo = new TaskOneClickScheduleRespVO(); + vo.setDeviceId(chosen.getDeviceId()); + vo.setDeviceName(chosen.getDeviceName()); + vo.setRatedCapacity(chosen.getCapacityValue()); + vo.setPlans(new ArrayList<>()); + return vo; + }); + TaskDO taskDO = getTaskCached(item.getTaskId(), taskCache); + ErpProductDO productDO = getProductCached(item.getProductId(), productCache); + + TaskOneClickScheduleRespVO.PlanRespItem p = new TaskOneClickScheduleRespVO.PlanRespItem(); + p.setDeviceId(chosen.getDeviceId()); + p.setProductId(item.getProductId()); + p.setTaskId(item.getTaskId()); + p.setTaskDetailId(item.getTaskDetailId()); + p.setPlanNumber(item.getPlanNumber()); + p.setScheduleDays(days); +// p.setPlanStartTime(planStartTime); +// p.setPlanEndTime(planEndTime); +// p.setLatestStartTime(latestStartTime); + p.setSourceType("CURRENT"); + p.setTaskCode(taskDO == null ? null : taskDO.getCode()); + p.setProductCode(productDO == null ? null : productDO.getBarCode()); + p.setProductName(productDO == null ? null : productDO.getName()); + p.setPlanStartTimeStr(planStartTime.format(DATETIME_FMT)); + p.setPlanEndTimeStr(planEndTime.format(DATETIME_FMT)); + p.setLatestStartTimeStr(latestStartTime.format(DATETIME_FMT)); + + LocalDate deliveryDate = null; + if (item.getOrderDetailDeliveryDate() != null) { + deliveryDate = item.getOrderDetailDeliveryDate().toLocalDate(); + } else if (item.getDeliveryDate() != null) { + deliveryDate = item.getDeliveryDate().toLocalDate(); + } else if (taskDO != null && taskDO.getDeliveryDate() != null) { + deliveryDate = taskDO.getDeliveryDate().toLocalDate(); + } + p.setDeliveryDateStr(deliveryDate == null ? null : deliveryDate.format(DATE_FMT)); + + deviceResp.getPlans().add(p); + } + + //查询所有设备,注释即返回符合设备 + Set historyDeviceIds = queryDeviceIdsFromCurrentMonth(); + for (Long deviceId : historyDeviceIds) { + deviceResultMap.computeIfAbsent(deviceId, k -> { + DeviceLedgerDO device = deviceLedgerMapper.selectById(deviceId); + TaskOneClickScheduleRespVO vo = new TaskOneClickScheduleRespVO(); + vo.setDeviceId(deviceId); + vo.setDeviceName(device == null ? null : device.getDeviceName()); + vo.setRatedCapacity(capacityType.getCapacity(device)); // device为空时注意判空 + vo.setPlans(new ArrayList<>()); + return vo; + }); + } + + //组装设备历史记录 + for (TaskOneClickScheduleRespVO deviceResp : deviceResultMap.values()) { + List histories = queryLatestPlanInCurrentMonth(deviceResp.getDeviceId()); + if (CollUtil.isEmpty(histories)) { + continue; + } + + List historyItems = new ArrayList<>(); + for (PlanDO history : histories) { + TaskDO taskDO = getTaskCached(history.getTaskId(), taskCache); + ErpProductDO productDO = getProductCached(history.getProductId(), productCache); + + TaskOneClickScheduleRespVO.PlanRespItem h = new TaskOneClickScheduleRespVO.PlanRespItem(); + h.setDeviceId(deviceResp.getDeviceId()); + h.setProductId(history.getProductId()); + h.setTaskId(history.getTaskId()); + h.setTaskDetailId(history.getTaskDetailId()); + h.setPlanNumber(history.getPlanNumber()); +// h.setPlanStartTime(history.getPlanStartTime()); +// h.setPlanEndTime(history.getPlanEndTime()); +// h.setLatestStartTime(history.getLatestStartTime()); + h.setSourceType("HISTORY"); + h.setTaskCode(taskDO == null ? null : taskDO.getCode()); + h.setProductCode(productDO == null ? null : productDO.getBarCode()); + h.setProductName(productDO == null ? null : productDO.getName()); + h.setPlanStartTimeStr(history.getPlanStartTime() == null ? null : history.getPlanStartTime().format(DATETIME_FMT)); + h.setPlanEndTimeStr(history.getPlanEndTime() == null ? null : history.getPlanEndTime().format(DATETIME_FMT)); + h.setLatestStartTimeStr(history.getLatestStartTime() == null ? null : history.getLatestStartTime().format(DATETIME_FMT)); + + LocalDate deliveryDate = null; + if (history.getDeliveryDate() != null) { + deliveryDate = history.getDeliveryDate().toLocalDate(); + } else if (taskDO != null && taskDO.getDeliveryDate() != null) { + deliveryDate = taskDO.getDeliveryDate().toLocalDate(); + } + h.setDeliveryDateStr(deliveryDate == null ? null : deliveryDate.format(DATE_FMT)); + + historyItems.add(h); + } + + // 放在最前,前端甘特图先看到历史衔接(按查询排序整体前置) + deviceResp.getPlans().addAll(0, historyItems); + } + + + return new ArrayList<>(deviceResultMap.values()); + } + + /** + * 初始化设备可开工时间: + * - 有历史启用计划:取最后结束时间+1秒 + * - 没有历史计划:取当前时间(并对齐到当天开始) + */ + private LocalDateTime queryDeviceNextAvailableTime(Long deviceId) { + PlanDO lastPlan = planMapper.selectOne(new LambdaQueryWrapper() + .eq(PlanDO::getDeviceId, deviceId) + .eq(PlanDO::getIsEnable, true) + .orderByDesc(PlanDO::getPlanEndTime) + .last("limit 1")); + + LocalDate today = LocalDate.now(); + LocalDate historyNextDate; + if (lastPlan == null || lastPlan.getPlanEndTime() == null) { + historyNextDate = today; + } else { + historyNextDate = lastPlan.getPlanEndTime().toLocalDate().plusDays(1); + } + + // 取 max(今天, 历史下一天) + LocalDate nextDate = historyNextDate.isBefore(today) ? today : historyNextDate; + return nextDate.atStartOfDay(); + } + + private Set queryDeviceIdsFromCurrentMonth() { + LocalDateTime monthStart = LocalDate.now().withDayOfMonth(1).atStartOfDay(); + List plans = planMapper.selectList(new LambdaQueryWrapper() + .eq(PlanDO::getIsEnable, true) + .isNotNull(PlanDO::getDeviceId) + .ge(PlanDO::getPlanEndTime, monthStart) + .select(PlanDO::getDeviceId)); + + return plans.stream() + .map(PlanDO::getDeviceId) + .collect(Collectors.toSet()); + } + + + private List queryLatestPlanInCurrentMonth(Long deviceId) { + LocalDate now = LocalDate.now(); + LocalDateTime monthStart = now.withDayOfMonth(1).atStartOfDay(); + + return planMapper.selectList(new LambdaQueryWrapper() + .eq(PlanDO::getDeviceId, deviceId) + .eq(PlanDO::getIsEnable, true) + .ge(PlanDO::getPlanEndTime, monthStart) // 本月及以后(未结束) + .orderByAsc(PlanDO::getPlanStartTime) + .orderByAsc(PlanDO::getPlanEndTime)); + } + + + private TaskDO getTaskCached(Long taskId, Map cache) { + if (taskId == null) { + return null; + } + return cache.computeIfAbsent(taskId, taskMapper::selectById); + } + + private ErpProductDO getProductCached(Long productId, Map cache) { + if (productId == null) { + return null; + } + return cache.computeIfAbsent(productId, erpProductMapper::selectById); + } + + } \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule2OrderPriorityStrategy.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule2OrderPriorityStrategy.java new file mode 100644 index 000000000..4f1c5d4a8 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule2OrderPriorityStrategy.java @@ -0,0 +1,29 @@ +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.util.Comparator; +import java.util.List; + +// 2) 规则2:订单优先级 +@Component +public class Rule2OrderPriorityStrategy implements ScheduleSortStrategy { + + private static final Comparator COMPARATOR = Comparator + .comparing(PlanSaveReqVO::getOrderPriority, Comparator.nullsLast(Comparator.reverseOrder())) + .thenComparing(PlanSaveReqVO::getDeliveryDate, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(PlanSaveReqVO::getOrderDetailDeliveryDate, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(PlanSaveReqVO::getWorkOrderCode, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(PlanSaveReqVO::getOrderDetailId, Comparator.nullsLast(Comparator.naturalOrder())); + + @Override + public Integer ruleCode() { + return 2; + } + + @Override + public void sort(List plans) { + plans.sort(COMPARATOR); + } +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule3ProductCategoryStrategy.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule3ProductCategoryStrategy.java new file mode 100644 index 000000000..5060bfdc7 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule3ProductCategoryStrategy.java @@ -0,0 +1,36 @@ +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.util.Comparator; +import java.util.List; + +// 规则3:按产品类别(产品编码)优先 +@Component +public class Rule3ProductCategoryStrategy implements ScheduleSortStrategy { + + private static final Comparator COMPARATOR = Comparator + // 1) 产品编码 ASC + .comparing(PlanSaveReqVO::getProductCode, Comparator.nullsLast(Comparator.naturalOrder())) + // 2) 订单交期 ASC + .thenComparing(PlanSaveReqVO::getDeliveryDate, Comparator.nullsLast(Comparator.naturalOrder())) + // 3) 订单优先级 DESC + .thenComparing(PlanSaveReqVO::getOrderPriority, Comparator.nullsLast(Comparator.reverseOrder())) + // 4) 订单明细交期 ASC + .thenComparing(PlanSaveReqVO::getOrderDetailDeliveryDate, Comparator.nullsLast(Comparator.naturalOrder())) + // 5) 订单号 ASC + .thenComparing(PlanSaveReqVO::getWorkOrderCode, Comparator.nullsLast(Comparator.naturalOrder())) + // 6) 明细行号 ASC + .thenComparing(PlanSaveReqVO::getOrderDetailId, Comparator.nullsLast(Comparator.naturalOrder())); + + @Override + public Integer ruleCode() { + return 3; + } + + @Override + public void sort(List plans) { + plans.sort(COMPARATOR); + } +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule4DeliveryFirstStrategy.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule4DeliveryFirstStrategy.java new file mode 100644 index 000000000..679cb7d84 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/Rule4DeliveryFirstStrategy.java @@ -0,0 +1,29 @@ +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.util.Comparator; +import java.util.List; + +// 3) 规则4:订单交期优先 +@Component +public class Rule4DeliveryFirstStrategy implements ScheduleSortStrategy { + + private static final Comparator COMPARATOR = Comparator + .comparing(PlanSaveReqVO::getDeliveryDate, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(PlanSaveReqVO::getOrderPriority, Comparator.nullsLast(Comparator.reverseOrder())) + .thenComparing(PlanSaveReqVO::getOrderDetailDeliveryDate, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(PlanSaveReqVO::getWorkOrderCode, Comparator.nullsLast(Comparator.naturalOrder())) + .thenComparing(PlanSaveReqVO::getOrderDetailId, Comparator.nullsLast(Comparator.naturalOrder())); + + @Override + public Integer ruleCode() { + return 4; + } + + @Override + public void sort(List plans) { + plans.sort(COMPARATOR); + } +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/ScheduleSortStrategy.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/ScheduleSortStrategy.java new file mode 100644 index 000000000..7718b0258 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/ScheduleSortStrategy.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.module.mes.strategy.task; + +import cn.iocoder.yudao.module.mes.controller.admin.plan.vo.PlanSaveReqVO; + +import java.util.List; + +public interface ScheduleSortStrategy { + Integer ruleCode(); + void sort(List plans); +} diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/ScheduleSortStrategyFactory.java b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/ScheduleSortStrategyFactory.java new file mode 100644 index 000000000..3474fc1ec --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/java/cn/iocoder/yudao/module/mes/strategy/task/ScheduleSortStrategyFactory.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.mes.strategy.task; + +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*; + +@Component +public class ScheduleSortStrategyFactory { + + private final Map strategyMap; + + public ScheduleSortStrategyFactory(List strategies) { + this.strategyMap = strategies.stream() + .collect(Collectors.toMap(ScheduleSortStrategy::ruleCode, Function.identity())); + } + + public ScheduleSortStrategy get(Integer ruleCode) { + if (ruleCode == null) { + throw exception(SORT_RULE_NOT_EXISTS); + } + ScheduleSortStrategy strategy = strategyMap.get(ruleCode); + if (strategy == null) { + throw exception(UNSUPPORTED_SORTING_RULE,ruleCode); + } + return strategy; + } +} \ No newline at end of file diff --git a/yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/deviceledger/DeviceledgerMapper.xml b/yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/deviceledger/DeviceledgerMapper.xml new file mode 100644 index 000000000..bc02b73a0 --- /dev/null +++ b/yudao-module-mes/yudao-module-mes-biz/src/main/resources/mapper/deviceledger/DeviceledgerMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + \ No newline at end of file