source: branches/features/purchaseOrders/grails-app/services/InventoryItemService.groovy @ 893

Last change on this file since 893 was 893, checked in by gav, 9 years ago

Formatting only, no code change.

File size: 17.5 KB
Line 
1import org.codehaus.groovy.grails.commons.ConfigurationHolder
2import org.apache.commons.lang.WordUtils
3
4/**
5* Provides a service class for the InventoryItem domain class.
6*/
7class InventoryItemService {
8
9    boolean transactional = false
10
11    def createDataService
12
13    def sessionFactory
14    def propertyInstanceMap = org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP
15
16    /**
17    * Prepare the data for the show view.
18    * The result can be used to easily construct the model for the show view.
19    * @param params The incoming params as normally passed to the show view
20    * primarily including the id of the inventoryItem.
21    * @returns A map containing result.error, if any error, otherwise result.inventoryItemInstance.
22    */
23    def show(params) {
24        def result = [:]
25
26        def fail = { Map m ->
27            result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
28            return result
29        }
30
31        result.inventoryItemInstance = InventoryItem.get( params.id )
32
33        if(!result.inventoryItemInstance)
34            return fail(code:"default.not.found")
35
36        def p = [:]
37
38        if(params.paginate == "purchases") {
39            params.showTab = "showPurchasingTab"
40            p.max = Math.min(params.max?.toInteger() ?: 10, 100)
41            p.offset = params.offset?.toInteger() ?: 0
42            p.sort = params.sort ?: null
43            p.order = params.order ?: null
44        }
45        else {
46            p.max = 10
47            p.offset = 0
48        }
49
50        result.inventoryItemPurchasesTotal = InventoryItemPurchase.countByInventoryItem(result.inventoryItemInstance)
51
52        result.inventoryItemPurchases = InventoryItemPurchase.withCriteria {
53                eq("inventoryItem", result.inventoryItemInstance)
54                maxResults(p.max)
55                firstResult(p.offset)
56                // Sorting:
57                // Default is to sort by order number then id.
58                // When a sortable column is clicked then we sort by that.
59                // If the sortable column clicked is order number then we add id as the second sort.
60                if(p.sort && p.order) {
61                    order(p.sort, p.order)
62                    if(p.sort == "purchaseOrderNumber") 
63                        order('id', 'asc')
64                }
65                else {
66                    order('purchaseOrder', 'desc')
67                    order('id', 'asc')
68                }
69            }
70
71        result.showTab = [:]
72        switch (params.showTab) {
73            case "showMovementTab":
74                result.showTab.movement =  new String("true")
75                break
76            case "showPurchasingTab":
77                result.showTab.purchasing =  new String("true")
78                break
79            default:
80                result.showTab.inventory = new String("true")
81        }
82
83        p.max = result.inventoryMovementListMax = 10
84        p.offset = 0
85        p.order = "desc"
86        p.sort = "id"
87        result.inventoryMovementList = InventoryMovement.findAllByInventoryItem(result.inventoryItemInstance, p)
88        result.inventoryMovementListTotal = InventoryMovement.countByInventoryItem(result.inventoryItemInstance)
89
90
91        // Success.
92        return result
93
94    } // end show()
95
96    def delete(params) {
97        InventoryItem.withTransaction { status ->
98            def result = [:]
99
100            def fail = { Map m ->
101                status.setRollbackOnly()
102                if(result.inventoryItemInstance && m.field)
103                    result.inventoryItemInstance.errors.rejectValue(m.field, m.code)
104                result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
105                return result
106            }
107
108            result.inventoryItemInstance = InventoryItem.get(params.id)
109
110            if(!result.inventoryItemInstance)
111                return fail(code:"default.not.found")
112
113            if(result.inventoryItemInstance.inventoryMovements)
114                return fail(code:"inventoryMovement.still.associated")
115
116            try {
117                result.inventoryItemInstance.delete(flush:true)
118                return result //Success.
119            }
120            catch(org.springframework.dao.DataIntegrityViolationException e) {
121                return fail(code:"default.delete.failure")
122            }
123
124        } //end withTransaction
125    } // end delete()
126
127    def edit(params) {
128        def result = [:]
129        def fail = { Map m ->
130            result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
131            return result
132        }
133
134        result.inventoryItemInstance = InventoryItem.get(params.id)
135
136        if(!result.inventoryItemInstance)
137            return fail(code:"default.not.found")
138
139        // Success.
140        return result
141    }
142
143    def update(params) {
144        InventoryItem.withTransaction { status ->
145            def result = [:]
146
147            def fail = { Map m ->
148                status.setRollbackOnly()
149                if(result.inventoryItemInstance && m.field)
150                    result.inventoryItemInstance.errors.rejectValue(m.field, m.code)
151                result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
152                return result
153            }
154
155            result.inventoryItemInstance = InventoryItem.get(params.id)
156
157            if(!result.inventoryItemInstance)
158                return fail(code:"default.not.found")
159
160            // Optimistic locking check.
161            if(params.version) {
162                if(result.inventoryItemInstance.version > params.version.toLong())
163                    return fail(field:"version", code:"default.optimistic.locking.failure")
164            }
165
166            result.inventoryItemInstance.properties = params
167            result.inventoryItemInstance.setAlternateSuppliersFromCheckBoxList(params.alternateSuppliers)
168            result.inventoryItemInstance.setSpareForFromCheckBoxList(params.spareFor)
169
170            // Fetch to prevent lazy initialization error.
171            result.inventoryItemInstance.unitOfMeasure
172
173            if(result.inventoryItemInstance.hasErrors() || !result.inventoryItemInstance.save())
174                return fail(code:"default.update.failure")
175
176            // Success.
177            return result
178
179        } //end withTransaction
180    }  // end update()
181
182    def create(params) {
183        def result = [:]
184        def fail = { Map m ->
185            result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
186            return result
187        }
188
189        result.inventoryItemInstance = new InventoryItem()
190        result.inventoryItemInstance.properties = params
191
192        // success
193        return result
194    }
195
196    def save(params) {
197        InventoryItem.withTransaction { status ->
198            def result = [:]
199
200            def fail = { Map m ->
201                status.setRollbackOnly()
202                if(result.inventoryItemInstance && m.field)
203                    result.inventoryItemInstance.errors.rejectValue(m.field, m.code)
204                result.error = [ code: m.code, args: ["InventoryItem", params.id] ]
205                return result
206            }
207
208            result.inventoryItemInstance = new InventoryItem(params)
209            result.inventoryItemInstance.setAlternateSuppliersFromCheckBoxList(params.alternateSuppliers)
210            result.inventoryItemInstance.setSpareForFromCheckBoxList(params.spareFor)
211
212            if(result.inventoryItemInstance.hasErrors() || !result.inventoryItemInstance.save())
213                return fail(code:"default.create.failure")
214
215            // success
216            return result
217
218        } //end withTransaction
219    }
220
221    /**
222    * Save an inventory item picture.
223    * @param params An object or map containing at least the inventoryItem ID.
224    * @param pictureSource A supported source to get the picture image from.
225    * Supported sources:
226    * HttpServletRequest e.g: 'request' var from controller to run getFile('file') against.
227    * ServletContextResource e.g: grailsApplication.mainContext.getResource('images/logo.png')
228    * File e.g: new File('picture.jpg')
229    */
230    def savePicture(params, pictureSource) {
231        InventoryItem.withTransaction { status ->
232            def result = [:]
233
234            def kByteMultiplier = 1000
235
236            def fail = { Map m ->
237                status.setRollbackOnly()
238                if(result.inventoryItemInstance && m.field)
239                    result.inventoryItemInstance.errors.rejectValue(m.field, m.code)
240                result.error = [ code: m.code, args: m.args ?: ["InventoryItem", params.id] ]
241                return result
242            }
243
244            result.inventoryItemInstance = InventoryItem.get(params.id)
245
246            if(!result.inventoryItemInstance)
247                return fail(code:"default.not.found")
248
249            // Optimistic locking check.
250            if(params.version) {
251                if(result.inventoryItemInstance.version > params.version.toLong())
252                    return fail(field:"version", code:"default.optimistic.locking.failure")
253            }
254
255            if(result.inventoryItemInstance.picture)
256                return fail(field:"picture", code:"inventory.item.already.has.picture")
257
258            // Declare some more variables, since we appear to have most of what we need.
259            def picture = new Picture(inventoryItem: result.inventoryItemInstance)
260            def imaging = new Imaging()
261            def images = null
262            def pictureFile
263            def pictureFileName = ''
264            def pictureInputStream
265
266            // Check the supplied pictureSource and get the inputStream.
267            if(pictureSource instanceof javax.servlet.http.HttpServletRequest) {
268                def multiPartFile = pictureSource.getFile('file')
269                pictureFileName = multiPartFile.originalFilename
270
271                if(!multiPartFile || multiPartFile.isEmpty())
272                    return fail(code: "default.file.not.supplied")
273
274                if (multiPartFile.getSize() > Image.MAX_SIZE)
275                    return fail(code: "default.file.over.max.size", args: [Image.MAX_SIZE/kByteMultiplier, "kB"])
276
277                pictureInputStream = multiPartFile.inputStream
278            }
279            else if(pictureSource instanceof org.springframework.web.context.support.ServletContextResource) {
280                pictureFile = pictureSource.getFile()
281                pictureFileName = pictureFile.name
282
283                if ( !pictureFile.isFile() || (pictureFile.length() == 0) )
284                    return fail(code:"default.file.not.supplied")
285
286                if (pictureFile.length() > Image.MAX_SIZE)
287                    return fail(code:"default.file.over.max.size", args: [Image.MAX_SIZE/kByteMultiplier, "kB"])
288
289                pictureInputStream = pictureSource.inputStream
290            }
291            else if(pictureSource instanceof File) {
292                pictureFile = pictureSource
293                pictureFileName = pictureFile.name
294
295                if ( !pictureFile.isFile() || (pictureFile.length() == 0) )
296                    return fail(code:"default.file.not.supplied")
297
298                if (pictureFile.length() > Image.MAX_SIZE)
299                    return fail(code:"default.file.over.max.size", args: [Image.MAX_SIZE/kByteMultiplier, "kB"])
300
301                pictureInputStream = new FileInputStream(pictureSource)
302            }
303            else {
304                    return fail(code:"inventory.item.picture.source.not.supported")
305            }
306
307            // Create the Images.
308            try {
309                images = imaging.createAll(result.inventoryItemInstance, picture, pictureInputStream)
310                // Ensure the stream is closed.
311                pictureInputStream.close()
312            }
313            catch(Exception ex) {
314                log.error("picture save", ex)
315                // Ensure the stream is closed.
316                pictureInputStream.close()
317                return fail(code:"inventory.item.picture.file.unrecognised", args: [pictureFileName])
318            }
319
320            // Add images to picture.
321            images.each { image ->
322                picture.addToImages(image)
323            }
324
325            // Save picture.
326            if(picture.hasErrors() || !picture.save())
327                return fail(code:"default.create.failure", args: ["Picture"])
328
329            result.inventoryItemInstance.picture = picture
330
331            // Save inventoryItem.
332            if(result.inventoryItemInstance.hasErrors() || !result.inventoryItemInstance.save())
333                return fail(code:"default.create.failure")
334
335            // success
336            return result
337
338        } // end withTransaction
339    } // savePicture
340
341    /**
342    * Import inventory pictures from an uploaded zip file or picture.
343    * @param request The http request to run getFile against.
344    * Get file should return a zip format file containing the inventory item pictures or a picture file.
345    */
346    def importInventoryItemPictures(request) {
347            def result = [:]
348
349            def kByteMultiplier = 1000
350            def mByteMultiplier = 1000 * kByteMultiplier
351            def fileMaxSize = 100 * mByteMultiplier
352
353            def fail = { Map m ->
354                result.error = [ code: m.code, args: m.args ]
355                return result
356            }
357
358            // Get file from request.
359            def multiPartFile = request.getFile('file')
360            def uploadedFileName = multiPartFile.originalFilename
361
362            if(!multiPartFile || multiPartFile.isEmpty())
363                return fail(code: "default.file.not.supplied")
364
365            if (multiPartFile.getSize() > fileMaxSize)
366                return fail(code: "default.file.over.max.size", args: [fileMaxSize/mByteMultiplier, "MB"])
367
368            // Check and create import dir.
369            def dir = new File(ConfigurationHolder.config.globalDirs.tempInventoryItemPicturesDirectory)
370
371            if(!dir.exists())
372                dir.mkdirs()
373
374            if(!dir.isDirectory()) {
375                return fail(code:'inventoryItemPictures.import.failure.no.directory')
376            }
377
378            // Write file to disk.
379            def diskFile = new File(dir.absolutePath + File.separator + uploadedFileName)
380            multiPartFile.transferTo(diskFile)
381
382            // File patterns
383            def zipFilePattern = ~/[^\s].*(\.(?i)(zip))$/
384            def pictureFilePattern = ~/[^\s].*(\.(?i)(jpg|png|gif|bmp))$/
385
386            // If file claims to be a zip file then try using ant to unzip.
387            if(diskFile.name.matches(zipFilePattern)) {
388                def ant = new AntBuilder()
389                try {
390                    ant.unzip(  src: diskFile.absolutePath,
391                                        dest: dir.absolutePath,
392                                        overwrite:"true" )
393                }
394                catch(e) {
395                    log.error e
396                    return fail(code:'inventoryItemPictures.import.failure.to.unzip')
397                }
398            }
399
400            // Recurse through dir building list of pictureFiles.
401            def pictureFiles = []
402            dir.eachFileMatch(pictureFilePattern) {
403                pictureFiles << it
404            }
405
406            dir.eachDirRecurse { subDir ->
407                subDir.eachFileMatch(pictureFilePattern) {
408                    pictureFiles << it
409                }
410            }
411
412            pictureFiles.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }
413
414            // Find inventoryItems by name of picture and call savePicture.
415            def inventoryItemInstance
416            def itemName
417            def savePictureResult
418            def pictureCount = 0
419            def picturesSavedCount = 0
420
421            // Turn off index mirroring.
422            createDataService.stopSearchableIndex()
423
424            for(pictureFile in pictureFiles) {
425                pictureCount++
426
427                if(pictureCount % 10 == 0) {
428                    cleanUpGorm()
429                }
430
431                itemName = WordUtils.capitalize(pictureFile.name[0..-5])
432                inventoryItemInstance = InventoryItem.findByName(itemName)
433                if(!inventoryItemInstance) {
434                    log.warn 'InventoryItem not found with name: ' + itemName
435                    continue
436                }
437                if(inventoryItemInstance.picture) {
438                    log.warn 'InventoryItem already has picture: ' + itemName
439                    continue
440                }
441                savePictureResult = savePicture(inventoryItemInstance, pictureFile)
442                if(savePictureResult.error)
443                    log.error savePictureResult.error
444                else {
445                    picturesSavedCount++
446                    log.info 'InventoryItem picture saved: ' + itemName
447                }
448            }
449
450            // Start mirroring again and rebuild index.
451            createDataService.startSearchableIndex()
452
453            log.info 'InventoryItem pictures saved: ' + picturesSavedCount
454            log.info 'InventoryItem pictures total: ' + pictureCount
455
456            // Cleanup.
457            dir.eachFile() {
458                if(it.isDirectory())
459                    it.deleteDir()
460                else
461                    it.delete()
462            }
463
464            // Success.
465            return result
466
467    } // importInventoryItemPictures
468
469    /**
470    * This cleans up the hibernate session and a grails map.
471    * For more info see: http://naleid.com/blog/2009/10/01/batch-import-performance-with-grails-and-mysql/
472    * The hibernate session flush is normal for hibernate.
473    * The map is apparently used by grails for domain object validation errors.
474    * A starting point for clean up is every 100 objects.
475    */
476    def cleanUpGorm() {
477        def session = sessionFactory.currentSession
478        session.flush()
479        session.clear()
480        propertyInstanceMap.get().clear()
481    }
482
483} // end class
Note: See TracBrowser for help on using the repository browser.