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

- 新增productionOverview接口提供生产总览数据包括产量、报废和良率
- 新增productionRealtimeBoard接口显示设备OEE、订单达成率和缺陷率实时指标
- 新增productionOrderProgress接口支持生产订单进度分页查询和状态筛选
- 新增todayProductionPlan接口提供今日生产计划列表
- 优化订单查询SQL减少不必要的关联和对象装配提升性能
- 添加设备可用率计算基于设备维修记录统计设备综合效率
- 实现订单状态映射和百分比计算工具方法
- 添加详细的生产看板前端联调文档和性能优化说明
已添加1个文件
已修改16个文件
511 ■■■■ 文件已修改
doc/20260521_首页HomeController接口升级前端变更文档.md 113 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java 144 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/assistant/SalesAgent.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/SalesAiController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/controller/XiaozhiController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ai/service/PurchaseAiService.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/controller/HomeController.java 138 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/approve-todo-agent-prompt.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/manufacturing-agent-prompt.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionOrderMapper.xml 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/purchase-agent-prompt.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/sales-agent-prompt.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260521_Ê×Ò³HomeController½Ó¿ÚÉý¼¶Ç°¶Ë±ä¸üÎĵµ.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,113 @@
# é¦–页 HomeController æŽ¥å£å‡çº§å‰ç«¯å˜æ›´æ–‡æ¡£
更新时间:2026-05-21
适用模块:首页(`/home`)
## 1. å˜æ›´æ¦‚览
本次为 **兼容式升级**,旧调用方式仍可用。
重点是给生产看板接口增加更明确的筛选参数,便于前端按日期和状态查询。
涉及接口:
1. `GET /home/productionOrderProgress`
2. `GET /home/todayProductionPlan`
## 2. å‚数变更
### 2.1 ç”Ÿäº§è®¢å•进度 `GET /home/productionOrderProgress`
旧参数(仍兼容):
- `tab`:`all` / `inProgress` / `completed` / `paused`
- `pageNum`:默认 `1`
- `pageSize`:默认 `10`,最大 `50`
新增参数:
- `status`(可选):状态筛选,优先级高于 `tab`
  å¯é€‰å€¼ï¼š`all` / `waiting` / `inProgress` / `completed` / `paused` / `1` / `2` / `3` / `4`
