package cn.iocoder.yudao.module.mes.controller.admin.dv.statistics;
|
|
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
import cn.iocoder.yudao.module.mes.controller.admin.dv.statistics.vo.MesDvOeeRespVO;
|
import cn.iocoder.yudao.module.mes.controller.admin.dv.statistics.vo.MesDvStatisticsRespVO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryTypeDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.maintenrecord.MesDvMaintenRecordDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.dv.repair.MesDvRepairDO;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.machinery.MesDvMachineryTypeMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.maintenrecord.MesDvMaintenRecordMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.dv.repair.MesDvRepairMapper;
|
import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
|
import cn.iocoder.yudao.module.mes.enums.dv.MesDvMachineryStatusEnum;
|
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
import jakarta.annotation.Resource;
|
import org.springframework.format.annotation.DateTimeFormat;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.validation.annotation.Validated;
|
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RestController;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.Duration;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
|
@Tag(name = "管理后台 - MES 设备统计")
|
@RestController
|
@RequestMapping("/mes/dv/statistics")
|
@Validated
|
public class MesDvStatisticsController {
|
|
@Resource
|
private MesDvMachineryMapper machineryMapper;
|
@Resource
|
private MesDvMachineryTypeMapper machineryTypeMapper;
|
@Resource
|
private MesDvRepairMapper repairMapper;
|
@Resource
|
private MesDvMaintenRecordMapper maintenRecordMapper;
|
@Resource
|
private MesProTaskMapper taskMapper;
|
|
@GetMapping("/summary")
|
@Operation(summary = "设备统计汇总(台账/维修/保养/类型分布)")
|
@PreAuthorize("@ss.hasPermission('mes:dv-machinery:query')")
|
public CommonResult<MesDvStatisticsRespVO> getSummary() {
|
MesDvStatisticsRespVO vo = new MesDvStatisticsRespVO();
|
|
// 1. 设备台账统计
|
Long total = machineryMapper.selectCount();
|
Long producing = machineryMapper.selectCount(new LambdaQueryWrapperX<MesDvMachineryDO>()
|
.eq(MesDvMachineryDO::getStatus, MesDvMachineryStatusEnum.PRODUCING.getStatus()));
|
Long stopped = machineryMapper.selectCount(new LambdaQueryWrapperX<MesDvMachineryDO>()
|
.eq(MesDvMachineryDO::getStatus, MesDvMachineryStatusEnum.STOP.getStatus()));
|
Long maintaining = machineryMapper.selectCount(new LambdaQueryWrapperX<MesDvMachineryDO>()
|
.eq(MesDvMachineryDO::getStatus, MesDvMachineryStatusEnum.MAINTENANCE.getStatus()));
|
vo.setTotalMachinery(total);
|
vo.setProducingCount(producing);
|
vo.setStoppedCount(stopped);
|
vo.setMaintainingCount(maintaining);
|
vo.setFaultRate(total != null && total > 0
|
? (double) (stopped + maintaining) / total * 100 : 0D);
|
|
// 2. 维修统计
|
vo.setTotalRepair(repairMapper.selectCount());
|
vo.setFinishedRepair(repairMapper.selectCount(new LambdaQueryWrapperX<MesDvRepairDO>()
|
.eq(MesDvRepairDO::getStatus, cn.iocoder.yudao.module.mes.enums.dv.MesDvRepairStatusEnum.FINISHED.getStatus())));
|
vo.setProcessingRepair((vo.getTotalRepair() != null ? vo.getTotalRepair() : 0L)
|
- (vo.getFinishedRepair() != null ? vo.getFinishedRepair() : 0L));
|
|
// 3. 保养统计
|
vo.setTotalMainten(maintenRecordMapper.selectCount());
|
vo.setFinishedMainten(maintenRecordMapper.selectCount(new LambdaQueryWrapperX<MesDvMaintenRecordDO>()
|
.eq(MesDvMaintenRecordDO::getStatus, cn.iocoder.yudao.module.mes.enums.dv.MesDvMaintenRecordStatusEnum.SUBMITTED.getStatus())));
|
|
// 4. 按设备类型分布
|
List<MesDvMachineryDO> machineryList = machineryMapper.selectList();
|
Map<Long, Long> typeCountMap = new HashMap<>();
|
for (MesDvMachineryDO machinery : machineryList) {
|
if (machinery.getMachineryTypeId() == null) {
|
continue;
|
}
|
typeCountMap.merge(machinery.getMachineryTypeId(), 1L, Long::sum);
|
}
|
List<MesDvMachineryTypeDO> typeList = machineryTypeMapper.selectList();
|
Map<Long, String> typeNameMap = new HashMap<>();
|
for (MesDvMachineryTypeDO type : typeList) {
|
typeNameMap.put(type.getId(), type.getName());
|
}
|
List<MesDvStatisticsRespVO.TypeItem> typeDistribution = new ArrayList<>();
|
typeCountMap.forEach((typeId, count) -> {
|
MesDvStatisticsRespVO.TypeItem item = new MesDvStatisticsRespVO.TypeItem();
|
item.setMachineryTypeId(typeId);
|
item.setMachineryTypeName(typeNameMap.get(typeId));
|
item.setCount(count);
|
typeDistribution.add(item);
|
});
|
vo.setTypeDistribution(typeDistribution);
|
return success(vo);
|
}
|
|
@GetMapping("/oee")
|
@Operation(summary = "设备 OEE 统计(稼动率×性能开动率×良品率)")
|
@PreAuthorize("@ss.hasPermission('mes:dv-machinery:query')")
|
public CommonResult<MesDvOeeRespVO> getOee(
|
@RequestParam(value = "startTime", required = false)
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime,
|
@RequestParam(value = "endTime", required = false)
|
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
|
// 1. 查询统计区间内任务(按完成时间过滤;不传区间则统计全部)
|
List<MesProTaskDO> allTasks = taskMapper.selectList(new LambdaQueryWrapperX<MesProTaskDO>()
|
.betweenIfPresent(MesProTaskDO::getFinishDate, new LocalDateTime[]{startTime, endTime}));
|
|
// 2. 整体 OEE
|
MesDvOeeRespVO vo = new MesDvOeeRespVO();
|
OeeCalc overall = calcOee(allTasks);
|
vo.setOverallOee(overall.oee);
|
vo.setOverallAvailability(overall.availability);
|
vo.setOverallPerformance(overall.performance);
|
vo.setOverallQuality(overall.quality);
|
vo.setTaskCount((long) allTasks.size());
|
|
// 3. 按设备聚合:任务按工作站分组,设备通过 workstationId 关联工作站
|
Map<Long, List<MesProTaskDO>> tasksByWorkstation = new HashMap<>();
|
for (MesProTaskDO task : allTasks) {
|
if (task.getWorkstationId() == null) {
|
continue;
|
}
|
tasksByWorkstation.computeIfAbsent(task.getWorkstationId(), k -> new ArrayList<>()).add(task);
|
}
|
List<MesDvOeeRespVO.MachineryOee> items = new ArrayList<>();
|
for (MesDvMachineryDO machinery : machineryMapper.selectList()) {
|
if (machinery.getWorkstationId() == null) {
|
continue; // 未绑定工作站,无法关联任务
|
}
|
List<MesProTaskDO> tasks = tasksByWorkstation.getOrDefault(machinery.getWorkstationId(), Collections.emptyList());
|
OeeCalc calc = calcOee(tasks);
|
MesDvOeeRespVO.MachineryOee item = new MesDvOeeRespVO.MachineryOee();
|
item.setMachineryId(machinery.getId());
|
item.setMachineryCode(machinery.getCode());
|
item.setMachineryName(machinery.getName());
|
item.setOee(calc.oee);
|
item.setAvailability(calc.availability);
|
item.setPerformance(calc.performance);
|
item.setQuality(calc.quality);
|
item.setTaskCount((long) tasks.size());
|
item.setPlannedQuantity(calc.plannedQuantity);
|
item.setProducedQuantity(calc.producedQuantity);
|
items.add(item);
|
}
|
vo.setItems(items);
|
return success(vo);
|
}
|
|
private static OeeCalc calcOee(List<MesProTaskDO> tasks) {
|
double totalPlannedHours = 0, totalActualHours = 0;
|
BigDecimal totalPlannedQty = BigDecimal.ZERO, totalProducedQty = BigDecimal.ZERO, totalQualifyQty = BigDecimal.ZERO;
|
for (MesProTaskDO task : tasks) {
|
if (task.getDuration() != null) {
|
totalPlannedHours += task.getDuration() * 8D; // 1 工作日 = 8 小时
|
}
|
if (task.getFirstFeedbackTime() != null && task.getLastFeedbackTime() != null
|
&& task.getLastFeedbackTime().isAfter(task.getFirstFeedbackTime())) {
|
totalActualHours += Duration.between(task.getFirstFeedbackTime(), task.getLastFeedbackTime()).toMinutes() / 60D;
|
}
|
if (task.getQuantity() != null) {
|
totalPlannedQty = totalPlannedQty.add(task.getQuantity());
|
}
|
if (task.getProducedQuantity() != null) {
|
totalProducedQty = totalProducedQty.add(task.getProducedQuantity());
|
}
|
if (task.getQualifyQuantity() != null) {
|
totalQualifyQty = totalQualifyQty.add(task.getQualifyQuantity());
|
}
|
}
|
OeeCalc calc = new OeeCalc();
|
// 稼动率 = 实际报工时长 / 计划时长(无计划时长时按 1 处理)
|
calc.availability = percent(totalPlannedHours > 0 ? Math.min(1D, totalActualHours / totalPlannedHours) : 1D);
|
// 性能开动率 = 实际产量 / 计划产量
|
calc.performance = totalPlannedQty.compareTo(BigDecimal.ZERO) > 0
|
? percent(Math.min(1D, totalProducedQty.divide(totalPlannedQty, 4, RoundingMode.HALF_UP).doubleValue())) : 0D;
|
// 良品率 = 合格品 / 实际产量(无产量时按 1 处理)
|
calc.quality = totalProducedQty.compareTo(BigDecimal.ZERO) > 0
|
? percent(Math.min(1D, totalQualifyQty.divide(totalProducedQty, 4, RoundingMode.HALF_UP).doubleValue())) : 100D;
|
// OEE(%) = 稼动率(%) × 性能(%) × 良品率(%) / 10000,保留两位小数
|
calc.oee = Math.round(calc.availability * calc.performance * calc.quality / 10000D * 100D) / 100D;
|
calc.plannedQuantity = totalPlannedQty;
|
calc.producedQuantity = totalProducedQty;
|
return calc;
|
}
|
|
/** 比例(0-1) 转百分比,保留两位小数 */
|
private static double percent(double ratio) {
|
return Math.round(ratio * 10000D) / 100D;
|
}
|
|
private static class OeeCalc {
|
private Double availability;
|
private Double performance;
|
private Double quality;
|
private Double oee;
|
private BigDecimal plannedQuantity;
|
private BigDecimal producedQuantity;
|
}
|
|
}
|