Merge remote-tracking branch 'origin/main'

main
liutao 6 days ago
commit 1a389d8df3

@ -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.<ErpProductDO>lambdaQuery().eq(ErpProductDO::getBarCode,code)) != null) {
if (productMapper.selectOne(Wrappers.<ErpProductDO>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);

@ -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={}");
}

@ -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;
}
}

@ -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;
}

@ -163,4 +163,10 @@ public class DeviceLedgerRespVO extends BaseDO {
@Schema(description = "额定产能")
private Integer ratedCapacity;
@Schema(description = "每日报工平均值(根据工单报工计算)")
private Integer dailyAverageValue;
@Schema(description = "数据采集产能")
private Integer dataCollectionCapacity;
}

@ -87,4 +87,10 @@ public class DeviceLedgerSaveReqVO {
@Schema(description = "额定产能")
private Integer ratedCapacity;
@Schema(description = "每日报工平均值(根据工单报工计算)")
private Integer dailyAverageValue;
@Schema(description = "数据采集产能")
private Integer dataCollectionCapacity;
}

@ -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<List<Long>> createPlanBatch(@Valid @RequestBody PlanBatchCreateReqVO reqVO) {
List<PlanSaveReqVO> createReqVOList = reqVO.getCreateReqVOList();
List<Long> 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<ZjProductDO> 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<Long, OrganizationDO> organizationMap = organizationService.getOrganizationVOMap(
convertSet(list, PlanRespVO::getFeedingPipeline));
// 新增:设备名称映射(按你项目里的设备 service 替换)
Map<Long, DeviceLedgerDO> 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<Map<String, Object>> dayCounts = planService.getLastSevenDaysCompletedCount();
return success(dayCounts);
}
@GetMapping("/gantt-by-device")
@Operation(summary = "按设备分组查询计划甘特图")
@PreAuthorize("@ss.hasPermission('mes:plan:query')")
public CommonResult<List<DevicePlanGanttRespVO>> ganttByDevice(@Valid DevicePlanGanttReqVO reqVO) {
return success(planService.getDevicePlanGantt(reqVO));
}
}

@ -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<Long> 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;
}

@ -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<PlanItem> 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;
}
}

@ -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<PlanSaveReqVO> createReqVOList;
@Schema(description = "排序规则(可选)")
private Integer sortRule;
@Schema(description = "产能类型(可选)")
private Integer capacityType;
}

@ -145,4 +145,10 @@ public class PlanRespVO {
@Schema(description = "合格率")
@ExcelProperty("合格率")
private BigDecimal passRate;
@Schema(description = "设备id")
private Long deviceId;
@Schema(description = "设备名称")
private String deviceName;
}

@ -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;
}

@ -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<List<TaskOneClickScheduleRespVO>> oneClickSchedule(@Valid @RequestBody TaskOneClickScheduleReqVO reqVO) {
return success(taskService.oneClickSchedule(reqVO));
}
// ==================== 子表(生产任务单明细) ====================
@GetMapping("/task-detail/list-by-task-id")

@ -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;
}

@ -104,4 +104,8 @@ public class TaskDetailRespVO {
@Schema(description = "订单单号", example = "你猜")
@ExcelProperty("订单单号")
private String saleOrderCode;
@Schema(description = "任务单编码", example = "")
@ExcelProperty("任务单编码")
private String taskCode;
}

@ -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<PlanSaveReqVO> createReqVO;
@Schema(description = "排序规则2=订单优先级规则")
@NotNull(message = "排序规则不能为空")
private Integer sortRule;
@Schema(description = "产能来源1-额定产能 2-每日报工平均值 3-数据采集产能", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "产能来源不能为空")
private Integer capacityType;
}

@ -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<PlanRespItem> 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;
}
}

@ -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;
}
@Schema(description = "是否急单", example = "")
private Integer isUrgent;
@Schema(description = "是否排产", example = "")
private Integer isScheduled;
}

