| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package com.ruoyi.ai.tools; |
| | | |
| | | import com.alibaba.fastjson2.JSON; |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.baomidou.mybatisplus.core.toolkit.support.SFunction; |
| | | import com.ruoyi.ai.context.AiSessionUserContext; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.device.mapper.DeviceDefectRecordMapper; |
| | | import com.ruoyi.device.mapper.DeviceLedgerMapper; |
| | | import com.ruoyi.device.mapper.DeviceRepairMapper; |
| | | import com.ruoyi.device.pojo.DeviceDefectRecord; |
| | | import com.ruoyi.device.pojo.DeviceLedger; |
| | | import com.ruoyi.device.pojo.DeviceRepair; |
| | | import com.ruoyi.framework.security.LoginUser; |
| | | import com.ruoyi.procurementrecord.mapper.ProcurementExceptionRecordMapper; |
| | | import com.ruoyi.procurementrecord.pojo.ProcurementExceptionRecord; |
| | | import com.ruoyi.production.mapper.ProductionOperationTaskMapper; |
| | | import com.ruoyi.production.mapper.ProductionOrderMapper; |
| | | import com.ruoyi.production.mapper.ProductionPlanMapper; |
| | | import com.ruoyi.production.mapper.ProductionProductMainMapper; |
| | | import com.ruoyi.production.pojo.ProductionOperationTask; |
| | | import com.ruoyi.production.pojo.ProductionOrder; |
| | | import com.ruoyi.production.pojo.ProductionPlan; |
| | | import com.ruoyi.production.pojo.ProductionProductMain; |
| | | import com.ruoyi.quality.mapper.QualityInspectMapper; |
| | | import com.ruoyi.quality.mapper.QualityUnqualifiedMapper; |
| | | import com.ruoyi.quality.pojo.QualityInspect; |
| | | import com.ruoyi.quality.pojo.QualityUnqualified; |
| | | import com.ruoyi.stock.mapper.StockInventoryMapper; |
| | | import com.ruoyi.stock.pojo.StockInventory; |
| | | import dev.langchain4j.agent.tool.P; |
| | | import dev.langchain4j.agent.tool.Tool; |
| | | import dev.langchain4j.agent.tool.ToolMemoryId; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalDateTime; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.temporal.ChronoUnit; |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.stream.Collectors; |
| | | |
| | | @Component |
| | | public class ManufacturingAgentTools { |
| | | |
| | | private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); |
| | | private static final int DEFAULT_LIMIT = 10; |
| | | private static final int MAX_LIMIT = 30; |
| | | private static final int DEVICE_REPAIR_STATUS_PENDING = 0; |
| | | |
| | | private final ProductionPlanMapper productionPlanMapper; |
| | | private final ProductionOrderMapper productionOrderMapper; |
| | | private final ProductionOperationTaskMapper productionOperationTaskMapper; |
| | | private final ProductionProductMainMapper productionProductMainMapper; |
| | | private final DeviceLedgerMapper deviceLedgerMapper; |
| | | private final DeviceRepairMapper deviceRepairMapper; |
| | | private final DeviceDefectRecordMapper deviceDefectRecordMapper; |
| | | private final QualityInspectMapper qualityInspectMapper; |
| | | private final QualityUnqualifiedMapper qualityUnqualifiedMapper; |
| | | private final StockInventoryMapper stockInventoryMapper; |
| | | private final ProcurementExceptionRecordMapper procurementExceptionRecordMapper; |
| | | private final AiSessionUserContext aiSessionUserContext; |
| | | |
| | | public ManufacturingAgentTools(ProductionPlanMapper productionPlanMapper, |
| | | ProductionOrderMapper productionOrderMapper, |
| | | ProductionOperationTaskMapper productionOperationTaskMapper, |
| | | ProductionProductMainMapper productionProductMainMapper, |
| | | DeviceLedgerMapper deviceLedgerMapper, |
| | | DeviceRepairMapper deviceRepairMapper, |
| | | DeviceDefectRecordMapper deviceDefectRecordMapper, |
| | | QualityInspectMapper qualityInspectMapper, |
| | | QualityUnqualifiedMapper qualityUnqualifiedMapper, |
| | | StockInventoryMapper stockInventoryMapper, |
| | | ProcurementExceptionRecordMapper procurementExceptionRecordMapper, |
| | | AiSessionUserContext aiSessionUserContext) { |
| | | this.productionPlanMapper = productionPlanMapper; |
| | | this.productionOrderMapper = productionOrderMapper; |
| | | this.productionOperationTaskMapper = productionOperationTaskMapper; |
| | | this.productionProductMainMapper = productionProductMainMapper; |
| | | this.deviceLedgerMapper = deviceLedgerMapper; |
| | | this.deviceRepairMapper = deviceRepairMapper; |
| | | this.deviceDefectRecordMapper = deviceDefectRecordMapper; |
| | | this.qualityInspectMapper = qualityInspectMapper; |
| | | this.qualityUnqualifiedMapper = qualityUnqualifiedMapper; |
| | | this.stockInventoryMapper = stockInventoryMapper; |
| | | this.procurementExceptionRecordMapper = procurementExceptionRecordMapper; |
| | | this.aiSessionUserContext = aiSessionUserContext; |
| | | } |
| | | |
| | | @Tool(name = "æ¥è¯¢å¶é ä¸å¡åæ°æ®", value = "æä¸å¡åæ¥è¯¢ç产ç°åºã计åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤çç¸å
³æ°æ®ã") |
| | | public String queryDomain(@ToolMemoryId String memoryId, |
| | | @P(value = "ä¸å¡åï¼site/plan/workorder/device/quality/material/exception") String domain, |
| | | @P(value = "å
³é®åï¼å¯ä¸ä¼ ", required = false) String keyword, |
| | | @P(value = "è¿åæ¡æ°ï¼é»è®¤10ï¼æå¤§30", required = false) Integer limit, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦ä»å¹´ãæ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | int finalLimit = normalizeLimit(limit); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange); |
| | | boolean hasTimeConstraint = hasTimeConstraint(startDate, endDate, timeRange); |
| | | String normalizedDomain = normalizeDomain(domain); |
| | | |
| | | return switch (normalizedDomain) { |
| | | case "site" -> siteSnapshot(loginUser, range); |
| | | case "plan" -> listProductionPlans(loginUser, keyword, finalLimit, range); |
| | | case "workorder" -> listWorkOrders(loginUser, keyword, finalLimit, range); |
| | | case "device" -> isRepairIntent(keyword, timeRange) |
| | | ? listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint) |
| | | : listDevices(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit); |
| | | case "repair" -> listDeviceRepairs(loginUser, normalizeDeviceQueryKeyword(keyword, timeRange), finalLimit, range, hasTimeConstraint); |
| | | case "quality" -> listQualityIssues(loginUser, keyword, finalLimit, range); |
| | | case "material" -> listMaterialInventory(loginUser, keyword, finalLimit); |
| | | case "exception" -> listExceptions(loginUser, keyword, finalLimit, range); |
| | | default -> jsonResponse(false, "manufacturing_query", "䏿¯æçä¸å¡å: " + safe(domain), Map.of(), Map.of(), Map.of()); |
| | | }; |
| | | } |
| | | |
| | | @Tool(name = "å¶é é¢è¦çæ¿", value = "计ç®è®¡åãå·¥åã设å¤ãè´¨éãç©æãå¼å¸¸å¤ççé¢è¦ä¿¡æ¯ã") |
| | | public String getWarningBoard(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦ä»å¤©ãæ¬å¨ãæ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange); |
| | | LocalDate today = LocalDate.now(); |
| | | |
| | | long overduePlanCount = countOverduePlans(loginUser, today); |
| | | long overdueWorkOrderCount = countOverdueWorkOrders(loginUser, today); |
| | | long pendingRepairCount = countPendingRepairs(loginUser); |
| | | long qualityOpenCount = countOpenQualityIssues(loginUser, range); |
| | | long lowStockCount = countLowStock(loginUser); |
| | | long exceptionCount = countExceptionRecords(loginUser, range); |
| | | |
| | | List<Map<String, Object>> warningItems = new ArrayList<>(); |
| | | if (overduePlanCount > 0) { |
| | | warningItems.add(warningItem("high", "计å龿", overduePlanCount, "æç产计åè¶
è¿éæ±æ¥æä»æªå®æ")); |
| | | } |
| | | if (overdueWorkOrderCount > 0) { |
| | | warningItems.add(warningItem("high", "å·¥å龿", overdueWorkOrderCount, "æå·¥å计åç»ææ¥æå·²è¿ä»æªå®å·¥")); |
| | | } |
| | | if (pendingRepairCount > 0) { |
| | | warningItems.add(warningItem("medium", "设å¤å¾
ç»´ä¿®", pendingRepairCount, "åå¨å¾
ç»´ä¿®/ç»´ä¿®ä¸ç设å¤")); |
| | | } |
| | | if (qualityOpenCount > 0) { |
| | | warningItems.add(warningItem("high", "è´¨éæªéç¯", qualityOpenCount, "å卿ªå¤ç宿çä¸åæ ¼è®°å½")); |
| | | } |
| | | if (lowStockCount > 0) { |
| | | warningItems.add(warningItem("medium", "ç©æä½åºå", lowStockCount, "åºåæ°éä½äºæçäºé¢è¦éå¼")); |
| | | } |
| | | if (exceptionCount > 0) { |
| | | warningItems.add(warningItem("medium", "å¼å¸¸è®°å½", exceptionCount, "æ¶é´èå´å
åå¨å¼å¸¸å¤çè®°å½")); |
| | | } |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("warningCount", warningItems.size()); |
| | | summary.put("overduePlanCount", overduePlanCount); |
| | | summary.put("overdueWorkOrderCount", overdueWorkOrderCount); |
| | | summary.put("pendingRepairCount", pendingRepairCount); |
| | | summary.put("qualityOpenCount", qualityOpenCount); |
| | | summary.put("lowStockCount", lowStockCount); |
| | | summary.put("exceptionCount", exceptionCount); |
| | | |
| | | return jsonResponse(true, "manufacturing_warning", "å·²è¿åå¶é é¢è¦çæ¿ã", summary, |
| | | Map.of("items", warningItems), Map.of()); |
| | | } |
| | | |
| | | @Tool(name = "å¶é ç»è¥åæ", value = "ææ¶é´èå´è¾åºå¶é å
³é®ææ ï¼æ¯ææ¥ãé®ãåæåºæ¯ã") |
| | | public String analyzeFactory(@ToolMemoryId String memoryId, |
| | | @P(value = "å¼å§æ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String startDate, |
| | | @P(value = "ç»ææ¥æ yyyy-MM-ddï¼å¯ä¸ä¼ ", required = false) String endDate, |
| | | @P(value = "æ¶é´èå´æè¿°ï¼ä¾å¦æ¬æãè¿30天", required = false) String timeRange) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | DateRange range = resolveDateRange(startDate, endDate, timeRange); |
| | | |
| | | long planTotal = countPlans(loginUser, range); |
| | | long planCompleted = countPlansByStatus(loginUser, range, 2); |
| | | long workOrderTotal = countWorkOrders(loginUser, range); |
| | | long workOrderCompleted = countWorkOrdersByStatus(loginUser, range, 2); |
| | | long workOrderInProgress = countWorkOrdersByStatus(loginUser, range, 1); |
| | | |
| | | long outputCount = countOutputs(loginUser, range); |
| | | long deviceTotal = countDevices(loginUser); |
| | | long pendingRepairCount = countPendingRepairs(loginUser); |
| | | long qualityInspectTotal = countQualityInspect(loginUser, range); |
| | | long qualityNgCount = countOpenQualityIssues(loginUser, range); |
| | | long materialSkuCount = countInventorySku(loginUser); |
| | | long lowStockCount = countLowStock(loginUser); |
| | | long exceptionCount = countExceptionRecords(loginUser, range); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("planTotal", planTotal); |
| | | summary.put("planCompleted", planCompleted); |
| | | summary.put("planCompletionRate", toRate(planCompleted, planTotal)); |
| | | summary.put("workOrderTotal", workOrderTotal); |
| | | summary.put("workOrderCompleted", workOrderCompleted); |
| | | summary.put("workOrderInProgress", workOrderInProgress); |
| | | summary.put("workOrderCompletionRate", toRate(workOrderCompleted, workOrderTotal)); |
| | | summary.put("outputCount", outputCount); |
| | | summary.put("deviceTotal", deviceTotal); |
| | | summary.put("pendingRepairCount", pendingRepairCount); |
| | | summary.put("qualityInspectTotal", qualityInspectTotal); |
| | | summary.put("qualityNgCount", qualityNgCount); |
| | | summary.put("qualityIssueRate", toRate(qualityNgCount, qualityInspectTotal)); |
| | | summary.put("materialSkuCount", materialSkuCount); |
| | | summary.put("lowStockCount", lowStockCount); |
| | | summary.put("exceptionCount", exceptionCount); |
| | | |
| | | List<Map<String, Object>> coreMetrics = List.of( |
| | | metric("计å宿ç", toRate(planCompleted, planTotal)), |
| | | metric("å·¥å宿ç", toRate(workOrderCompleted, workOrderTotal)), |
| | | metric("è´¨éå¼å¸¸ç", toRate(qualityNgCount, qualityInspectTotal)), |
| | | metric("ä½åºåå æ¯", toRate(lowStockCount, materialSkuCount)) |
| | | ); |
| | | |
| | | Map<String, Object> charts = new LinkedHashMap<>(); |
| | | charts.put("domainBarOption", buildDomainBarOption(summary)); |
| | | charts.put("qualityPieOption", buildQualityPieOption(qualityInspectTotal, qualityNgCount)); |
| | | |
| | | return jsonResponse(true, "manufacturing_analysis", "å·²è¿åå¶é åæç»æã", summary, |
| | | Map.of("coreMetrics", coreMetrics), charts); |
| | | } |
| | | |
| | | @Tool(name = "çæå¶é åç建议", value = "æ ¹æ®ç¨æ·é®é¢è¾åºå¯æ§è¡çåçå¨ä½å»ºè®®ï¼å
æ¬ç®æ ä¸å¡æ¥å£ãå¿
å¡«åæ®µå示ä¾ã") |
| | | public String planActions(@ToolMemoryId String memoryId, |
| | | @P("ç¨æ·è¯æ±åæ") String userQuery) { |
| | | LoginUser loginUser = currentLoginUser(memoryId); |
| | | List<Map<String, Object>> actionCards = new ArrayList<>(); |
| | | |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "å·¥å", "派工", "ä½ä¸")) { |
| | | actionCards.add(actionCard( |
| | | "workorder_assign", |
| | | "工忴¾å·¥", |
| | | "POST", |
| | | "/productionOperationTask/assign", |
| | | List.of("id", "userIds"), |
| | | Map.of("id", 10001, "userIds", "12,13"), |
| | | "å°å·¥ååé
ç»æå®äººåï¼éç¨äºç°åºè°åº¦ã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "设å¤", "ç»´ä¿®", "æ
é")) { |
| | | actionCards.add(actionCard( |
| | | "device_repair_create", |
| | | "å建设å¤ç»´ä¿®å", |
| | | "POST", |
| | | "/device/repair", |
| | | List.of("deviceLedgerId", "deviceName", "repairName", "remark"), |
| | | Map.of("deviceLedgerId", 1001, "deviceName", "ç©ºåæºA-01", "repairName", "å¼ ä¸", "remark", "å¼å并伴鿏©å"), |
| | | "æ°å»ºç»´ä¿®åï¼è¿å
¥è®¾å¤å¼å¸¸å¤çéç¯ã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "è´¨é", "ä¸åæ ¼", "éç¯")) { |
| | | actionCards.add(actionCard( |
| | | "quality_unqualified_deal", |
| | | "å¤çä¸åæ ¼å", |
| | | "POST", |
| | | "/quality/qualityUnqualified/deal", |
| | | List.of("id", "dealResult", "dealName"), |
| | | Map.of("id", 3001, "dealResult", "è¿å·¥å夿£", "dealName", "æå"), |
| | | "对ä¸åæ ¼è®°å½æ§è¡å¤ç½®å¹¶éç¯ã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "ç©æ", "åºå", "è¡¥æ")) { |
| | | actionCards.add(actionCard( |
| | | "material_inbound", |
| | | "è¡¥å
åºå", |
| | | "POST", |
| | | "/stockInventory/addstockInventory", |
| | | List.of("productModelId", "batchNo", "qualitity"), |
| | | Map.of("productModelId", 5001, "batchNo", "B2026051601", "qualitity", 120), |
| | | "å½ä½åºåé¢è¦è§¦åæ¶ï¼å¢å åºåæ°éã")); |
| | | } |
| | | if (!StringUtils.hasText(userQuery) || containsAny(userQuery, "å¼å¸¸", "éè´å¼å¸¸", "æ¥æå¼å¸¸")) { |
| | | actionCards.add(actionCard( |
| | | "procurement_exception_add", |
| | | "ç»è®°å¼å¸¸è®°å½", |
| | | "POST", |
| | | "/procurementExceptionRecord/add", |
| | | List.of("purchaseLedgerId", "exceptionReason", "exceptionNum"), |
| | | Map.of("purchaseLedgerId", 888, "exceptionReason", "å°æç缺", "exceptionNum", 24), |
| | | "ç»è®°éè´/æ¥æå¼å¸¸ï¼ä¾¿äºåç»è¿½è¸ªååæã")); |
| | | } |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("actionCount", actionCards.size()); |
| | | summary.put("userId", loginUser.getUserId()); |
| | | summary.put("tenantId", loginUser.getTenantId()); |
| | | |
| | | return jsonResponse(true, "manufacturing_action_plan", "å·²çæåç建议ï¼è¯·å端å¼å¯¼ç¨æ·ç¡®è®¤åè°ç¨ç®æ ä¸å¡æ¥å£ã", |
| | | summary, Map.of("actionCards", actionCards), Map.of()); |
| | | } |
| | | |
| | | private String siteSnapshot(LoginUser loginUser, DateRange range) { |
| | | long planTotal = countPlans(loginUser, range); |
| | | long workOrderTotal = countWorkOrders(loginUser, range); |
| | | long outputCount = countOutputs(loginUser, range); |
| | | long deviceTotal = countDevices(loginUser); |
| | | long pendingRepairCount = countPendingRepairs(loginUser); |
| | | long qualityOpenCount = countOpenQualityIssues(loginUser, range); |
| | | long lowStockCount = countLowStock(loginUser); |
| | | long exceptionCount = countExceptionRecords(loginUser, range); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("planTotal", planTotal); |
| | | summary.put("workOrderTotal", workOrderTotal); |
| | | summary.put("outputCount", outputCount); |
| | | summary.put("deviceTotal", deviceTotal); |
| | | summary.put("pendingRepairCount", pendingRepairCount); |
| | | summary.put("qualityOpenCount", qualityOpenCount); |
| | | summary.put("lowStockCount", lowStockCount); |
| | | summary.put("exceptionCount", exceptionCount); |
| | | |
| | | return jsonResponse(true, "manufacturing_site_snapshot", "å·²è¿åç产ç°åºæ¦è§ã", summary, Map.of(), Map.of()); |
| | | } |
| | | |
| | | private String listProductionPlans(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end()); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(ProductionPlan::getMpsNo, keyword) |
| | | .or().like(ProductionPlan::getRemark, keyword) |
| | | .or().like(ProductionPlan::getSource, keyword)); |
| | | } |
| | | wrapper.orderByDesc(ProductionPlan::getRequiredDate, ProductionPlan::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(productionPlanMapper.selectList(wrapper)).stream() |
| | | .map(this::toPlanItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_plan_list", "å·²è¿åç产计åå表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listWorkOrders(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start()) |
| | | .le(ProductionOperationTask::getPlanEndTime, range.end()); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(ProductionOperationTask::getWorkOrderNo, keyword) |
| | | .or().like(ProductionOperationTask::getUserIds, keyword)); |
| | | } |
| | | wrapper.orderByDesc(ProductionOperationTask::getPlanEndTime, ProductionOperationTask::getId) |
| | | .last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(productionOperationTaskMapper.selectList(wrapper)).stream() |
| | | .map(this::toWorkOrderItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_workorder_list", "å·²è¿åå·¥åå表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listDevices(LoginUser loginUser, String keyword, int limit) { |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword) |
| | | .or().like(DeviceLedger::getDeviceModel, keyword) |
| | | .or().like(DeviceLedger::getDeviceBrand, keyword)); |
| | | } |
| | | wrapper.orderByDesc(DeviceLedger::getId).last("limit " + limit); |
| | | |
| | | Map<Long, Long> pendingRepairMap = pendingRepairCountByDevice(loginUser); |
| | | List<Map<String, Object>> items = defaultList(deviceLedgerMapper.selectList(wrapper)).stream() |
| | | .map(item -> toDeviceItem(item, pendingRepairMap.getOrDefault(item.getId(), 0L))) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_device_list", "å·²è¿å设å¤å表ã", |
| | | Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listDeviceRepairs(LoginUser loginUser, String keyword, int limit, DateRange range, boolean hasTimeConstraint) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | Long currentDeptId = loginUser.getCurrentDeptId(); |
| | | if (currentDeptId != null) { |
| | | wrapper.and(w -> w.eq(DeviceRepair::getDeptId, currentDeptId).or().isNull(DeviceRepair::getDeptId)); |
| | | } |
| | | if (hasTimeConstraint) { |
| | | wrapper.ge(DeviceRepair::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(DeviceRepair::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | } |
| | | if (StringUtils.hasText(keyword)) { |
| | | List<Long> matchedDeviceIds = findDeviceLedgerIdsByKeyword(loginUser, keyword); |
| | | wrapper.and(w -> { |
| | | w.like(DeviceRepair::getDeviceName, keyword) |
| | | .or().like(DeviceRepair::getDeviceModel, keyword) |
| | | .or().like(DeviceRepair::getRemark, keyword) |
| | | .or().like(DeviceRepair::getRepairName, keyword) |
| | | .or().like(DeviceRepair::getMaintenanceName, keyword); |
| | | if (!matchedDeviceIds.isEmpty()) { |
| | | w.or().in(DeviceRepair::getDeviceLedgerId, matchedDeviceIds); |
| | | } |
| | | }); |
| | | } |
| | | wrapper.orderByDesc(DeviceRepair::getCreateTime, DeviceRepair::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(deviceRepairMapper.selectList(wrapper)).stream() |
| | | .map(this::toDeviceRepairItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_device_repair_list", "å·²è¿å设å¤ç»´ä¿®è®°å½ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listQualityIssues(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId); |
| | | wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start())) |
| | | .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end())); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(QualityUnqualified::getProductName, keyword) |
| | | .or().like(QualityUnqualified::getDefectivePhenomena, keyword) |
| | | .or().like(QualityUnqualified::getDealResult, keyword)); |
| | | } |
| | | wrapper.orderByDesc(QualityUnqualified::getCheckTime, QualityUnqualified::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(qualityUnqualifiedMapper.selectList(wrapper)).stream() |
| | | .map(this::toQualityItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_quality_list", "å·²è¿åè´¨éå¼å¸¸å表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listMaterialInventory(LoginUser loginUser, String keyword, int limit) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.and(w -> w.like(StockInventory::getBatchNo, keyword) |
| | | .or().like(StockInventory::getProductModelId, keyword)); |
| | | } |
| | | wrapper.orderByDesc(StockInventory::getId).last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(stockInventoryMapper.selectList(wrapper)).stream() |
| | | .map(this::toMaterialItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_material_list", "å·²è¿åç©æåºåå表ã", |
| | | Map.of("count", items.size(), "keyword", safe(keyword)), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private String listExceptions(LoginUser loginUser, String keyword, int limit, DateRange range) { |
| | | LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId); |
| | | wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | if (StringUtils.hasText(keyword)) { |
| | | wrapper.like(ProcurementExceptionRecord::getExceptionReason, keyword); |
| | | } |
| | | wrapper.orderByDesc(ProcurementExceptionRecord::getCreateTime, ProcurementExceptionRecord::getId) |
| | | .last("limit " + limit); |
| | | |
| | | List<Map<String, Object>> items = defaultList(procurementExceptionRecordMapper.selectList(wrapper)).stream() |
| | | .map(this::toExceptionItem) |
| | | .collect(Collectors.toList()); |
| | | return jsonResponse(true, "manufacturing_exception_list", "å·²è¿åå¼å¸¸å¤çå表ã", |
| | | rangeSummary(range, items.size(), keyword), Map.of("items", items), Map.of()); |
| | | } |
| | | |
| | | private long countPlans(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.ge(ProductionPlan::getRequiredDate, range.start()).le(ProductionPlan::getRequiredDate, range.end()); |
| | | return productionPlanMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countPlansByStatus(LoginUser loginUser, DateRange range, int status) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.ge(ProductionPlan::getRequiredDate, range.start()) |
| | | .le(ProductionPlan::getRequiredDate, range.end()) |
| | | .eq(ProductionPlan::getStatus, status); |
| | | return productionPlanMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countWorkOrders(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start()) |
| | | .le(ProductionOperationTask::getPlanEndTime, range.end()); |
| | | return productionOperationTaskMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countWorkOrdersByStatus(LoginUser loginUser, DateRange range, int status) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.ge(ProductionOperationTask::getPlanStartTime, range.start()) |
| | | .le(ProductionOperationTask::getPlanEndTime, range.end()) |
| | | .eq(ProductionOperationTask::getStatus, status); |
| | | return productionOperationTaskMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOutputs(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProductionProductMain> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionProductMain::getDeptId); |
| | | wrapper.ge(ProductionProductMain::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProductionProductMain::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | return productionProductMainMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countDevices(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceLedger::getDeptId); |
| | | return deviceLedgerMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countPendingRepairs(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId); |
| | | wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING); |
| | | return deviceRepairMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countQualityInspect(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<QualityInspect> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), QualityInspect::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityInspect::getDeptId); |
| | | wrapper.ge(QualityInspect::getCheckTime, toDate(range.start())) |
| | | .lt(QualityInspect::getCheckTime, toExclusiveEndDate(range.end())); |
| | | return qualityInspectMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOpenQualityIssues(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<QualityUnqualified> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), QualityUnqualified::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), QualityUnqualified::getDeptId); |
| | | wrapper.ge(QualityUnqualified::getCheckTime, toDate(range.start())) |
| | | .lt(QualityUnqualified::getCheckTime, toExclusiveEndDate(range.end())) |
| | | .ne(QualityUnqualified::getInspectState, 2); |
| | | return qualityUnqualifiedMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countInventorySku(LoginUser loginUser) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | return stockInventoryMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countLowStock(LoginUser loginUser) { |
| | | LambdaQueryWrapper<StockInventory> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), StockInventory::getDeptId); |
| | | wrapper.isNotNull(StockInventory::getWarnNum); |
| | | List<StockInventory> stocks = defaultList(stockInventoryMapper.selectList(wrapper)); |
| | | return stocks.stream() |
| | | .filter(this::isLowStock) |
| | | .count(); |
| | | } |
| | | |
| | | private long countExceptionRecords(LoginUser loginUser, DateRange range) { |
| | | LambdaQueryWrapper<ProcurementExceptionRecord> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), ProcurementExceptionRecord::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProcurementExceptionRecord::getDeptId); |
| | | wrapper.ge(ProcurementExceptionRecord::getCreateTime, range.start().atStartOfDay()) |
| | | .lt(ProcurementExceptionRecord::getCreateTime, range.end().plusDays(1).atStartOfDay()); |
| | | return procurementExceptionRecordMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOverduePlans(LoginUser loginUser, LocalDate today) { |
| | | LambdaQueryWrapper<ProductionPlan> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionPlan::getDeptId); |
| | | wrapper.lt(ProductionPlan::getRequiredDate, today).ne(ProductionPlan::getStatus, 2); |
| | | return productionPlanMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private long countOverdueWorkOrders(LoginUser loginUser, LocalDate today) { |
| | | LambdaQueryWrapper<ProductionOperationTask> wrapper = new LambdaQueryWrapper<>(); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), ProductionOperationTask::getDeptId); |
| | | wrapper.lt(ProductionOperationTask::getPlanEndTime, today).ne(ProductionOperationTask::getStatus, 2); |
| | | return productionOperationTaskMapper.selectCount(wrapper); |
| | | } |
| | | |
| | | private Map<Long, Long> pendingRepairCountByDevice(LoginUser loginUser) { |
| | | LambdaQueryWrapper<DeviceRepair> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceRepair::getTenantId); |
| | | applyDeptFilter(wrapper, loginUser.getCurrentDeptId(), DeviceRepair::getDeptId); |
| | | wrapper.eq(DeviceRepair::getStatus, DEVICE_REPAIR_STATUS_PENDING); |
| | | return defaultList(deviceRepairMapper.selectList(wrapper)).stream() |
| | | .filter(item -> item.getDeviceLedgerId() != null) |
| | | .collect(Collectors.groupingBy(DeviceRepair::getDeviceLedgerId, Collectors.counting())); |
| | | } |
| | | |
| | | private Map<String, Object> toPlanItem(ProductionPlan item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("mpsNo", safe(item.getMpsNo())); |
| | | map.put("requiredDate", formatDate(item.getRequiredDate())); |
| | | map.put("promisedDeliveryDate", formatDate(item.getPromisedDeliveryDate())); |
| | | map.put("qtyRequired", item.getQtyRequired()); |
| | | map.put("quantityIssued", item.getQuantityIssued()); |
| | | map.put("status", item.getStatus()); |
| | | map.put("source", safe(item.getSource())); |
| | | map.put("remark", safe(item.getRemark())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toWorkOrderItem(ProductionOperationTask item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("workOrderNo", safe(item.getWorkOrderNo())); |
| | | map.put("productionOrderId", item.getProductionOrderId()); |
| | | map.put("planStartTime", formatDate(item.getPlanStartTime())); |
| | | map.put("planEndTime", formatDate(item.getPlanEndTime())); |
| | | map.put("actualStartTime", formatDate(item.getActualStartTime())); |
| | | map.put("actualEndTime", formatDate(item.getActualEndTime())); |
| | | map.put("planQuantity", item.getPlanQuantity()); |
| | | map.put("completeQuantity", item.getCompleteQuantity()); |
| | | map.put("status", item.getStatus()); |
| | | map.put("userIds", safe(item.getUserIds())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toDeviceItem(DeviceLedger item, long pendingRepairCount) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("deviceName", safe(item.getDeviceName())); |
| | | map.put("deviceModel", safe(item.getDeviceModel())); |
| | | map.put("deviceBrand", safe(item.getDeviceBrand())); |
| | | map.put("status", safe(item.getStatus())); |
| | | map.put("storageLocation", safe(item.getStorageLocation())); |
| | | map.put("supplierName", safe(item.getSupplierName())); |
| | | map.put("pendingRepairCount", pendingRepairCount); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toDeviceRepairItem(DeviceRepair item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("deviceLedgerId", item.getDeviceLedgerId()); |
| | | map.put("deviceName", safe(item.getDeviceName())); |
| | | map.put("deviceModel", safe(item.getDeviceModel())); |
| | | map.put("repairTime", formatDate(item.getRepairTime())); |
| | | map.put("repairName", safe(item.getRepairName())); |
| | | map.put("maintenanceName", safe(item.getMaintenanceName())); |
| | | map.put("maintenanceTime", formatDateTime(item.getMaintenanceTime())); |
| | | map.put("maintenanceResult", safe(item.getMaintenanceResult())); |
| | | map.put("acceptanceName", safe(item.getAcceptanceName())); |
| | | map.put("acceptanceTime", formatDateTime(item.getAcceptanceTime())); |
| | | map.put("status", item.getStatus()); |
| | | map.put("remark", safe(item.getRemark())); |
| | | map.put("createTime", formatDateTime(item.getCreateTime())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toQualityItem(QualityUnqualified item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("checkTime", formatDate(item.getCheckTime())); |
| | | map.put("inspectState", item.getInspectState()); |
| | | map.put("productId", item.getProductId()); |
| | | map.put("productName", safe(item.getProductName())); |
| | | map.put("model", safe(item.getModel())); |
| | | map.put("quantity", item.getQuantity()); |
| | | map.put("defectivePhenomena", safe(item.getDefectivePhenomena())); |
| | | map.put("dealResult", safe(item.getDealResult())); |
| | | map.put("dealName", safe(item.getDealName())); |
| | | map.put("dealTime", formatDate(item.getDealTime())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toMaterialItem(StockInventory item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("productModelId", item.getProductModelId()); |
| | | map.put("batchNo", safe(item.getBatchNo())); |
| | | map.put("qualitity", item.getQualitity()); |
| | | map.put("lockedQuantity", item.getLockedQuantity()); |
| | | map.put("warnNum", item.getWarnNum()); |
| | | map.put("lowStock", isLowStock(item)); |
| | | map.put("remark", safe(item.getRemark())); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> toExceptionItem(ProcurementExceptionRecord item) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("id", item.getId()); |
| | | map.put("purchaseLedgerId", item.getPurchaseLedgerId()); |
| | | map.put("exceptionReason", safe(item.getExceptionReason())); |
| | | map.put("exceptionNum", item.getExceptionNum()); |
| | | map.put("createTime", formatDateTime(item.getCreateTime())); |
| | | return map; |
| | | } |
| | | |
| | | private boolean isLowStock(StockInventory item) { |
| | | BigDecimal quantity = item.getQualitity(); |
| | | BigDecimal warnNum = item.getWarnNum(); |
| | | if (quantity == null || warnNum == null) { |
| | | return false; |
| | | } |
| | | return quantity.compareTo(warnNum) <= 0; |
| | | } |
| | | |
| | | private Map<String, Object> warningItem(String level, String title, long count, String detail) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("level", level); |
| | | map.put("title", title); |
| | | map.put("count", count); |
| | | map.put("detail", detail); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> actionCard(String code, |
| | | String name, |
| | | String method, |
| | | String targetApi, |
| | | List<String> requiredFields, |
| | | Map<String, Object> examplePayload, |
| | | String description) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("code", code); |
| | | map.put("name", name); |
| | | map.put("method", method); |
| | | map.put("targetApi", targetApi); |
| | | map.put("requiredFields", requiredFields); |
| | | map.put("examplePayload", examplePayload); |
| | | map.put("description", description); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> metric(String label, String value) { |
| | | Map<String, Object> map = new LinkedHashMap<>(); |
| | | map.put("label", label); |
| | | map.put("value", value); |
| | | return map; |
| | | } |
| | | |
| | | private Map<String, Object> rangeSummary(DateRange range, int count, String keyword) { |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("timeRange", range.label()); |
| | | summary.put("startDate", range.start().toString()); |
| | | summary.put("endDate", range.end().toString()); |
| | | summary.put("count", count); |
| | | summary.put("keyword", safe(keyword)); |
| | | return summary; |
| | | } |
| | | |
| | | private Map<String, Object> buildDomainBarOption(Map<String, Object> summary) { |
| | | List<String> xData = List.of("计å", "å·¥å", "设å¤", "è´¨é", "ç©æ", "å¼å¸¸"); |
| | | List<Number> yData = List.of( |
| | | numberValue(summary.get("planTotal")), |
| | | numberValue(summary.get("workOrderTotal")), |
| | | numberValue(summary.get("deviceTotal")), |
| | | numberValue(summary.get("qualityNgCount")), |
| | | numberValue(summary.get("lowStockCount")), |
| | | numberValue(summary.get("exceptionCount")) |
| | | ); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "å¶é åå
³é®æ°é", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "axis")); |
| | | option.put("xAxis", Map.of("type", "category", "data", xData)); |
| | | option.put("yAxis", Map.of("type", "value")); |
| | | option.put("series", List.of(Map.of("name", "æ°é", "type", "bar", "data", yData))); |
| | | return option; |
| | | } |
| | | |
| | | private Map<String, Object> buildQualityPieOption(long inspectTotal, long ngCount) { |
| | | long passCount = Math.max(inspectTotal - ngCount, 0); |
| | | List<Map<String, Object>> data = List.of( |
| | | Map.of("name", "ä¸åæ ¼", "value", ngCount), |
| | | Map.of("name", "éä¸åæ ¼", "value", passCount) |
| | | ); |
| | | Map<String, Object> option = new LinkedHashMap<>(); |
| | | option.put("title", Map.of("text", "è´¨éç»æåå¸", "left", "center")); |
| | | option.put("tooltip", Map.of("trigger", "item")); |
| | | option.put("series", List.of(Map.of("name", "è´¨é", "type", "pie", "radius", "60%", "data", data))); |
| | | return option; |
| | | } |
| | | |
| | | private int numberValue(Object value) { |
| | | if (value instanceof Number number) { |
| | | return number.intValue(); |
| | | } |
| | | return 0; |
| | | } |
| | | |
| | | private String toRate(long numerator, long denominator) { |
| | | if (denominator <= 0) { |
| | | return "0.00%"; |
| | | } |
| | | BigDecimal rate = new BigDecimal(numerator) |
| | | .multiply(new BigDecimal("100")) |
| | | .divide(new BigDecimal(denominator), 2, RoundingMode.HALF_UP); |
| | | return rate.toPlainString() + "%"; |
| | | } |
| | | |
| | | private String normalizeDomain(String domain) { |
| | | if (!StringUtils.hasText(domain)) { |
| | | return ""; |
| | | } |
| | | String value = domain.trim().toLowerCase(); |
| | | return switch (value) { |
| | | case "ç产ç°åº", "site", "factory", "workshop" -> "site"; |
| | | case "计å", "plan", "schedule" -> "plan"; |
| | | case "å·¥å", "workorder", "work_order", "task" -> "workorder"; |
| | | case "设å¤", "device", "equipment" -> "device"; |
| | | case "ç»´ä¿®", "repair", "maintenance" -> "repair"; |
| | | case "è´¨é", "quality", "qc" -> "quality"; |
| | | case "ç©æ", "material", "inventory", "stock" -> "material"; |
| | | case "å¼å¸¸", "exception", "abnormal" -> "exception"; |
| | | default -> value; |
| | | }; |
| | | } |
| | | |
| | | private boolean isRepairIntent(String keyword, String userQuery) { |
| | | String query = safe(userQuery); |
| | | return containsAny(safe(keyword), "ç»´ä¿®", "æ¥ä¿®", "æ£ä¿®", "ç»´æ¤") |
| | | || containsAny(query, "ç»´ä¿®", "æ¥ä¿®", "æ£ä¿®", "ç»´æ¤"); |
| | | } |
| | | |
| | | private String normalizeDeviceQueryKeyword(String keyword, String userQuery) { |
| | | String source = StringUtils.hasText(keyword) ? keyword : userQuery; |
| | | if (!StringUtils.hasText(source)) { |
| | | return null; |
| | | } |
| | | String cleaned = source |
| | | .replace("æ¥è¯¢", "") |
| | | .replace("æ¥ç", "") |
| | | .replace("帮æ", "") |
| | | .replace("请", "") |
| | | .replace("æ¥", "") |
| | | .replace("设å¤", "") |
| | | .replace("维修记å½", "") |
| | | .replace("ç»´ä¿®æ
åµ", "") |
| | | .replace("æ¥ä¿®è®°å½", "") |
| | | .replace("æ¥ä¿®æ
åµ", "") |
| | | .replace("ç»´ä¿®", "") |
| | | .replace("æ¥ä¿®", "") |
| | | .replace("æ
åµ", "") |
| | | .replace("è®°å½", "") |
| | | .replace("ä¿¡æ¯", "") |
| | | .replace("ç", "") |
| | | .replace("ä¸ä¸", "") |
| | | .trim(); |
| | | return cleaned.length() >= 2 ? cleaned : null; |
| | | } |
| | | |
| | | private List<Long> findDeviceLedgerIdsByKeyword(LoginUser loginUser, String keyword) { |
| | | if (!StringUtils.hasText(keyword)) { |
| | | return List.of(); |
| | | } |
| | | LambdaQueryWrapper<DeviceLedger> wrapper = new LambdaQueryWrapper<>(); |
| | | applyTenantFilter(wrapper, loginUser.getTenantId(), DeviceLedger::getTenantId); |
| | | Long currentDeptId = loginUser.getCurrentDeptId(); |
| | | if (currentDeptId != null) { |
| | | wrapper.and(w -> w.eq(DeviceLedger::getDeptId, currentDeptId).or().isNull(DeviceLedger::getDeptId)); |
| | | } |
| | | wrapper.and(w -> w.like(DeviceLedger::getDeviceName, keyword) |
| | | .or().like(DeviceLedger::getDeviceModel, keyword) |
| | | .or().like(DeviceLedger::getDeviceBrand, keyword)); |
| | | wrapper.orderByDesc(DeviceLedger::getId).last("limit 200"); |
| | | return defaultList(deviceLedgerMapper.selectList(wrapper)).stream() |
| | | .map(DeviceLedger::getId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | private boolean hasTimeConstraint(String startDate, String endDate, String userQuery) { |
| | | if (StringUtils.hasText(startDate) || StringUtils.hasText(endDate)) { |
| | | return true; |
| | | } |
| | | if (!StringUtils.hasText(userQuery)) { |
| | | return false; |
| | | } |
| | | String text = userQuery.trim(); |
| | | return containsAny(text, "ä»å¤©", "æ¨å¤©", "æ¬å¨", "ä¸å¨", "æ¬æ", "䏿", "ä»å¹´", "å»å¹´", "è¿", "æè¿"); |
| | | } |
| | | |
| | | private DateRange resolveDateRange(String startDate, String endDate, String timeRange) { |
| | | LocalDate today = LocalDate.now(); |
| | | LocalDate start = parseLocalDate(startDate); |
| | | LocalDate end = parseLocalDate(endDate); |
| | | if (start != null || end != null) { |
| | | LocalDate s = start != null ? start : end; |
| | | LocalDate e = end != null ? end : start; |
| | | if (s.isAfter(e)) { |
| | | LocalDate temp = s; |
| | | s = e; |
| | | e = temp; |
| | | } |
| | | return new DateRange(s, e, s + "è³" + e); |
| | | } |
| | | if (!StringUtils.hasText(timeRange)) { |
| | | return new DateRange(today.minusDays(29), today, "è¿30天"); |
| | | } |
| | | String text = timeRange.trim(); |
| | | if (text.contains("ä»å¤©")) { |
| | | return new DateRange(today, today, "ä»å¤©"); |
| | | } |
| | | if (text.contains("æ¬å¨")) { |
| | | LocalDate startOfWeek = today.minusDays(today.getDayOfWeek().getValue() - 1L); |
| | | return new DateRange(startOfWeek, today, "æ¬å¨"); |
| | | } |
| | | if (text.contains("æ¬æ")) { |
| | | return new DateRange(today.withDayOfMonth(1), today, "æ¬æ"); |
| | | } |
| | | if (text.contains("æ¬å¹´") || text.contains("ä»å¹´")) { |
| | | return new DateRange(today.withDayOfYear(1), today, "ä»å¹´"); |
| | | } |
| | | if (text.contains("å»å¹´")) { |
| | | LocalDate firstDay = today.minusYears(1).withDayOfYear(1); |
| | | LocalDate lastDay = today.minusYears(1).withMonth(12).withDayOfMonth(31); |
| | | return new DateRange(firstDay, lastDay, "å»å¹´"); |
| | | } |
| | | if (text.contains("䏿")) { |
| | | LocalDate startOfLastMonth = today.minusMonths(1).withDayOfMonth(1); |
| | | return new DateRange(startOfLastMonth, startOfLastMonth.withDayOfMonth(startOfLastMonth.lengthOfMonth()), "䏿"); |
| | | } |
| | | java.util.regex.Matcher matcher = java.util.regex.Pattern.compile("(è¿|æè¿)(\\d+)(天|å¨|个æ|æ|å¹´)").matcher(text); |
| | | if (matcher.find()) { |
| | | int amount = Integer.parseInt(matcher.group(2)); |
| | | String unit = matcher.group(3); |
| | | LocalDate relativeStart = 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(relativeStart, today, "è¿" + amount + unit); |
| | | } |
| | | return new DateRange(today.minusDays(29), today, "è¿30天"); |
| | | } |
| | | |
| | | private LocalDate parseLocalDate(String text) { |
| | | if (!StringUtils.hasText(text)) { |
| | | return null; |
| | | } |
| | | return LocalDate.parse(text.trim(), DATE_FMT); |
| | | } |
| | | |
| | | private Date toDate(LocalDate date) { |
| | | return Date.from(date.atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } |
| | | |
| | | private Date toExclusiveEndDate(LocalDate date) { |
| | | return Date.from(date.plusDays(1).atStartOfDay(ZoneId.systemDefault()).toInstant()); |
| | | } |
| | | |
| | | private String formatDate(LocalDate date) { |
| | | return date == null ? "" : DATE_FMT.format(date); |
| | | } |
| | | |
| | | private String formatDate(Date date) { |
| | | if (date == null) { |
| | | return ""; |
| | | } |
| | | return DATE_FMT.format(date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); |
| | | } |
| | | |
| | | private String formatDateTime(LocalDateTime time) { |
| | | if (time == null) { |
| | | return ""; |
| | | } |
| | | return time.truncatedTo(ChronoUnit.SECONDS).toString().replace('T', ' '); |
| | | } |
| | | |
| | | private int normalizeLimit(Integer limit) { |
| | | if (limit == null || limit <= 0) { |
| | | return DEFAULT_LIMIT; |
| | | } |
| | | return Math.min(limit, MAX_LIMIT); |
| | | } |
| | | |
| | | private boolean containsAny(String text, String... values) { |
| | | for (String value : values) { |
| | | if (text.contains(value)) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private <T> void applyTenantFilter(LambdaQueryWrapper<T> wrapper, Long tenantId, SFunction<T, Long> field) { |
| | | if (tenantId != null) { |
| | | wrapper.eq(field, tenantId); |
| | | } |
| | | } |
| | | |
| | | private <T> void applyDeptFilter(LambdaQueryWrapper<T> wrapper, Long deptId, SFunction<T, Long> field) { |
| | | if (deptId != null) { |
| | | wrapper.eq(field, deptId); |
| | | } |
| | | } |
| | | |
| | | private LoginUser currentLoginUser(String memoryId) { |
| | | LoginUser loginUser = aiSessionUserContext.get(memoryId); |
| | | if (loginUser != null) { |
| | | return loginUser; |
| | | } |
| | | return SecurityUtils.getLoginUser(); |
| | | } |
| | | |
| | | private String safe(Object value) { |
| | | return value == null ? "" : String.valueOf(value).replace('\n', ' ').replace('\r', ' '); |
| | | } |
| | | |
| | | private <T> List<T> defaultList(List<T> list) { |
| | | return list == null ? List.of() : list; |
| | | } |
| | | |
| | | private String jsonResponse(boolean success, |
| | | String type, |
| | | String description, |
| | | Map<String, Object> summary, |
| | | Map<String, Object> data, |
| | | Map<String, Object> charts) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("success", success); |
| | | result.put("type", type); |
| | | result.put("description", description); |
| | | result.put("summary", summary == null ? Map.of() : summary); |
| | | result.put("data", data == null ? Map.of() : data); |
| | | result.put("charts", charts == null ? Map.of() : charts); |
| | | return JSON.toJSONString(result); |
| | | } |
| | | |
| | | private record DateRange(LocalDate start, LocalDate end, String label) { |
| | | } |
| | | } |