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

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

Formatting only, no code change.

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)
[893]62                    if(p.sort == "purchaseOrderNumber") 
63                        order('id', 'asc')
[441]64                }
65                else {
[891]66                    order('purchaseOrder', 'desc')
[441]67                    order('id', 'asc')
68                }
69            }
70
[225]71        result.showTab = [:]
72        switch (params.showTab) {
73            case "showMovementTab":
74                result.showTab.movement =  new String("true")
75                break
[441]76            case "showPurchasingTab":
77                result.showTab.purchasing =  new String("true")
78                break
[225]79            default:
80                result.showTab.inventory = new String("true")
81        }
82
83        p.max = result.inventoryMovementListMax = 10
[441]84        p.offset = 0
[225]85        p.order = "desc"
86        p.sort = "id"
87        result.inventoryMovementList = InventoryMovement.findAllByInventoryItem(result.inventoryItemInstance, p)
88        result.inventoryMovementListTotal = InventoryMovement.countByInventoryItem(result.inventoryItemInstance)
89
[441]90
[405]91        // Success.
[225]92        return result
93
[405]94    } // end show()
[225]95
[405]96    def delete(params) {
[425]97        InventoryItem.withTransaction { status ->
98            def result = [:]
[405]99
[425]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            }
[405]107
[425]108            result.inventoryItemInstance = InventoryItem.get(params.id)
[405]109
[425]110            if(!result.inventoryItemInstance)
111                return fail(code:"default.not.found")
[405]112
[425]113            if(result.inventoryItemInstance.inventoryMovements)
114                return fail(code:"inventoryMovement.still.associated")
[405]115
[425]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
[405]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
[727]167            result.inventoryItemInstance.setAlternateSuppliersFromCheckBoxList(params.alternateSuppliers)
168            result.inventoryItemInstance.setSpareForFromCheckBoxList(params.spareFor)
[405]169
[727]170            // Fetch to prevent lazy initialization error.
171            result.inventoryItemInstance.unitOfMeasure
172
[405]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)
[727]209            result.inventoryItemInstance.setAlternateSuppliersFromCheckBoxList(params.alternateSuppliers)
210            result.inventoryItemInstance.setSpareForFromCheckBoxList(params.spareFor)
[405]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
[548]221    /**
222    * Save an inventory item picture.
[635]223    * @param params An object or map containing at least the inventoryItem ID.
[548]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')
[635]228    * File e.g: new File('picture.jpg')
[548]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
[549]258            // Declare some more variables, since we appear to have most of what we need.
[548]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
[549]266            // Check the supplied pictureSource and get the inputStream.
[548]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            }
[635]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            }
[548]303            else {
[549]304                    return fail(code:"inventory.item.picture.source.not.supported")
[548]305            }
306
[549]307            // Create the Images.
[548]308            try {
309                images = imaging.createAll(result.inventoryItemInstance, picture, pictureInputStream)
[549]310                // Ensure the stream is closed.
311                pictureInputStream.close()
[548]312            }
313            catch(Exception ex) {
314                log.error("picture save", ex)
[549]315                // Ensure the stream is closed.
316                pictureInputStream.close()
[548]317                return fail(code:"inventory.item.picture.file.unrecognised", args: [pictureFileName])
318            }
319
[549]320            // Add images to picture.
[548]321            images.each { image ->
322                picture.addToImages(image)
323            }
324
[549]325            // Save picture.
[548]326            if(picture.hasErrors() || !picture.save())
327                return fail(code:"default.create.failure", args: ["Picture"])
328
329            result.inventoryItemInstance.picture = picture
330
[549]331            // Save inventoryItem.
[548]332            if(result.inventoryItemInstance.hasErrors() || !result.inventoryItemInstance.save())
333                return fail(code:"default.create.failure")
334
335            // success
336            return result
337
[635]338        } // end withTransaction
339    } // savePicture
[548]340
[635]341    /**
[636]342    * Import inventory pictures from an uploaded zip file or picture.
[635]343    * @param request The http request to run getFile against.
[636]344    * Get file should return a zip format file containing the inventory item pictures or a picture file.
[635]345    */
346    def importInventoryItemPictures(request) {
347            def result = [:]
348
349            def kByteMultiplier = 1000
350            def mByteMultiplier = 1000 * kByteMultiplier
[636]351            def fileMaxSize = 100 * mByteMultiplier
[635]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')
[636]360            def uploadedFileName = multiPartFile.originalFilename
[635]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
[636]368            // Check and create import dir.
[635]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
[636]378            // Write file to disk.
379            def diskFile = new File(dir.absolutePath + File.separator + uploadedFileName)
380            multiPartFile.transferTo(diskFile)
[635]381
[636]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                }
[635]398            }
399
[636]400            // Recurse through dir building list of pictureFiles.
401            def pictureFiles = []
402            dir.eachFileMatch(pictureFilePattern) {
403                pictureFiles << it
[635]404            }
405
406            dir.eachDirRecurse { subDir ->
[636]407                subDir.eachFileMatch(pictureFilePattern) {
408                    pictureFiles << it
[635]409                }
410            }
411
[636]412            pictureFiles.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }
413
[635]414            // Find inventoryItems by name of picture and call savePicture.
415            def inventoryItemInstance
416            def itemName
417            def savePictureResult
[636]418            def pictureCount = 0
419            def picturesSavedCount = 0
[635]420
[636]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])
[635]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                }
[636]441                savePictureResult = savePicture(inventoryItemInstance, pictureFile)
[635]442                if(savePictureResult.error)
443                    log.error savePictureResult.error
[636]444                else {
445                    picturesSavedCount++
[635]446                    log.info 'InventoryItem picture saved: ' + itemName
[636]447                }
[635]448            }
449
[636]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
[635]464            // Success.
465            return result
466
467    } // importInventoryItemPictures
468
[636]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
[225]483} // end class
Note: See TracBrowser for help on using the repository browser.