From 482b2982ba27b18e6391c3862ec239c89a801a46 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 21 五月 2026 13:09:05 +0800
Subject: [PATCH] feat(home): 添加生产看板功能并优化订单查询性能

---
 src/main/java/com/ruoyi/home/controller/HomeController.java |  435 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 429 insertions(+), 6 deletions(-)

diff --git a/src/main/java/com/ruoyi/home/controller/HomeController.java b/src/main/java/com/ruoyi/home/controller/HomeController.java
index 8ef87a5..9bd23f6 100644
--- a/src/main/java/com/ruoyi/home/controller/HomeController.java
+++ b/src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -1,6 +1,10 @@
 package com.ruoyi.home.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ruoyi.approve.pojo.ApproveProcess;
+import com.ruoyi.device.mapper.DeviceLedgerMapper;
+import com.ruoyi.device.mapper.DeviceRepairMapper;
+import com.ruoyi.device.pojo.DeviceRepair;
 import com.ruoyi.dto.MapDto;
 import com.ruoyi.framework.aspectj.lang.annotation.Log;
 import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
@@ -9,17 +13,31 @@
 import com.ruoyi.home.annotation.DefaultType;
 import com.ruoyi.home.dto.*;
 import com.ruoyi.home.service.HomeService;
-import io.swagger.v3.oas.annotations.Operation;
+import com.ruoyi.production.mapper.ProductionOrderMapper;
+import com.ruoyi.production.mapper.ProductionProductOutputMapper;
+import com.ruoyi.production.pojo.ProductionOrder;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import io.swagger.v3.oas.annotations.Operation;
 import lombok.AllArgsConstructor;
 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.text.ParseException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * @author :yys