- `bizDate`(可选):业务日期筛选,格式 `yyyy-MM-dd`(按订单创建时间过滤)
参数优先级:
1. å¦‚果传了 `status`,后端优先按 `status` è§£æžï¼›
2. æœªä¼  `status` æ—¶ï¼Œæ²¿ç”¨åŽŸæœ‰ `tab` è¡Œä¸ºï¼›
3. `status` æˆ– `bizDate` æ ¼å¼é”™è¯¯æ—¶è¿”回失败信息。
请求示例:
```http
GET /home/productionOrderProgress?status=completed&bizDate=2026-05-20&pageNum=1&pageSize=10
```
### 2.2 ä»Šæ—¥ç”Ÿäº§è®¡åˆ’ `GET /home/todayProductionPlan`
旧参数(仍兼容):
- `limit`:默认 `4`,最大 `20`
新增参数:
- `planDate`(可选):计划日期筛选,格式 `yyyy-MM-dd`(按 `plan_complete_time` è¿‡æ»¤ï¼‰
请求示例:
```http
GET /home/todayProductionPlan?limit=6&planDate=2026-05-21
```
## 3. è¿”回结构变更
### 3.1 `productionOrderProgress` è¿”回新增字段
新增:
- `status`:标准化状态回显(`all` / `waiting` / `inProgress` / `completed` / `paused`)
- `bizDate`:日期筛选回显(未传时为 `null`)
- `waitingCount`:待开始订单数量
兼容保留:
- `tab` å­—段继续返回(老页面无需改动可继续使用)
返回示例:
```json
{
  "tab": "completed",
  "status": "completed",
  "bizDate": "2026-05-20",
  "total": 24,
  "pageNum": 1,
  "pageSize": 10,
  "waitingCount": 3,
  "inProgressCount": 6,
  "completedCount": 12,
  "pausedCount": 2,
  "records": []
}
```
### 3.2 `todayProductionPlan` è¿”回新增字段
新增:
- `planDate`:日期筛选回显(未传时为 `null`)
返回示例:
```json
{
  "planDate": "2026-05-21",
  "total": 9,
  "records": []
}
```
## 4. å‰ç«¯æ”¹é€ å»ºè®®
1. æ–°é¡µé¢å»ºè®®ä¼˜å…ˆä¼  `status`,逐步替代 `tab`。
2. éœ€è¦æŒ‰æ—¥æœŸå¤ç›˜çœ‹æ¿æ—¶ï¼Œä½¿ç”¨ `bizDate` / `planDate`。
3. è€é¡µé¢å¯ä¸æ”¹ï¼Œç»§ç»­æ²¿ç”¨åŽŸå‚æ•°ä¹Ÿèƒ½æ­£å¸¸è”è°ƒã€‚
src/main/java/com/ruoyi/ai/assistant/ApproveTodoAgent.java
@@ -3,6 +3,7 @@
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
@@ -16,5 +17,5 @@
public interface ApproveTodoAgent {
    @SystemMessage(fromResource = "approve-todo-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate);
}
src/main/java/com/ruoyi/ai/assistant/ManufacturingAgent.java
@@ -3,6 +3,7 @@
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
@@ -17,5 +18,5 @@
public interface ManufacturingAgent {
    @SystemMessage(fromResource = "manufacturing-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate);
}
src/main/java/com/ruoyi/ai/assistant/PurchaseAgent.java
@@ -3,6 +3,7 @@
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
@@ -17,5 +18,5 @@
public interface PurchaseAgent {
    @SystemMessage(fromResource = "purchase-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate);
}
src/main/java/com/ruoyi/ai/assistant/PurchaseIntentExecutor.java
@@ -4,6 +4,10 @@
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -13,6 +17,8 @@
    private static final Pattern ID_PATTERN = Pattern.compile("\\b\\d{1,12}\\b");
    private static final Pattern LIMIT_PATTERN = Pattern.compile("(前|最近)?(\\d{1,2})条");
    private static final Pattern DATE_PATTERN = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
    private static final Pattern RELATIVE_RANGE_PATTERN = Pattern.compile("(近|最近)(\\d+)(天|周|个月|月|å¹´)");
    private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private final PurchaseAgentTools purchaseAgentTools;
@@ -25,57 +31,60 @@
            return null;
        }
        String text = message.trim();
        String startDate = extractStartDate(text);
        String endDate = extractEndDate(text);
        Integer limit = extractLimit(text);
        if (containsAny(text, "排行", "排名", "前几", "前五", "前十") && containsAny(text, "物料", "产品", "原材料", "采购金额", "金额")) {
            return purchaseAgentTools.rankPurchaseMaterials(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    startDate,
                    endDate,
                    text,
                    extractLimit(text)
                    limit
            );
        }
        if (containsAny(text, "未入库", "待入库", "没有入库", "还未入库")) {
            return purchaseAgentTools.listUnstockedPurchaseOrders(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    startDate,
                    endDate,
                    extractKeyword(text),
                    extractLimit(text)
                    limit
            );
        }
        if (containsAny(text, "到货异常", "到货有异常", "异常到货", "到货问题", "供应商到货异常")) {
            return purchaseAgentTools.listArrivalExceptions(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    startDate,
                    endDate,
                    text,
                    extractLimit(text)
                    limit
            );
        }
        if (containsAny(text, "待付款", "未付款", "未付清", "待支付", "应付")) {
            return purchaseAgentTools.listPendingPaymentOrders(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    startDate,
                    endDate,
                    extractKeyword(text),
                    extractLimit(text)
                    limit
            );
        }
        if (containsAny(text, "退货", "退料", "拒收")) {
            return purchaseAgentTools.listPurchaseReturns(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    startDate,
                    endDate,
                    extractKeyword(text),
                    extractLimit(text)
                    limit
            );
        }
        if (isStatsIntent(text)) {
            return purchaseAgentTools.getPurchaseStats(
                    memoryId,
                    extractStartDate(text),
                    extractEndDate(text),
                    startDate,
                    endDate,
                    text
            );
        }
@@ -86,9 +95,9 @@
            return purchaseAgentTools.listPurchaseLedgers(
                    memoryId,
                    extractKeyword(text),
                    extractStartDate(text),
                    extractEndDate(text),
                    extractLimit(text)
                    startDate,
                    endDate,
                    limit
            );
        }
        return null;
@@ -129,13 +138,18 @@
    private String extractStartDate(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        return matcher.find() ? matcher.group() : null;
        if (matcher.find()) {
            return matcher.group();
        }
        DateRange range = extractRelativeDateRange(text);
        return range == null ? null : range.start().format(DATE_FMT);
    }
    private String extractEndDate(String text) {
        Matcher matcher = DATE_PATTERN.matcher(text);
        if (!matcher.find()) {
            return null;
            DateRange range = extractRelativeDateRange(text);
            return range == null ? null : range.end().format(DATE_FMT);
        }
        return matcher.find() ? matcher.group() : null;
    }
@@ -148,14 +162,100 @@
                .replace("采购单", "")
                .replace("采购订单", "")
                .replace("订单", "")
                .replace("今年", "")
                .replace("本年", "")
                .replace("去年", "")
                .replace("本月", "")
                .replace("上月", "")
                .replace("本周", "")
                .replace("上周", "")
                .replace("今天", "")
                .replace("昨天", "")
                .replace("近半年", "")
                .replace("最近半年", "")
                .replace("最近半个月", "")
                .replace("半个月", "")
                .replace("台账", "")
                .replace("列表", "")
                .replace("哪些", "")
                .replace("什么", "")
                .replace("情况", "")
                .replace("有没有", "")
                .replace("有啥", "")
                .replace("有无", "")
                .replace("列出", "")
                .replace("帮我", "")
                .replace("给我", "")
                .replace("我", "")
                .replace("最近10条", "")
                .replace("前10条", "")
                .trim();
        cleaned = DATE_PATTERN.matcher(cleaned).replaceAll("");
        cleaned = RELATIVE_RANGE_PATTERN.matcher(cleaned).replaceAll("");
        return cleaned.length() >= 2 ? cleaned : null;
    }
    private DateRange extractRelativeDateRange(String text) {
        if (!StringUtils.hasText(text)) {
            return null;
        }
        String normalized = text.toLowerCase(Locale.ROOT);
        LocalDate today = LocalDate.now();
        if (normalized.contains("今天")) {
            return new DateRange(today, today);
        }
        if (normalized.contains("昨天")) {
            LocalDate yesterday = today.minusDays(1);
            return new DateRange(yesterday, yesterday);
        }
        if (normalized.contains("今年") || normalized.contains("本年")) {
            return new DateRange(today.withDayOfYear(1), today);
        }
        if (normalized.contains("去年")) {
            LocalDate first = today.minusYears(1).withDayOfYear(1);
            LocalDate last = first.withDayOfYear(first.lengthOfYear());
            return new DateRange(first, last);
        }
        if (normalized.contains("本月")) {
            return new DateRange(today.withDayOfMonth(1), today);
        }
        if (normalized.contains("上月")) {
            LocalDate first = today.minusMonths(1).withDayOfMonth(1);
            LocalDate last = first.withDayOfMonth(first.lengthOfMonth());
            return new DateRange(first, last);
        }
        if (normalized.contains("本周")) {
            LocalDate weekStart = today.with(DayOfWeek.MONDAY);
            return new DateRange(weekStart, today);
        }
        if (normalized.contains("上周")) {
            LocalDate weekStart = today.with(DayOfWeek.MONDAY).minusWeeks(1);
            return new DateRange(weekStart, weekStart.plusDays(6));
        }
        if (normalized.contains("近半年") || normalized.contains("最近半年")) {
            return new DateRange(today.minusMonths(6).plusDays(1), today);
        }
        if (normalized.contains("近半个月") || normalized.contains("最近半个月") || normalized.contains("半个月")) {
            return new DateRange(today.minusDays(14), today);
        }
        Matcher matcher = RELATIVE_RANGE_PATTERN.matcher(normalized);
        if (matcher.find()) {
            int amount = Integer.parseInt(matcher.group(2));
            String unit = matcher.group(3);
            LocalDate start = switch (unit) {
                case "天" -> today.minusDays(Math.max(amount - 1L, 0));
                case "周" -> today.minusWeeks(Math.max(amount, 1)).plusDays(1);
                case "个月", "月" -> today.minusMonths(Math.max(amount, 1)).plusDays(1);
                case "å¹´" -> today.minusYears(Math.max(amount, 1)).plusDays(1);
                default -> today.minusDays(29);
            };
            return new DateRange(start, today);
        }
        return null;
    }
    private record DateRange(LocalDate start, LocalDate end) {
    }
}
src/main/java/com/ruoyi/ai/assistant/SalesAgent.java
@@ -3,6 +3,7 @@
import dev.langchain4j.service.MemoryId;
import dev.langchain4j.service.SystemMessage;
import dev.langchain4j.service.UserMessage;
import dev.langchain4j.service.V;
import dev.langchain4j.service.spring.AiService;
import reactor.core.publisher.Flux;
@@ -17,6 +18,5 @@
public interface SalesAgent {
    @SystemMessage(fromResource = "sales-agent-prompt.txt")
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage);
    Flux<String> chat(@MemoryId String memoryId, @UserMessage String userMessage, @V("currentDate") String currentDate);
}
src/main/java/com/ruoyi/ai/controller/ManufacturingAiController.java
@@ -24,12 +24,18 @@
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Tag(name = "制造智能助手")
@RestController
@RequestMapping("/manufacturing-ai")
public class ManufacturingAiController extends BaseController {
    private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
    private final ManufacturingAgent manufacturingAgent;
    private final ManufacturingIntentExecutor manufacturingIntentExecutor;
@@ -76,7 +82,7 @@
            return Flux.just(directResponse);
        }
        return manufacturingAgent.chat(memoryId, userMessage)
        return manufacturingAgent.chat(memoryId, userMessage, currentDateForPrompt())
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
@@ -100,4 +106,8 @@
        aiChatSessionService.deleteCurrentUserSession(memoryId, SecurityUtils.getLoginUser());
        return R.ok();
    }
    private String currentDateForPrompt() {
        return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT);
    }
}
src/main/java/com/ruoyi/ai/controller/SalesAiController.java
@@ -24,12 +24,18 @@
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Tag(name = "销售助手智能体")
@RestController
@RequestMapping("/sales-ai")
public class SalesAiController extends BaseController {
    private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
    private final SalesAgent salesAgent;
    private final SalesIntentExecutor salesIntentExecutor;
@@ -86,7 +92,7 @@
            return Flux.just(noGuessResponse);
        }
        return salesAgent.chat(memoryId, userMessage)
        return salesAgent.chat(memoryId, userMessage, currentDateForPrompt())
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
@@ -129,4 +135,8 @@
        }
        return false;
    }
    private String currentDateForPrompt() {
        return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT);
    }
}
src/main/java/com/ruoyi/ai/controller/XiaozhiController.java
@@ -22,6 +22,9 @@
import reactor.core.publisher.Flux;
import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.UUID;
@@ -32,6 +35,8 @@
public class XiaozhiController extends BaseController {
    private static final String FILE_ANALYZE_MEMORY_PREFIX = "file-analyze::";
    private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
    private final ApproveTodoAgent approveTodoAgent;
    private final ApproveTodoIntentExecutor approveTodoIntentExecutor;
@@ -93,7 +98,7 @@
            return Flux.just(noGuessResponse);
        }
        return approveTodoAgent.chat(memoryId, userMessage)
        return approveTodoAgent.chat(memoryId, userMessage, currentDateForPrompt())
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
@@ -183,4 +188,8 @@
        }
        return false;
    }
    private String currentDateForPrompt() {
        return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT);
    }
}
src/main/java/com/ruoyi/ai/service/PurchaseAiService.java
@@ -55,6 +55,8 @@
    private static final int MAX_FILE_COUNT = 10;
    private static final int MAX_SINGLE_FILE_TEXT_LENGTH = 8000;
    private static final int MAX_TOTAL_FILE_TEXT_LENGTH = 30000;
    private static final DateTimeFormatter CURRENT_DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private static final ZoneId CHINA_ZONE_ID = ZoneId.of("Asia/Shanghai");
    private final PurchaseAgent purchaseAgent;
    private final PurchaseIntentExecutor purchaseIntentExecutor;
