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

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

Add CostCode and InventoryItemPurchase domain classes with import features.
Includes some fixes to inventory imports, where manufacturer and supplier were crossed.

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.