package com.vincent.rsf.server.ai.tool; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.vincent.rsf.framework.exception.CoolException; import com.vincent.rsf.server.common.utils.FieldsUtils; import com.vincent.rsf.server.manager.entity.Task; import com.vincent.rsf.server.manager.entity.TaskItem; import com.vincent.rsf.server.manager.service.TaskItemService; import com.vincent.rsf.server.manager.service.TaskService; import lombok.RequiredArgsConstructor; import org.springframework.ai.tool.annotation.Tool; import org.springframework.ai.tool.annotation.ToolParam; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @Component @RequiredArgsConstructor public class RsfWmsTaskTools { private static final Pattern TASK_CODE_PATTERN = Pattern.compile("[A-Za-z]{1,8}\\d{6,}"); private static final Pattern TASK_CODE_DIGIT_PATTERN = Pattern.compile("\\d{8,}"); private final TaskService taskService; private final TaskItemService taskItemService; /** * 统一任务查询。 * 传精确 taskCode 时返回任务汇总和明细;否则按条件返回任务列表。 */ @Tool(name = "rsf_query_task", description = "只读查询工具。支持按任务号、状态、类型、源站点、目标站点查询任务;传精确任务号时返回任务明细,不传过滤条件时返回最近任务。") public Map queryTask( @ToolParam(description = "任务号,可模糊查询;支持直接传入自然语言,工具会自动提取任务号") String taskCode, @ToolParam(description = "任务状态,可选;空字符串或 0 视为未传") String taskStatus, @ToolParam(description = "任务类型,可选;空字符串或 0 视为未传") String taskType, @ToolParam(description = "源站点,可选") String orgSite, @ToolParam(description = "目标站点,可选") String targSite, @ToolParam(description = "返回条数,默认 10,最大 50;空字符串或 0 视为默认值") String limit) { String normalizedTaskCode = normalizeTaskCode(taskCode); Integer normalizedTaskStatus = normalizeOptionalFilterNumber(taskStatus, "任务状态"); Integer normalizedTaskType = normalizeOptionalFilterNumber(taskType, "任务类型"); String normalizedOrgSite = BuiltinToolGovernanceSupport.sanitizeQueryText(orgSite, "源站点", 64); String normalizedTargSite = BuiltinToolGovernanceSupport.sanitizeQueryText(targSite, "目标站点", 64); int finalLimit = normalizeLimit(limit, 10, 50); Task exactMatchedTask = findExactMatchedTask(normalizedTaskCode, normalizedTaskStatus, normalizedTaskType, normalizedOrgSite, normalizedTargSite); if (exactMatchedTask != null) { Map response = new LinkedHashMap<>(); response.put("queryMode", "detail"); response.put("taskCount", 1); response.put("limit", finalLimit); response.put("resolvedTaskCode", exactMatchedTask.getTaskCode()); response.put("tasks", List.of(buildTaskDetail(exactMatchedTask))); return response; } LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); applyCommonFilters(queryWrapper, normalizedTaskCode, normalizedTaskStatus, normalizedTaskType, normalizedOrgSite, normalizedTargSite, true); queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit); List tasks = taskService.list(queryWrapper); List> result = new ArrayList<>(); for (Task task : tasks) { result.add(buildTaskSummary(task)); } Map response = new LinkedHashMap<>(); response.put("queryMode", "list"); response.put("taskCount", result.size()); response.put("limit", finalLimit); response.put("resolvedTaskCode", normalizedTaskCode); response.put("tasks", result); return response; } private Task findExactMatchedTask(String taskCode, Integer taskStatus, Integer taskType, String orgSite, String targSite) { if (!StringUtils.hasText(taskCode)) { return null; } Task exactTask = findUniqueTask(taskCode, taskStatus, taskType, orgSite, targSite, false); if (exactTask != null) { return exactTask; } String digitCandidate = extractDigitCandidate(taskCode); if (!StringUtils.hasText(digitCandidate) || digitCandidate.equals(taskCode)) { return null; } return findUniqueTask(digitCandidate, taskStatus, taskType, orgSite, targSite, true); } private Task findUniqueTask(String taskCode, Integer taskStatus, Integer taskType, String orgSite, String targSite, boolean fuzzyTaskCode) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); applyCommonFilters(queryWrapper, taskCode, taskStatus, taskType, orgSite, targSite, fuzzyTaskCode); queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT 2"); List tasks = taskService.list(queryWrapper); if (tasks.size() == 1) { return tasks.get(0); } return null; } private void applyCommonFilters(LambdaQueryWrapper queryWrapper, String taskCode, Integer taskStatus, Integer taskType, String orgSite, String targSite, boolean fuzzyTaskCode) { if (StringUtils.hasText(taskCode)) { if (fuzzyTaskCode) { queryWrapper.like(Task::getTaskCode, taskCode); } else { queryWrapper.eq(Task::getTaskCode, taskCode); } } if (taskStatus != null) { queryWrapper.eq(Task::getTaskStatus, taskStatus); } if (taskType != null) { queryWrapper.eq(Task::getTaskType, taskType); } if (StringUtils.hasText(orgSite)) { queryWrapper.eq(Task::getOrgSite, orgSite); } if (StringUtils.hasText(targSite)) { queryWrapper.eq(Task::getTargSite, targSite); } } private String normalizeTaskCode(String taskCode) { String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "任务号", 128); if (!StringUtils.hasText(normalized)) { return null; } Matcher taskCodeMatcher = TASK_CODE_PATTERN.matcher(normalized); if (taskCodeMatcher.find()) { return taskCodeMatcher.group().toUpperCase(); } Matcher digitMatcher = TASK_CODE_DIGIT_PATTERN.matcher(normalized); if (digitMatcher.find()) { return digitMatcher.group(); } return normalized.replace("*", "").trim(); } private String extractDigitCandidate(String taskCode) { if (!StringUtils.hasText(taskCode)) { return null; } Matcher digitMatcher = TASK_CODE_DIGIT_PATTERN.matcher(taskCode); if (digitMatcher.find()) { return digitMatcher.group(); } return null; } private Integer normalizeOptionalFilterNumber(String value, String fieldLabel) { String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(value, fieldLabel, 32); if (!StringUtils.hasText(normalized)) { return null; } try { int parsed = Integer.parseInt(normalized); return parsed <= 0 ? null : parsed; } catch (NumberFormatException e) { throw new CoolException(fieldLabel + "必须是整数"); } } private int normalizeLimit(String value, int defaultValue, int maxValue) { String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(value, "limit", 16); if (!StringUtils.hasText(normalized)) { return defaultValue; } try { int parsed = Integer.parseInt(normalized); if (parsed <= 0) { return defaultValue; } return BuiltinToolGovernanceSupport.normalizeLimit(parsed, defaultValue, maxValue); } catch (NumberFormatException e) { throw new CoolException("limit 必须是整数"); } } private Map buildTaskSummary(Task task) { /** 把任务实体收敛为适合模型阅读和前端展示的摘要结构。 */ Map item = new LinkedHashMap<>(); item.put("id", task.getId()); item.put("taskCode", task.getTaskCode()); item.put("taskStatus", task.getTaskStatus()); item.put("taskStatusLabel", task.getTaskStatus$()); item.put("taskType", task.getTaskType()); item.put("taskTypeLabel", task.getTaskType$()); item.put("orgSite", task.getOrgSite()); item.put("orgSiteLabel", task.getOrgSite$()); item.put("targSite", task.getTargSite()); item.put("targSiteLabel", task.getTargSite$()); item.put("status", task.getStatus()); item.put("statusLabel", task.getStatus$()); item.put("createTime", task.getCreateTime$()); item.put("updateTime", task.getUpdateTime$()); return item; } private Map buildTaskDetail(Task task) { Map result = buildTaskSummary(task); List taskItems = taskItemService.list(new LambdaQueryWrapper() .eq(TaskItem::getTaskId, task.getId())); result.put("itemCount", taskItems.size()); double totalAnfme = 0D; double totalWorkQty = 0D; double totalQty = 0D; List> items = new ArrayList<>(); for (TaskItem taskItem : taskItems) { totalAnfme += taskItem.getAnfme() == null ? 0D : taskItem.getAnfme(); totalWorkQty += taskItem.getWorkQty() == null ? 0D : taskItem.getWorkQty(); totalQty += taskItem.getQty() == null ? 0D : taskItem.getQty(); items.add(buildTaskItemRow(taskItem)); } result.put("totalAnfme", totalAnfme); result.put("totalWorkQty", totalWorkQty); result.put("totalQty", totalQty); result.put("items", items); return result; } private Map buildTaskItemRow(TaskItem taskItem) { if (!Objects.isNull(taskItem.getFieldsIndex())) { taskItem.setExtendFields(FieldsUtils.getFields(taskItem.getFieldsIndex())); } Map item = new LinkedHashMap<>(); item.put("id", taskItem.getId()); item.put("taskId", taskItem.getTaskId()); item.put("matnrId", taskItem.getMatnrId()); item.put("matnrCode", taskItem.getMatnrCode()); item.put("maktx", taskItem.getMaktx()); item.put("trackCode", taskItem.getTrackCode()); item.put("splrBatch", taskItem.getSplrBatch()); item.put("batch", taskItem.getBatch()); item.put("spec", taskItem.getSpec()); item.put("model", taskItem.getModel()); item.put("unit", taskItem.getUnit()); item.put("anfme", taskItem.getAnfme()); item.put("workQty", taskItem.getWorkQty()); item.put("qty", taskItem.getQty()); item.put("ableQty", taskItem.getAbleQty()); item.put("source", taskItem.getSource()); item.put("sourceId", taskItem.getSourceId()); item.put("sourceCode", taskItem.getSourceCode()); item.put("orderId", taskItem.getOrderId()); item.put("orderItemId", taskItem.getOrderItemId()); item.put("platItemId", taskItem.getPlatItemId()); item.put("platOrderCode", taskItem.getPlatOrderCode()); item.put("platWorkCode", taskItem.getPlatWorkCode()); item.put("projectCode", taskItem.getProjectCode()); item.put("orderType", taskItem.getOrderType()); item.put("orderTypeLabel", taskItem.getOrderType$()); item.put("wkType", taskItem.getWkType()); item.put("wkTypeLabel", taskItem.getWkType$()); item.put("isptResult", taskItem.getIsptResult()); item.put("isptResultLabel", taskItem.getIsptResult$()); item.put("fieldsIndex", taskItem.getFieldsIndex()); item.put("extendFields", taskItem.getExtendFields()); item.put("status", taskItem.getStatus()); item.put("statusLabel", taskItem.getStatus$()); item.put("memo", taskItem.getMemo()); item.put("createTime", taskItem.getCreateTime$()); item.put("updateTime", taskItem.getUpdateTime$()); return item; } }