| | |
| | | |
| | | 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 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_list", description = "只读查询工具。按任务号、状态、任务类型、源站点、目标站点等条件查询任务列表。") |
| | | public List<Map<String, Object>> queryTaskList( |
| | | @ToolParam(description = "任务号,可模糊查询") String taskCode, |
| | | @ToolParam(description = "任务状态,可选") Integer taskStatus, |
| | | @ToolParam(description = "任务类型,可选") Integer taskType, |
| | | @Tool(name = "rsf_query_task", description = "只读查询工具。支持按任务号、状态、类型、源站点、目标站点查询任务;传精确任务号时返回任务明细,不传过滤条件时返回最近任务。") |
| | | public Map<String, Object> 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") Integer limit) { |
| | | String normalizedTaskCode = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "任务号", 64); |
| | | @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); |
| | | BuiltinToolGovernanceSupport.requireAnyFilter("任务列表查询至少需要提供一个过滤条件", |
| | | normalizedTaskCode, normalizedOrgSite, normalizedTargSite, |
| | | taskStatus == null ? null : String.valueOf(taskStatus), |
| | | taskType == null ? null : String.valueOf(taskType)); |
| | | int finalLimit = normalizeLimit(limit, 10, 50); |
| | | |
| | | Task exactMatchedTask = findExactMatchedTask(normalizedTaskCode, normalizedTaskStatus, normalizedTaskType, |
| | | normalizedOrgSite, normalizedTargSite); |
| | | if (exactMatchedTask != null) { |
| | | Map<String, Object> 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<Task> queryWrapper = new LambdaQueryWrapper<>(); |
| | | int finalLimit = BuiltinToolGovernanceSupport.normalizeLimit(limit, 10, 50); |
| | | if (StringUtils.hasText(normalizedTaskCode)) { |
| | | queryWrapper.like(Task::getTaskCode, normalizedTaskCode); |
| | | applyCommonFilters(queryWrapper, normalizedTaskCode, normalizedTaskStatus, normalizedTaskType, |
| | | normalizedOrgSite, normalizedTargSite, true); |
| | | queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit); |
| | | List<Task> tasks = taskService.list(queryWrapper); |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (Task task : tasks) { |
| | | result.add(buildTaskSummary(task)); |
| | | } |
| | | Map<String, Object> 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<Task> queryWrapper = new LambdaQueryWrapper<>(); |
| | | applyCommonFilters(queryWrapper, taskCode, taskStatus, taskType, orgSite, targSite, fuzzyTaskCode); |
| | | queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT 2"); |
| | | List<Task> tasks = taskService.list(queryWrapper); |
| | | if (tasks.size() == 1) { |
| | | return tasks.get(0); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private void applyCommonFilters(LambdaQueryWrapper<Task> 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(normalizedOrgSite)) { |
| | | queryWrapper.eq(Task::getOrgSite, normalizedOrgSite); |
| | | if (StringUtils.hasText(orgSite)) { |
| | | queryWrapper.eq(Task::getOrgSite, orgSite); |
| | | } |
| | | if (StringUtils.hasText(normalizedTargSite)) { |
| | | queryWrapper.eq(Task::getTargSite, normalizedTargSite); |
| | | if (StringUtils.hasText(targSite)) { |
| | | queryWrapper.eq(Task::getTargSite, targSite); |
| | | } |
| | | queryWrapper.orderByDesc(Task::getCreateTime).last("LIMIT " + finalLimit); |
| | | List<Task> tasks = taskService.list(queryWrapper); |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (Task task : tasks) { |
| | | result.add(buildTaskSummary(task)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 查询单个任务详情。 |
| | | * 与列表查询不同,这里允许返回更丰富的字段,但仍然要求调用方通过任务 ID 或任务号做精确定位。 |
| | | */ |
| | | @Tool(name = "rsf_query_task_detail", description = "只读查询工具。根据任务 ID 或任务号查询任务详情。") |
| | | public Map<String, Object> queryTaskDetail( |
| | | @ToolParam(description = "任务 ID") Long taskId, |
| | | @ToolParam(description = "任务号") String taskCode) { |
| | | String normalizedTaskCode = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "任务号", 64); |
| | | if (taskId == null && !StringUtils.hasText(normalizedTaskCode)) { |
| | | throw new CoolException("任务 ID 和任务号至少需要提供一个"); |
| | | private String normalizeTaskCode(String taskCode) { |
| | | String normalized = BuiltinToolGovernanceSupport.sanitizeQueryText(taskCode, "任务号", 128); |
| | | if (!StringUtils.hasText(normalized)) { |
| | | return null; |
| | | } |
| | | Task task; |
| | | if (taskId != null) { |
| | | task = taskService.getById(taskId); |
| | | } else { |
| | | task = taskService.getOne(new LambdaQueryWrapper<Task>().eq(Task::getTaskCode, normalizedTaskCode)); |
| | | Matcher taskCodeMatcher = TASK_CODE_PATTERN.matcher(normalized); |
| | | if (taskCodeMatcher.find()) { |
| | | return taskCodeMatcher.group().toUpperCase(); |
| | | } |
| | | if (task == null) { |
| | | throw new CoolException("未查询到任务"); |
| | | Matcher digitMatcher = TASK_CODE_DIGIT_PATTERN.matcher(normalized); |
| | | if (digitMatcher.find()) { |
| | | return digitMatcher.group(); |
| | | } |
| | | Map<String, Object> result = buildTaskSummary(task); |
| | | result.put("resource", task.getResource()); |
| | | result.put("exceStatus", task.getExceStatus()); |
| | | result.put("orgLoc", task.getOrgLoc()); |
| | | result.put("targLoc", task.getTargLoc()); |
| | | result.put("orgSite", task.getOrgSite()); |
| | | result.put("orgSiteLabel", task.getOrgSite$()); |
| | | result.put("targSite", task.getTargSite()); |
| | | result.put("targSiteLabel", task.getTargSite$()); |
| | | result.put("barcode", task.getBarcode()); |
| | | result.put("robotCode", task.getRobotCode()); |
| | | result.put("memo", task.getMemo()); |
| | | result.put("expCode", task.getExpCode()); |
| | | result.put("expDesc", task.getExpDesc()); |
| | | result.put("startTime", task.getStartTime$()); |
| | | result.put("endTime", task.getEndTime$()); |
| | | result.put("createTime", task.getCreateTime$()); |
| | | result.put("updateTime", task.getUpdateTime$()); |
| | | return result; |
| | | 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<String, Object> buildTaskSummary(Task task) { |
| | |
| | | return item; |
| | | } |
| | | |
| | | private Map<String, Object> buildTaskDetail(Task task) { |
| | | Map<String, Object> result = buildTaskSummary(task); |
| | | List<TaskItem> taskItems = taskItemService.list(new LambdaQueryWrapper<TaskItem>() |
| | | .eq(TaskItem::getTaskId, task.getId())); |
| | | result.put("itemCount", taskItems.size()); |
| | | |
| | | double totalAnfme = 0D; |
| | | double totalWorkQty = 0D; |
| | | double totalQty = 0D; |
| | | List<Map<String, Object>> 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<String, Object> buildTaskItemRow(TaskItem taskItem) { |
| | | if (!Objects.isNull(taskItem.getFieldsIndex())) { |
| | | taskItem.setExtendFields(FieldsUtils.getFields(taskItem.getFieldsIndex())); |
| | | } |
| | | Map<String, Object> 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; |
| | | } |
| | | |
| | | } |