diff --git a/klp-da/src/main/java/com/klp/da/controller/OeeReportController.java b/klp-da/src/main/java/com/klp/da/controller/OeeReportController.java index bd1b3daa..ed696a1e 100644 --- a/klp-da/src/main/java/com/klp/da/controller/OeeReportController.java +++ b/klp-da/src/main/java/com/klp/da/controller/OeeReportController.java @@ -12,6 +12,7 @@ import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo; import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo; import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo; import com.klp.pocket.acid.service.IAcidOeeService; +import com.klp.pocket.galvanize1.service.IGalvanizeOeeService; import lombok.Data; import lombok.RequiredArgsConstructor; import org.apache.poi.xwpf.usermodel.ParagraphAlignment; @@ -61,6 +62,7 @@ import java.util.stream.Collectors; public class OeeReportController extends BaseController { private final IAcidOeeService acidOeeService; + private final IGalvanizeOeeService galvanizeOeeService; private final StringRedisTemplate stringRedisTemplate; private final OeeReportJobService oeeReportJobService; private final OeeWordAiAnalysisService oeeWordAiAnalysisService; @@ -83,11 +85,13 @@ public class OeeReportController extends BaseController { @GetMapping("/acid/summary") public R> getAcidSummary( @RequestParam(required = false) String startDate, - @RequestParam(required = false) String endDate + @RequestParam(required = false) String endDate, + @RequestParam(required = false, defaultValue = "acid") String lineType ) { String[] range = resolveDateRange(startDate, endDate); - List dailyList = - acidOeeService.getDailySummary(range[0], range[1]); + List dailyList = isGalvanize(lineType) + ? galvanizeOeeService.getDailySummary(range[0], range[1]) + : acidOeeService.getDailySummary(range[0], range[1]); return R.ok(dailyList); } @@ -101,21 +105,15 @@ public class OeeReportController extends BaseController { */ @GetMapping("/acid/loss7") public R> getAcidLoss7( - @RequestParam(required = false, defaultValue = "50") Integer topN + @RequestParam(required = false, defaultValue = "50") Integer topN, + @RequestParam(required = false) String startDate, + @RequestParam(required = false) String endDate, + @RequestParam(required = false, defaultValue = "acid") String lineType ) { - String yyyyMM = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM")); - String loss7Key = String.format("oee:report:month:loss7:%s:SY", yyyyMM); - - // 1. 优先从 Redis 读取当月 7 大损失预计算结果 - String json = stringRedisTemplate.opsForValue().get(loss7Key); - List lossList; - if (StringUtils.isNotBlank(json)) { - lossList = JSON.parseArray(json, AcidOeeLoss7Vo.class); - } else { - // 2. 缓存缺失时,回退为实时计算当前月 - String[] range = resolveDateRange(null, null); - lossList = acidOeeService.getLoss7Summary(range[0], range[1]); - } + String[] range = resolveDateRange(startDate, endDate); + List lossList = isGalvanize(lineType) + ? galvanizeOeeService.getLoss7Summary(range[0], range[1]) + : acidOeeService.getLoss7Summary(range[0], range[1]); if (topN != null && topN > 0 && lossList.size() > topN) { lossList = new ArrayList<>(lossList.subList(0, topN)); @@ -139,14 +137,16 @@ public class OeeReportController extends BaseController { @RequestParam(required = false) String stopType, @RequestParam(required = false) String keyword, @RequestParam(required = false, defaultValue = "1") Integer pageNum, - @RequestParam(required = false, defaultValue = "10") Integer pageSize + @RequestParam(required = false, defaultValue = "10") Integer pageSize, + @RequestParam(required = false, defaultValue = "acid") String lineType ) { // 事件明细底层按「日期」查询,这里从时间字符串中截取日期部分 String startDate = extractDateOrDefault(startTime, true); String endDate = extractDateOrDefault(endTime, false); - List events = - acidOeeService.getStoppageEvents(startDate, endDate); + List events = isGalvanize(lineType) + ? galvanizeOeeService.getStoppageEvents(startDate, endDate) + : acidOeeService.getStoppageEvents(startDate, endDate); // 业务筛选:stopType、关键字(目前对 stopType / remark 做 contains 匹配) List filtered = events.stream() @@ -192,9 +192,12 @@ public class OeeReportController extends BaseController { @GetMapping("/acid/idealCycle") public R getAcidIdealCycle( @RequestParam String startDate, - @RequestParam String endDate + @RequestParam String endDate, + @RequestParam(required = false, defaultValue = "acid") String lineType ) { - AcidOeeIdealCycleVo data = acidOeeService.getIdealCycle(startDate, endDate); + AcidOeeIdealCycleVo data = isGalvanize(lineType) + ? galvanizeOeeService.getIdealCycle(startDate, endDate) + : acidOeeService.getIdealCycle(startDate, endDate); return R.ok(data); } @@ -415,6 +418,10 @@ public class OeeReportController extends BaseController { /** * 若未显式传入日期范围,则默认当前月 [1号, 今天]。 */ + private boolean isGalvanize(String lineType) { + return "galvanize1".equalsIgnoreCase(lineType) || "galvanize".equalsIgnoreCase(lineType) || "dx".equalsIgnoreCase(lineType); + } + private String[] resolveDateRange(String startDate, String endDate) { if (StringUtils.isNotBlank(startDate) && StringUtils.isNotBlank(endDate)) { return new String[]{startDate, endDate}; diff --git a/klp-pocket/src/main/java/com/klp/pocket/galvanize1/mapper/GalvanizeOeeMasterMapper.java b/klp-pocket/src/main/java/com/klp/pocket/galvanize1/mapper/GalvanizeOeeMasterMapper.java new file mode 100644 index 00000000..4c148f3e --- /dev/null +++ b/klp-pocket/src/main/java/com/klp/pocket/galvanize1/mapper/GalvanizeOeeMasterMapper.java @@ -0,0 +1,22 @@ +package com.klp.pocket.galvanize1.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo; +import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +@DS("master") +public interface GalvanizeOeeMasterMapper { + + List selectDailySummary(@Param("startDate") String startDate, + @Param("endDate") String endDate, + @Param("createBy") String createBy); + + List selectCoilInfoByDate(@Param("startDate") String startDate, + @Param("endDate") String endDate, + @Param("createBy") String createBy); +} diff --git a/klp-pocket/src/main/java/com/klp/pocket/galvanize1/service/IGalvanizeOeeService.java b/klp-pocket/src/main/java/com/klp/pocket/galvanize1/service/IGalvanizeOeeService.java new file mode 100644 index 00000000..eceb77dd --- /dev/null +++ b/klp-pocket/src/main/java/com/klp/pocket/galvanize1/service/IGalvanizeOeeService.java @@ -0,0 +1,19 @@ +package com.klp.pocket.galvanize1.service; + +import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo; +import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo; +import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo; +import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo; + +import java.util.List; + +public interface IGalvanizeOeeService { + + List getDailySummary(String startDate, String endDate); + + List getStoppageEvents(String startDate, String endDate); + + List getLoss7Summary(String startDate, String endDate); + + AcidOeeIdealCycleVo getIdealCycle(String startDate, String endDate); +} diff --git a/klp-pocket/src/main/java/com/klp/pocket/galvanize1/service/impl/GalvanizeOeeServiceImpl.java b/klp-pocket/src/main/java/com/klp/pocket/galvanize1/service/impl/GalvanizeOeeServiceImpl.java new file mode 100644 index 00000000..7dd8700c --- /dev/null +++ b/klp-pocket/src/main/java/com/klp/pocket/galvanize1/service/impl/GalvanizeOeeServiceImpl.java @@ -0,0 +1,274 @@ +package com.klp.pocket.galvanize1.service.impl; + +import com.klp.common.utils.StringUtils; +import com.klp.pocket.acid.domain.vo.AcidOeeCoilInfoByDateVo; +import com.klp.pocket.acid.domain.vo.AcidOeeDailySummaryVo; +import com.klp.pocket.acid.domain.vo.AcidOeeIdealCycleVo; +import com.klp.pocket.acid.domain.vo.AcidOeeLoss7Vo; +import com.klp.pocket.acid.domain.vo.Klptcm1ProStoppageVo; +import com.klp.pocket.galvanize1.domain.bo.ProStoppageBo; +import com.klp.pocket.galvanize1.domain.vo.ProStoppageVo; +import com.klp.pocket.galvanize1.mapper.GalvanizeOeeMasterMapper; +import com.klp.pocket.galvanize1.service.IGalvanizeOeeService; +import com.klp.pocket.galvanize1.service.IProStoppageService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.text.SimpleDateFormat; +import java.util.*; + +@Slf4j +@RequiredArgsConstructor +@Service +public class GalvanizeOeeServiceImpl implements IGalvanizeOeeService { + + private static final Set CD_SERIES = new HashSet<>(Arrays.asList("C+", "C", "C-", "D+", "D", "D-")); + + private final GalvanizeOeeMasterMapper galvanizeOeeMasterMapper; + private final IProStoppageService proStoppageService; + + @Value("${oee.galvanize1.coil-create-by:duxinkuguan}") + private String galvanizeCreateBy; + + @Override + public List getDailySummary(String startDate, String endDate) { + List summaries = galvanizeOeeMasterMapper.selectDailySummary(startDate, endDate, galvanizeCreateBy); + if (summaries == null || summaries.isEmpty()) return Collections.emptyList(); + + Map downtimeByDate = aggregateDowntimeByDate(startDate, endDate); + Map> coilInfoByDate = getCoilNosByDate(startDate, endDate); + + List dailyCycles = new ArrayList<>(); + for (AcidOeeDailySummaryVo s : summaries) { + s.setLineId("DX1"); + s.setLineName("镀锌一线"); + Long downtime = downtimeByDate.getOrDefault(s.getStatDate(), 0L); + s.setDowntimeMin(downtime); + Long loading = s.getLoadingTimeMin() == null ? 0L : s.getLoadingTimeMin(); + Long run = Math.max(0, loading - downtime); + s.setRunTimeMin(run); + BigDecimal ton = s.getTotalOutputTon(); + if (run > 0 && ton != null && ton.compareTo(BigDecimal.ZERO) > 0) { + dailyCycles.add(BigDecimal.valueOf(run).divide(ton, 6, RoundingMode.HALF_UP)); + } + } + dailyCycles.sort(BigDecimal::compareTo); + BigDecimal ideal = applyEightyPercent(median(dailyCycles)); + + for (AcidOeeDailySummaryVo s : summaries) { + if (ideal != null) s.setIdealCycleTimeMinPerTon(ideal); + List coilInfos = coilInfoByDate.get(s.getStatDate()); + if (coilInfos != null) { + calculateQualityOutput(s, coilInfos); + } else { + s.setGoodOutputTon(BigDecimal.ZERO); + s.setGoodOutputCoil(0L); + s.setQualifiedOutputTon(BigDecimal.ZERO); + s.setQualifiedOutputCoil(0L); + s.setAbOutputTon(BigDecimal.ZERO); + s.setAbOutputCoil(0L); + s.setDefectOutputTon(BigDecimal.ZERO); + s.setDefectOutputCoil(0L); + s.setPendingOutputTon(s.getTotalOutputTon()); + s.setPendingOutputCoil(s.getTotalOutputCoil()); + } + calculateDerivedMetrics(s); + } + return summaries; + } + + @Override + public List getStoppageEvents(String startDate, String endDate) { + ProStoppageBo bo = new ProStoppageBo(); + bo.setStartDate(parseDate(startDate)); + bo.setEndDate(parseDate(endDate)); + List list = proStoppageService.queryList(bo); + List out = new ArrayList<>(); + for (ProStoppageVo p : list) { + Klptcm1ProStoppageVo v = new Klptcm1ProStoppageVo(); + v.setStopid(p.getStopid()); + v.setShift(p.getShift()); + v.setCrew(p.getCrew()); + v.setArea(p.getArea()); + v.setUnit(p.getUnit()); + v.setSeton(p.getSeton()); + v.setStartDate(p.getStartDate()); + v.setEndDate(p.getEndDate()); + v.setRemark(p.getRemark()); + v.setStopType(p.getStopType()); + long durationSec = p.getDuration() == null ? 0L : Math.round(p.getDuration()); + v.setDuration(durationSec); + out.add(v); + } + return out; + } + + @Override + public List getLoss7Summary(String startDate, String endDate) { + List events = getStoppageEvents(startDate, endDate); + if (events.isEmpty()) return Collections.emptyList(); + Map m = new HashMap<>(); + long total = 0L; + for (Klptcm1ProStoppageVo e : events) { + String k = StringUtils.isBlank(e.getStopType()) ? "未分类" : e.getStopType(); + long min = Math.max(1, (e.getDuration() == null ? 0L : e.getDuration()) / 60); + if (min <= 0) continue; + m.computeIfAbsent(k, x -> new long[2]); + m.get(k)[0] += min; + m.get(k)[1] += 1; + total += min; + } + List out = new ArrayList<>(); + for (Map.Entry en : m.entrySet()) { + AcidOeeLoss7Vo vo = new AcidOeeLoss7Vo(); + vo.setLossCategoryCode(en.getKey()); + vo.setLossCategoryName(en.getKey()); + vo.setLossTimeMin(en.getValue()[0]); + vo.setCount((int) en.getValue()[1]); + vo.setAvgDurationMin(BigDecimal.valueOf(en.getValue()[0]).divide(BigDecimal.valueOf(Math.max(1, en.getValue()[1])), 2, RoundingMode.HALF_UP)); + vo.setLossTimeRate(total == 0 ? BigDecimal.ZERO : BigDecimal.valueOf(en.getValue()[0]).divide(BigDecimal.valueOf(total), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + out.add(vo); + } + out.sort(Comparator.comparingLong(AcidOeeLoss7Vo::getLossTimeMin).reversed()); + return out; + } + + @Override + public AcidOeeIdealCycleVo getIdealCycle(String startDate, String endDate) { + List daily = getDailySummary(startDate, endDate); + AcidOeeIdealCycleVo rsp = new AcidOeeIdealCycleVo(); + rsp.setLineId("DX1"); + rsp.setLineName("镀锌一线"); + rsp.setStartDate(startDate); + rsp.setEndDate(endDate); + if (daily.isEmpty()) { + rsp.setDailyComparePoints(Collections.emptyList()); + rsp.setSampleDays(0); + return rsp; + } + List dailyCycles = new ArrayList<>(); + List points = new ArrayList<>(); + for (AcidOeeDailySummaryVo d : daily) { + if (d.getRunTimeMin() == null || d.getRunTimeMin() <= 0 || d.getTotalOutputTon() == null || d.getTotalOutputTon().compareTo(BigDecimal.ZERO) <= 0) continue; + BigDecimal c = BigDecimal.valueOf(d.getRunTimeMin()).divide(d.getTotalOutputTon(), 6, RoundingMode.HALF_UP); + dailyCycles.add(c); + } + dailyCycles.sort(BigDecimal::compareTo); + BigDecimal med = median(dailyCycles); + BigDecimal ideal = applyEightyPercent(med); + for (AcidOeeDailySummaryVo d : daily) { + if (ideal == null || d.getTotalOutputTon() == null || d.getRunTimeMin() == null) continue; + AcidOeeIdealCycleVo.DailyComparePointVo p = new AcidOeeIdealCycleVo.DailyComparePointVo(); + p.setStatDate(d.getStatDate()); + p.setActualRunTimeMin(d.getRunTimeMin()); + p.setTheoreticalTimeMin(ideal.multiply(d.getTotalOutputTon())); + points.add(p); + } + rsp.setIdealCycleTimeMinPerTon(ideal); + rsp.setMedianCycleTimeMinPerTon(med); + rsp.setSampleDays(daily.size()); + rsp.setDailyComparePoints(points); + return rsp; + } + + private Map> getCoilNosByDate(String startDate, String endDate) { + List list = galvanizeOeeMasterMapper.selectCoilInfoByDate(startDate, endDate, galvanizeCreateBy); + Map> map = new HashMap<>(); + for (AcidOeeCoilInfoByDateVo i : list) { + map.computeIfAbsent(i.getStatDate(), k -> new ArrayList<>()).add(new CoilInfo(i.getWeight(), i.getQualityStatus())); + } + return map; + } + + private void calculateQualityOutput(AcidOeeDailySummaryVo summary, List coilInfos) { + BigDecimal aTon = BigDecimal.ZERO, bTon = BigDecimal.ZERO, cdTon = BigDecimal.ZERO, oTon = BigDecimal.ZERO; + long a = 0, b = 0, cd = 0, o = 0; + for (CoilInfo c : coilInfos) { + BigDecimal w = c.weight == null ? BigDecimal.ZERO : c.weight; + String q = StringUtils.upperCase(StringUtils.trim(c.qualityStatus)); + if (StringUtils.isBlank(q) || "O".equals(q)) { + oTon = oTon.add(w); o++; continue; + } + if (q.startsWith("A")) { aTon = aTon.add(w); a++; } + else if (q.startsWith("B")) { bTon = bTon.add(w); b++; } + else if (CD_SERIES.contains(q)) { cdTon = cdTon.add(w); cd++; } + else { oTon = oTon.add(w); o++; } + } + summary.setGoodOutputTon(aTon); summary.setGoodOutputCoil(a); + summary.setQualifiedOutputTon(bTon); summary.setQualifiedOutputCoil(b); + summary.setAbOutputTon(aTon.add(bTon)); summary.setAbOutputCoil(a + b); + summary.setDefectOutputTon(cdTon); summary.setDefectOutputCoil(cd); + summary.setPendingOutputTon(oTon); summary.setPendingOutputCoil(o); + } + + private void calculateDerivedMetrics(AcidOeeDailySummaryVo s) { + long loading = s.getLoadingTimeMin() == null ? 0 : s.getLoadingTimeMin(); + long downtime = s.getDowntimeMin() == null ? 0 : s.getDowntimeMin(); + if (loading > 0) s.setAvailability(BigDecimal.valueOf(loading - downtime).divide(BigDecimal.valueOf(loading), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + long run = s.getRunTimeMin() == null ? 0 : s.getRunTimeMin(); + BigDecimal ideal = s.getIdealCycleTimeMinPerTon(); + BigDecimal ton = s.getTotalOutputTon(); + if (run > 0 && ideal != null && ideal.compareTo(BigDecimal.ZERO) > 0 && ton != null && ton.compareTo(BigDecimal.ZERO) > 0) { + s.setPerformanceTon(ideal.multiply(ton).divide(BigDecimal.valueOf(run), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + } + if (ton != null && ton.compareTo(BigDecimal.ZERO) > 0) { + BigDecimal a = Optional.ofNullable(s.getGoodOutputTon()).orElse(BigDecimal.ZERO); + BigDecimal b = Optional.ofNullable(s.getQualifiedOutputTon()).orElse(BigDecimal.ZERO); + BigDecimal cd = Optional.ofNullable(s.getDefectOutputTon()).orElse(BigDecimal.ZERO); + BigDecimal o = Optional.ofNullable(s.getPendingOutputTon()).orElse(BigDecimal.ZERO); + s.setQuality(a.divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + s.setQualifiedRate(a.add(b).divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + s.setDefectRate(cd.divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + s.setPendingRate(o.divide(ton, 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100))); + } + if (s.getAvailability() != null && s.getPerformanceTon() != null && s.getQuality() != null) { + s.setOee(s.getAvailability().multiply(s.getPerformanceTon()).multiply(s.getQuality()).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); + } + } + + private Map aggregateDowntimeByDate(String startDate, String endDate) { + List events = getStoppageEvents(startDate, endDate); + Map map = new HashMap<>(); + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + for (Klptcm1ProStoppageVo e : events) { + if (e.getStartDate() == null || e.getDuration() == null || e.getDuration() <= 0) continue; + long min = Math.max(1, (e.getDuration() + 59) / 60); + map.merge(df.format(e.getStartDate()), min, Long::sum); + } + return map; + } + + private BigDecimal median(List values) { + if (values == null || values.isEmpty()) return null; + int n = values.size(); + if (n % 2 == 1) return values.get(n / 2); + return values.get(n / 2 - 1).add(values.get(n / 2)).divide(BigDecimal.valueOf(2), 6, RoundingMode.HALF_UP); + } + + private BigDecimal applyEightyPercent(BigDecimal v) { + return v == null ? null : v.multiply(BigDecimal.valueOf(0.7)).setScale(6, RoundingMode.HALF_UP); + } + + private Date parseDate(String dateStr) { + if (StringUtils.isBlank(dateStr)) return null; + try { + return new SimpleDateFormat("yyyy-MM-dd").parse(dateStr); + } catch (Exception e) { + return null; + } + } + + private static class CoilInfo { + final BigDecimal weight; + final String qualityStatus; + + CoilInfo(BigDecimal weight, String qualityStatus) { + this.weight = weight; + this.qualityStatus = qualityStatus; + } + } +} diff --git a/klp-pocket/src/main/resources/mapper/galvanize1/GalvanizeOeeMasterMapper.xml b/klp-pocket/src/main/resources/mapper/galvanize1/GalvanizeOeeMasterMapper.xml new file mode 100644 index 00000000..ffd89a14 --- /dev/null +++ b/klp-pocket/src/main/resources/mapper/galvanize1/GalvanizeOeeMasterMapper.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/klp-ui/src/views/da/oee/index.vue b/klp-ui/src/views/da/oee/index.vue index b3e55679..b59c1ae6 100644 --- a/klp-ui/src/views/da/oee/index.vue +++ b/klp-ui/src/views/da/oee/index.vue @@ -12,6 +12,10 @@
+ + + + + + @@ -383,6 +396,7 @@ export default { const today = new Date() const firstDay = new Date(today.getFullYear(), today.getMonth(), 1) return { + lineType: 'acid', queryRange: [ this.formatDate(firstDay), this.formatDate(today) @@ -542,6 +556,7 @@ export default { buildQuery() { const [start, end] = this.queryRange || [] return { + lineType: this.lineType, startDate: start, endDate: end } @@ -734,6 +749,10 @@ export default { gap: 8px; } +.oee-top-warning { + margin-bottom: 8px; +} + .oee-main-row { margin-top: 8px; }