706e628715
QueryType était figé à 0 en dur dans executeQuery/executeScalarQuery : le modèle Writing, opérationnel et mesuré, était inatteignable. Paramètre query_type (défaut 0) sur call_query_api, query_wms_entities, count_wms_entities — get_entity_schema et search_wms_data restent des raccourcis Reading. D3 reste la règle par défaut : la bascule est un opt-in, avertie dans les descriptions d'outils (statuts en énumérations en Writing). Garde de valeur assertValidQueryType dans le code (le wrapper D23 ne valide pas les valeurs), avant tout réseau. En query_type != 0, un nom inconnu du Metadata Reading passe tel quel avec warning (allowUnknown du resolver) — conservé aussi dans la réponse d'erreur si le WMS échoue ensuite. Acté en D25 ; CLAUDE.md nuancé, L4.1 retiré de la ROADMAP (reliquat : exploration Metrics, rapport à part). Vérifications rejouées via le protocole (LIMAGRAIN / LIMAGRAI2512) : - call_query_api(Products, query_type:1, limit:1) -> success, 1 ligne, queryType:1 dans la réponse. - call_query_api(Products, query_type:3) -> erreur structurée contenant 'ApplicationMetricDataContext' ne contient pas de définition pour 'Products'. - call_query_api(Products, query_type:7) -> erreur locale nommant les 4 contextes, aucun [API] POST/GET dans stderr. - query_wms_entities(Container, limit:1) sans query_type -> comportement inchangé (resolvedTableName Containers, Reading, pas de champ queryType). - call_query_api(FooBar123, query_type:1) -> transmis tel quel (payload QueryType:1 Expression Context.FooBar123...), erreur WMS ApplicationWritingRepository + warning de résolution dans la réponse. - count_wms_entities(Product, query_type:1) -> count 51145 (~51160). Baseline : tools/list 23, resources 6, rejet D23 d'un paramètre inconnu OK. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
292 lines
9.7 KiB
JavaScript
292 lines
9.7 KiB
JavaScript
/**
|
|
* WMS Query Tools
|
|
* MCP tools for querying WMS entities via LINQ expressions
|
|
*/
|
|
|
|
const wmsQueryService = require('../services/wms-query-service');
|
|
|
|
/**
|
|
* List available WMS query tools
|
|
*/
|
|
function listTools() {
|
|
return [
|
|
{
|
|
name: 'query_wms_entities',
|
|
description: `Query WMS entities using LINQ expressions. Returns rows (up to 1000).
|
|
Uses QueryExecute with QueryType=Reading by default — status fields are STRINGS (enum names, not integers). Other contexts via query_type (opt-in, see the parameter warning).
|
|
Common entities: Products, Containers, Tasks, Stocks, ProductLocations, Locations, InboundOrders, OutboundOrders, Receptions, Accounts, Suppliers, Kits, Alias. Full list via get_entity_metadata.
|
|
entity_type accepts the AD entity name (Container) or the TableName (Containers), case-insensitive — resolved via the Metadata API.
|
|
|
|
IMPORTANT — before building a filter with a status/enum field:
|
|
1. Check docs first: read resource docs://entities/ (e.g. easywms_reading_entites_outboundorder_OutboundOrderStatus for OutboundOrders)
|
|
2. If no doc found: call get_ad_elements(element_type="Entity", search=entity_name)
|
|
3. Last resort: call get_entity_metadata(entity_name=...) to get field names
|
|
Never guess enum string values — they differ between Reading and Writing models.`,
|
|
inputSchema: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
entity_type: {
|
|
type: 'string',
|
|
description: 'Entity type — AD name (Container) or TableName (Containers), case-insensitive, resolved via the Metadata API. E.g. Products, Containers, Tasks, Stocks, ProductLocations, InboundOrders, OutboundOrders, Accounts, Suppliers, Kits, Alias, Receptions.',
|
|
},
|
|
select_expression: {
|
|
type: 'string',
|
|
description: 'LINQ select expression (default: "z => z" for all fields, or "z => new { z.Id, z.Name }" for specific fields)',
|
|
default: 'z => z',
|
|
},
|
|
filter: {
|
|
type: 'string',
|
|
description: 'Optional LINQ filter (e.g., "z => z.Id > 100" or "z => z.Status == \'Active\'")',
|
|
},
|
|
limit: {
|
|
type: 'number',
|
|
description: 'Maximum results to return (default: 100, max: 1000)',
|
|
default: 100,
|
|
},
|
|
query_type: {
|
|
type: 'number',
|
|
description: 'QueryContextType (default: 0 = Reading — status fields are strings, keep it unless you know why). Opt-in: 1 = Writing (status fields become ENUMS — string comparisons like == "Release" FAIL), 2 = DataWarehouse (often not configured), 3 = Metrics (different data model). Entity names unknown to the Reading metadata are passed through as-is with a warning when query_type != 0.',
|
|
default: 0,
|
|
},
|
|
},
|
|
required: ['entity_type'],
|
|
},
|
|
},
|
|
{
|
|
name: 'get_entity_schema',
|
|
description: 'Get the schema/structure of a WMS entity by querying one sample record',
|
|
inputSchema: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
entity_type: {
|
|
type: 'string',
|
|
description: 'Entity type to inspect',
|
|
},
|
|
},
|
|
required: ['entity_type'],
|
|
},
|
|
},
|
|
{
|
|
name: 'count_wms_entities',
|
|
description: `PREFERRED TOOL for counting ("combien", "nombre de", "how many"). Uses QueryScalarExecute → Reading model. Never adds Take(), safe on all entity types.
|
|
IMPORTANT: Always use this tool first for any counting question — do not use query_wms_entities for counts.
|
|
|
|
IMPORTANT — enum/status values MUST be verified before use. Reading model uses string enum names, NOT integers.
|
|
Lookup order for enum values:
|
|
1. Read the relevant doc: docs://entities/ (e.g. easywms_reading_entites_outboundorder_OutboundOrderStatus for OutboundOrders)
|
|
2. If no doc: call get_ad_elements(element_type="Entity", search=entity_name)
|
|
3. Last resort: call get_entity_metadata(entity_name=...) — gives field names but not all enum values
|
|
Never hardcode enum values without checking — Reading vs Writing model distinction has caused bugs before.
|
|
|
|
Verified values (curl-tested):
|
|
- Tasks actives: filter='z.TaskStatus == "InProcess" || z.TaskStatus == "Pending"'
|
|
- Tasks en cours: filter='z.TaskStatus == "InProcess"'
|
|
- Tasks en attente: filter='z.TaskStatus == "Pending"'
|
|
- ODS lancés: filter='z.OutboundOrderStatus == "Release"'
|
|
- OS incomplètes actives: filter='z.IncompleteOrder == true && z.IsActive == true'
|
|
(par classe: ajouter && z.OutboundClassCode == "X")
|
|
- Containers bloquants: filter='z.NumContainerPendingTasks > 0'
|
|
(sur un emplacement: ajouter && z.LocationCode == "X")`,
|
|
inputSchema: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
entity_type: {
|
|
type: 'string',
|
|
description: 'Entity type (Products, Containers, Tasks, Stocks, OutboundOrders, InboundOrders, etc.)',
|
|
},
|
|
filter: {
|
|
type: 'string',
|
|
description: 'Optional LINQ filter condition. Status fields are strings (enum names from Reading model). Always verify enum values via docs://entities/ before use.',
|
|
},
|
|
query_type: {
|
|
type: 'number',
|
|
description: 'QueryContextType (default: 0 = Reading — status fields are strings, keep it unless you know why). Opt-in: 1 = Writing (status fields become ENUMS — string comparisons like == "Release" FAIL), 2 = DataWarehouse (often not configured), 3 = Metrics (different data model). Entity names unknown to the Reading metadata are passed through as-is with a warning when query_type != 0.',
|
|
default: 0,
|
|
},
|
|
},
|
|
required: ['entity_type'],
|
|
},
|
|
},
|
|
{
|
|
name: 'search_wms_data',
|
|
description: 'Search for a keyword across multiple WMS entities',
|
|
inputSchema: {
|
|
type: 'object',
|
|
additionalProperties: false,
|
|
properties: {
|
|
keyword: {
|
|
type: 'string',
|
|
description: 'Keyword to search for',
|
|
},
|
|
entity_types: {
|
|
type: 'array',
|
|
items: { type: 'string' },
|
|
description: 'List of entity types to search in (default: Products, Containers, Tasks)',
|
|
},
|
|
limit: {
|
|
type: 'number',
|
|
description: 'Maximum results per entity (default: 50)',
|
|
default: 50,
|
|
},
|
|
},
|
|
required: ['keyword'],
|
|
},
|
|
},
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Execute WMS query tool
|
|
*/
|
|
async function executeTool(name, args) {
|
|
try {
|
|
switch (name) {
|
|
case 'query_wms_entities':
|
|
return await queryWmsEntities(args);
|
|
|
|
case 'count_wms_entities':
|
|
return await countWmsEntities(args);
|
|
|
|
case 'get_entity_schema':
|
|
return await getEntitySchema(args);
|
|
|
|
case 'search_wms_data':
|
|
return await searchWmsData(args);
|
|
|
|
default:
|
|
throw new Error(`Unknown WMS query tool: ${name}`);
|
|
}
|
|
} catch (error) {
|
|
console.error(`[WMSQueryTools] Error executing ${name}:`, error.message);
|
|
return {
|
|
content: [{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: false,
|
|
error: error.message,
|
|
tool: name
|
|
}, null, 2)
|
|
}],
|
|
isError: true
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tool: query_wms_entities
|
|
*/
|
|
async function queryWmsEntities(args) {
|
|
const { entity_type, select_expression = 'z => z', filter, limit = 100, query_type = 0 } = args;
|
|
|
|
console.error(`[WMSQueryTools] Querying ${entity_type}: limit=${limit} query_type=${query_type}`);
|
|
|
|
const result = await wmsQueryService.queryEntities(
|
|
entity_type,
|
|
select_expression,
|
|
filter,
|
|
limit,
|
|
query_type
|
|
);
|
|
|
|
return {
|
|
content: [{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: true,
|
|
...result
|
|
}, null, 2)
|
|
}]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Tool: count_wms_entities
|
|
*/
|
|
async function countWmsEntities(args) {
|
|
const { entity_type, filter, query_type = 0 } = args;
|
|
|
|
console.error(`[WMSQueryTools] Counting ${entity_type}${filter ? ` where ${filter}` : ''} query_type=${query_type}`);
|
|
|
|
const result = await wmsQueryService.countEntities(entity_type, filter || null, query_type);
|
|
|
|
return {
|
|
content: [{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: true,
|
|
...result
|
|
}, null, 2)
|
|
}]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Tool: get_entity_schema
|
|
*/
|
|
async function getEntitySchema(args) {
|
|
const { entity_type } = args;
|
|
|
|
console.error(`[WMSQueryTools] Getting schema for ${entity_type}`);
|
|
|
|
const result = await wmsQueryService.getEntitySchema(entity_type);
|
|
|
|
return {
|
|
content: [{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: true,
|
|
...result
|
|
}, null, 2)
|
|
}]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Tool: search_wms_data
|
|
*/
|
|
async function searchWmsData(args) {
|
|
const { keyword, entity_types = ['Products', 'Containers', 'Tasks'], limit = 50 } = args;
|
|
|
|
console.error(`[WMSQueryTools] Searching for "${keyword}" in ${entity_types.join(', ')}`);
|
|
|
|
const results = {};
|
|
let totalFound = 0;
|
|
|
|
for (const entityType of entity_types) {
|
|
try {
|
|
const result = await wmsQueryService.searchEntities(entityType, keyword, limit);
|
|
results[entityType] = {
|
|
count: result.count,
|
|
data: result.data
|
|
};
|
|
totalFound += result.count;
|
|
} catch (error) {
|
|
console.error(`[WMSQueryTools] Search failed for ${entityType}:`, error.message);
|
|
results[entityType] = {
|
|
error: error.message,
|
|
count: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
content: [{
|
|
type: 'text',
|
|
text: JSON.stringify({
|
|
success: true,
|
|
keyword,
|
|
totalFound,
|
|
results
|
|
}, null, 2)
|
|
}]
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
listTools,
|
|
executeTool,
|
|
};
|