source: trunk/grails-app/services/InventoryItemSearchService.groovy @ 629

Last change on this file since 629 was 629, checked in by gav, 14 years ago

Improvements to inventory text search.

File size: 11.9 KB
Line 
1import grails.orm.PagedResultList
2import org.compass.core.engine.SearchEngineQueryParseException
3
4/**
5* Service class that encapsulates the business logic for InventoryItem searches.
6*/
7class InventoryItemSearchService {
8
9    boolean transactional = false
10
11    def dateUtilService
12    def messageSource
13
14    def paramsMax = 100000
15
16    /**
17    * Selects and returns the correct search results based on the supplied quickSearch.
18    * @param params The request params, may contain params.quickSearch string to specify the search.
19    * @param locale The locale to use when generating result.message.
20    */
21    def getQuickSearch(params, locale) {
22        def result = [:]
23        result.quickSearch = params.quickSearch ?: "all"
24
25        def getMessage = { Map m ->
26            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
27        }
28
29        switch (result.quickSearch) {
30            case "inventoryBelowReorder":
31                result.inventoryItemList = getInventoryBelowReorder(params)
32                if(result.inventoryItemList.totalCount > 0)
33                    result.message = getMessage(code:"inventoryItem.search.text.below.reorder.description")
34                else
35                    result.message = getMessage(code:"inventoryItem.search.text.below.reorder.none.found")
36                break
37            case "inventoryBelowReorderAll":
38                result.inventoryItemList = getInventoryBelowReorder(params, false)
39                if(result.inventoryItemList.totalCount > 0)
40                    result.message = getMessage(code:"inventoryItem.search.text.below.reorder.all.description")
41                else
42                    result.message = getMessage(code:"inventoryItem.search.text.below.reorder.none.found")
43                break
44            case "recentlyUsed":
45                result.daysBack = params.daysBack?.isInteger() ? params.daysBack.toInteger() : 14
46                result.inventoryItemList = getRecentlyUsed(params, result.daysBack)
47                if(result.inventoryItemList.totalCount > 0)
48                    result.message = getMessage(code:"inventoryItem.search.text.recently.used.description", args:[result.daysBack])
49                else
50                    result.message = getMessage(code:"inventoryItem.search.text.recently.used.none.found", args:[result.daysBack])
51                break
52            default:
53                result.inventoryItemList = getAll(params)
54                if(result.inventoryItemList.totalCount > 0)
55                    result.message = getMessage(code:"inventoryItem.search.text.all.description")
56                else
57                    result.message = getMessage(code:"inventoryItem.search.text.all.none.found")
58                break
59        } // switch.
60
61        // Success.
62        return result
63
64    } // getQuickSearch
65
66    /**
67    * Get all inventory items.
68    * @param params The request params.
69    */
70    def getAll(params) {
71        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
72        params.offset = params?.offset?.toInteger() ?: 0
73        params.sort = params?.sort ?: "name"
74        params.order = params?.order ?: "asc"
75
76        def inventoryItemList = InventoryItem.createCriteria().list(
77            max: params.max,
78            offset: params.offset,
79            sort: params.sort,
80            order: params.order) {
81            } // createCriteria
82    } // getAll
83
84    /**
85    * List inventory items that are below reorder point.
86    * @param params The request params.
87    * @param onlyReorderEnabled Only include items with reorder enabled, defaults to true.
88    */
89    def getInventoryBelowReorder(params, onlyReorderEnabled=true) {
90        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
91        params.offset = params?.offset?.toInteger() ?: 0
92        params.sort = params?.sort ?: "name"
93        params.order = params?.order ?: "asc"
94
95        def inventoryItemList = InventoryItem.createCriteria().list(
96            max: params.max,
97            offset: params.offset,
98            sort: params.sort,
99            order: params.order) {
100                eq("isActive", true)
101                if(onlyReorderEnabled)
102                    eq("enableReorderListing", true)
103                leProperty("unitsInStock", "reorderPoint")
104            } // createCriteria
105    } // getInventoryBelowReorder
106
107    /**
108    * Get a list of recently used inventory items.
109    * @param params The request params.
110    * @param daysBack The number of days back to get results for.
111    */
112    def getRecentlyUsed(params, daysBack) {
113        def paginateParams = [:]
114        paginateParams.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
115        paginateParams.offset = params?.offset?.toInteger() ?: 0
116
117        def sort = "inventoryItem." + (params?.sort ?: "name")
118        def order = params?.order == "desc" ? "desc" : "asc"
119        def orderBy = " order by " + sort + ' ' + order
120
121        def namedParams = [:]
122        namedParams.startOfDay = dateUtilService.today - daysBack
123
124        def baseQuery = "from InventoryItem as inventoryItem \
125                                        left join inventoryItem.inventoryMovements as inventoryMovement \
126                                        where (inventoryItem.isActive = true \
127                                                    and inventoryMovement.date > :startOfDay \
128                                                    and inventoryMovement.inventoryMovementType = 1 \
129                                                    )"
130
131        def searchQuery = "select distinct inventoryItem " + baseQuery + orderBy
132        def list = InventoryItem.executeQuery(searchQuery, namedParams, paginateParams)
133
134        def countQuery = "select count(distinct inventoryItem) as inventoryItemCount " + baseQuery
135        def totalCount = InventoryItem.executeQuery(countQuery, namedParams)[0].toInteger()
136
137        def inventoryItemInstanceList = new PagedResultList(list, totalCount)
138        return inventoryItemInstanceList
139    } // getRecentlyUsed
140
141    /**
142    * Get a list of inventory items by search text.
143    * @param params The request params.
144    * @param locale The locale to use when generating result.message.
145    */
146    def getTextSearch(params, locale) {
147        def result = [:]
148        result.searchText = params.searchText.trim() ?: "" // User supplied text.
149        result.queryString = "" // Modified string that will be passed to searchable query.
150
151        def getMessage = { Map m ->
152            messageSource.getMessage(m.code, m.args == null ? null : m.args.toArray(), locale)
153        }
154
155        params.max = Math.min(params?.max?.toInteger() ?: 10, paramsMax)
156        params.offset = params?.offset?.toInteger() ?: 0
157        params.sort = params?.sort ?: "id"
158        params.order = params?.order ?: "asc"
159
160        // Build searchableParams.
161        // Do not include params.sort, since not all properites are indexed.
162        def searchableParams = [:]
163        searchableParams.max = params.max
164        searchableParams.offset = params.offset
165        searchableParams.reload = true
166        searchableParams.defaultOperator =  'or'
167        def properitesList = []
168        if(params.searchName)
169            properitesList << '$/InventoryItem/name'
170        if(params.searchDescription)
171            properitesList << '$/InventoryItem/description'
172        if(params.searchComment)
173            properitesList << '$/InventoryItem/comment'
174        if(params.searchLocation)
175            properitesList << '$/InventoryItem/inventoryLocation/name'
176        if(params.searchGroup)
177            properitesList << '$/InventoryItem/inventoryGroup/name'
178        if(params.searchSpareFor) {
179            properitesList << '$/InventoryItem/spareFor/name'
180            properitesList << '$/InventoryItem/spareFor/description'
181            properitesList << '$/InventoryItem/spareFor/comment'
182        }
183        if(properitesList)
184            searchableParams.properties = properitesList
185
186        // Check searchText for key words and modifiers.
187        def hasIsActive = result.searchText.contains('isActive')
188        def hasIsObsolete = result.searchText.contains('isObsolete')
189        def hasBracket = result.searchText.contains('(') || result.searchText.contains(')')
190        def containsModifier = { s ->
191            s.contains('"') ||
192            s.contains('~') ||
193            s.contains('*') ||
194            s.contains('(') ||
195            s.contains(')') ||
196            s.contains('+') ||
197            s.contains('-') ||
198            s.contains('^') ||
199            s.contains('OR') ||
200            s.contains('AND') ||
201            s.contains('NOT') ||
202            s.contains('TO') ||
203            s.contains('isObsolete') ||
204            s.contains('isActive')
205        }
206
207        // Expand search with wildcards.
208        def addWildcards = { text ->
209            text = text.tokenize().collect { token ->
210                if(!containsModifier(token))
211                    '*'+token+'*'
212                else
213                    token
214            }.join(' ')
215            return text
216        }
217
218        // Default isActive and isObsolete.
219        def addDefaultFlags = { text ->
220            if(!hasBracket)
221                text = '( '+text+' )'
222            if(!hasIsActive)
223                text = text + ' AND isActive:"true" '
224            if(!hasIsObsolete)
225                text = text + ' AND isObsolete:"false" '
226            return text
227        }
228
229        result.queryString = addWildcards(result.searchText)
230        result.queryString = addDefaultFlags(result.queryString)
231
232        // Perform the searchable query.
233        try {
234            result.inventoryItemList = InventoryItem.search(result.queryString, searchableParams)
235
236            // Would be nice if this worked.
237//             result.inventoryItemList = InventoryItem.search(result.searchText, searchableParams) {
238//                 must(term("isActive", true))
239//                 must(term("isObsolete", false))
240//             }
241
242        } catch (e) {
243            log.error e
244            result.inventoryItemList = [:]
245            result.inventoryItemList.results = []
246            result.inventoryItemList.total = 0
247        }
248
249        // Sort the returned instances.
250        if(params.sort != 'id') {
251            if(params.order == 'asc') {
252                if(params.sort == 'name' || params.sort == 'description')
253                    result.inventoryItemList.results.sort { p1, p2 -> p1[params.sort].compareToIgnoreCase(p2[params.sort]) }
254                else if(params.sort == 'inventoryGroup') {
255                    result.inventoryItemList.results.sort { p1, p2 ->
256                        p1.inventoryGroup.name.compareToIgnoreCase(p2.inventoryGroup.name)
257                    }
258                }
259                else if(params.sort == 'unitsInStock')
260                    result.inventoryItemList.results.sort {p1, p2 -> p1[params.sort]  <=> p2[params.sort] }
261            } // asc.
262            else {
263                if(params.sort == 'name' || params.sort == 'description')
264                    result.inventoryItemList.results.sort { p1, p2 -> p2[params.sort].compareToIgnoreCase(p1[params.sort]) }
265                else if(params.sort == 'inventoryGroup') {
266                    result.inventoryItemList.results.sort { p1, p2 ->
267                        p2.inventoryGroup.name.compareToIgnoreCase(p1.inventoryGroup.name)
268                    }
269                }
270                else if(params.sort == 'unitsInStock')
271                    result.inventoryItemList.results.sort {p1, p2 -> p2[params.sort] <=> p1[params.sort]}
272            } // desc.
273        } // sort.
274
275        // Create a PagedResultList.
276        result.inventoryItemList = new PagedResultList(result.inventoryItemList.results, result.inventoryItemList.total)
277
278        // Get the result message.
279        if(result.inventoryItemList.totalCount > 0)
280            result.message = getMessage(code:"inventoryItem.search.text.found", args: [result.queryString])
281        else
282            result.message = getMessage(code:"inventoryItem.search.text.none.found", args: [result.queryString])
283
284        // Success.
285        return result
286
287    } // getTextSearch()
288
289} // end class
Note: See TracBrowser for help on using the repository browser.