L4.1 : expose query_type sur les outils de requête (D25)

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>
This commit is contained in:
Arthur Ria
2026-08-25 11:17:10 +02:00
parent 88dab289cc
commit 706e628715
9 changed files with 171 additions and 49 deletions
+4 -2
View File
@@ -205,8 +205,10 @@ la plus fréquente :
Autres règles : Autres règles :
- **`QueryType: 0` (Reading)**, jamais 1 : les statuts sont alors des chaînes - **`QueryType: 0` (Reading) par défaut** : les statuts sont alors des chaînes
(D3). (D3). La bascule vers Writing/Metrics passe par le paramètre `query_type`
des outils de requête — un opt-in documenté (D25), jamais un défaut : ne
recopiez aucun exemple en `QueryType: 1`.
- **Pas de date relative.** `DateTime.Now`, `DateTime.Today`, `AddDays()` ne - **Pas de date relative.** `DateTime.Now`, `DateTime.Today`, `AddDays()` ne
sont pas traduisibles : écrire `new DateTime(2026, 8, 1)` (D12). sont pas traduisibles : écrire `new DateTime(2026, 8, 1)` (D12).
- **`select_expression` est instable** : les projections via le paramètre - **`select_expression` est instable** : les projections via le paramètre
+39
View File
@@ -480,3 +480,42 @@ Deux garde-fous de cadrage :
Au passage, `totalParameters` a changé de sens : c'était le nombre brut Au passage, `totalParameters` a changé de sens : c'était le nombre brut
d'entités `Parameter` chargées, c'est désormais le total correspondant aux d'entités `Parameter` chargées, c'est désormais le total correspondant aux
filtres avant pagination (identique sans filtre). filtres avant pagination (identique sans filtre).
---
## D25 — `query_type` : opt-in explicite, D3 reste la règle par défaut
**Contexte (mesures des 24-25/08/2026, `LIMAGRAI2512`).** `QueryType` était
figé à `0` en dur dans `executeQuery()` et `executeScalarQuery()`, rendant le
modèle Writing — opérationnel et mesuré (`Context.Products` en `QueryType: 1`
répond) — inatteignable.
**Décision.** Un paramètre `query_type` (entier, défaut `0`) est exposé sur
**trois outils** : `call_query_api`, `query_wms_entities`,
`count_wms_entities`. `get_entity_schema` et `search_wms_data` restent des
raccourcis Reading, sans paramètre.
**Rapport à D3.** D3 n'est pas révisée : le Reading reste la règle par défaut,
car en Writing les statuts sont des **énumérations** — les comparaisons de
chaînes (`== "Release"`), cas le plus courant en debug, y échouent. La bascule
est un opt-in explicite et les descriptions d'outils portent l'avertissement.
Modalités :
- **Garde de valeur dans le code de l'outil**, pas dans le wrapper : D23 valide
les noms de paramètres, pas les valeurs. Hors `0..3` (ou non entier) →
erreur locale via `assertValidQueryType()` (`wms-query-service.js`), **avant
tout appel réseau**, nommant les quatre contextes.
- **`2` et `3` sont transmis tels quels** : le WMS répond et son diagnostic
remonte entier (L1.1). Sur `LIMAGRAI2512` : `2` = DataWarehouse non configuré
(`Could not resolve serviceType 'IDataWarehouse…'`), `3` = Metrics, contexte
présent mais modèle distinct (`'ApplicationMetricDataContext' ne contient pas
de définition pour 'Products'`).
- **Interaction avec le resolver (D21)** : la table de résolution est
construite sur le Metadata **Reading**. Quand `query_type != 0`, un nom qui
se résout se résout normalement (`Products` marche en Writing, mesuré) ; un
nom **inconnu** du Reading n'est **pas** bloqué — il passe tel quel avec un
`warning` dans la réponse (`allowUnknown` du resolver, même mécanique que le
repli « Metadata injoignable »), car le modèle Writing/Metrics peut contenir
des entités hors Reading. Le `warning` est conservé aussi dans la réponse
d'erreur si le WMS échoue ensuite.
+6 -18
View File
@@ -17,25 +17,13 @@ contient que ce qui reste à faire.
Deux angles morts constatés le 24/08/2026, plus larges que les lots 2 et 3. Les Deux angles morts constatés le 24/08/2026, plus larges que les lots 2 et 3. Les
chiffres ci-dessous sont mesurés sur le tenant `LIMAGRAI2512`. chiffres ci-dessous sont mesurés sur le tenant `LIMAGRAI2512`.
### L4.1 — Le modèle Writing est inatteignable ### L4.1 (reliquat) — Explorer le contexte Metrics
`QueryType` est figé à `0` (Reading) en dur dans `api-service.js` L'exposition de `query_type` est livrée (D25). Reste l'investigation : le
(`executeQuery` et `executeScalarQuery`). Or `QueryContextType` a **quatre** contexte `Metrics` (`QueryType: 3`, `ApplicationMetricDataContext`) mérite une
valeurs. Testées une à une : exploration à part — c'est probablement là que vivent les données agrégées
produites par les jobs `MetricGatherer`. Livrable : un rapport, pas du code
| Valeur | Contexte | Résultat sur `LIMAGRAI2512` | (même phase d'investigation que L4.4).
|---|---|---|
| `0` | Reading | opérationnel (seul utilisé aujourd'hui) |
| `1` | Writing | **opérationnel**`Context.Products` répond |
| `2` | DataWarehouse | **non configuré** : `Could not resolve serviceType 'IDataWarehouse…'` |
| `3` | Metrics | contexte présent (`ApplicationMetricDataContext`), modèle non exploré |
Exposer `query_type` sur les outils de requête, défaut `0`. Attention : D3 reste
vrai — en Writing les champs de statut sont des **énumérations**, donc
`== "Release"` échoue. La bascule doit être un choix explicite et documenté.
Le contexte `Metrics` mérite une exploration à part : c'est probablement là que
vivent les données agrégées produites par les jobs `MetricGatherer`.
### L4.2 — Une seule application sur neuf est visible ### L4.2 — Une seule application sur neuf est visible
+1 -1
View File
@@ -75,7 +75,7 @@ Rules (see the query tools for details):
- **\`QueryType: 0\` (Reading) is the default** — status fields are strings - **\`QueryType: 0\` (Reading) is the default** — status fields are strings
(\`"Release"\`). \`QueryType: 1\` (Writing) exists but status fields become (\`"Release"\`). \`QueryType: 1\` (Writing) exists but status fields become
enums there: string comparisons fail. Old examples using \`1\` must not be enums there: string comparisons fail. Old examples using \`1\` must not be
copied. copied. The query tools expose this as the opt-in \`query_type\` parameter.
- **\`Where\` and \`OrderBy\` go in the Expression; \`Take\`/\`Skip\` are API - **\`Where\` and \`OrderBy\` go in the Expression; \`Take\`/\`Skip\` are API
parameters.** \`OrderBy\` is mandatory as soon as \`Take\` is used. parameters.** \`OrderBy\` is mandatory as soon as \`Take\` is used.
- **No \`Select\` projections** — the \`Select\` parameter causes server-side - **No \`Select\` projections** — the \`Select\` parameter causes server-side
+10 -5
View File
@@ -323,14 +323,17 @@ class APIService {
* e.g. "Context.OutboundOrders.Where(z => z.OutboundOrderStatus == \"Release\").OrderBy(z => z.Id)" * e.g. "Context.OutboundOrders.Where(z => z.OutboundOrderStatus == \"Release\").OrderBy(z => z.Id)"
* *
* @param {string} expression - LINQ expression (Context.Entity or Context.Entity.Where(...)) * @param {string} expression - LINQ expression (Context.Entity or Context.Entity.Where(...))
* @param {object} options - { take, skip, select, orderBy, inlineCount } * @param {object} options - { take, skip, select, orderBy, inlineCount, queryType }
*/ */
async executeQuery(expression, options = {}) { async executeQuery(expression, options = {}) {
const { take, skip, select, orderBy, inlineCount } = options; const { take, skip, select, orderBy, inlineCount, queryType } = options;
const body = { const body = {
Application: profileManager.getCurrent().application, Application: profileManager.getCurrent().application,
QueryType: 0, // Reading = 0 (status fields are strings), Writing = 1 (enums) // Reading = 0 par défaut (statuts en chaînes) ; Writing/Metrics en
// opt-in explicite via query_type (D25) — la garde de valeur vit dans
// wms-query-service.assertValidQueryType, pas ici.
QueryType: queryType ?? 0,
Expression: expression, Expression: expression,
}; };
@@ -363,11 +366,13 @@ class APIService {
* Execute a scalar LINQ query (Count, Sum, etc.) via QueryScalarExecute. * Execute a scalar LINQ query (Count, Sum, etc.) via QueryScalarExecute.
* Returns the scalar value directly. * Returns the scalar value directly.
* @param {string} fullExpression - e.g. "Context.OutboundOrders.Where(...).Count()" * @param {string} fullExpression - e.g. "Context.OutboundOrders.Where(...).Count()"
* @param {object} options - { queryType }
*/ */
async executeScalarQuery(fullExpression) { async executeScalarQuery(fullExpression, options = {}) {
const body = { const body = {
Application: profileManager.getCurrent().application, Application: profileManager.getCurrent().application,
QueryType: 0, // Reading = 0 — string enum names in filters (Writing=1 fails with enum comparisons) // Reading = 0 par défaut — voir executeQuery / D25.
QueryType: options.queryType ?? 0,
Expression: fullExpression, Expression: fullExpression,
}; };
+19 -3
View File
@@ -119,15 +119,22 @@ function suggestClosest(input, limit = 5) {
* Résout un nom d'entité vers son TableName. * Résout un nom d'entité vers son TableName.
* *
* @param {string} entityType - Name AD ou TableName, insensible à la casse * @param {string} entityType - Name AD ou TableName, insensible à la casse
* @param {object} [options]
* @param {boolean} [options.allowUnknown=false] - Un nom inconnu du Reading
* passe tel quel avec un warning au lieu d'échouer. Utilisé quand
* query_type != 0 (D25) : la table est construite sur le Metadata Reading,
* or le modèle Writing/Metrics peut contenir des entités hors Reading.
* @returns {Promise<{tableName: string, warning?: string}>} * @returns {Promise<{tableName: string, warning?: string}>}
* - nom connu : { tableName } (le TableName exact) * - nom connu : { tableName } (le TableName exact)
* - Metadata injoignable : { tableName: entityType, warning } — on laisse * - Metadata injoignable : { tableName: entityType, warning } — on laisse
* passer le nom tel quel (comportement historique) plutôt que de tout * passer le nom tel quel (comportement historique) plutôt que de tout
* bloquer, et on le dit dans la réponse * bloquer, et on le dit dans la réponse
* @throws {Error} nom inconnu du modèle Reading — AVANT tout appel réseau de * @throws {Error} nom inconnu du modèle Reading (sauf allowUnknown) — AVANT
* requête, avec suggestions proches et renvoi vers get_entity_metadata * tout appel réseau de requête, avec suggestions proches et renvoi vers
* get_entity_metadata
*/ */
async function resolveEntityType(entityType) { async function resolveEntityType(entityType, options = {}) {
const { allowUnknown = false } = options;
if (!entityType || typeof entityType !== 'string' || entityType.trim() === '') { if (!entityType || typeof entityType !== 'string' || entityType.trim() === '') {
throw new Error('entity_type est requis. Utilisez get_entity_metadata pour la liste des entités interrogeables.'); throw new Error('entity_type est requis. Utilisez get_entity_metadata pour la liste des entités interrogeables.');
} }
@@ -150,6 +157,15 @@ async function resolveEntityType(entityType) {
const suggestions = suggestClosest(trimmed); const suggestions = suggestClosest(trimmed);
const closest = suggestions.length > 0 ? ` Proches : ${suggestions.join(', ')}.` : ''; const closest = suggestions.length > 0 ? ` Proches : ${suggestions.join(', ')}.` : '';
if (allowUnknown) {
console.error(`[EntityResolver] "${trimmed}" unknown to Reading metadata, passing through (allowUnknown)`);
return {
tableName: trimmed,
warning: `"${trimmed}" est inconnu du modèle Reading (Metadata) ; il est transmis tel quel car query_type != 0 — le contexte demandé peut contenir des entités hors Reading.${closest}`,
};
}
throw new Error( throw new Error(
`"${trimmed}" n'existe pas dans le modèle Reading.${closest} ` + `"${trimmed}" n'existe pas dans le modèle Reading.${closest} ` +
`${tableNames.length} entités disponibles — utilisez get_entity_metadata pour la liste.` `${tableNames.length} entités disponibles — utilisez get_entity_metadata pour la liste.`
+51 -10
View File
@@ -6,6 +6,27 @@
const apiService = require('./api-service').getInstance(); const apiService = require('./api-service').getInstance();
const entityResolver = require('./entity-resolver'); const entityResolver = require('./entity-resolver');
/**
* Garde de valeur de query_type (D25). Le wrapper D23 valide les noms de
* paramètres, pas les valeurs — cette garde s'exécute AVANT tout appel réseau
* (y compris la résolution d'entité) et nomme les quatre contextes.
* @param {*} queryType - valeur reçue de l'outil (défaut 0 si absent)
* @returns {number} la valeur validée
*/
function assertValidQueryType(queryType) {
if (queryType == null) return 0;
if (!Number.isInteger(queryType) || queryType < 0 || queryType > 3) {
throw new Error(
`query_type invalide : ${JSON.stringify(queryType)}. Valeurs acceptées : ` +
`0 = Reading (défaut — statuts en chaînes, ex. "Release"), ` +
`1 = Writing (statuts en énumérations : les comparaisons de chaînes échouent), ` +
`2 = DataWarehouse (souvent non configuré), ` +
`3 = Metrics (modèle de données distinct).`
);
}
return queryType;
}
/** /**
* Build a LINQ select expression * Build a LINQ select expression
* @param {string} entityType - Entity type (Products, Containers, etc.) * @param {string} entityType - Entity type (Products, Containers, etc.)
@@ -19,11 +40,19 @@ const entityResolver = require('./entity-resolver');
* @param {string} selectExpression - LINQ select expression * @param {string} selectExpression - LINQ select expression
* @param {string|null} filter - Optional filter * @param {string|null} filter - Optional filter
* @param {number} limit - Result limit * @param {number} limit - Result limit
* @param {number} queryType - QueryContextType (0 = Reading par défaut, D25)
*/ */
async function queryEntities(entityType, selectExpression = 'z => z', filter = null, limit = 100) { async function queryEntities(entityType, selectExpression = 'z => z', filter = null, limit = 100, queryType = 0) {
// Garde de valeur avant tout réseau (D25).
queryType = assertValidQueryType(queryType);
// Résolution Name/TableName -> TableName (D21). Un nom inconnu échoue ici, // Résolution Name/TableName -> TableName (D21). Un nom inconnu échoue ici,
// avant tout appel réseau de requête — l'erreur porte les suggestions. // avant tout appel réseau de requête — l'erreur porte les suggestions.
const { tableName, warning } = await entityResolver.resolveEntityType(entityType); // En query_type != 0, un nom hors Reading passe tel quel avec warning : le
// modèle Writing/Metrics peut contenir des entités hors Reading (D25).
const { tableName, warning } = await entityResolver.resolveEntityType(entityType, {
allowUnknown: queryType !== 0,
});
try { try {
// Enforce max limit // Enforce max limit
@@ -39,17 +68,19 @@ async function queryEntities(entityType, selectExpression = 'z => z', filter = n
// OrderBy must be embedded in the expression (not as a separate API param) // OrderBy must be embedded in the expression (not as a separate API param)
expression += `.OrderBy(z => z.Id)`; expression += `.OrderBy(z => z.Id)`;
console.error(`[WMSQuery] Querying ${entityType}: ${expression} | take=${actualLimit} select=${selectExpression}`); console.error(`[WMSQuery] Querying ${entityType}: ${expression} | take=${actualLimit} select=${selectExpression} queryType=${queryType}`);
const result = await apiService.executeQuery(expression, { const result = await apiService.executeQuery(expression, {
take: actualLimit, take: actualLimit,
select: selectExpression !== 'z => z' ? selectExpression : undefined, select: selectExpression !== 'z => z' ? selectExpression : undefined,
queryType,
}); });
return { return {
entityType, entityType,
resolvedTableName: tableName, resolvedTableName: tableName,
...(warning ? { warning } : {}), ...(warning ? { warning } : {}),
...(queryType !== 0 ? { queryType } : {}),
expression, expression,
limit: actualLimit, limit: actualLimit,
count: Array.isArray(result) ? result.length : 0, count: Array.isArray(result) ? result.length : 0,
@@ -57,7 +88,9 @@ async function queryEntities(entityType, selectExpression = 'z => z', filter = n
}; };
} catch (error) { } catch (error) {
console.error(`[WMSQuery] Query failed:`, error.message); console.error(`[WMSQuery] Query failed:`, error.message);
throw new Error(`Query failed for ${entityType}: ${error.message}`); // Le warning de résolution (nom hors Reading en query_type != 0) reste
// visible même quand le WMS échoue ensuite.
throw new Error(`Query failed for ${entityType}: ${error.message}${warning ? `\nWarning: ${warning}` : ''}`);
} }
} }
@@ -149,11 +182,17 @@ async function getEntitySchema(entityType) {
* Count entities with optional filter * Count entities with optional filter
* @param {string} entityType - Entity type * @param {string} entityType - Entity type
* @param {string|null} filter - Optional filter * @param {string|null} filter - Optional filter
* @param {number} queryType - QueryContextType (0 = Reading par défaut, D25)
*/ */
async function countEntities(entityType, filter = null) { async function countEntities(entityType, filter = null, queryType = 0) {
// Garde de valeur avant tout réseau (D25).
queryType = assertValidQueryType(queryType);
// Résolution Name/TableName -> TableName (D21) — échec avant appel réseau // Résolution Name/TableName -> TableName (D21) — échec avant appel réseau
// sur nom inconnu. // sur nom inconnu, sauf en query_type != 0 (passage tel quel + warning, D25).
const { tableName, warning } = await entityResolver.resolveEntityType(entityType); const { tableName, warning } = await entityResolver.resolveEntityType(entityType, {
allowUnknown: queryType !== 0,
});
try { try {
// Build: Context.Entity.Where(...).Count() // Build: Context.Entity.Where(...).Count()
@@ -165,20 +204,21 @@ async function countEntities(entityType, filter = null) {
parts.push('Count()'); parts.push('Count()');
const fullExpression = parts.join('.'); const fullExpression = parts.join('.');
console.error(`[WMSQuery] Counting ${entityType}: ${fullExpression}`); console.error(`[WMSQuery] Counting ${entityType}: ${fullExpression} | queryType=${queryType}`);
const count = await apiService.executeScalarQuery(fullExpression); const count = await apiService.executeScalarQuery(fullExpression, { queryType });
return { return {
entityType, entityType,
resolvedTableName: tableName, resolvedTableName: tableName,
...(warning ? { warning } : {}), ...(warning ? { warning } : {}),
...(queryType !== 0 ? { queryType } : {}),
filter, filter,
count count
}; };
} catch (error) { } catch (error) {
console.error(`[WMSQuery] Count failed:`, error.message); console.error(`[WMSQuery] Count failed:`, error.message);
throw new Error(`Count failed for ${entityType}: ${error.message}`); throw new Error(`Count failed for ${entityType}: ${error.message}${warning ? `\nWarning: ${warning}` : ''}`);
} }
} }
@@ -188,4 +228,5 @@ module.exports = {
searchEntities, searchEntities,
getEntitySchema, getEntitySchema,
countEntities, countEntities,
assertValidQueryType,
}; };
+23 -3
View File
@@ -1,5 +1,6 @@
const apiService = require('../services/api-service').getInstance(); const apiService = require('../services/api-service').getInstance();
const entityResolver = require('../services/entity-resolver'); const entityResolver = require('../services/entity-resolver');
const { assertValidQueryType } = require('../services/wms-query-service');
/** /**
* Tools MCP pour interagir avec les APIs WMS * Tools MCP pour interagir avec les APIs WMS
@@ -36,6 +37,11 @@ function listTools() {
description: 'Limite de résultats (défaut: 100)', description: 'Limite de résultats (défaut: 100)',
default: 100, default: 100,
}, },
query_type: {
type: 'number',
description: 'QueryContextType (défaut: 0 = Reading — statuts en chaînes, à garder sauf raison explicite). Opt-in : 1 = Writing (statuts en ÉNUMÉRATIONS — les comparaisons de chaînes comme == "Release" ÉCHOUENT), 2 = DataWarehouse (souvent non configuré), 3 = Metrics (modèle de données distinct). En query_type != 0, un nom d\'entité inconnu du Metadata Reading est transmis tel quel avec un warning.',
default: 0,
},
}, },
required: ['entity_type'], required: ['entity_type'],
}, },
@@ -85,12 +91,23 @@ async function executeTool(name, args) {
* Tool: call_query_api * Tool: call_query_api
*/ */
async function callQueryAPI(args) { async function callQueryAPI(args) {
const { entity_type, expression = 'z => z', filter, limit = 100 } = args; const { entity_type, expression = 'z => z', filter, limit = 100, query_type = 0 } = args;
// Conservé hors du try : si la requête échoue ensuite côté WMS, le warning
// de résolution (nom hors Reading) reste dans la réponse d'erreur.
let resolution = null;
try { try {
// Garde de valeur avant tout réseau (D25) — le wrapper D23 ne valide pas
// les valeurs.
const queryType = assertValidQueryType(query_type);
// Résolution Name/TableName -> TableName (D21) — échec avant appel réseau // Résolution Name/TableName -> TableName (D21) — échec avant appel réseau
// sur nom inconnu. // sur nom inconnu, sauf en query_type != 0 (passage tel quel + warning, D25).
const { tableName, warning } = await entityResolver.resolveEntityType(entity_type); resolution = await entityResolver.resolveEntityType(entity_type, {
allowUnknown: queryType !== 0,
});
const { tableName, warning } = resolution;
// Expression = Context.Entity + optional Where + OrderBy (required by EF when Take is used) // Expression = Context.Entity + optional Where + OrderBy (required by EF when Take is used)
let linqExpression = `Context.${tableName}`; let linqExpression = `Context.${tableName}`;
@@ -103,6 +120,7 @@ async function callQueryAPI(args) {
const result = await apiService.executeQuery(linqExpression, { const result = await apiService.executeQuery(linqExpression, {
take: limit || undefined, take: limit || undefined,
select: expression !== 'z => z' ? expression : undefined, select: expression !== 'z => z' ? expression : undefined,
queryType,
}); });
return { return {
@@ -115,6 +133,7 @@ async function callQueryAPI(args) {
entityType: entity_type, entityType: entity_type,
resolvedTableName: tableName, resolvedTableName: tableName,
...(warning ? { warning } : {}), ...(warning ? { warning } : {}),
...(queryType !== 0 ? { queryType } : {}),
result, result,
}, },
null, null,
@@ -132,6 +151,7 @@ async function callQueryAPI(args) {
{ {
success: false, success: false,
error: err.message, error: err.message,
...(resolution?.warning ? { warning: resolution.warning } : {}),
}, },
null, null,
2 2
+18 -7
View File
@@ -13,7 +13,7 @@ function listTools() {
{ {
name: 'query_wms_entities', name: 'query_wms_entities',
description: `Query WMS entities using LINQ expressions. Returns rows (up to 1000). description: `Query WMS entities using LINQ expressions. Returns rows (up to 1000).
Uses QueryExecute with QueryType=Reading — status fields are STRINGS (enum names, not integers). 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. 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. entity_type accepts the AD entity name (Container) or the TableName (Containers), case-insensitive — resolved via the Metadata API.
@@ -44,6 +44,11 @@ Never guess enum string values — they differ between Reading and Writing model
description: 'Maximum results to return (default: 100, max: 1000)', description: 'Maximum results to return (default: 100, max: 1000)',
default: 100, 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'], required: ['entity_type'],
}, },
@@ -96,6 +101,11 @@ Verified values (curl-tested):
type: 'string', 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.', 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'], required: ['entity_type'],
}, },
@@ -169,15 +179,16 @@ async function executeTool(name, args) {
* Tool: query_wms_entities * Tool: query_wms_entities
*/ */
async function queryWmsEntities(args) { async function queryWmsEntities(args) {
const { entity_type, select_expression = 'z => z', filter, limit = 100 } = args; const { entity_type, select_expression = 'z => z', filter, limit = 100, query_type = 0 } = args;
console.error(`[WMSQueryTools] Querying ${entity_type}: limit=${limit}`); console.error(`[WMSQueryTools] Querying ${entity_type}: limit=${limit} query_type=${query_type}`);
const result = await wmsQueryService.queryEntities( const result = await wmsQueryService.queryEntities(
entity_type, entity_type,
select_expression, select_expression,
filter, filter,
limit limit,
query_type
); );
return { return {
@@ -195,11 +206,11 @@ async function queryWmsEntities(args) {
* Tool: count_wms_entities * Tool: count_wms_entities
*/ */
async function countWmsEntities(args) { async function countWmsEntities(args) {
const { entity_type, filter } = args; const { entity_type, filter, query_type = 0 } = args;
console.error(`[WMSQueryTools] Counting ${entity_type}${filter ? ` where ${filter}` : ''}`); console.error(`[WMSQueryTools] Counting ${entity_type}${filter ? ` where ${filter}` : ''} query_type=${query_type}`);
const result = await wmsQueryService.countEntities(entity_type, filter || null); const result = await wmsQueryService.countEntities(entity_type, filter || null, query_type);
return { return {
content: [{ content: [{