from typing import Any, Literal from django.db.models import Q from apps.wpmw.models import Wpr, WprDefect from mcp_server.context import authenticated_factory_user from mcp_server.tools.common import json_safe_result, validate_result_size WprLocation = Literal["all", "workshop", "warehouse", "unassigned"] def _base_queryset(): return Wpr.objects.select_related( "material", "material_start", "wm", "mb", "wpr_from", ) def _wpr_summary(wpr: Wpr) -> dict[str, Any]: material = wpr.material return { "id": str(wpr.id), "number": wpr.number, "number_out": wpr.number_out, "version": wpr.version, "state": wpr.state, "state_name": wpr.get_state_display(), "material": { "id": str(material.id), "name": material.name, "model": material.model, "specification": material.specification, }, "workshop_batch": wpr.wm.batch if wpr.wm_id else None, "warehouse_batch": wpr.mb.batch if wpr.mb_id else None, "create_time": wpr.create_time, "update_time": wpr.update_time, } def search_wprs( query: str = "", state: int | None = None, material_id: str | None = None, batch: str = "", location: WprLocation = "all", limit: int = 20, ) -> dict[str, Any]: """按编号、物料或批次搜索单件产品;仅提供只读摘要。""" authenticated_factory_user() if location not in {"all", "workshop", "warehouse", "unassigned"}: raise ValueError(f"不支持的 WPR 位置:{location}") safe_limit = max(1, min(limit, 100)) queryset = _base_queryset() if query.strip(): keyword = query.strip() queryset = queryset.filter( Q(number__icontains=keyword) | Q(number_out__icontains=keyword) | Q(material__name__icontains=keyword) | Q(material__model__icontains=keyword) | Q(material__specification__icontains=keyword) ) if state is not None: queryset = queryset.filter(state=state) if material_id: queryset = queryset.filter(material_id=material_id) if batch.strip(): queryset = queryset.filter( Q(wm__batch__icontains=batch.strip()) | Q(mb__batch__icontains=batch.strip()) ) if location == "workshop": queryset = queryset.filter(wm__isnull=False) elif location == "warehouse": queryset = queryset.filter(mb__isnull=False) elif location == "unassigned": queryset = queryset.filter(wm__isnull=True, mb__isnull=True) items = [ _wpr_summary(wpr) for wpr in queryset.distinct().order_by("number", "create_time")[:safe_limit] ] result = json_safe_result({"items": items, "limit": safe_limit}) validate_result_size(result) return result def get_wpr(identifier: str) -> dict[str, Any]: """按 WPR ID、内部编号或对外编号读取单件详情。""" authenticated_factory_user() lookup = Q(number=identifier) | Q(number_out=identifier) if identifier.isdigit(): lookup |= Q(pk=identifier) wpr = _base_queryset().filter(lookup).order_by("-version", "-update_time").first() if wpr is None: raise ValueError(f"未找到 WPR:{identifier}") result = _wpr_summary(wpr) material_start = wpr.material_start result.update( { "material_start": ( { "id": str(material_start.id), "name": material_start.name, "model": material_start.model, "specification": material_start.specification, } if material_start else None ), "wpr_from": ( {"id": str(wpr.wpr_from.id), "number": wpr.wpr_from.number} if wpr.wpr_from_id else None ), "oinfo": wpr.oinfo, "data": wpr.data, "pre_info": wpr.pre_info, "defects": list( WprDefect.objects.filter(wpr=wpr) .select_related("defect") .values("defect_id", "defect__name", "is_main") ), } ) result = json_safe_result(result) validate_result_size(result) return result def register_wpr_tools(server) -> None: server.tool()(search_wprs) server.tool()(get_wpr)