6 天以前 864037580c3e4d9f8c756b66ef40464d23262e6c
feat(home): 添加生产看板功能并优化订单查询性能

- 新增productionOverview接口提供生产总览数据包括产量、报废和良率
- 新增productionRealtimeBoard接口显示设备OEE、订单达成率和缺陷率实时指标
- 新增productionOrderProgress接口支持生产订单进度分页查询和状态筛选
- 新增todayProductionPlan接口提供今日生产计划列表
- 优化订单查询SQL减少不必要的关联和对象装配提升性能
- 添加设备可用率计算基于设备维修记录统计设备综合效率
- 实现订单状态映射和百分比计算工具方法
- 添加详细的生产看板前端联调文档和性能优化说明
已添加2个文件
已修改3个文件
672 ■■■■■ 文件已修改
doc/20260520_首页生产看板前端联调文档.md 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260520_首页生产看板性能优化前端变更文档.md 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/controller/HomeController.java 335 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderMapper.xml 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260520_Ê×Ò³Éú²ú¿´°åǰ¶ËÁªµ÷Îĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
# é¦–页生产看板前端联调文档
更新时间:2026-05-20
模块:`/home`(首页)
## 1. æŽ¥å£æ¸…单
1. `GET /home/productionOverview`:生产总览
2. `GET /home/productionRealtimeBoard`:生产实时看板
3. `GET /home/productionOrderProgress`:生产订单进度
4. `GET /home/todayProductionPlan`:今日生产计划
所有接口统一返回 `AjaxResult`:
```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {}
}
```
## 2. ç”Ÿäº§æ€»è§ˆ
### 2.1 è¯·æ±‚
```http
GET /home/productionOverview
```
### 2.2 è¿”回 `data`
```json
{
  "totalOutput": 1280.00,
  "totalScrap": 25.00,
  "yieldRate": 98.08
}
```
字段说明:
- `totalOutput`:累计产出(件,合格数)
- `totalScrap`:累计报废(件)
- `yieldRate`:良率(0-100,前端展示时可拼接 `%`)
## 3. ç”Ÿäº§å®žæ—¶çœ‹æ¿
### 3.1 è¯·æ±‚
```http
GET /home/productionRealtimeBoard
```
### 3.2 è¿”回 `data`
```json
{
  "deviceOee": {
    "value": 74.00,
    "compareYesterday": 2.50
  },
  "orderAchievementRate": {
    "value": 81.30,
    "compareYesterday": -1.20
  },
  "defectRate": {
    "value": 1.40,
    "compareYesterday": 0.30
  }
}
```
字段说明:
- `value`:当日指标值(0-100)
- `compareYesterday`:较昨日变化值(可正可负;前端按正负决定箭头方向和颜色)
## 4. ç”Ÿäº§è®¢å•进度
### 4.1 è¯·æ±‚
```http
GET /home/productionOrderProgress?tab=all&pageNum=1&pageSize=10
```
参数:
- `tab`:`all` / `inProgress` / `completed` / `paused`
- `pageNum`:页码(默认 `1`)
- `pageSize`:每页条数(默认 `10`,最大 `50`)
### 4.2 è¿”回 `data`
```json
{
  "tab": "all",
  "total": 24,
  "pageNum": 1,
  "pageSize": 10,
  "inProgressCount": 6,
  "completedCount": 12,
  "pausedCount": 2,
  "records": [
    {
      "orderNo": "MO-20260518-001",
      "productName": "智能控制器",
      "plannedQuantity": 1000.00,
      "completedQuantity": 860.00,
      "completionRate": 86.00,
      "dueDate": "2026-05-20",
      "status": 2,
      "statusLabel": "进行中"
    }
  ]
}
```
字段说明:
- `completionRate`:完成率(0-100)
- `status`:后端状态码(`1`待开始,`2`进行中,`3`已完成,`4`已暂停)
- `statusLabel`:状态中文展示值
## 5. ä»Šæ—¥ç”Ÿäº§è®¡åˆ’
### 5.1 è¯·æ±‚
```http
GET /home/todayProductionPlan?limit=4
```
参数:
- `limit`:返回条数(默认 `4`,最大 `20`)
### 5.2 è¿”回 `data`
```json
{
  "total": 9,
  "records": [
    {
      "orderNo": "MO-20260518-004",
      "productName": "结构件A",
      "plannedQuantity": 1200.00,
      "dueDate": "2026-05-15",
      "status": 2,
      "statusLabel": "进行中"
    }
  ]
}
```
## 6. å‰ç«¯å±•示约定
- ç™¾åˆ†æ¯”字段统一是数值(如 `74.00`),前端自行拼接 `%`。
- æ‰€æœ‰æ•°å€¼ä¿ç•™ä¸¤ä½å°æ•°ã€‚
- `dueDate` å¯èƒ½ä¸º `null`,前端需兜底展示(如 `--`)。
- `compareYesterday` æ­£è´Ÿéƒ½å¯èƒ½å‡ºçŽ°ï¼Œå»ºè®®æŒ‰ `>0` ä¸Šå‡ã€`<0` ä¸‹é™ã€`=0` æŒå¹³å¤„理。
doc/20260520_Ê×Ò³Éú²ú¿´°åÐÔÄÜÓÅ»¯Ç°¶Ë±ä¸üÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,107 @@
# é¦–页生产看板性能优化前端变更文档
更新时间:2026-05-20
适用页面:首页
涉及区块:
1. ç”Ÿäº§è®¢å•进度
2. ä»Šæ—¥ç”Ÿäº§è®¡åˆ’
## 1. æœ¬æ¬¡ä¼˜åŒ–目标
针对大数据量场景(订单数量多、生产历史长)优化查询性能,降低首页接口响应时间和内存占用。
## 2. æ¶‰åŠæŽ¥å£
1. `GET /home/productionOrderProgress`
2. `GET /home/todayProductionPlan`
## 3. å‰ç«¯æ˜¯å¦éœ€è¦æ”¹ä»£ç 
结论:**无强制改动,接口入参与返回结构保持兼容**。
你现有页面可以直接联调,不需要改字段映射。
## 4. æŽ¥å£è¯´æ˜Žï¼ˆä¿æŒä¸å˜ï¼‰
### 4.1 ç”Ÿäº§è®¢å•进度
请求:
```http
GET /home/productionOrderProgress?tab=all&pageNum=1&pageSize=10
```
参数:
- `tab`:`all` / `inProgress` / `completed` / `paused`
- `pageNum`:页码,默认 `1`
- `pageSize`:每页条数,默认 `10`,最大 `50`
返回 `data`(结构不变):
```json
{
  "tab": "all",
  "total": 1200,
  "pageNum": 1,
  "pageSize": 10,
  "inProgressCount": 180,
  "completedCount": 900,
  "pausedCount": 20,
  "records": [
    {
      "orderNo": "MO-20260518-001",
      "productName": "智能控制器",
      "plannedQuantity": 1000.00,
      "completedQuantity": 860.00,
      "completionRate": 86.00,
      "dueDate": "2026-05-20",
      "status": 2,
      "statusLabel": "进行中"
    }
  ]
}
```
### 4.2 ä»Šæ—¥ç”Ÿäº§è®¡åˆ’
请求:
```http
GET /home/todayProductionPlan?limit=4
```
参数:
- `limit`:返回条数,默认 `4`,最大 `20`
返回 `data`(结构不变):
```json
{
  "total": 230,
  "records": [
    {
      "orderNo": "MO-20260518-004",
      "productName": "结构件A",
      "plannedQuantity": 1200.00,
      "dueDate": "2026-05-15",
      "status": 2,
      "statusLabel": "进行中"
    }
  ]
}
```
## 5. åŽç«¯ä¼˜åŒ–点(供前端知悉)
1. è®¢å•进度与今日计划改为轻量 SQL,仅查询首页必需字段。
2. åŽ»æŽ‰äº†é¦–é¡µæŸ¥è¯¢è·¯å¾„ä¸­ä¸å¿…è¦çš„å¤§å…³è”ã€å›¾ç‰‡å¡«å……å’Œå¯¹è±¡è£…é…ã€‚
3. çŠ¶æ€ç»Ÿè®¡æ”¹ä¸ºæ•°æ®åº“èšåˆè®¡æ•°ï¼Œä¸å†é€æ¡æ‹‰å–è®¡ç®—ã€‚
4. åˆ†é¡µä¸Žæ¡æ•°ä¸Šé™ä¿ç•™ï¼ˆ`pageSize <= 50`, `limit <= 20`)。
## 6. å‰ç«¯å»ºè®®
1. åˆ‡æ¢ `tab` æ—¶ä¿ç•™çŽ°æœ‰è°ƒç”¨æ–¹å¼å³å¯ã€‚
2. `dueDate` å¯èƒ½ä¸ºç©ºï¼Œç»§ç»­æŒ‰ `--` å…œåº•展示。
3. ç™¾åˆ†æ¯”字段仍为数值,前端继续追加 `%` å±•示。
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,29 @@
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.ZoneId;
import java.time.format.DateTimeFormatter;
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
@@ -29,9 +45,19 @@
@Tag(name = "首页统计")
@RequestMapping("/home")
@AllArgsConstructor
public class HomeController extends BaseController {
    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 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")
@@ -179,6 +205,117 @@
    }
    /********************************************************质量类*****************************************************/
    @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(defaultValue = "1") Long pageNum,
                                              @RequestParam(defaultValue = "10") Long pageSize) {
        long safePageNum = pageNum == null || pageNum < 1 ? 1 : pageNum;
        long safePageSize = pageSize == null || pageSize < 1 ? 10 : Math.min(pageSize, 50);
        Integer status = resolveOrderStatus(tab);
        long offset = (safePageNum - 1) * safePageSize;
        List<Map<String, Object>> rawRows = productionOrderMapper.selectHomeOrderProgressPage(status, offset, safePageSize);
        List<Map<String, Object>> records = new ArrayList<>();
        if (rawRows != null) {
            for (Map<String, Object> rawRow : rawRows) {
                records.add(buildOrderProgressRow(rawRow));
            }
        }
        long inProgressCount = 0L;
        long completedCount = 0L;
        long pausedCount = 0L;
        List<Map<String, Object>> statusCountRows = productionOrderMapper.countHomeOrderProgressByStatus();
        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_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", tab);
        result.put("total", toLong(productionOrderMapper.countHomeOrderProgress(status)));
        result.put("pageNum", safePageNum);
        result.put("pageSize", safePageSize);
        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) {
        long safeLimit = limit == null || limit < 1 ? 4 : Math.min(limit, 20);
        List<Map<String, Object>> records = new ArrayList<>();
        List<Map<String, Object>> rawRows = productionOrderMapper.selectHomeTodayProductionPlan(safeLimit);
        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("total", toLong(productionOrderMapper.countHomeTodayProductionPlan()));
        result.put("records", records);
        return R.ok(result);
    }
    @GetMapping("/rawMaterialDetection")
    @Operation(summary = "原材料检测")
    public R rawMaterialDetection(@DefaultType Integer type){
@@ -321,4 +458,198 @@
        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 tab) {
        if (tab == null) {
            return null;
        }
        String normalized = tab.trim().toLowerCase();
        if ("inprogress".equals(normalized)) {
            return ORDER_STATUS_RUNNING;
        }
        if ("completed".equals(normalized)) {
            return ORDER_STATUS_COMPLETED;
        }
        if ("paused".equals(normalized)) {
            return ORDER_STATUS_PAUSED;
        }
        return null;
    }
    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 BigDecimal zeroIfNull(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
    private BigDecimal scale(BigDecimal value) {
        return zeroIfNull(value).setScale(2, RoundingMode.HALF_UP);
    }
}
src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java
@@ -12,6 +12,7 @@
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
 * <p>