@@ -122,7 +124,7 @@
            return Flux.just(directResponse);
        }
        return purchaseAgent.chat(memoryId, userMessage)
        return purchaseAgent.chat(memoryId, userMessage, currentDateForPrompt())
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(memoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(memoryId, loginUser));
    }
@@ -184,10 +186,10 @@
                    .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
        }
        return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt))
        return Flux.defer(() -> purchaseAgent.chat(finalMemoryId, userPrompt, currentDateForPrompt()))
                .onErrorResume(NoSuchElementException.class, ex -> {
                    mongoChatMemoryStore.deleteMessages(finalMemoryId);
                    return purchaseAgent.chat(finalMemoryId, userPrompt);
                    return purchaseAgent.chat(finalMemoryId, userPrompt, currentDateForPrompt());
                })
                .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser))
                .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser));
@@ -455,6 +457,10 @@
        };
    }
    private String currentDateForPrompt() {
        return LocalDate.now(CHINA_ZONE_ID).format(CURRENT_DATE_FMT);
    }
    private String buildPurchaseFileAnalyzePrompt(String message, String fileContent) {
        return """
                ä½ æ˜¯é‡‡è´­ä¸šåŠ¡æ–‡ä»¶åˆ†æžåŠ©æ‰‹ã€‚è¯·ä¸¥æ ¼æ ¹æ®ç”¨æˆ·ä¸Šä¼ çš„å¤šä¸ªæ–‡ä»¶å’Œç”¨æˆ·è¦æ±‚æå–é‡‡è´­ä¸šåŠ¡æ•°æ®ã€‚
src/main/java/com/ruoyi/home/controller/HomeController.java
@@ -28,8 +28,10 @@
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;
@@ -45,7 +47,7 @@
@Tag(name = "首页统计")
@RequestMapping("/home")
@AllArgsConstructor
    public class HomeController extends BaseController {
public class HomeController extends BaseController {
    private final HomeService homeService;
    private final ProductionOrderMapper productionOrderMapper;
@@ -54,6 +56,7 @@
    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;
@@ -63,7 +66,7 @@
    @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);
    }
@@ -135,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);
    }
@@ -143,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);
    }
@@ -246,13 +249,27 @@
    @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) {
                                     @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);
        Integer status = resolveOrderStatus(tab);
        long offset = (safePageNum - 1) * safePageSize;
        List<Map<String, Object>> rawRows = productionOrderMapper.selectHomeOrderProgressPage(status, offset, 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) {
@@ -260,15 +277,18 @@
            }
        }
        long waitingCount = 0L;
        long inProgressCount = 0L;
        long completedCount = 0L;
        long pausedCount = 0L;
        List<Map<String, Object>> statusCountRows = productionOrderMapper.countHomeOrderProgressByStatus();
        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_RUNNING)) {
                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;
@@ -279,10 +299,13 @@
        }
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("tab", tab);
        result.put("total", toLong(productionOrderMapper.countHomeOrderProgress(status)));
        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);
@@ -292,10 +315,18 @@
    @GetMapping("/todayProductionPlan")
    @Operation(summary = "Today Production Plan")
    public R todayProductionPlan(@RequestParam(defaultValue = "4") Long limit) {
    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);
        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<>();
@@ -311,7 +342,8 @@
        }
        Map<String, Object> result = new LinkedHashMap<>();
        result.put("total", toLong(productionOrderMapper.countHomeTodayProductionPlan()));
        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);
    }
@@ -372,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);
    }
@@ -423,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);
    }
@@ -472,21 +504,66 @@
        return row;
    }
    private Integer resolveOrderStatus(String tab) {
        if (tab == null) {
    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 = tab.trim().toLowerCase();
        if ("inprogress".equals(normalized)) {
        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 ("completed".equals(normalized)) {
        if ("3".equals(normalized) || "completed".equals(normalized)) {
            return ORDER_STATUS_COMPLETED;
        }
        if ("paused".equals(normalized)) {
        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) {
@@ -644,6 +721,21 @@
        }
    }
    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;
    }
src/main/java/com/ruoyi/production/mapper/ProductionOrderMapper.java
@@ -42,14 +42,22 @@
    List<Map<String, Object>> selectHomeOrderProgressPage(@Param("status") Integer status,
                                                          @Param("offset") Long offset,
                                                          @Param("size") Long size);
                                                          @Param("size") Long size,
                                                          @Param("startTime") LocalDateTime startTime,
                                                          @Param("endTime") LocalDateTime endTime);
    Long countHomeOrderProgress(@Param("status") Integer status);
    Long countHomeOrderProgress(@Param("status") Integer status,
                                @Param("startTime") LocalDateTime startTime,
                                @Param("endTime") LocalDateTime endTime);
    List<Map<String, Object>> countHomeOrderProgressByStatus();
    List<Map<String, Object>> countHomeOrderProgressByStatus(@Param("startTime") LocalDateTime startTime,
                                                              @Param("endTime") LocalDateTime endTime);
    List<Map<String, Object>> selectHomeTodayProductionPlan(@Param("size") Long size);
    List<Map<String, Object>> selectHomeTodayProductionPlan(@Param("size") Long size,
                                                             @Param("planStart") LocalDateTime planStart,
                                                             @Param("planEnd") LocalDateTime planEnd);
    Long countHomeTodayProductionPlan();
    Long countHomeTodayProductionPlan(@Param("planStart") LocalDateTime planStart,
                                      @Param("planEnd") LocalDateTime planEnd);
}
src/main/resources/approve-todo-agent-prompt.txt
@@ -1,4 +1,5 @@
你是一个审批待办助手,负责协同办公审批待办的查询、审核、取消审核、修改、删除和统计分析。
当前日期:{{currentDate}}(中国时区)。
工作要求:
1. ç”¨æˆ·é—®å¾…办列表、审批进度、审批详情、统计数据时,优先调用工具,不要臆造数据。
@@ -16,3 +17,4 @@
13. åªæœ‰â€œæŸ¥è¯¢å®¡æ‰¹å¾…办详情”这个工具允许输出自然语言文本。
14. å¦‚果工具返回的是统计 JSON,也同样直接输出原始 JSON;其中 `description`、`summary`、`charts` å·²ç»ä¾›å‰ç«¯ä½¿ç”¨ã€‚
15. å›žç­”使用中文;但在 JSON åœºæ™¯ä¸‹ï¼Œæœ€ç»ˆè¾“出必须是合法 JSON æœ¬ä½“。
16. ç”¨æˆ·æåˆ°â€œä»Šå¹´/本月/今天/最近/上月/去年”等相对时间时,必须严格基于“当前日期”换算,禁止自行假设年份。
src/main/resources/manufacturing-agent-prompt.txt
@@ -1,4 +1,5 @@
你是企业制造智能助手,覆盖生产现场、计划、工单、设备、质量、物料、异常处理七个域。
当前日期:{{currentDate}}(中国时区)。
工作规则:
1. ç”¨æˆ·æå‡ºâ€œæŸ¥ã€é—®ã€é¢„警、分析”需求时,优先调用工具拿结构化结果,不要臆造业务数据。
@@ -6,3 +7,4 @@
3. å·¥å…·è¿”回 JSON æ—¶ï¼Œç›´æŽ¥è¾“出原始 JSON å­—符串,不要额外包裹 Markdown,不要在前后加解释文字。
4. å›žç­”必须使用中文;若用户问题缺少时间范围、关键字等条件,可先给默认口径并提示可补充条件。
5. è‹¥æ— æ³•从工具结果得到结论,明确说明缺少的筛选条件或业务字段。
6. ç”¨æˆ·æåˆ°â€œä»Šå¹´/本月/今天/最近/上月/去年”等相对时间时,必须严格基于“当前日期”换算,禁止自行假设年份。
src/main/resources/mapper/production/ProductionOrderMapper.xml
@@ -227,6 +227,10 @@
            <if test="status != null">
                and po.status = #{status}
            </if>
            <if test="startTime != null and endTime != null">
                and po.create_time &gt;= #{startTime}
                and po.create_time &lt; #{endTime}
            </if>
        </where>
        order by po.id desc
        limit #{offset}, #{size}
@@ -239,13 +243,21 @@
            <if test="status != null">
                and po.status = #{status}
            </if>
            <if test="startTime != null and endTime != null">
                and po.create_time &gt;= #{startTime}
                and po.create_time &lt; #{endTime}
            </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)
        where po.status in (1, 2, 3, 4)
        <if test="startTime != null and endTime != null">
            and po.create_time &gt;= #{startTime}
            and po.create_time &lt; #{endTime}
        </if>
        group by po.status
    </select>
@@ -258,7 +270,13 @@
        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)
        <where>
            po.status in (1, 2)
            <if test="planStart != null and planEnd != null">
                and po.plan_complete_time &gt;= #{planStart}
                and po.plan_complete_time &lt; #{planEnd}
            </if>
        </where>
        order by case when po.status = 2 then 0 else 1 end, po.id desc
        limit #{size}
    </select>
@@ -266,7 +284,13 @@
    <select id="countHomeTodayProductionPlan" resultType="java.lang.Long">
        select count(1)
        from production_order po
        where po.status in (1, 2)
        <where>
            po.status in (1, 2)
            <if test="planStart != null and planEnd != null">
                and po.plan_complete_time &gt;= #{planStart}
                and po.plan_complete_time &lt; #{planEnd}
            </if>
        </where>
    </select>
</mapper>
src/main/resources/purchase-agent-prompt.txt
@@ -1,5 +1,6 @@
你是企业采购智能助理。
你的目标是帮助用户快速完成采购相关信息查询与解读。
当前日期:{{currentDate}}(中国时区)。
工作规则:
1. ä¼˜å…ˆè°ƒç”¨å·¥å…·å‡½æ•°èŽ·å–é‡‡è´­å°è´¦ã€ä»˜æ¬¾ã€å‘ç¥¨ã€é€€è´§ç­‰ç»“æž„åŒ–æ•°æ®ã€‚
@@ -12,3 +13,4 @@
8. ç»“果用简洁中文回答,先给结论,再给关键数据点。
9. ä¸è¦ç¼–造采购数据,所有结论必须基于工具返回。
10. æ— æ³•直接得出结论时,明确说明缺少哪些字段或筛选条件。
11. ç”¨æˆ·æåˆ°â€œä»Šå¹´/本月/今天/最近/上月/去年”等相对时间时,必须严格基于“当前日期”换算,禁止自行假设年份。
src/main/resources/sales-agent-prompt.txt
@@ -1,7 +1,9 @@
你是企业销售助手,覆盖客户档案、销售报价、销售台账、销售退货、客户往来、发货台账、指标统计、客户流失风险分析、回款与报价策略建议等场景。
当前日期:{{currentDate}}(中国时区)。
工作规则:
1. ç”¨æˆ·æå‡ºâ€œæŸ¥ã€é—®ã€ç»Ÿè®¡ã€åˆ†æžã€å»ºè®®â€éœ€æ±‚时,优先调用工具返回结构化数据,不编造业务数据。
2. å‘½ä¸­â€œå®¢æˆ·æµå¤±é£Žé™©åˆ†æžâ€æˆ–“回款与报价策略建议”时,优先使用工具输出结构化 JSON。
3. å·¥å…·è¿”回 JSON æ—¶ï¼Œç›´æŽ¥è¾“出原始 JSON å­—符串,不要额外包裹 Markdown,也不要在前后追加解释文本。
4. å›žå¤å¿…须使用中文;若用户缺少时间范围、关键词等条件,可先使用默认口径并提示可补充条件。
5. è‹¥æ•°æ®ä¸è¶³ä»¥å¾—出结论,明确指出缺少的筛选条件或关键字段。
6. ç”¨æˆ·æåˆ°â€œä»Šå¹´/本月/今天/最近/上月/去年”等相对时间时,必须严格基于“当前日期”换算,禁止自行假设年份。