From 4bd29e2010c7da127148a0f7baea76d275066958 Mon Sep 17 00:00:00 2001 From: Tom Butcher Date: Sun, 9 Aug 2026 22:52:05 +0100 Subject: [PATCH] Enhance utility functions in utils.js for improved reference model handling and filtering logic This update introduces new functions such as `getRefModelEntryFromPrefix`, `stripPrefixFromOperand`, and `getRefModelEntryForToken` to streamline the processing of reference models and tokens. Additionally, the `resolveRefLeaf` and `resolveRefCondition` functions have been refactored to utilize these enhancements, improving the overall efficiency and clarity of the filtering and reference resolution processes. Minor formatting adjustments were also made in orderitems.js for consistency. --- src/routes/inventory/orderitems.js | 54 +++++++++++----------- src/utils.js | 74 +++++++++++++++++++++--------- 2 files changed, 79 insertions(+), 49 deletions(-) diff --git a/src/routes/inventory/orderitems.js b/src/routes/inventory/orderitems.js index c32d228..258d6a0 100644 --- a/src/routes/inventory/orderitems.js +++ b/src/routes/inventory/orderitems.js @@ -5,31 +5,31 @@ import { getFilter, convertPropertiesString } from '../../utils.js'; const router = express.Router(); const listAllowedFilters = [ - 'name', - 'itemType', - 'item', - 'item._id', - 'order', - 'order._id', - 'orderType', - 'shipment', - 'shipment._id', - 'state', - 'createdAt', - 'updatedAt', - '_reference' - ]; + 'name', + 'itemType', + 'item', + 'item._id', + 'order', + 'order._id', + 'orderType', + 'shipment', + 'shipment._id', + 'state', + 'createdAt', + 'updatedAt', + '_reference', +]; const propertiesAllowedFilters = [ - 'name', - 'itemType', - 'item', - 'item._id', - 'order', - 'order._id', - 'orderType', - 'shipment', - 'shipment._id', - ]; + 'name', + 'itemType', + 'item', + 'item._id', + 'order', + 'order._id', + 'orderType', + 'shipment', + 'shipment._id', +]; import { listOrderItemsRouteHandler, getOrderItemRouteHandler, @@ -42,14 +42,13 @@ import { getOrderItemHistoryRouteHandler, searchOrderItemsRouteHandler, getOrderItemPropertyValuesRouteHandler, - - getOrderItemNeighborsRouteHandler + getOrderItemNeighborsRouteHandler, } from '../../services/inventory/orderitems.js'; // list of order items router.get('/', isAuthenticated, async (req, res) => { const { page, limit, property, search, sort, order } = req.query; - const filter = await getFilter(req.query, listAllowedFilters); + const filter = await getFilter(req.query, listAllowedFilters); listOrderItemsRouteHandler(req, res, page, limit, property, filter, search, sort, order); }); @@ -72,7 +71,6 @@ router.get('/search', isAuthenticated, async (req, res) => { searchOrderItemsRouteHandler(req, res, search); }); - router.post('/', isAuthenticated, async (req, res) => { newOrderItemRouteHandler(req, res); }); diff --git a/src/utils.js b/src/utils.js index ab37300..6d9d9d2 100644 --- a/src/utils.js +++ b/src/utils.js @@ -9,7 +9,7 @@ import log4js from 'log4js'; import config from './config.js'; import crypto from 'crypto'; import canonicalize from 'canonical-json'; -import { getModelByName } from './services/misc/model.js'; +import { getModelByName, getModelByPrefix } from './services/misc/model.js'; import { models } from './database/schemas/models.js'; import { createEmailRenderAuthCode } from './services/misc/emailRenderAuth.js'; import { Worker } from 'worker_threads'; @@ -185,6 +185,30 @@ function getRefModelEntryFromSchemaRef(refName) { }; } +function getRefModelEntryFromPrefix(prefix) { + const entry = getModelByPrefix(prefix); + if (!entry?.model) return null; + return { + model: entry.model, + idField: entry.idField ?? '_id', + referenceField: entry.referenceField ?? '_reference', + }; +} + +function stripPrefixFromOperand(operand) { + const { suffix, hadPrefix } = parsePrefixedValue(String(operand).trim()); + return hadPrefix ? suffix : String(operand).trim(); +} + +function getRefModelEntryForToken(token, fallbackRefName) { + const { prefix, hadPrefix } = parsePrefixedValue(String(token).trim()); + if (hadPrefix) { + const entry = getRefModelEntryFromPrefix(prefix); + if (entry) return entry; + } + return getRefModelEntryFromSchemaRef(fallbackRefName); +} + function parseBooleanOperand(value) { const lower = String(value).trim().toLowerCase(); if (lower === 'yes') return true; @@ -615,8 +639,10 @@ function combineAndRefIds(children) { } async function resolveRefOperand(operand, refModelEntry, operator = 'eq') { - const operandText = String(operand).trim(); - const objectId = extractObjectIdFromOperand(operandText); + if (!refModelEntry?.model) return NO_MATCH_CONDITION; + + const lookupOperand = String(operand).trim(); + const objectId = extractObjectIdFromOperand(lookupOperand); if (objectId) { if (operator === 'ne') return { op: { $ne: objectId } }; @@ -624,7 +650,7 @@ async function resolveRefOperand(operand, refModelEntry, operator = 'eq') { return NO_MATCH_CONDITION; } - const expression = buildRefListExpression(operator, operandText); + const expression = buildRefListExpression(operator, lookupOperand); const ids = await listRefModelIds(refModelEntry, expression); if (operator === 'ne') { @@ -635,13 +661,18 @@ async function resolveRefOperand(operand, refModelEntry, operator = 'eq') { return idsToCondition(ids); } -async function resolveRefLeaf(rawToken, refModelEntry) { +async function resolveRefLeaf(rawToken, fallbackRefName) { const token = rawToken.trim(); if (token === '') return NO_MATCH_CONDITION; - const rangeIdx = token.indexOf('..'); + const refModelEntry = getRefModelEntryForToken(token, fallbackRefName); + if (!refModelEntry?.model) return NO_MATCH_CONDITION; + + const lookupToken = stripPrefixFromOperand(token); + + const rangeIdx = lookupToken.indexOf('..'); if (rangeIdx !== -1) { - const ids = await listRefModelIds(refModelEntry, token); + const ids = await listRefModelIds(refModelEntry, lookupToken); return idsToCondition(ids); } @@ -654,37 +685,33 @@ async function resolveRefLeaf(rawToken, refModelEntry) { ['=', 'eq'], ]; for (const [symbol, name] of operators) { - if (token.startsWith(symbol)) { - return resolveRefOperand(token.slice(symbol.length), refModelEntry, name); + if (lookupToken.startsWith(symbol)) { + return resolveRefOperand(lookupToken.slice(symbol.length), refModelEntry, name); } } - const objectId = extractObjectIdFromOperand(token); + const objectId = extractObjectIdFromOperand(lookupToken); if (objectId) return { value: objectId }; - const ids = await listRefModelIds(refModelEntry, token); + const ids = await listRefModelIds(refModelEntry, lookupToken); return idsToCondition(ids); } -async function resolveRefCondition(node, refModelEntry) { +async function resolveRefCondition(node, fallbackRefName) { if (node.type === 'leaf') { - return resolveRefLeaf(node.token, refModelEntry); + return resolveRefLeaf(node.token, fallbackRefName); } const children = await Promise.all( - node.items.map((item) => resolveRefCondition(item, refModelEntry)) + node.items.map((item) => resolveRefCondition(item, fallbackRefName)) ); return node.type === 'and' ? combineAndRefIds(children) : combineOrRefIds(children); } -async function resolveObjectRefFilter(filterProperty, expression, refName) { - const refModelEntry = getRefModelEntryFromSchemaRef(refName); - - if (!refModelEntry?.model) return { [filterProperty]: { $in: [] } }; - +async function resolveObjectRefFilter(filterProperty, expression, fallbackRefName) { const tree = parseExpression(expression); - const condition = await resolveRefCondition(tree, refModelEntry); + const condition = await resolveRefCondition(tree, fallbackRefName); return conditionToQuery(condition, filterProperty); } @@ -1524,7 +1551,12 @@ function mergeFilterClauses(clauses) { // Returns a filter object based on allowed filters and req.query async function getFilter(query, allowedFilters, parse = true, model = null) { const clauses = []; - for (const [key, value] of Object.entries(query)) { + const queryClean = { ...query }; + for (const key of ['sort', 'order', 'page', 'limit']) { + if (key in queryClean) delete queryClean[key]; + } + logger.info('queryExcludingSortAndOrder', queryClean); + for (const [key, value] of Object.entries(queryClean)) { if (allowedFilters.includes(key)) { clauses.push(parse ? await parseFilter(key, value, model) : { [key]: value }); }