source: trunk/grails-app/services/InventoryCsvService.groovy @ 718

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

Domain change: remove InventoryItem.averageDeliveryPeriod and averageDeliveryTime.

File size: 39.1 KB
Line 
1import grails.util.GrailsUtil
2import au.com.bytecode.opencsv.CSVWriter
3import au.com.bytecode.opencsv.CSVReader
4import org.apache.commons.lang.WordUtils
5
6/**
7 * Provides some csv import/export methods.
8 * Requires the opencsv jar to be available which is included in the grails-export plugin.
9 */
10class InventoryCsvService {
11
12    boolean transactional = false
13
14    def dateUtilService
15
16    def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib()
17
18    def sessionFactory
19    def propertyInstanceMap = org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP
20
21    /**
22    * Import inventory creating items as required.
23    * @param request The http request to run getFile against.
24    * Get file should return a csv format file containing the inventory as per template.
25    */
26    def importInventory(request) {
27        InventoryItem.withTransaction { status ->
28            def result = [:]
29
30            def kByteMultiplier = 1000
31            def fileMaxSize = 800 * kByteMultiplier
32            def logFileLink = g.link(controller: "appCore", action: "appLog") {"log"}
33
34            def multiPartFile = request.getFile('file')
35
36            InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
37            CSVReader reader = new CSVReader(sr)
38
39            def fail = { Map m ->
40                status.setRollbackOnly()
41                reader.close()
42                result.error = [ code: m.code, args: m.args ]
43                return result
44            }
45
46            if(!multiPartFile || multiPartFile.isEmpty())
47                return fail(code: "default.file.not.supplied")
48
49            if (multiPartFile.getSize() > fileMaxSize)
50                return fail(code: "default.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])
51
52            def line = []
53            def lineNumber = 0
54            def maxNumberOfColumns = 23
55            def inventoryParams = [:]
56            def inventoryProperties = ["name", "description", "comment", "unitsInStock", "reorderPoint", "reorderQuantity",
57                                                        "unitOfMeasure", "estimatedUnitPriceAmount", "estimatedUnitPriceCurrency",
58                                                        "enableReorderListing", "inventoryLocation", "inventoryStore", "site",
59                                                        "inventoryGroup", "inventoryType",
60                                                        "suppliersPartNumber", "preferredSupplier", "alternateSuppliers",
61                                                        "manufacturersPartNumber", "preferredManufacturer", "alternateManufacturers",
62                                                        "alternateItems", "spareFor"]
63
64            def siteInstance
65            def alternateSupplierInstance
66            def preferredSupplierInstance
67            def supplierTypeInstance
68            def supplierTypeUnknown = SupplierType.get(1)
69            def spareForInstance
70            def alternateItemInstance
71            def preferredManufacturerInstance
72            def alternateManufacturerInstance
73            def manufacturerTypeInstance
74            def manufacturerTypeUnknown = ManufacturerType.get(1)
75            def inventoryTypeInstance
76            def unitOfMeasureInstance
77            def inventoryGroupInstance
78            def inventoryItemInstance
79            def inventoryStoreInstance
80            def inventoryLocationInstance
81
82            def tempPreferredSupplierItemAndType = ''
83            def tempPreferredSupplierItem = ''
84            def tempPreferredSupplierType = ''
85
86            def tempAlternateSuppliers = []
87            def tempSupplierItem = ''
88            def tempSupplierType = ''
89            def tempSupplierItemAndType = []
90
91            def tempPreferredManufacturerItemAndType = ''
92            def tempPreferredManufacturerItem = ''
93            def tempPreferredManufacturerType = ''
94
95            def tempAlternateManufacturers = []
96            def tempManufacturerItem = ''
97            def tempManufacturerType = ''
98            def tempManufacturerItemAndType = []
99
100            def tempSpareFor = []
101            def tempAlternateItems = []
102
103            def nextLine = {
104                    line = reader.readNext()
105                    lineNumber ++
106                    log.info "Processing line: " + lineNumber
107            }
108
109            def parseInputList = {
110                if( (it == null) || (it.trim() == '') ) return []
111                return it.split(";").collect{it.trim()}
112            }
113
114            def parseItemAndType = {
115                return it.split("@").collect{it.trim()}
116            }
117
118            // Get first line.
119            nextLine()
120
121            // Check for header line 1.
122            if(line != templateHeaderLine1) {
123                log.error "Failed to find header line 1. "
124                log.error "Required: " + templateHeaderLine1.toString()
125                log.error "Supplied: " + line.toString()
126                return fail(code: "default.file.no.header")
127            }
128
129            log.info "Header line found."
130
131            // Prepare the first body line.
132            nextLine()
133
134            // Primary loop.
135            while(line) {
136
137                if(line.size() > maxNumberOfColumns) {
138                    log.error "Too many columns on line: " + lineNumber
139                    return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
140                }
141
142                // Ignore comment lines.
143                if(line.toString().toLowerCase().contains("comment")) {
144                    log.info "Comment line found."
145                    nextLine()
146                    continue
147                }
148
149                // Ignore example lines.
150                if(line.toString().toLowerCase().contains("example")) {
151                    log.info "Example line found."
152                    nextLine()
153                    continue
154                }
155
156                // Parse the line into the params map.
157                inventoryParams = [:]
158                line.eachWithIndex { it, j ->
159                    inventoryParams."${inventoryProperties[j]}" = it.trim()
160                }
161
162                // Debug
163                log.debug " Supplied params: "
164                log.debug inventoryParams
165
166                // Ignore blank lines.
167                if(inventoryParams.name == '') {
168                    log.info "No name found."
169                    nextLine()
170                    continue
171                }
172
173                /** Prepare the params and create supporting items as required. */
174
175                // Site
176                inventoryParams.site = WordUtils.capitalize(inventoryParams.site)
177                siteInstance = Site.findByName(inventoryParams.site)
178                if(!siteInstance) {
179                    siteInstance = new Site(name: inventoryParams.site)
180                    if(!siteInstance.save()) {
181                        log.error "Failed to create site on line: " + lineNumber
182                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
183                    }
184                }
185
186                // InventoryStore
187                inventoryParams.inventoryStore = WordUtils.capitalizeFully(inventoryParams.inventoryStore)
188                inventoryStoreInstance = InventoryStore.findByName(inventoryParams.inventoryStore)
189                if(!inventoryStoreInstance) {
190                    inventoryStoreInstance = new InventoryStore(name: inventoryParams.inventoryStore,
191                                                                                                site: siteInstance)
192                    if(!inventoryStoreInstance.save()) {
193                        log.error "Failed to create inventory store on line: " + lineNumber
194                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
195                    }
196                }
197
198                // InventoryLocation
199                inventoryParams.inventoryLocation = WordUtils.capitalize(inventoryParams.inventoryLocation)
200                inventoryLocationInstance = InventoryLocation.findByName(inventoryParams.inventoryLocation)
201                if(!inventoryLocationInstance) {
202                    inventoryLocationInstance = new InventoryLocation(name: inventoryParams.inventoryLocation,
203                                                                                                        inventoryStore: inventoryStoreInstance)
204                    if(!inventoryLocationInstance.save()) {
205                        log.error "Failed to create inventory location on line: " + lineNumber
206                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
207                    }
208                }
209
210                // InventoryGroup
211                inventoryParams.inventoryLocation = WordUtils.capitalizeFully(inventoryParams.inventoryLocation)
212                inventoryGroupInstance = InventoryGroup.findByName(inventoryParams.inventoryGroup)
213                if(!inventoryGroupInstance) {
214                    inventoryGroupInstance = new InventoryGroup(name: inventoryParams.inventoryGroup)
215                    if(!inventoryGroupInstance.save()) {
216                        log.error "Failed to create inventory group on line: " + lineNumber
217                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
218                    }
219                }
220
221                // InventoryType
222                inventoryParams.inventoryType = WordUtils.capitalizeFully(inventoryParams.inventoryType)
223                inventoryTypeInstance = InventoryType.findByName(inventoryParams.inventoryType)
224                if(!inventoryTypeInstance) {
225                    inventoryTypeInstance = new InventoryType(name: inventoryParams.inventoryType)
226                    if(!inventoryTypeInstance.save()) {
227                        log.error "Failed to create inventory type on line: " + lineNumber
228                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
229                    }
230                }
231
232                // UnitOfMeasure.
233                unitOfMeasureInstance = UnitOfMeasure.findByName(inventoryParams.unitOfMeasure)
234                if(!unitOfMeasureInstance) {
235                    unitOfMeasureInstance = new UnitOfMeasure(name: inventoryParams.unitOfMeasure)
236                    if(!unitOfMeasureInstance.save()) {
237                        log.error "Failed to create unit of measure on line: " + lineNumber
238                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
239                    }
240                }
241
242                // Preferred Manufacturer
243                if(inventoryParams.preferredManufacturer) {
244                    tempPreferredManufacturerItemAndType = parseItemAndType(inventoryParams.preferredManufacturer)
245                    tempPreferredManufacturerItem = WordUtils.capitalize(tempPreferredManufacturerItemAndType[0])
246
247                    preferredManufacturerInstance = Manufacturer.findByName(tempPreferredManufacturerItem)
248                    if(!preferredManufacturerInstance) {
249
250                        // Manufacturer Type.
251                        if(tempPreferredManufacturerItemAndType.size == 2) {
252                            tempPreferredManufacturerType = WordUtils.capitalize(tempPreferredManufacturerItemAndType[1])
253                            manufacturerTypeInstance = ManufacturerType.findByName(tempPreferredManufacturerType)
254                        }
255                        else
256                            manufacturerTypeInstance = manufacturerTypeUnknown
257                        if(!manufacturerTypeInstance) {
258                            log.error "Failed to find preferred manufacturer type on line: " + lineNumber
259                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
260                        }
261
262                        preferredManufacturerInstance = new Manufacturer(name: tempPreferredManufacturerItem,
263                                                                                                            manufacturerType: manufacturerTypeInstance)
264                        if(!preferredManufacturerInstance.save()) {
265                            log.error "Failed to create preferred manufacturer on line: " + lineNumber
266                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
267                        }
268                    }
269                }
270                else
271                    preferredManufacturerInstance = null
272
273                // Alternate Manufacturers.
274                tempAlternateManufacturers = parseInputList(inventoryParams.alternateManufacturers)
275                inventoryParams.alternateManufacturers = []
276
277                for(tempManufacturer in tempAlternateManufacturers) {
278                    tempManufacturerItemAndType = parseItemAndType(tempManufacturer)
279                    tempManufacturerItem = WordUtils.capitalizeFully(tempManufacturerItemAndType[0])
280
281                    alternateManufacturerInstance = Manufacturer.findByName(tempManufacturerItem)
282                    if(!alternateManufacturerInstance) {
283
284                        // ManufacturerType.
285                        if(tempManufacturerItemAndType.size == 2) {
286                            tempManufacturerType = WordUtils.capitalize(tempManufacturerItemAndType[1])
287                            manufacturerTypeInstance = ManufacturerType.findByName(tempManufacturerType)
288                        }
289                        else
290                            manufacturerTypeInstance = manufacturerTypeUnknown
291                        if(!manufacturerTypeInstance) {
292                            log.error "Failed to find manufacturer type on line: " + lineNumber
293                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
294                        }
295
296                        alternateManufacturerInstance = new Manufacturer(name: tempManufacturerItem,
297                                                                                                manufacturerType: manufacturerTypeInstance)
298                        if(!alternateManufacturerInstance.save()) {
299                            log.error "Failed to create manufacturers on line: " + lineNumber
300                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
301                        }
302                    }
303
304                    inventoryParams.alternateManufacturers.add(alternateManufacturerInstance)
305                }
306
307                // Preferred Supplier
308                if(inventoryParams.preferredSupplier) {
309                    tempPreferredSupplierItemAndType = parseItemAndType(inventoryParams.preferredSupplier)
310                    tempPreferredSupplierItem = WordUtils.capitalize(tempPreferredSupplierItemAndType[0])
311
312                    preferredSupplierInstance = Supplier.findByName(tempPreferredSupplierItem)
313                    if(!preferredSupplierInstance) {
314
315                        // SupplierType.
316                        if(tempPreferredSupplierItemAndType.size == 2) {
317                            tempPreferredSupplierType = WordUtils.capitalize(tempPreferredSupplierItemAndType[1])
318                            supplierTypeInstance = SupplierType.findByName(tempPreferredSupplierType)
319                        }
320                        else
321                            supplierTypeInstance = supplierTypeUnknown
322                        if(!supplierTypeInstance) {
323                            log.error "Failed to find preferred supplier type on line: " + lineNumber
324                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
325                        }
326
327                        preferredSupplierInstance = new Supplier(name: tempPreferredSupplierItem,
328                                                                                            supplierType: supplierTypeInstance)
329                        if(!preferredSupplierInstance.save()) {
330                            log.error "Failed to create preferred supplier on line: " + lineNumber
331                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
332                        }
333                    }
334                }
335                else
336                    preferredSupplierInstance = null
337
338                // Alternate Suppliers.
339                tempAlternateSuppliers = parseInputList(inventoryParams.alternateSuppliers)
340                inventoryParams.alternateSuppliers = []
341
342                for(tempSupplier in tempAlternateSuppliers) {
343                    tempSupplierItemAndType = parseItemAndType(tempSupplier)
344                    tempSupplierItem = WordUtils.capitalizeFully(tempSupplierItemAndType[0])
345
346                    alternateSupplierInstance = Supplier.findByName(tempSupplierItem)
347                    if(!alternateSupplierInstance) {
348
349                        // SupplierType.
350                        if(tempSupplierItemAndType.size == 2) {
351                            tempSupplierType = WordUtils.capitalize(tempSupplierItemAndType[1])
352                            supplierTypeInstance = SupplierType.findByName(tempSupplierType)
353                        }
354                        else
355                            supplierTypeInstance = supplierTypeUnknown
356                        if(!supplierTypeInstance) {
357                            log.error "Failed to find alternate supplier type on line: " + lineNumber
358                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
359                        }
360
361                        alternateSupplierInstance = new Supplier(name: tempSupplierItem,
362                                                                            supplierType: supplierTypeInstance)
363                        if(!alternateSupplierInstance.save()) {
364                            log.error "Failed to create alternate suppliers on line: " + lineNumber
365                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
366                        }
367                    }
368
369                    inventoryParams.alternateSuppliers.add(alternateSupplierInstance)
370                }
371
372                // AlternateItems.
373                tempAlternateItems = parseInputList(inventoryParams.alternateItems)
374                inventoryParams.alternateItems = []
375
376                for(tempAlternateItem in tempAlternateItems) {
377                    tempAlternateItem = WordUtils.capitalize(tempAlternateItem)
378                    alternateItemInstance = InventoryItem.findByName(tempAlternateItem)
379                    if(!alternateItemInstance) {
380                        alternateItemInstance = new InventoryItem(name: tempAlternateItem,
381                                                                                                description: "Generated from alternateItems during import, details may not be correct.",
382                                                                                                reorderPoint: 0,
383                                                                                                inventoryGroup: inventoryGroupInstance,
384                                                                                                inventoryType: inventoryTypeInstance,
385                                                                                                unitOfMeasure: unitOfMeasureInstance,
386                                                                                                inventoryLocation: inventoryLocationInstance)
387                        if(!alternateItemInstance.save()) {
388                            log.error "Failed to create alternateItems on line: " + lineNumber
389                            return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
390                        }
391                    }
392
393                    inventoryParams.alternateItems.add(alternateItemInstance)
394                }
395
396                // spareFor.
397                tempSpareFor = parseInputList(inventoryParams.spareFor)
398                inventoryParams.spareFor = []
399
400                for(asset in tempSpareFor) {
401
402                    asset = WordUtils.capitalize(asset)
403
404                    spareForInstance = Asset.findByName(asset)
405                    if(!spareForInstance) {
406                        log.error "Failed to find 'Spare For' asset on line: " + lineNumber
407                        return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
408                    }
409
410                    inventoryParams.spareFor.add(spareForInstance)
411                }
412
413                // Assign the retrieved or created instances to params.
414                inventoryParams.inventoryLocation = inventoryLocationInstance
415                inventoryParams.inventoryGroup = inventoryGroupInstance
416                inventoryParams.inventoryType = inventoryTypeInstance
417                inventoryParams.unitOfMeasure = unitOfMeasureInstance
418                inventoryParams.preferredSupplier = preferredSupplierInstance
419                inventoryParams.preferredManufacturer = preferredManufacturerInstance
420
421                // Name.
422                // Checked above for blank string.
423                inventoryParams.name = WordUtils.capitalize(inventoryParams.name)
424
425                // Description.
426                if(inventoryParams.description != '')
427                    inventoryParams.description = inventoryParams.description[0].toUpperCase() + inventoryParams.description[1..-1]
428
429                // Debug
430                log.debug "InventoryParams: "
431                log.debug inventoryParams
432
433                // Create new or update.
434                inventoryItemInstance = InventoryItem.findByName(inventoryParams.name)
435                if(inventoryItemInstance) {
436                    log.info "Updating existing item: " + inventoryItemInstance
437                    inventoryItemInstance.properties = inventoryParams
438                }
439                else {
440                    log.info "Creating new item: " + inventoryParams.name
441                    inventoryItemInstance = new InventoryItem(inventoryParams)
442                }
443
444                // Save inventoryItem.
445                if(inventoryItemInstance.hasErrors() || !inventoryItemInstance.save()) {
446                    log.error "Failed to create item on line: " + lineNumber
447                    log.debug inventoryItemInstance.errors
448                    return fail(code: "inventory.import.failure", args: [lineNumber, logFileLink])
449                }
450
451                if(lineNumber % 100 == 0)
452                    cleanUpGorm()
453
454                if(!result.error) nextLine()
455            } //while(line)
456
457            // Success.
458            log.info "End of file."
459            reader.close()
460            return result
461
462        } //end withTransaction
463    } // end importInventory()
464
465    /**
466    * Build an inventory template csv file.
467    * This template can then be populated for import.
468    * @returns The template as a String in csv format.
469    */
470    def buildInventoryTemplate() {
471
472        StringWriter sw = new StringWriter()
473        CSVWriter writer = new CSVWriter(sw)
474
475        writeTemplateLines(writer)
476
477        writer.close()
478        return sw.toString()
479    }
480
481    private writeTemplateLines(writer) {
482        writer.writeNext(templateHeaderLine1 as String[])
483        writer.writeNext()
484        writer.writeNext("Comment: The header line is required.")
485        writer.writeNext("Comment: Required columns are marked with a (*) in the header line.")
486        writer.writeNext("Comment: Lists of items in a column must be separated by a semicolon (;), not a comma.")
487        writer.writeNext("Comment: The at symbol (@) is reserved for indicating supplier and manufacturer types.")
488        writer.writeNext("Comment: Identical and existing names will be considered as the same item.")
489        writer.writeNext("Comment: Lines containing 'comment' will be ignored.")
490        writer.writeNext("Comment: Lines containing 'example' will be ignored.")
491        writer.writeNext("Comment: This file must be saved as a CSV file before import.")
492        writer.writeNext()
493    }
494
495    /**
496    * Build an inventory example/test file.
497    * This test file can be imported to test the import and export methods.
498    * @returns The test file as a String in csv format.
499    */
500    def buildInventoryExample() {
501
502        StringWriter sw = new StringWriter()
503        CSVWriter writer = new CSVWriter(sw)
504
505        writeTemplateLines(writer)
506
507        // Requires creation of some of the base/group/type data.
508        writer.writeNext(["Split19", "19mm split pin", "Very usefull item.",
509                                        "1024", "0", "1",
510                                        "each", "5", "NZD",
511                                        "false", "BR4",
512                                        "Store #99", "Inventory Depot",
513                                        "Mechanical Stock",
514                                        "Consumable",
515                                        "123", "Multi Supplier@Local",
516                                        "Multi Distributors1@OEM; Multi Distributors2@Local",
517                                        "321", "Master Manufacturer@OEM",
518                                        "Mega Manufacturer1@OEM;Mega Manufacturer2@Alternate",
519                                        "2204E-2RS", ""
520                                        ] as String[])
521
522        // Using existing base data.
523        writer.writeNext(["2204E-2RS", "Double Row Self Align Ball Bearing 2204E-2RS - Sealed - 20/47x18", "",
524                                        "4", "1", "9",
525                                        "each", "16.35", "USD",
526                                        "TRUE", "BR4",
527                                        "Store #99", "Inventory Depot",
528                                        "Mechanical Stock",
529                                        "Consumable",
530                                        "456KL", "Multi Supplier",
531                                        "Multi Distributors1; Multi Distributors2",
532                                        "654OP", "Master Manufacturer",
533                                        "Mega Manufacturer1;Mega Manufacturer2",
534                                        "", ""
535                                        ] as String[])
536
537        writer.close()
538        return sw.toString()
539    }
540
541    /**
542    * Build complete inventory for export.
543    * @param inventoryItemList The list of inventory items to build.
544    * @returns The inventory as a String in csv format.
545    */
546    def buildInventory(List inventoryItemList) {
547
548        def sw = new StringWriter()
549        def writer = new CSVWriter(sw)
550
551        writeTemplateLines(writer)
552
553        //Rows
554        def row
555
556        inventoryItemList.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) }.each() { inventoryItem ->
557            row = []
558            row.add(inventoryItem.name)
559            row.add(inventoryItem.description)
560            row.add(inventoryItem.comment)
561            row.add(inventoryItem.unitsInStock)
562            row.add(inventoryItem.reorderPoint)
563            row.add(inventoryItem.reorderQuantity)
564            row.add(inventoryItem.unitOfMeasure)
565            row.add(inventoryItem.estimatedUnitPriceAmount)
566            row.add(inventoryItem.estimatedUnitPriceCurrency)
567            row.add(inventoryItem.enableReorderListing)
568            row.add(inventoryItem.inventoryLocation)
569            row.add(inventoryItem.inventoryLocation.inventoryStore)
570            row.add(inventoryItem.inventoryLocation.inventoryStore.site)
571            row.add(inventoryItem.inventoryGroup)
572            row.add(inventoryItem.inventoryType)
573            row.add(inventoryItem.suppliersPartNumber)
574
575            if(inventoryItem.preferredSupplier)
576                row.add( inventoryItem.preferredSupplier.name + "@" + inventoryItem.preferredSupplier.supplierType )
577            else
578                row.add('')
579
580            row.add( inventoryItem.alternateSuppliers.sort { p1, p2 ->
581                p1.name.compareToIgnoreCase(p2.name)
582            }.collect { it.name + "@" + it.supplierType }.join(';') )
583
584            row.add(inventoryItem.manufacturersPartNumber)
585
586            if(inventoryItem.preferredManufacturer)
587                row.add( inventoryItem.preferredManufacturer.name + "@" + inventoryItem.preferredManufacturer.manufacturerType )
588            else
589                row.add('')
590
591            row.add(inventoryItem.alternateManufacturers.sort { p1, p2 ->
592                p1.name.compareToIgnoreCase(p2.name)
593            }.collect { it.name + "@" + it.manufacturerType }.join(';'))
594
595            row.add(inventoryItem.alternateItems.sort { p1, p2 ->
596                p1.name.compareToIgnoreCase(p2.name)
597            }.collect { it.name }.join(';') )
598
599            row.add(inventoryItem.spareFor.sort { p1, p2 ->
600                p1.name.compareToIgnoreCase(p2.name)
601            }.collect { it.name }.join(';'))
602
603            writer.writeNext(row as String[])
604        }
605
606        writer.close()
607        return sw.toString()
608    } // end buildInventory()
609
610    /**
611    * Import inventoryItemPurchases creating items as required.
612    */
613    def importInventoryItemPurchases(request) {
614        InventoryItemPurchase.withTransaction { status ->
615            def result = [:]
616
617            def kByteMultiplier = 1000
618            def fileMaxSize = 800 * kByteMultiplier
619            def logFileLink = g.link(controller: "appCore", action: "appLog") {"log"}
620
621            def multiPartFile = request.getFile('file')
622
623            InputStreamReader sr = new InputStreamReader(multiPartFile.inputStream)
624            CSVReader reader = new CSVReader(sr)
625
626            def fail = { Map m ->
627                status.setRollbackOnly()
628                reader.close()
629                result.error = [ code: m.code, args: m.args ]
630                return result
631            }
632
633            if(!multiPartFile || multiPartFile.isEmpty())
634                return fail(code: "default.file.not.supplied")
635
636            if (multiPartFile.getSize() > fileMaxSize)
637                return fail(code: "default.file.over.max.size", args: [fileMaxSize/kByteMultiplier, "kB"])
638
639            def line = []
640            def lineNumber = 0
641            def maxNumberOfColumns = 10
642            def inventoryItemPurchaseParams = [:]
643            def inventoryItemPurchaseProperties = ["inventoryItem", "purchaseOrderNumber", "quantity",
644                                                                                "inventoryItemPurchaseType",
645                                                                                "costCode", "enteredBy", "dateEntered",
646                                                                                "orderValueAmount", "orderValueCurrency", "invoiceNumber"]
647
648            def personInstance
649            def costCodeInstance
650            def inventoryItemInstance
651            def inventoryItemPurchaseInstance
652            def inventoryItemPurchaseTypeInstance
653
654            def nextLine = {
655                    line = reader.readNext()
656                    lineNumber ++
657                    log.info "Processing line: " + lineNumber
658            }
659
660            def parseInputDate = {
661                if( (it == null) || (it.trim() == '') ) {
662                    log.error "Failed to find any date on line: " + lineNumber
663                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
664                }
665
666                def d = it.split("/").collect{it.trim()}
667                if(d.size() != 3) {
668                    log.error "Failed to find full date on line: " + lineNumber
669                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
670                }
671                dateUtilService.makeDate(d[0], d[1], d[2])
672            }
673
674            // Get first line.
675            nextLine()
676
677            // Check for header line 1.
678            if(line != purchasesTemplateHeaderLine1) {
679                log.error "Failed to find header line 1. "
680                log.error "Required: " + purchasesTemplateHeaderLine1.toString()
681                log.error "Supplied: " + line.toString()
682                return fail(code: "default.file.no.header")
683            }
684
685            log.info "Header line found."
686
687            // Prepare the first body line.
688            nextLine()
689
690            // Primary loop.
691            while(line) {
692
693                if(line.size() > maxNumberOfColumns) {
694                    log.error "Too many columns on line: " + lineNumber
695                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
696                }
697
698                // Ignore comment lines.
699                if(line.toString().toLowerCase().contains("comment")) {
700                    log.info "Comment line found."
701                    nextLine()
702                    continue
703                }
704
705                // Ignore example lines.
706                if(line.toString().toLowerCase().contains("example")) {
707                    log.info "Example line found."
708                    nextLine()
709                    continue
710                }
711
712                // Parse the line into the params map.
713                inventoryItemPurchaseParams = [:]
714                line.eachWithIndex { it, j ->
715                    inventoryItemPurchaseParams."${inventoryItemPurchaseProperties[j]}" = it.trim()
716                }
717
718                // Debug
719                log.debug " Supplied params: "
720                log.debug inventoryItemPurchaseParams
721
722                // Ignore blank lines.
723                if(inventoryItemPurchaseParams.inventoryItem == '') {
724                    log.info "No inventory item name found."
725                    nextLine()
726                    continue
727                }
728
729                // Inventory Item.
730                inventoryItemPurchaseParams.inventoryItem = WordUtils.capitalize(inventoryItemPurchaseParams.inventoryItem)
731                inventoryItemInstance = InventoryItem.findByName(inventoryItemPurchaseParams.inventoryItem)
732                if(!inventoryItemInstance) {
733                    log.error "Inventory item not found on line: " + lineNumber
734                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
735                }
736                inventoryItemPurchaseParams.inventoryItem = inventoryItemInstance
737
738                // Quantity.
739                if(inventoryItemPurchaseParams.quantity.isInteger())
740                    inventoryItemPurchaseParams.quantity = inventoryItemPurchaseParams.quantity.toInteger()
741                else {
742                    log.error "Quantity is not a valid number on line: " + lineNumber
743                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
744                }
745
746                // InventoryItemPurchaseType.
747                inventoryItemPurchaseParams.inventoryItemPurchaseType = WordUtils.capitalizeFully(inventoryItemPurchaseParams.inventoryItemPurchaseType)
748                inventoryItemPurchaseTypeInstance = InventoryItemPurchaseType.findByName(inventoryItemPurchaseParams.inventoryItemPurchaseType)
749                if(!inventoryItemPurchaseTypeInstance) {
750                    log.error "Inventory item purchase type not found on line: " + lineNumber
751                    log.debug inventoryItemPurchaseParams.inventoryItemPurchaseType
752                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
753                }
754                inventoryItemPurchaseParams.inventoryItemPurchaseType = inventoryItemPurchaseTypeInstance
755
756                // CostCode.
757                if(inventoryItemPurchaseParams.costCode != '') {
758                    inventoryItemPurchaseParams.costCode = WordUtils.capitalizeFully(inventoryItemPurchaseParams.costCode)
759                    costCodeInstance = CostCode.findByName(inventoryItemPurchaseParams.costCode)
760                    if(!costCodeInstance) {
761                        costCodeInstance = new CostCode(name: inventoryItemPurchaseParams.costCode)
762                        if(!costCodeInstance.save()) {
763                            log.error "Failed to create cost code on line: " + lineNumber
764                            return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
765                        }
766                    }
767                    inventoryItemPurchaseParams.costCode = costCodeInstance
768                }
769
770                // Entered By.
771                inventoryItemPurchaseParams.enteredBy = inventoryItemPurchaseParams.enteredBy.toLowerCase()
772                personInstance = Person.findByLoginName(inventoryItemPurchaseParams.enteredBy)
773                if(!personInstance) {
774                    log.error "Entered by person not found on line: " + lineNumber
775                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
776                }
777                inventoryItemPurchaseParams.enteredBy = personInstance
778
779                // Date Entered.
780                inventoryItemPurchaseParams.dateEntered = parseInputDate(inventoryItemPurchaseParams.dateEntered)
781
782                // Debug
783                log.debug "InventoryItemPurchaseParams: "
784                log.debug inventoryItemPurchaseParams
785
786                // Save inventoryItem.
787                log.info "Creating new purchase."
788                inventoryItemPurchaseInstance = new InventoryItemPurchase(inventoryItemPurchaseParams)
789
790                if(inventoryItemPurchaseInstance.hasErrors() || !inventoryItemPurchaseInstance.save()) {
791                    log.error "Failed to create item on line: " + lineNumber
792                    log.debug inventoryItemPurchaseInstance.errors
793                    return fail(code: "inventoryItemPurchase.import.failure", args: [lineNumber, logFileLink])
794                }
795
796                if(lineNumber % 100 == 0)
797                    cleanUpGorm()
798
799                if(!result.error) nextLine()
800            } //while(line)
801
802            // Success.
803            log.info "End of file."
804            reader.close()
805            return result
806
807
808         } //end withTransaction
809    } // end importInventoryItemPurchases()
810
811    private getTemplateHeaderLine1() {
812            ["Name*", "Description", "Comment", "Units In Stock", "Reorder Point*", "Reorder Quantity", "Unit Of Measure*",
813            "Estimated Unit Price", "Currency", "Enable Reorder", "Location*", "Store*", "Site*", "Group*", "Type*",
814            "Supplier's Part Number", "Preferred Supplier", "Alternate Suppliers",
815            "Manufacturer's Part Number", "Preferred Manufacturer", "Alternate Manufacturers", "Alternate Item", "Spare For"]
816    }
817
818    private getPurchasesTemplateHeaderLine1() {
819            ["Inventory Item*", "Purchase Order Number*", "Quantity*", "Purchase Type*", "Cost Code*", "Entered By*",
820            "Date Entered*", "Order Value", "Currency", "Invoice Number"]
821    }
822
823    /**
824    * This cleans up the hibernate session and a grails map.
825    * For more info see: http://naleid.com/blog/2009/10/01/batch-import-performance-with-grails-and-mysql/
826    * The hibernate session flush is normal for hibernate.
827    * The map is apparently used by grails for domain object validation errors.
828    * A starting point for clean up is every 100 objects.
829    */
830    def cleanUpGorm() {
831        def session = sessionFactory.currentSession
832        session.flush()
833        session.clear()
834        propertyInstanceMap.get().clear()
835    }
836
837} // end class
Note: See TracBrowser for help on using the repository browser.