@@ -32,12 +50,23 @@
 public class HomeController extends BaseController {
 
     private final HomeService homeService;
+    private final ProductionOrderMapper productionOrderMapper;
+    private final ProductionProductOutputMapper productionProductOutputMapper;
+    private final DeviceLedgerMapper deviceLedgerMapper;
+    private final DeviceRepairMapper deviceRepairMapper;
+
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
+    private static final Integer ORDER_STATUS_WAIT = 1;
+    private static final Integer ORDER_STATUS_RUNNING = 2;
+    private static final Integer ORDER_STATUS_COMPLETED = 3;
+    private static final Integer ORDER_STATUS_PAUSED = 4;
 
     /********************************************************鍩虹绫�*****************************************************/
     @GetMapping("/todos")
     @Log(title = "寰呭姙浜嬮」", businessType = BusinessType.OTHER)
     @Operation(summary = "寰呭姙浜嬮」")
-    public R todos(ApproveProcess req) throws ParseException {
+    public R todos() throws ParseException {
         List<ApproveProcess> approveProcessList = homeService.todos();
         return R.ok(approveProcessList);
     }
@@ -109,7 +138,7 @@
     @GetMapping("/business")
     @Log(title = "閿�鍞�-閲囪喘-搴撳瓨鏁版嵁", businessType = BusinessType.OTHER)
     @Operation(summary = "閿�鍞�-閲囪喘-搴撳瓨鏁版嵁")
-    public R business(HomeBusinessDto req) {
+    public R business() {
         HomeBusinessDto homeBusinessDto = homeService.business();
         return R.ok(homeBusinessDto);
     }
@@ -117,7 +146,7 @@
     @GetMapping("/analysisCustomerContractAmounts")
     @Log(title = "瀹㈡埛鍚堝悓閲戦鍒嗘瀽", businessType = BusinessType.OTHER)
     @Operation(summary = "瀹㈡埛鍚堝悓閲戦鍒嗘瀽")
-    public R analysisCustomerContractAmounts(AnalysisCustomerContractAmountsDto req) {
+    public R analysisCustomerContractAmounts() {
         AnalysisCustomerContractAmountsDto analysisCustomerContractAmounts = homeService.analysisCustomerContractAmounts();
         return R.ok(analysisCustomerContractAmounts);
     }
@@ -179,6 +208,146 @@
     }
 
     /********************************************************璐ㄩ噺绫�*****************************************************/
+    @GetMapping("/productionOverview")
+    @Operation(summary = "Production Overview")
+    public R productionOverview() {
+        LocalDate today = LocalDate.now();
+        Map<String, BigDecimal> totalStats = loadOutputStats(LocalDate.of(2000, 1, 1), today.plusDays(1));
+        BigDecimal totalOutput = totalStats.get("quantity");
+        BigDecimal totalScrap = totalStats.get("scrapQty");
+        BigDecimal yieldRate = calcRate(totalOutput, totalOutput.add(totalScrap));
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("totalOutput", scale(totalOutput));
+        result.put("totalScrap", scale(totalScrap));
+        result.put("yieldRate", scale(yieldRate));
+        return R.ok(result);
+    }
+
+    @GetMapping("/productionRealtimeBoard")
+    @Operation(summary = "Production Realtime Board")
+    public R productionRealtimeBoard() {
+        LocalDate today = LocalDate.now();
+        LocalDate yesterday = today.minusDays(1);
+
+        BigDecimal todayDeviceOee = calcDeviceOee(today);
+        BigDecimal yesterdayDeviceOee = calcDeviceOee(yesterday);
+
+        BigDecimal todayOrderAchievementRate = calcOrderAchievementRate(today);
+        BigDecimal yesterdayOrderAchievementRate = calcOrderAchievementRate(yesterday);
+
+        BigDecimal todayDefectRate = calcDefectRate(today);
+        BigDecimal yesterdayDefectRate = calcDefectRate(yesterday);
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("deviceOee", buildRealtimeMetric(todayDeviceOee, todayDeviceOee.subtract(yesterdayDeviceOee)));
+        result.put("orderAchievementRate", buildRealtimeMetric(todayOrderAchievementRate, todayOrderAchievementRate.subtract(yesterdayOrderAchievementRate)));
+        result.put("defectRate", buildRealtimeMetric(todayDefectRate, todayDefectRate.subtract(yesterdayDefectRate)));
+        return R.ok(result);
+    }
+
+    @GetMapping("/productionOrderProgress")
+    @Operation(summary = "Production Order Progress")
+    public R productionOrderProgress(@RequestParam(defaultValue = "all") String tab,
+                                     @RequestParam(required = false) String status,
+                                     @RequestParam(required = false) String bizDate,
+                                     @RequestParam(defaultValue = "1") Long pageNum,
+                                     @RequestParam(defaultValue = "10") Long pageSize) {
+        LocalDate queryDate = parseDateOrNull(bizDate);
+        if (!isBlank(bizDate) && queryDate == null) {
+            return R.fail("bizDate鏍煎紡閿欒锛岃浣跨敤yyyy-MM-dd");
+        }
+        Integer statusFromParam = parseOrderStatus(status);
+        if (!isBlank(status) && statusFromParam == null && !"all".equalsIgnoreCase(status.trim())) {
+            return R.fail("status鍙傛暟涓嶅悎娉曪紝鍙�夊�硷細all/waiting/inProgress/completed/paused 鎴� 1/2/3/4");
+        }
+        Integer queryStatus = resolveOrderStatus(status, tab);
+
+        long safePageNum = pageNum == null || pageNum < 1 ? 1 : pageNum;
+        long safePageSize = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 50);
+        long offset = (safePageNum - 1) * safePageSize;
+        LocalDateTime startTime = queryDate == null ? null : queryDate.atStartOfDay();
+        LocalDateTime endTime = queryDate == null ? null : queryDate.plusDays(1).atStartOfDay();
+
+        List<Map<String, Object>> rawRows = productionOrderMapper.selectHomeOrderProgressPage(queryStatus, offset, safePageSize, startTime, endTime);
+        List<Map<String, Object>> records = new ArrayList<>();
+        if (rawRows != null) {
+            for (Map<String, Object> rawRow : rawRows) {
+                records.add(buildOrderProgressRow(rawRow));
+            }
+        }
+
+        long waitingCount = 0L;
+        long inProgressCount = 0L;
+        long completedCount = 0L;
+        long pausedCount = 0L;
+        List<Map<String, Object>> statusCountRows = productionOrderMapper.countHomeOrderProgressByStatus(startTime, endTime);
+        if (statusCountRows != null) {
+            for (Map<String, Object> countRow : statusCountRows) {
+                Integer statusKey = toInteger(countRow.get("status"));
+                long cnt = toLong(countRow.get("cnt"));
+                if (Objects.equals(statusKey, ORDER_STATUS_WAIT)) {
+                    waitingCount = cnt;
+                } else if (Objects.equals(statusKey, ORDER_STATUS_RUNNING)) {
+                    inProgressCount = cnt;
+                } else if (Objects.equals(statusKey, ORDER_STATUS_COMPLETED)) {
+                    completedCount = cnt;
+                } else if (Objects.equals(statusKey, ORDER_STATUS_PAUSED)) {
+                    pausedCount = cnt;
+                }
+            }
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("tab", mapOrderTab(queryStatus));
+        result.put("status", mapOrderStatus(queryStatus));
+        result.put("bizDate", queryDate == null ? null : queryDate.format(DATE_FORMATTER));
+        result.put("total", toLong(productionOrderMapper.countHomeOrderProgress(queryStatus, startTime, endTime)));
+        result.put("pageNum", safePageNum);
+        result.put("pageSize", safePageSize);
+        result.put("waitingCount", waitingCount);
+        result.put("inProgressCount", inProgressCount);
+        result.put("completedCount", completedCount);
+        result.put("pausedCount", pausedCount);
+        result.put("records", records);
+        return R.ok(result);
+    }
+
+    @GetMapping("/todayProductionPlan")
+    @Operation(summary = "Today Production Plan")
+    public R todayProductionPlan(@RequestParam(defaultValue = "4") Long limit,
+                                 @RequestParam(required = false) String planDate) {
+        LocalDate queryDate = parseDateOrNull(planDate);
+        if (!isBlank(planDate) && queryDate == null) {
+            return R.fail("planDate鏍煎紡閿欒锛岃浣跨敤yyyy-MM-dd");
+        }
+
+        long safeLimit = limit == null || limit < 1 ? 4 : Math.min(limit, 20);
+        LocalDateTime planStart = queryDate == null ? null : queryDate.atStartOfDay();
+        LocalDateTime planEnd = queryDate == null ? null : queryDate.plusDays(1).atStartOfDay();
+        List<Map<String, Object>> records = new ArrayList<>();
+        List<Map<String, Object>> rawRows = productionOrderMapper.selectHomeTodayProductionPlan(safeLimit, planStart, planEnd);
+        if (rawRows != null) {
+            for (Map<String, Object> rawRow : rawRows) {
+                Map<String, Object> row = new LinkedHashMap<>();
+                Integer rowStatus = toInteger(rawRow.get("status"));
+                row.put("orderNo", rawRow.get("orderNo"));
+                row.put("productName", rawRow.get("productName"));
+                row.put("plannedQuantity", scale(toBigDecimal(rawRow.get("plannedQuantity"))));
+                row.put("dueDate", rawRow.get("dueDate"));
+                row.put("status", rowStatus);
+                row.put("statusLabel", mapOrderStatusLabel(rowStatus));
+                records.add(row);
+            }
+        }
+
+        Map<String, Object> result = new LinkedHashMap<>();
+        result.put("planDate", queryDate == null ? null : queryDate.format(DATE_FORMATTER));
+        result.put("total", toLong(productionOrderMapper.countHomeTodayProductionPlan(planStart, planEnd)));
+        result.put("records", records);
+        return R.ok(result);
+    }
+
     @GetMapping("/rawMaterialDetection")
     @Operation(summary = "鍘熸潗鏂欐娴�")
     public R rawMaterialDetection(@DefaultType Integer type){
@@ -235,7 +404,7 @@
     @GetMapping("/qualityStatistics")
     @Log(title = "璐ㄩ噺鍒嗘瀽", businessType = BusinessType.OTHER)
     @Operation(summary = "璐ㄩ噺鍒嗘瀽")
-    public R qualityStatistics(QualityStatisticsDto req) {
+    public R qualityStatistics() {
         QualityStatisticsDto qualityStatisticsDto = homeService.qualityStatistics();
         return R.ok(qualityStatisticsDto);
     }
@@ -286,7 +455,7 @@
     @GetMapping("/statisticsReceivablePayable")
     @Log(title = "搴旀敹搴斾粯缁熻", businessType = BusinessType.OTHER)
     @Operation(summary = "搴旀敹搴斾粯缁熻")
-    public R statisticsReceivablePayable(StatisticsReceivablePayableDto req, @DefaultType Integer type ) {
+    public R statisticsReceivablePayable(@DefaultType Integer type ) {
         StatisticsReceivablePayableDto statisticsReceivablePayable = homeService.statisticsReceivablePayable(type);
         return R.ok(statisticsReceivablePayable);
     }
@@ -321,4 +490,258 @@
         return R.ok(list);
     }
 
+    private Map<String, Object> buildOrderProgressRow(Map<String, Object> rawRow) {
+        Map<String, Object> row = new LinkedHashMap<>();
+        Integer rowStatus = toInteger(rawRow.get("status"));
+        row.put("orderNo", rawRow.get("orderNo"));
+        row.put("productName", rawRow.get("productName"));
+        row.put("plannedQuantity", scale(toBigDecimal(rawRow.get("plannedQuantity"))));
+        row.put("completedQuantity", scale(toBigDecimal(rawRow.get("completedQuantity"))));
+        row.put("completionRate", scale(toBigDecimal(rawRow.get("completionRate"))));
+        row.put("dueDate", rawRow.get("dueDate"));
+        row.put("status", rowStatus);
+        row.put("statusLabel", mapOrderStatusLabel(rowStatus));
+        return row;
+    }
+
+    private Integer resolveOrderStatus(String status, String tab) {
+        if (!isBlank(status)) {
+            return parseOrderStatus(status);
+        }
+        return parseOrderStatus(tab);
+    }
+
+    private Integer parseOrderStatus(String rawStatus) {
+        if (isBlank(rawStatus)) {
+            return null;
+        }
+        String normalized = rawStatus.trim().toLowerCase();
+        if ("all".equals(normalized)) {
+            return null;
+        }
+        if ("1".equals(normalized) || "waiting".equals(normalized) || "wait".equals(normalized)) {
+            return ORDER_STATUS_WAIT;
+        }
+        if ("2".equals(normalized) || "inprogress".equals(normalized) || "running".equals(normalized)) {
+            return ORDER_STATUS_RUNNING;
+        }
+        if ("3".equals(normalized) || "completed".equals(normalized)) {
+            return ORDER_STATUS_COMPLETED;
+        }
+        if ("4".equals(normalized) || "paused".equals(normalized)) {
+            return ORDER_STATUS_PAUSED;
+        }
+        return null;
+    }
+
+    private String mapOrderTab(Integer status) {
+        if (Objects.equals(status, ORDER_STATUS_RUNNING)) {
+            return "inProgress";
+        }
+        if (Objects.equals(status, ORDER_STATUS_COMPLETED)) {
+            return "completed";
+        }
+        if (Objects.equals(status, ORDER_STATUS_PAUSED)) {
+            return "paused";
+        }
+        if (Objects.equals(status, ORDER_STATUS_WAIT)) {
+            return "waiting";
+        }
+        return "all";
+    }
+
+    private String mapOrderStatus(Integer status) {
+        if (Objects.equals(status, ORDER_STATUS_WAIT)) {
+            return "waiting";
+        }
+        if (Objects.equals(status, ORDER_STATUS_RUNNING)) {
+            return "inProgress";
+        }
+        if (Objects.equals(status, ORDER_STATUS_COMPLETED)) {
+            return "completed";
+        }
+        if (Objects.equals(status, ORDER_STATUS_PAUSED)) {
+            return "paused";
+        }
+        return "all";
+    }
+
+    private String mapOrderStatusLabel(Integer status) {
+        if (Objects.equals(status, ORDER_STATUS_WAIT)) {
+            return "寰呭紑濮�";
+        }
+        if (Objects.equals(status, ORDER_STATUS_RUNNING)) {
+            return "杩涜涓�";
+        }
+        if (Objects.equals(status, ORDER_STATUS_COMPLETED)) {
+            return "宸插畬鎴�";
+        }
+        if (Objects.equals(status, ORDER_STATUS_PAUSED)) {
+            return "宸叉殏鍋�";
+        }
+        return "鏈煡";
+    }
+
+    private Map<String, BigDecimal> loadOutputStats(LocalDate startDate, LocalDate endDateExclusive) {
+        String start = startDate.atStartOfDay().format(DATE_TIME_FORMATTER);
+        String end = endDateExclusive.atStartOfDay().format(DATE_TIME_FORMATTER);
+
+        BigDecimal quantity = BigDecimal.ZERO;
+        BigDecimal scrapQty = BigDecimal.ZERO;
+        List<Map<String, Object>> rows = productionProductOutputMapper.selectDailyOutputStats(start, end);
+        if (rows != null) {
+            for (Map<String, Object> row : rows) {
+                quantity = quantity.add(toBigDecimal(row.get("quantity")));
+                scrapQty = scrapQty.add(toBigDecimal(row.get("scrapQty")));
+            }
+        }
+
+        Map<String, BigDecimal> stats = new LinkedHashMap<>();
+        stats.put("quantity", quantity);
+        stats.put("scrapQty", scrapQty);
+        return stats;
+    }
+
+    private BigDecimal calcDeviceOee(LocalDate day) {
+        long totalDeviceCount = deviceLedgerMapper.selectCount(new LambdaQueryWrapper<>());
+        if (totalDeviceCount <= 0) {
+            return BigDecimal.ZERO;
+        }
+
+        Date start = Date.from(day.atStartOfDay(ZoneId.systemDefault()).toInstant());
+        Date end = Date.from(day.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant());
+
+        List<DeviceRepair> repairList = deviceRepairMapper.selectList(new LambdaQueryWrapper<DeviceRepair>()
+                .select(DeviceRepair::getDeviceLedgerId)
+                .ge(DeviceRepair::getRepairTime, start)
+                .lt(DeviceRepair::getRepairTime, end)
+                .in(DeviceRepair::getStatus, 0, 3));
+
+        long repairingDeviceCount = repairList == null ? 0 : repairList.stream()
+                .map(DeviceRepair::getDeviceLedgerId)
+                .filter(Objects::nonNull)
+                .distinct()
+                .count();
+
+        long availableDeviceCount = Math.max(totalDeviceCount - repairingDeviceCount, 0);
+        return new BigDecimal(availableDeviceCount)
+                .multiply(new BigDecimal("100"))
+                .divide(new BigDecimal(totalDeviceCount), 2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal calcOrderAchievementRate(LocalDate day) {
+        List<ProductionOrder> orderList = productionOrderMapper.selectList(new LambdaQueryWrapper<ProductionOrder>()
+                .select(ProductionOrder::getQuantity, ProductionOrder::getCompleteQuantity)
+                .ge(ProductionOrder::getCreateTime, day.atStartOfDay())
+                .lt(ProductionOrder::getCreateTime, day.plusDays(1).atStartOfDay())
+                .ne(ProductionOrder::getStatus, ORDER_STATUS_PAUSED));
+
+        BigDecimal totalQuantity = BigDecimal.ZERO;
+        BigDecimal totalCompleteQuantity = BigDecimal.ZERO;
+        if (orderList != null) {
+            for (ProductionOrder order : orderList) {
+                totalQuantity = totalQuantity.add(zeroIfNull(order.getQuantity()));
+                totalCompleteQuantity = totalCompleteQuantity.add(zeroIfNull(order.getCompleteQuantity()));
+            }
+        }
+        return calcRate(totalCompleteQuantity, totalQuantity);
+    }
+
+    private BigDecimal calcDefectRate(LocalDate day) {
+        Map<String, BigDecimal> stats = loadOutputStats(day, day.plusDays(1));
+        BigDecimal quantity = stats.get("quantity");
+        BigDecimal scrapQty = stats.get("scrapQty");
+        return calcRate(scrapQty, quantity.add(scrapQty));
+    }
+
+    private Map<String, Object> buildRealtimeMetric(BigDecimal value, BigDecimal change) {
+        Map<String, Object> metric = new LinkedHashMap<>();
+        metric.put("value", scale(value));
+        metric.put("compareYesterday", scale(change));
+        return metric;
+    }
+
+    private BigDecimal calcRate(BigDecimal numerator, BigDecimal denominator) {
+        if (denominator == null || denominator.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
+        }
+        return zeroIfNull(numerator)
+                .multiply(new BigDecimal("100"))
+                .divide(denominator, 2, RoundingMode.HALF_UP);
+    }
+
+    private BigDecimal toBigDecimal(Object value) {
+        if (value == null) {
+            return BigDecimal.ZERO;
+        }
+        if (value instanceof BigDecimal) {
+            return (BigDecimal) value;
+        }
+        if (value instanceof Number) {
+            return BigDecimal.valueOf(((Number) value).doubleValue());
+        }
+        try {
+            return new BigDecimal(String.valueOf(value));
+        } catch (Exception ex) {
+            return BigDecimal.ZERO;
+        }
+    }
+
+    private Integer toInteger(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof Integer) {
+            return (Integer) value;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).intValue();
+        }
+        try {
+            return Integer.valueOf(String.valueOf(value));
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    private long toLong(Object value) {
+        if (value == null) {
+            return 0L;
+        }
+        if (value instanceof Long) {
+            return (Long) value;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).longValue();
+        }
+        try {
+            return Long.parseLong(String.valueOf(value));
+        } catch (Exception ex) {
+            return 0L;
+        }
+    }
+
+    private LocalDate parseDateOrNull(String rawDate) {
+        if (isBlank(rawDate)) {
+            return null;
+        }
+        try {
+            return LocalDate.parse(rawDate.trim(), DATE_FORMATTER);
+        } catch (DateTimeParseException ex) {
+            return null;
+        }
+    }
+
+    private boolean isBlank(String value) {
+        return value == null || value.trim().isEmpty();
+    }
+
+    private BigDecimal zeroIfNull(BigDecimal value) {
+        return value == null ? BigDecimal.ZERO : value;
+    }
+
+    private BigDecimal scale(BigDecimal value) {
+        return zeroIfNull(value).setScale(2, RoundingMode.HALF_UP);
+    }
+
 }

--
Gitblit v1.9.3