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

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

Small spelling correction in InventoryItemSearchService comments

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