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

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

Add feature to import inventory item pictures from zip file.

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