@ -195,4 +195,16 @@ public class DeviceLedgerDO extends BaseDO {
*
*/
private Integer ratedCapacity;
/**
*
*/
private Integer dailyAverageValue;
/**
*
*/
private Integer dataCollectionCapacity;
}

@ -143,4 +143,22 @@ public class PlanDO extends BaseDO {
private Long workerId;
/**
* id-
*/
private Long deviceId;
/**
*
*/
private LocalDateTime latestStartTime;
/**
*
*/
private LocalDateTime deliveryDate;
}

@ -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<DeviceLedgerDO> {
.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<DeviceLedgerDO> {
return selectPage(reqVO, deviceLedgerDOLambdaQueryWrapperX);
}
List<DeviceLedgerDO> selectListByIds(@Param("ids") Collection<Long> ids);
}

@ -43,9 +43,11 @@ public interface PlanMapper extends BaseMapperX<PlanDO> {
.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) {

@ -26,6 +26,7 @@ public interface TaskMapper extends BaseMapperX<TaskDO> {
.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<TaskDO> {
.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())

@ -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<MoldVO> exportMold(Long id);
void regenerateCode(Long id, String code) throws UnsupportedEncodingException;
Map<Long, DeviceLedgerDO> getDeviceMap(Set<Long> longs);
}

@ -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<DeviceLedgerDO> getDeviceLedgerPage(DeviceLedgerPageReqVO pageReqVO) {
if(pageReqVO.getProductId() != null ){
List<ProductDeviceRelDO> relList = productDeviceRelMapper.selectList(
Wrappers.<ProductDeviceRelDO>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<DeviceLedgerDO> 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<Long, DeviceLedgerDO> getDeviceMap(Set<Long> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyMap();
}
return deviceLedgerMapper.selectListByIds(ids).stream()
.collect(Collectors.toMap(DeviceLedgerDO::getId, Function.identity(), (a, b) -> a));
}
}

@ -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<PlanDO> getPlanCapacity(List<Integer> statusList, LocalDateTime startTime, LocalDateTime endTime);
List<Map<String, Object>> getLastSevenDaysCompletedCount();
List<DevicePlanGanttRespVO> getDevicePlanGantt(DevicePlanGanttReqVO reqVO);
}

@ -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<PlanDO> planDOList = planMapper.selectBy(pageReqVO);
return planDOList;
}
@Override
public List<DevicePlanGanttRespVO> getDevicePlanGantt(DevicePlanGanttReqVO reqVO) {
LambdaQueryWrapper<PlanDO> qw = Wrappers.<PlanDO>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<PlanDO> planList = planMapper.selectList(qw);
if (CollUtil.isEmpty(planList)) {
return Collections.emptyList();
}
Set<Long> deviceIds = planList.stream().map(PlanDO::getDeviceId).filter(Objects::nonNull).collect(Collectors.toSet());
Set<Long> productIds = planList.stream().map(PlanDO::getProductId).filter(Objects::nonNull).collect(Collectors.toSet());
Map<Long, DeviceLedgerDO> deviceMap = deviceLedgerMapper.selectBatchIds(deviceIds).stream()
.collect(Collectors.toMap(DeviceLedgerDO::getId, d -> d, (a, b) -> a));
Map<Long, ErpProductDO> productMap = erpProductService.getProductMap(productIds);
Map<Long, List<PlanDO>> group = planList.stream()
.collect(Collectors.groupingBy(PlanDO::getDeviceId, LinkedHashMap::new, Collectors.toList()));
List<DevicePlanGanttRespVO> result = new ArrayList<>();
for (Map.Entry<Long, List<PlanDO>> 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<DevicePlanGanttRespVO.PlanItem> 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;
}
}