@@ -39,4 +40,16 @@
    Integer countPending(@Param("startDate") String startDate, @Param("endDate") String endDate);
    List<Map<String, Object>> selectHomeOrderProgressPage(@Param("status") Integer status,
                                                          @Param("offset") Long offset,
                                                          @Param("size") Long size);
    Long countHomeOrderProgress(@Param("status") Integer status);
    List<Map<String, Object>> countHomeOrderProgressByStatus();
    List<Map<String, Object>> selectHomeTodayProductionPlan(@Param("size") Long size);
    Long countHomeTodayProductionPlan();
}
src/main/resources/mapper/production/ProductionOrderMapper.xml
@@ -212,4 +212,61 @@
          and ifnull(complete_quantity, 0) &lt; quantity
    </select>
    <select id="selectHomeOrderProgressPage" resultType="java.util.Map">
        select po.nps_no as orderNo,
               p.product_name as productName,
               ifnull(po.quantity, 0) as plannedQuantity,
               ifnull(po.complete_quantity, 0) as completedQuantity,
               round(ifnull(po.complete_quantity, 0) / nullif(po.quantity, 0) * 100, 2) as completionRate,
               po.plan_complete_time as dueDate,
               po.status as status
        from production_order po
                 left join product_model pm on po.product_model_id = pm.id
                 left join product p on pm.product_id = p.id
        <where>
            <if test="status != null">
                and po.status = #{status}
            </if>
        </where>
        order by po.id desc
        limit #{offset}, #{size}
    </select>
    <select id="countHomeOrderProgress" resultType="java.lang.Long">
        select count(1)
        from production_order po
        <where>
            <if test="status != null">
                and po.status = #{status}
            </if>
        </where>
    </select>
    <select id="countHomeOrderProgressByStatus" resultType="java.util.Map">
        select po.status as status, count(1) as cnt
        from production_order po
        where po.status in (2, 3, 4)
        group by po.status
    </select>
    <select id="selectHomeTodayProductionPlan" resultType="java.util.Map">
        select po.nps_no as orderNo,
               p.product_name as productName,
               ifnull(po.quantity, 0) as plannedQuantity,
               po.plan_complete_time as dueDate,
               po.status as status
        from production_order po
                 left join product_model pm on po.product_model_id = pm.id
                 left join product p on pm.product_id = p.id
        where po.status in (1, 2)
        order by case when po.status = 2 then 0 else 1 end, po.id desc
        limit #{size}
    </select>
    <select id="countHomeTodayProductionPlan" resultType="java.lang.Long">
        select count(1)
        from production_order po
        where po.status in (1, 2)
    </select>
</mapper>