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

Last change on this file since 489 was 462, checked in by gav, 15 years ago

Indicate in header that purchase order number is required during import.

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