@ -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<PlanDO> generatePlan2(Long taskId, Long productId, Long totalNumber, int productsOfPlan);
/**
* ididplanDO
* */
List<PlanDO> generatePlan3(Long taskId,Long taskDetailId, Long productId, Long totalNumber, int productsOfPlan);
/**
* ididplanDO
*/
List<PlanDO> generatePlan3(Long taskId, Long taskDetailId, Long productId, Long totalNumber, int productsOfPlan);
/**
*
*/
List<TaskOneClickScheduleRespVO> oneClickSchedule(TaskOneClickScheduleReqVO reqVO);
}

@ -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.<TaskDO>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<TaskOneClickScheduleRespVO> oneClickSchedule(TaskOneClickScheduleReqVO reqVO) {
if (reqVO == null || CollUtil.isEmpty(reqVO.getCreateReqVO())) {
return Collections.emptyList();
}
Map<Long, TaskDO> taskCache = new HashMap<>();
Map<Long, ErpProductDO> productCache = new HashMap<>();
// 1) 先按规则排序
List<PlanSaveReqVO> 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<Long, TaskOneClickScheduleRespVO> deviceResultMap = new LinkedHashMap<>();
// 3) 设备下次可开工时间(内存态,边排边更新)
Map<Long, LocalDateTime> 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<ProductRelationRespVO> deviceRels = erpProductMapper.selectDevicesByProductId(item.getProductId());
if (CollUtil.isEmpty(deviceRels)) {
throw exception(SCHEDULE_PRODUCT_DEVICE_NOT_FOUND, item.getProductId());
}
// 构建设备候选(含选中口径的产能 + nextAvailable
List<DeviceCandidate> 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<Long> 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<PlanDO> histories = queryLatestPlanInCurrentMonth(deviceResp.getDeviceId());
if (CollUtil.isEmpty(histories)) {
continue;
}
List<TaskOneClickScheduleRespVO.PlanRespItem> 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<PlanDO>()
.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<Long> queryDeviceIdsFromCurrentMonth() {
LocalDateTime monthStart = LocalDate.now().withDayOfMonth(1).atStartOfDay();
List<PlanDO> plans = planMapper.selectList(new LambdaQueryWrapper<PlanDO>()
.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<PlanDO> queryLatestPlanInCurrentMonth(Long deviceId) {
LocalDate now = LocalDate.now();
LocalDateTime monthStart = now.withDayOfMonth(1).atStartOfDay();
return planMapper.selectList(new LambdaQueryWrapper<PlanDO>()
.eq(PlanDO::getDeviceId, deviceId)
.eq(PlanDO::getIsEnable, true)
.ge(PlanDO::getPlanEndTime, monthStart) // 本月及以后(未结束)
.orderByAsc(PlanDO::getPlanStartTime)
.orderByAsc(PlanDO::getPlanEndTime));
}
private TaskDO getTaskCached(Long taskId, Map<Long, TaskDO> cache) {
if (taskId == null) {
return null;
}
return cache.computeIfAbsent(taskId, taskMapper::selectById);
}
private ErpProductDO getProductCached(Long productId, Map<Long, ErpProductDO> cache) {
if (productId == null) {
return null;
}
return cache.computeIfAbsent(productId, erpProductMapper::selectById);
}
}

@ -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<PlanSaveReqVO> 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<PlanSaveReqVO> plans) {
plans.sort(COMPARATOR);
}
}

@ -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<PlanSaveReqVO> 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<PlanSaveReqVO> plans) {
plans.sort(COMPARATOR);
}
}

@ -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<PlanSaveReqVO> 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<PlanSaveReqVO> plans) {
plans.sort(COMPARATOR);
}
}

@ -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<PlanSaveReqVO> plans);
}

@ -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<Integer, ScheduleSortStrategy> strategyMap;
public ScheduleSortStrategyFactory(List<ScheduleSortStrategy> 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;
}
}

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.mes.dal.mysql.deviceledger.DeviceLedgerMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
<select id="selectListByIds" resultType="cn.iocoder.yudao.module.mes.dal.dataobject.deviceledger.DeviceLedgerDO">
SELECT *
FROM mes_device_ledger
WHERE id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND deleted = 0
</select>
</mapper>
Loading…
Cancel
Save