/** * WMS Query Service * Helper functions for building and executing LINQ queries via WMS API */ const apiService = require('./api-service').getInstance(); const entityResolver = require('./entity-resolver'); /** * Build a LINQ select expression * @param {string} entityType - Entity type (Products, Containers, etc.) * @param {string} selectFields - Fields to select (e.g., "z => new { z.Id, z.Name }") * @param {string|null} filter - Optional filter (e.g., "z.Status == 'Active'") * @param {number|null} limit - Optional limit */ /** * Query WMS entities with LINQ expression * @param {string} entityType - Entity type * @param {string} selectExpression - LINQ select expression * @param {string|null} filter - Optional filter * @param {number} limit - Result limit */ async function queryEntities(entityType, selectExpression = 'z => z', filter = null, limit = 100) { // Résolution Name/TableName -> TableName (D21). Un nom inconnu échoue ici, // avant tout appel réseau de requête — l'erreur porte les suggestions. const { tableName, warning } = await entityResolver.resolveEntityType(entityType); try { // Enforce max limit const maxLimit = parseInt(process.env.MAX_QUERY_ROWS) || 1000; const actualLimit = Math.min(limit, maxLimit); // Build expression: Context + optional Where + OrderBy (required by EF when Take is used) let expression = `Context.${tableName}`; if (filter) { const whereExpr = /^\s*\w+\s*=>/.test(filter) ? filter : `z => ${filter}`; expression += `.Where(${whereExpr})`; } // OrderBy must be embedded in the expression (not as a separate API param) expression += `.OrderBy(z => z.Id)`; console.error(`[WMSQuery] Querying ${entityType}: ${expression} | take=${actualLimit} select=${selectExpression}`); const result = await apiService.executeQuery(expression, { take: actualLimit, select: selectExpression !== 'z => z' ? selectExpression : undefined, }); return { entityType, resolvedTableName: tableName, ...(warning ? { warning } : {}), expression, limit: actualLimit, count: Array.isArray(result) ? result.length : 0, data: result }; } catch (error) { console.error(`[WMSQuery] Query failed:`, error.message); throw new Error(`Query failed for ${entityType}: ${error.message}`); } } /** * Query entity by ID * @param {string} entityType - Entity type * @param {string|number} id - Entity ID */ async function queryById(entityType, id) { const filter = `z => z.Id == ${id}`; const result = await queryEntities(entityType, 'z => z', filter, 1); if (!result.data || result.data.length === 0) { throw new Error(`${entityType} with ID ${id} not found`); } return result.data[0]; } /** * Search entities by keyword in common fields * @param {string} entityType - Entity type * @param {string} keyword - Search keyword * @param {number} limit - Result limit */ async function searchEntities(entityType, keyword, limit = 50) { // Common searchable fields by entity type const searchFields = { 'Products': ['Code', 'Description', 'Reference'], 'Containers': ['Code', 'Reference'], 'Tasks': ['Code', 'Reference'], 'InboundOrders': ['Code', 'Reference'], 'OutboundOrders': ['Code', 'Reference'], 'Accounts': ['Code', 'Name'], 'Suppliers': ['Code', 'Name'] }; const fields = searchFields[entityType] || ['Code']; // Build filter for multiple fields (OR condition) const conditions = fields.map(field => `z.${field}.Contains("${keyword}")`).join(' || '); const filter = `z => ${conditions}`; try { return await queryEntities(entityType, 'z => z', filter, limit); } catch (error) { // If Contains doesn't work, try simpler approach console.error(`[WMSQuery] Search failed, trying alternative method:`, error.message); return await queryEntities(entityType, 'z => z', null, limit); } } /** * Get entity schema information (example entity to show available fields) * @param {string} entityType - Entity type */ async function getEntitySchema(entityType) { try { // Query one entity to see its structure const result = await queryEntities(entityType, 'z => z', null, 1); if (!result.data || result.data.length === 0) { return { entityType, fields: [], message: `No ${entityType} found to inspect schema` }; } const sample = result.data[0]; const fields = Object.keys(sample).map(key => ({ name: key, type: typeof sample[key], sample: sample[key] })); return { entityType, fields, totalFields: fields.length }; } catch (error) { console.error(`[WMSQuery] Failed to get schema for ${entityType}:`, error.message); throw error; } } /** * Count entities with optional filter * @param {string} entityType - Entity type * @param {string|null} filter - Optional filter */ async function countEntities(entityType, filter = null) { // Résolution Name/TableName -> TableName (D21) — échec avant appel réseau // sur nom inconnu. const { tableName, warning } = await entityResolver.resolveEntityType(entityType); try { // Build: Context.Entity.Where(...).Count() const parts = [`Context.${tableName}`]; if (filter) { const whereExpr = /^\s*\w+\s*=>/.test(filter) ? filter : `z => ${filter}`; parts.push(`Where(${whereExpr})`); } parts.push('Count()'); const fullExpression = parts.join('.'); console.error(`[WMSQuery] Counting ${entityType}: ${fullExpression}`); const count = await apiService.executeScalarQuery(fullExpression); return { entityType, resolvedTableName: tableName, ...(warning ? { warning } : {}), filter, count }; } catch (error) { console.error(`[WMSQuery] Count failed:`, error.message); throw new Error(`Count failed for ${entityType}: ${error.message}`); } } module.exports = { queryEntities, queryById, searchEntities, getEntitySchema, countEntities, };