source: trunk/grails-app/services/InventoryItemService.groovy @ 955

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

Improvements to InventoryItem views.

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