const warehouseIds = [1, 44]; module.exports = Self => { Self.remoteMethod('calcCatalog', { description: 'Get the available and prices list for an item', accessType: 'READ', accepts: [ { arg: 'id', type: 'Number', description: 'The item id' }, { arg: 'dated', type: 'Date', description: 'The date' }, { arg: 'addressFk', type: 'Number', description: 'The address id' }, { arg: 'agencyModeFk', type: 'Number', description: 'The agency id' } ], returns: { type: ['Object'], description: 'The item available and prices list', root: true, }, http: { path: `/:id/calcCatalog`, verb: 'GET' } }); Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => { Self.dataSource.connector.query( `CALL hedera.item_calcCatalog(?, ?, ?, ?)`, [id, dated, addressFk, agencyModeFk], (err, res) => { if (err) return cb(err) return cb(null, res[0]) } ); }; 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: 'categoryFk', type: 'Number', description: 'The item category 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, categoryFk, 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 { let inbounds = await $.Inbound.find({ fields: ['itemFk'], where: inboundWhere }); itemIds = toValues(inbounds, 'itemFk'); if (categoryFk || typeFk || search) { let where = { id: {inq: itemIds} }; if (typeFk) { where.typeFk = typeFk; } else if (categoryFk) { let types = await $.ItemType.find({ fields: ['id'], where: {categoryFk} }); where.typeFk = {inq: toValues(types, 'id')}; } if (search) where.longName = {like: `%${search}%`}; let filter = { fields: ['id'], where }; let items = await Self.find(filter); itemIds = items.map(i => i.id); } } // 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 tags = []; if (typeFk || search) { 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()]; 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 items = await Self.find({ fields: ['id', 'longName', 'subName', 'image'], where: {id: {inq: itemIds}}, limit, order, include: [ { relation: 'tags', scope: { fields: ['value', 'tagFk'], where: {priority: {gt: 4}}, order: 'priority', include: { relation: 'tag', scope: {fields: ['name']} } } }, { relation: 'inbounds', scope: { fields: ['available', 'dated', 'tableId'], where: inboundWhere, order: 'dated DESC', include: { relation: 'buy', scope: {fields: ['id', 'price3']} }, } } ] }); for (let item of items) { item.inbound = item.inbounds()[0]; item.buy = item.inbound && item.inbound.buy(); item.available = sum(item.inbounds(), 'available'); } return {items, tags}; }; }; // Array functions function sum(array, key) { if (!Array.isArray(array)) return 0; return array.reduce((a, c) => a + c[key], 0); } 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; }