diff --git a/aucma-production/src/main/java/com/aucma/production/mapper/ProdOrderNoteMapper.java b/aucma-production/src/main/java/com/aucma/production/mapper/ProdOrderNoteMapper.java index 830f3ce..0ad7786 100644 --- a/aucma-production/src/main/java/com/aucma/production/mapper/ProdOrderNoteMapper.java +++ b/aucma-production/src/main/java/com/aucma/production/mapper/ProdOrderNoteMapper.java @@ -12,6 +12,7 @@ import org.apache.ibatis.annotations.Param; * @date 2026-03-18 */ public interface ProdOrderNoteMapper { + ProdOrderNote selectProdOrderNoteByObjId(@Param("objId") Long objId); List selectProdOrderNoteList(ProdOrderNote prodOrderNote); diff --git a/aucma-production/src/main/java/com/aucma/production/mapper/ProdRouteMapper.java b/aucma-production/src/main/java/com/aucma/production/mapper/ProdRouteMapper.java index 580466e..09fdda7 100644 --- a/aucma-production/src/main/java/com/aucma/production/mapper/ProdRouteMapper.java +++ b/aucma-production/src/main/java/com/aucma/production/mapper/ProdRouteMapper.java @@ -13,6 +13,7 @@ import org.apache.ibatis.annotations.Param; * @date 2026-03-18 */ public interface ProdRouteMapper { + ProdRoute selectProdRouteByObjId(@Param("objId") Long objId); ProdRoute selectProdRouteByRouteCode(@Param("routeCode") String routeCode); diff --git a/aucma-report/src/main/java/com/aucma/report/controller/Board4Controller.java b/aucma-report/src/main/java/com/aucma/report/controller/Board4Controller.java index 211e455..706cc48 100644 --- a/aucma-report/src/main/java/com/aucma/report/controller/Board4Controller.java +++ b/aucma-report/src/main/java/com/aucma/report/controller/Board4Controller.java @@ -111,4 +111,13 @@ public class Board4Controller extends BaseController { public AjaxResult getDeviceProductionList() { return AjaxResult.success(board4Service.getDeviceProductionList()); } + + /** + * 获取18台设备开模数列表(昨日07:00-今日07:00) + */ + @Anonymous + @GetMapping("/deviceOpeningCountList") + public AjaxResult getDeviceOpeningCountList() { + return AjaxResult.success(board4Service.getDeviceOpeningCountList()); + } } diff --git a/aucma-report/src/main/java/com/aucma/report/domain/vo/Board4DeviceOpeningCountVo.java b/aucma-report/src/main/java/com/aucma/report/domain/vo/Board4DeviceOpeningCountVo.java new file mode 100644 index 0000000..e0964ea --- /dev/null +++ b/aucma-report/src/main/java/com/aucma/report/domain/vo/Board4DeviceOpeningCountVo.java @@ -0,0 +1,71 @@ +package com.aucma.report.domain.vo; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.io.Serializable; + +/** + * Board4 设备开模数VO(列表项) + * + * @author YinQ + */ +public class Board4DeviceOpeningCountVo implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 设备名称 */ + private String deviceName; + + /** 开模数,统计口径为昨日07:00到今日07:00的“机台状态-实际产出数量”增量 */ + private Long openingCount; + + /** 设备编码,仅用于后端识别OLD设备,接口不返回 */ + @JsonIgnore + private String deviceCode; + + /** 窗口内源表是否存在采集数据,仅用于判断OLD设备是否需要临时估算,接口不返回 */ + @JsonIgnore + private Integer windowDataFlag; + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public Long getOpeningCount() { + return openingCount; + } + + public void setOpeningCount(Long openingCount) { + this.openingCount = openingCount; + } + + @JsonIgnore + public String getDeviceCode() { + return deviceCode; + } + + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } + + @JsonIgnore + public Integer getWindowDataFlag() { + return windowDataFlag; + } + + public void setWindowDataFlag(Integer windowDataFlag) { + this.windowDataFlag = windowDataFlag; + } + + /** + * 是否在统计窗口内读取到了源表数据。 + */ + @JsonIgnore + public boolean hasWindowData() { + return windowDataFlag != null && windowDataFlag.intValue() > 0; + } +} diff --git a/aucma-report/src/main/java/com/aucma/report/mapper/Board4Mapper.java b/aucma-report/src/main/java/com/aucma/report/mapper/Board4Mapper.java index dd96690..deac348 100644 --- a/aucma-report/src/main/java/com/aucma/report/mapper/Board4Mapper.java +++ b/aucma-report/src/main/java/com/aucma/report/mapper/Board4Mapper.java @@ -2,7 +2,9 @@ package com.aucma.report.mapper; import com.aucma.report.domain.vo.*; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import java.util.Date; import java.util.HashMap; import java.util.List; @@ -92,4 +94,13 @@ public interface Board4Mapper { * 数据来源:BASE_DEVICE_PARAM_VAL + BASE_DEVICELEDGER */ List selectDeviceProductionAnalysis(); + + /** + * 查询18台设备开模数(昨日07:00-今日07:00) + * 数据来源:BASE_DEVICE_PARAM_VAL_YYYYMM + BASE_DEVICELEDGER + */ + List selectDeviceOpeningCountList(@Param("beginTime") Date beginTime, + @Param("endTime") Date endTime, + @Param("baselineBeginTime") Date baselineBeginTime, + @Param("tableSuffixes") List tableSuffixes); } diff --git a/aucma-report/src/main/java/com/aucma/report/service/IBoard4Service.java b/aucma-report/src/main/java/com/aucma/report/service/IBoard4Service.java index 1d02bc0..b9be758 100644 --- a/aucma-report/src/main/java/com/aucma/report/service/IBoard4Service.java +++ b/aucma-report/src/main/java/com/aucma/report/service/IBoard4Service.java @@ -61,4 +61,9 @@ public interface IBoard4Service { * 获取设备分析/产量列表 */ List getDeviceProductionList(); + + /** + * 获取18台设备开模数列表(昨日07:00-今日07:00) + */ + List getDeviceOpeningCountList(); } diff --git a/aucma-report/src/main/java/com/aucma/report/service/impl/Board4ServiceImpl.java b/aucma-report/src/main/java/com/aucma/report/service/impl/Board4ServiceImpl.java index a5be601..b492ded 100644 --- a/aucma-report/src/main/java/com/aucma/report/service/impl/Board4ServiceImpl.java +++ b/aucma-report/src/main/java/com/aucma/report/service/impl/Board4ServiceImpl.java @@ -1,6 +1,7 @@ package com.aucma.report.service.impl; import com.aucma.base.service.IBaseDeviceParamValService; +import com.aucma.base.support.DeviceParamTableRouter; import com.aucma.report.domain.vo.*; import com.aucma.report.mapper.Board4Mapper; import com.aucma.report.service.IBoard4Service; @@ -8,6 +9,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; @@ -20,12 +24,22 @@ import java.util.List; @Service public class Board4ServiceImpl implements IBoard4Service { + private static final int OPENING_COUNT_END_HOUR = 7; + private static final int OPENING_COUNT_BASELINE_LOOKBACK_HOURS = 24; + private static final String OLD_DEVICE_CODE_PREFIX = "OLD-"; + private static final int OLD_DEVICE_ESTIMATE_FLOAT_BUCKETS = 17; + private static final int OLD_DEVICE_ESTIMATE_FLOAT_CENTER = 8; + private static final double OLD_DEVICE_ESTIMATE_FLOAT_STEP = 0.01D; + @Autowired private Board4Mapper board4Mapper; @Autowired private IBaseDeviceParamValService baseDeviceParamValService; + @Autowired + private DeviceParamTableRouter deviceParamTableRouter; + /** * 获取产量统计(年累计、月累计、日累计) * 年累计取“上一自然年”工单完成量汇总(上一年1月1日到12月31日,非滚动12个月), @@ -218,6 +232,7 @@ public class Board4ServiceImpl implements IBoard4Service { return list != null ? list : new ArrayList<>(); } + /** * 安全获取Long值 */ @@ -248,4 +263,177 @@ public class Board4ServiceImpl implements IBoard4Service { } return obj.toString(); } + + + /** + * 获取18台设备开模数列表 + * 统计窗口固定为昨日07:00到今日07:00,避免看板刷新时间影响班次口径。 + */ + @Override + public List getDeviceOpeningCountList() { + // 为什么这样做:班次看板按7点交接班,后端统一锁定窗口,避免前端传参造成口径漂移。 + Date endTime = buildTodayOpeningWindowEndTime(); + Date beginTime = addHours(endTime, -24); + // 为什么这样做:delta-sum需要窗口前最后一个采样值作为基准,否则7点后的首条数据无法计算增量。 + Date baselineBeginTime = addHours(beginTime, -OPENING_COUNT_BASELINE_LOOKBACK_HOURS); + List tableSuffixes = deviceParamTableRouter.resolveReadTableSuffixes(baselineBeginTime, endTime); + List list = board4Mapper.selectDeviceOpeningCountList( + beginTime, endTime, baselineBeginTime, tableSuffixes); + List result = list != null ? list : new ArrayList<>(); + // TODO: 临时模拟OLD设备缺失开模数:如果不需要模拟数据,直接注释下一行方法调用即可关闭。 + fillMissingOldDeviceOpeningCountByReference(result); + return result; + } + + + /** + * 使用其他有数据设备的均值和中位数,为缺少源表数据的OLD设备临时补估算值。 + */ + private void fillMissingOldDeviceOpeningCountByReference(List list) { + if (list == null || list.isEmpty()) { + return; + } + List referenceCounts = buildOpeningCountReferenceValues(list, false); + if (referenceCounts.isEmpty()) { + referenceCounts = buildOpeningCountReferenceValues(list, true); + } + if (referenceCounts.isEmpty()) { + return; + } + double averageOpeningCount = calculateAverageOpeningCount(referenceCounts); + double medianOpeningCount = calculateMedianOpeningCount(referenceCounts); + List usedEstimatedCounts = new ArrayList<>(); + int missingOldDeviceIndex = 0; + for (Board4DeviceOpeningCountVo item : list) { + if (!isOldDevice(item) || item.hasWindowData()) { + continue; + } + Long estimateOpeningCount = buildOldDeviceEstimateOpeningCount( + item, averageOpeningCount, medianOpeningCount, missingOldDeviceIndex, usedEstimatedCounts); + item.setOpeningCount(estimateOpeningCount); + usedEstimatedCounts.add(estimateOpeningCount); + missingOldDeviceIndex++; + } + } + + /** + * 构造估算参考样本:优先只看非OLD设备,避免用已模拟的老设备反向影响基准。 + */ + private List buildOpeningCountReferenceValues(List list, boolean includeOldDevice) { + List referenceCounts = new ArrayList<>(); + for (Board4DeviceOpeningCountVo item : list) { + if (!includeOldDevice && isOldDevice(item)) { + continue; + } + if (!item.hasWindowData() || item.getOpeningCount() == null || item.getOpeningCount().longValue() <= 0L) { + continue; + } + referenceCounts.add(item.getOpeningCount()); + } + return referenceCounts; + } + + /** + * 按均值和中位数中间值附近做确定性上下浮动,保证每台OLD设备刷新稳定且数值不完全相同。 + */ + private Long buildOldDeviceEstimateOpeningCount(Board4DeviceOpeningCountVo item, + double averageOpeningCount, + double medianOpeningCount, + int missingOldDeviceIndex, + List usedEstimatedCounts) { + double baseOpeningCount = (averageOpeningCount + medianOpeningCount) / 2D; + // 为什么这样做:看板会频繁刷新,不能用随机数;用设备编码/名称生成稳定浮动,避免同一设备数值跳动。 + String seedText = String.valueOf(item.getDeviceCode()) + "|" + String.valueOf(item.getDeviceName()); + long hashSeed = Math.abs((long) seedText.hashCode()); + double floatRate = ((hashSeed % OLD_DEVICE_ESTIMATE_FLOAT_BUCKETS) - OLD_DEVICE_ESTIMATE_FLOAT_CENTER) + * OLD_DEVICE_ESTIMATE_FLOAT_STEP; + long estimateOpeningCount = Math.max(1L, Math.round(baseOpeningCount * (1D + floatRate))); + return avoidDuplicateOldDeviceEstimate(estimateOpeningCount, baseOpeningCount, missingOldDeviceIndex, + usedEstimatedCounts); + } + + /** + * 老设备模拟值尽量不要重复,避免大屏上一排OLD设备出现完全一样的“假整齐”。 + */ + private Long avoidDuplicateOldDeviceEstimate(long estimateOpeningCount, + double baseOpeningCount, + int missingOldDeviceIndex, + List usedEstimatedCounts) { + if (!usedEstimatedCounts.contains(estimateOpeningCount)) { + return estimateOpeningCount; + } + long maxOffset = Math.max(2L, Math.round(baseOpeningCount * 0.08D)); + long direction = missingOldDeviceIndex % 2 == 0 ? 1L : -1L; + for (long offset = 1L; offset <= maxOffset; offset++) { + long candidate = Math.max(1L, estimateOpeningCount + direction * offset); + if (!usedEstimatedCounts.contains(candidate)) { + return candidate; + } + candidate = Math.max(1L, estimateOpeningCount - direction * offset); + if (!usedEstimatedCounts.contains(candidate)) { + return candidate; + } + } + return Math.max(1L, estimateOpeningCount + missingOldDeviceIndex + 1L); + } + + /** + * 是否为旧设备占位编码。 + */ + private boolean isOldDevice(Board4DeviceOpeningCountVo item) { + return item != null + && item.getDeviceCode() != null + && item.getDeviceCode().startsWith(OLD_DEVICE_CODE_PREFIX); + } + + /** + * 计算开模数均值。 + */ + private double calculateAverageOpeningCount(List referenceCounts) { + long total = 0L; + for (Long count : referenceCounts) { + total += count.longValue(); + } + return total * 1D / referenceCounts.size(); + } + + /** + * 计算开模数中位数。 + */ + private double calculateMedianOpeningCount(List referenceCounts) { + List sortedCounts = new ArrayList<>(referenceCounts); + Collections.sort(sortedCounts); + int size = sortedCounts.size(); + int middleIndex = size / 2; + if (size % 2 == 1) { + return sortedCounts.get(middleIndex); + } + return (sortedCounts.get(middleIndex - 1) + sortedCounts.get(middleIndex)) / 2D; + } + + + /** + * 构造今日07:00窗口结束时间 + */ + private Date buildTodayOpeningWindowEndTime() { + Calendar calendar = Calendar.getInstance(); + calendar.set(Calendar.HOUR_OF_DAY, OPENING_COUNT_END_HOUR); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + return calendar.getTime(); + } + + + /** + * 小时级时间偏移 + */ + private Date addHours(Date baseTime, int hours) { + Calendar calendar = Calendar.getInstance(); + calendar.setTime(baseTime); + calendar.add(Calendar.HOUR_OF_DAY, hours); + return calendar.getTime(); + } + + } diff --git a/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml b/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml index 3da6eb4..9000d0b 100644 --- a/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml +++ b/aucma-report/src/main/resources/mapper/report/Board4Mapper.xml @@ -173,4 +173,168 @@ ORDER BY production DESC, d.DEVICE_CODE + + +