const warehouseIds = [1, 44]; module.exports = Self => { Self.remoteMethod('catalog', { description: 'Get the catalog', accessType: 'READ', accepts: [ { arg: 'dated', type: 'Date', description: 'The available date' }, { arg: 'typeFk', type: 'Number', description: 'The item type id' }, { arg: 'search', type: 'String', description: 'The search string' }, { arg: 'order', type: 'String', description: 'The order string' }, { arg: 'limit', type: 'Number', description: 'The maximum number of registers' }, { arg: 'tagFilter', type: ['Object'], description: 'The tag filter object' } ], returns: { type: ['Object'], description: 'The item list', root: true, }, http: { path: `/catalog`, verb: 'GET' } }); Self.catalog = async (dated, typeFk, search, order, limit, tagFilter) => { let $ = Self.app.models; let itemIds; let inboundWhere = { warehouseFk: {inq: warehouseIds}, available: {gt: 0}, dated: {lte: dated}, and: [ {or: [ {expired: {gt: dated}}, {expired: null} ]} ] }; // Applies base filters if (/^[0-9]+$/.test(search)) { itemIds = [parseInt(search)]; } else { if (typeFk || search) { let where = {}; if (typeFk) where.typeFk = typeFk; if (search) where.longName = {like: `%${search}%`}; let filter = { fields: ['id'], where }; let items = await Self.find(filter); itemIds = items.map(i => i.id); } let where = Object.assign({}, inboundWhere); if (itemIds) where.itemFk = {inq: itemIds}; let inbounds = await $.Inbound.find({ fields: ['itemFk'], where }); itemIds = toValues(inbounds, 'itemFk'); } // Applies tag filters let baseItemIds = itemIds; let tagItems = []; let tagFilterIds = []; if (tagFilter && tagFilter.length) { for (let filter of tagFilter) { let cond; let values = filter.values; if (values.length) cond = {value: {inq: values}}; else if (values.min && values.max) cond = {intValue: {between: [values.min, values.max]}}; else if (values.min) cond = {intValue: {gte: values.min}}; else if (values.max) cond = {intValue: {lte: values.max}}; let where = { itemFk: {inq: itemIds}, tagFk: filter.tagFk }; Object.assign(where, cond); let itemTags = await $.ItemTag.find({ fields: ['itemFk'], where }); tagItems.push(toSet(itemTags, 'itemFk')); tagFilterIds.push(filter.tagFk); } itemIds = intersect(tagItems); } // Obtains distinct tags and it's distinct values let tagValues = await $.ItemTag.find({ fields: ['tagFk', 'value', 'intValue', 'priority'], where: { itemFk: {inq: itemIds}, tagFk: {nin: tagFilterIds} }, order: 'tagFk, value' }); let tagValueMap = toMultiMap(tagValues, 'tagFk'); for (let i = 0; i < tagItems.length; i++) { let tagFk = tagFilter[i].tagFk; let itemIds; if (tagItems.length > 1) { let siblings = tagItems.filter(v => v != tagItems[i]); itemIds = intersect(siblings); } else itemIds = baseItemIds; let tagValues = await $.ItemTag.find({ fields: ['value', 'intValue', 'priority'], where: { itemFk: {inq: itemIds}, tagFk: tagFk }, order: 'value' }); tagValueMap.set(tagFk, tagValues); } let tagIds = [...tagValueMap.keys()]; let tags = await $.Tag.find({ fields: ['id', 'name', 'isQuantitative', 'unit'], where: { id: {inq: tagIds} } }); for (let tag of tags) { let tagValues = tagValueMap.get(tag.id); let filter = tagFilter && tagFilter.find(i => i.tagFk == tag.id); filter = filter && filter.values; let values = toSet(tagValues, 'value'); if (Array.isArray(filter)) values = new Set([...filter, ...values]); if (tag.isQuantitative) { let intValues = toValues(tagValues, 'intValue'); if (filter) { if (filter.min) intValues.push(filter.min); if (filter.max) intValues.push(filter.max); } let min = Math.min(...intValues); let max = Math.max(...intValues); let dif = max - min; let digits = new String(dif).length; let step = Math.pow(10, digits - 1); if (digits > 1 && step * 5 > dif) step /= 10; Object.assign(tag, { step, min: Math.floor(min / step) * step, max: Math.ceil(max / step) * step }); } Object.assign(tag, { values: [...values], filter }); } // Obtains items data /* let inbounds = await $.Inbound.find({ fields: ['itemFk', 'available', 'dated'], include: 'item', where: Object.assign( {itemFk: {inq: itemIds}}, inboundWhere ) }); */ let items = await Self.find({ where: {id: {inq: itemIds}}, include: [ { relation: 'tags', scope: {include: 'tag'} }, { relation: 'inbounds', scope: { fields: ['available', 'dated'], where: inboundWhere } } ], limit: limit, order: order }); return {items, tags}; }; function toMap(objects, key) { let map = new Map(); for (let object of objects) map.set(object[key], object); return map; } function toMultiMap(objects, key) { let map = new Map(); for (let object of objects) { let value = map.get(object[key]); if (!value) map.set(object[key], value = []); value.push(object); } return map; } function toSet(objects, key) { let set = new Set(); for (let object of objects) set.add(object[key]); return set; } function toValues(objects, key) { return [...toSet(objects, key)]; } function intersect(sets) { if (!sets.length) return []; let array = []; let mySets = sets.slice(0); let firstSet = mySets.shift(); for (let value of firstSet) { let isOnAll = true; for (let set of mySets) if (!set.has(value)) { isOnAll = false; break; } if (isOnAll) array.push(value); } return array; } };