| [546] | 1 |  | 
|---|
| [668] | 2 | import net.kromhouts.HqlBuilder | 
|---|
 | 3 |  | 
|---|
| [546] | 4 | /** | 
|---|
 | 5 | * Service class that encapsulates the business logic for Inventory Reports. | 
|---|
 | 6 | */ | 
|---|
 | 7 | class InventoryReportService { | 
|---|
 | 8 |  | 
|---|
 | 9 |     boolean transactional = false | 
|---|
 | 10 |  | 
|---|
 | 11 | //     def authService | 
|---|
 | 12 | //     def dateUtilService | 
|---|
 | 13 | //     def messageSource | 
|---|
 | 14 |  | 
|---|
 | 15 |     def g = new org.codehaus.groovy.grails.plugins.web.taglib.ApplicationTagLib() | 
|---|
 | 16 |  | 
|---|
| [550] | 17 |     // Protect java heap memory. | 
|---|
 | 18 |     // Most likely want to set paramsMax and inClauseMax to the same values. | 
|---|
| [547] | 19 |     def paramsMax = 250 | 
|---|
| [546] | 20 |  | 
|---|
| [550] | 21 |     // At least with Oracle and MSSQL db limits are 1000 (in list) and 2000 (nodes) respectively. | 
|---|
 | 22 |     // But 255 has also been mentioned on the internet as a possible limit for some databases. | 
|---|
 | 23 |     def inClauseMax = 250 | 
|---|
 | 24 |  | 
|---|
| [546] | 25 |     /** | 
|---|
 | 26 |     * Get the data for the inventory stock take overiew report. | 
|---|
 | 27 |     * @param params The request params, may contain params to specify the search. | 
|---|
 | 28 |     * @param locale The locale to use when generating result.message. | 
|---|
 | 29 |     */ | 
|---|
 | 30 |     def getStockTakeOverview(params, locale) { | 
|---|
 | 31 |         def result = [:] | 
|---|
 | 32 |  | 
|---|
 | 33 |         result.summaryOfCalculationMethod = 'This report should be used in conjunction with the `Stock Take (By Location)` Report.' | 
|---|
 | 34 |  | 
|---|
 | 35 |         def namedParams = [:] | 
|---|
 | 36 |  | 
|---|
 | 37 |         result.query = "from InventoryLocation as inventoryLocation \ | 
|---|
 | 38 |                                         left join inventoryLocation.inventoryStore as inventoryStore \ | 
|---|
 | 39 |                                         where (inventoryLocation.isActive = true \ | 
|---|
 | 40 |                                                     ) \ | 
|---|
 | 41 |                                         order by inventoryStore.name, inventoryLocation.name" | 
|---|
 | 42 |  | 
|---|
 | 43 |         result.query = "select new Map(inventoryLocation.name as location, inventoryStore.name as store) " + result.query | 
|---|
 | 44 |         result.queryResult = InventoryLocation.executeQuery(result.query, namedParams) | 
|---|
 | 45 |         result.inventoryLocationCount = result.queryResult.size() | 
|---|
 | 46 |  | 
|---|
 | 47 |         result.inventoryLocationList = result.queryResult | 
|---|
 | 48 |  | 
|---|
 | 49 |         // Success. | 
|---|
 | 50 |         return result | 
|---|
 | 51 |  | 
|---|
 | 52 |     } // getStockTakeOverview() | 
|---|
 | 53 |  | 
|---|
 | 54 |     /** | 
|---|
 | 55 |     * Get the data for the inventory stock take by location report. | 
|---|
 | 56 |     * @param params The request params, may contain params to specify the search. | 
|---|
 | 57 |     * @param locale The locale to use when generating result.message. | 
|---|
 | 58 |     */ | 
|---|
 | 59 |     def getStockTakeByLocation(params, locale) { | 
|---|
 | 60 |         def result = [:] | 
|---|
 | 61 |  | 
|---|
| [550] | 62 |         result.inventoryItemList = [] | 
|---|
 | 63 |         result.inventoryItemCount = 0 | 
|---|
 | 64 |         result.locationCount = 0 | 
|---|
 | 65 |         result.errorMessage = null | 
|---|
| [546] | 66 |         result.summaryOfCalculationMethod = 'This report should be used in conjunction with the `Stock Take (Overview)` Report.' | 
|---|
 | 67 |  | 
|---|
| [550] | 68 |         def fail = { Map m -> | 
|---|
 | 69 |             result.error = [ code: m.code, args: m.args ] | 
|---|
 | 70 |             result.errorMessage = g.message(result.error) | 
|---|
 | 71 |             result.locations = '' | 
|---|
 | 72 |             return result | 
|---|
 | 73 |         } | 
|---|
 | 74 |  | 
|---|
 | 75 |         def paginateParams = [:] | 
|---|
 | 76 |         paginateParams.max = Math.min(params?.max?.toInteger() ?: paramsMax, paramsMax) | 
|---|
 | 77 |  | 
|---|
 | 78 |         def namedParams = [:] | 
|---|
 | 79 |         namedParams.locationList = [] | 
|---|
 | 80 |  | 
|---|
 | 81 |         // Sanitise the user supplied locations string and convert to a list. | 
|---|
| [546] | 82 |         result.locations = params.locationString.trim() | 
|---|
 | 83 |         if(result.locations.startsWith('e.g:')) | 
|---|
 | 84 |             result.locations = result.locations.split(':')[-1].trim() | 
|---|
 | 85 |         result.locations = result.locations.split(',') | 
|---|
 | 86 |         result.locations = result.locations.collect {it.trim()} | 
|---|
 | 87 |  | 
|---|
| [550] | 88 |         // Fill namedParams.locationList. | 
|---|
 | 89 |         result.locations.each() { location -> | 
|---|
 | 90 |             if(namedParams.locationList.size() < paramsMax) { | 
|---|
 | 91 |                 // paramsMax+1 to ensure the too many locations check bellow is triggered. | 
|---|
 | 92 |                 namedParams.locationList += InventoryLocation.findAllByNameIlike(location, [max: paramsMax+1]) | 
|---|
| [546] | 93 |             } | 
|---|
| [550] | 94 |             namedParams.locationList.unique() | 
|---|
| [546] | 95 |         } | 
|---|
 | 96 |  | 
|---|
| [550] | 97 |         // Return the actual locations as a string, along with a count. | 
|---|
 | 98 |         result.locationCount = namedParams.locationList.size() | 
|---|
 | 99 |         if(result.locationCount > 0) { | 
|---|
| [649] | 100 |             namedParams.locationList.sort { p1, p2 -> p1.name.compareToIgnoreCase(p2.name) } | 
|---|
| [550] | 101 |             result.locations = namedParams.locationList.toString()[1..-2] | 
|---|
 | 102 |         } | 
|---|
| [547] | 103 |         else | 
|---|
 | 104 |             result.locations = g.message(code: 'default.none.text') | 
|---|
 | 105 |  | 
|---|
| [550] | 106 |         // Exit if empty location list. | 
|---|
 | 107 |         // Protects against HQL unexpected end of subtree exception with an empty list. | 
|---|
 | 108 |         if(namedParams.locationList.isEmpty()) | 
|---|
 | 109 |             return fail(code:'report.error.no.locations.found') | 
|---|
 | 110 |  | 
|---|
 | 111 |         // Exit if IN clause list too big. | 
|---|
 | 112 |         if(namedParams.locationList.size() > inClauseMax) | 
|---|
 | 113 |             return fail(code:'report.error.too.many.locations', args: [inClauseMax]) | 
|---|
 | 114 |  | 
|---|
| [547] | 115 |         // Inventory List. | 
|---|
 | 116 |         result.inventoryListQuery = "from InventoryItem as inventoryItem \ | 
|---|
 | 117 |                                                         left join inventoryItem.inventoryLocation as inventoryLocation \ | 
|---|
 | 118 |                                                         where (inventoryItem.isActive = true \ | 
|---|
 | 119 |                                                                     and  inventoryItem.inventoryLocation in (:locationList) \ | 
|---|
 | 120 |                                                                     ) " | 
|---|
 | 121 |  | 
|---|
 | 122 |         result.inventoryCountQuery = "select count(distinct inventoryItem) " + result.inventoryListQuery | 
|---|
 | 123 |         result.inventoryItemCount = InventoryItem.executeQuery(result.inventoryCountQuery, namedParams)[0] | 
|---|
 | 124 |  | 
|---|
 | 125 |         // Exit if too many results. | 
|---|
| [550] | 126 |         if(result.inventoryItemCount > paramsMax)  | 
|---|
 | 127 |             return fail(code:'report.error.too.many.results', args: [paramsMax]) | 
|---|
| [547] | 128 |  | 
|---|
 | 129 |         result.inventoryListQuery = "select distinct inventoryItem " + result.inventoryListQuery | 
|---|
 | 130 |         def inventoryList = InventoryItem.executeQuery(result.inventoryListQuery, namedParams, paginateParams) | 
|---|
 | 131 |  | 
|---|
 | 132 |         // Reset namedParams for next query. | 
|---|
 | 133 |         namedParams = [:] | 
|---|
 | 134 |         namedParams.inventoryList = inventoryList | 
|---|
 | 135 |  | 
|---|
| [550] | 136 |         // Exit if empty inventory list. | 
|---|
 | 137 |         // Protects against HQL unexpected end of subtree exception with an empty list. | 
|---|
 | 138 |         if(namedParams.inventoryList.isEmpty()) | 
|---|
 | 139 |             return fail(code:'report.error.no.inventory.items.found') | 
|---|
 | 140 |  | 
|---|
 | 141 |         // Exit if inventory list too big. | 
|---|
 | 142 |         if(namedParams.inventoryList.size() > inClauseMax) | 
|---|
 | 143 |             return fail(code:'report.error.too.many.inventory.items', args: [inClauseMax]) | 
|---|
 | 144 |  | 
|---|
 | 145 |         // Note: HQL docs advise 'not using fetch aliases in where clause (or any other clause)'. | 
|---|
| [546] | 146 |         // Access is via the parent object, however that does not work for the order by clause in this case. | 
|---|
 | 147 |         result.query = "from InventoryItem as inventoryItem \ | 
|---|
 | 148 |                                         left join fetch inventoryItem.unitOfMeasure as unitOfMeasure \ | 
|---|
 | 149 |                                         left join fetch inventoryItem.inventoryLocation as inventoryLocation \ | 
|---|
 | 150 |                                         left join fetch inventoryLocation.inventoryStore as inventoryStore \ | 
|---|
 | 151 |                                         left join fetch inventoryItem.picture as picture \ | 
|---|
 | 152 |                                         left join fetch picture.images as Image \ | 
|---|
| [547] | 153 |                                         where (inventoryItem in (:inventoryList) \ | 
|---|
| [546] | 154 |                                                     ) \ | 
|---|
 | 155 |                                         order by inventoryStore.name, inventoryLocation.name" | 
|---|
 | 156 |  | 
|---|
| [564] | 157 |         // MSSQL will not do distinct here, for some reason it tries to compare the image data type! | 
|---|
 | 158 |         result.query = "select inventoryItem " + result.query | 
|---|
| [547] | 159 |         result.inventoryItemList = InventoryItem.executeQuery(result.query, namedParams, paginateParams) | 
|---|
| [546] | 160 |  | 
|---|
| [564] | 161 |         result.inventoryItemList.unique() | 
|---|
 | 162 |  | 
|---|
| [546] | 163 |         // Success. | 
|---|
 | 164 |         return result | 
|---|
 | 165 |  | 
|---|
 | 166 |     } // getStockTakeOverview() | 
|---|
 | 167 |  | 
|---|
| [668] | 168 |     /** | 
|---|
| [671] | 169 |     * Get the data for the inventory value with detail. | 
|---|
| [668] | 170 |     * @param params The request params, may contain params to specify the search. | 
|---|
 | 171 |     * @param locale The locale to use when generating result.message. | 
|---|
 | 172 |     */ | 
|---|
| [671] | 173 |     def getInventoryValueDetailed(params, locale) { | 
|---|
| [668] | 174 |         def result = [:] | 
|---|
 | 175 |  | 
|---|
 | 176 |         result.inventoryItemList = [] | 
|---|
 | 177 |         result.inventoryItemCount = 0 | 
|---|
 | 178 |         result.inventoryItemTotalValue = new BigDecimal(0) | 
|---|
 | 179 |         result.currency = null | 
|---|
 | 180 |         result.errorMessage = null | 
|---|
 | 181 |         result.summaryOfCalculationMethod = "This report does not convert between different currency.\n" | 
|---|
 | 182 |         result.summaryOfCalculationMethod += "Therefore all item's are checked to ensure that currency is the same." | 
|---|
 | 183 |  | 
|---|
 | 184 |         result.site = Site.get(params.site.id.toLong()) | 
|---|
 | 185 |  | 
|---|
| [690] | 186 |         result.inventoryTypes = [] | 
|---|
 | 187 |         if(params.inventoryTypes instanceof String) | 
|---|
 | 188 |             result.inventoryTypes << InventoryType.get(params.inventoryTypes.toInteger()) | 
|---|
 | 189 |         else if(params.inventoryTypes) | 
|---|
| [670] | 190 |             result.inventoryTypes = params.inventoryTypes.collect { InventoryType.get(it.toInteger()) } | 
|---|
 | 191 |         else | 
|---|
 | 192 |             result.inventoryTypes = InventoryType.findAllByIsActive(true, [max:254, sort:'name']) | 
|---|
 | 193 |  | 
|---|
| [690] | 194 |         result.inventoryGroups = [] | 
|---|
 | 195 |         if(params.inventoryGroups instanceof String) | 
|---|
 | 196 |             result.inventoryGroups << InventoryGroup.get(params.inventoryGroups.toInteger()) | 
|---|
 | 197 |         else if(params.inventoryGroups) | 
|---|
| [670] | 198 |             result.inventoryGroups = params.inventoryGroups.collect { InventoryGroup.get(it.toInteger()) } | 
|---|
 | 199 |         else | 
|---|
 | 200 |             result.inventoryGroups = InventoryGroup.findAllByIsActive(true, [max:254, sort:'name']) | 
|---|
 | 201 |  | 
|---|
| [668] | 202 |         def fail = { Map m -> | 
|---|
 | 203 |             result.error = [ code: m.code, args: m.args ] | 
|---|
 | 204 |             result.errorMessage = g.message(result.error) | 
|---|
 | 205 |             result.currency = null | 
|---|
 | 206 |             result.inventoryItemTotalValue = new BigDecimal(0) | 
|---|
 | 207 |             return result | 
|---|
 | 208 |         } | 
|---|
 | 209 |  | 
|---|
 | 210 |         def q = new HqlBuilder().query { | 
|---|
 | 211 |             select 'distinct inventoryItem' | 
|---|
 | 212 |             from 'InventoryItem as inventoryItem', | 
|---|
 | 213 |                     'left join fetch inventoryItem.inventoryLocation as inventoryLocation', | 
|---|
 | 214 |                     'left join fetch inventoryLocation.inventoryStore as inventoryStore', | 
|---|
| [669] | 215 |                     'left join fetch inventoryItem.unitOfMeasure as unitOfMeasure' | 
|---|
| [668] | 216 |             where 'inventoryItem.isActive = true' | 
|---|
 | 217 |                 namedParams.siteId = result.site.id | 
|---|
 | 218 |                 and 'inventoryStore.site.id = :siteId' | 
|---|
 | 219 |                 if(result.inventoryTypes) { | 
|---|
 | 220 |                     namedParams.inventoryTypeIds = result.inventoryTypes.collect {it.id} | 
|---|
 | 221 |                     and 'inventoryItem.inventoryType.id in(:inventoryTypeIds)' | 
|---|
 | 222 |                 } | 
|---|
 | 223 |                 if(result.inventoryGroups) { | 
|---|
 | 224 |                     namedParams.inventoryGroupIds = result.inventoryGroups.collect {it.id} | 
|---|
 | 225 |                     and 'inventoryItem.inventoryGroup.id in(:inventoryGroupIds)' | 
|---|
 | 226 |                 } | 
|---|
 | 227 |             order 'by inventoryItem.name asc' | 
|---|
 | 228 |         } | 
|---|
 | 229 |  | 
|---|
 | 230 |         result.inventoryItemList = InventoryItem.executeQuery(q.query, q.namedParams) | 
|---|
 | 231 |         result.inventoryItemCount = result.inventoryItemList.size() | 
|---|
 | 232 |         result.currency = result.inventoryItemList[0]?.estimatedUnitPriceCurrency | 
|---|
 | 233 |  | 
|---|
 | 234 |         for(inventoryItem in result.inventoryItemList) { | 
|---|
 | 235 |             // Check all currency is the same. | 
|---|
 | 236 |             if(result.currency != inventoryItem.estimatedUnitPriceCurrency) { | 
|---|
 | 237 |                 fail(code:'report.error.multiple.currency.found') // No return, populate errors but continue report. | 
|---|
 | 238 |                 break | 
|---|
 | 239 |             } | 
|---|
| [669] | 240 |             if(inventoryItem.estimatedUnitPriceAmount && inventoryItem.unitsInStock) // Some items have null estimatedUnitPriceAmount. | 
|---|
 | 241 |                 result.inventoryItemTotalValue += inventoryItem.estimatedUnitPriceAmount * inventoryItem.unitsInStock | 
|---|
| [668] | 242 |         } // for | 
|---|
 | 243 |  | 
|---|
| [677] | 244 |         /// @todo: This protects against a bug in the report layout, remove when solved. | 
|---|
 | 245 |         if(result.inventoryTypes.size() > 4) | 
|---|
 | 246 |             result.inventoryTypes = [[name:'More than 4']] | 
|---|
 | 247 |  | 
|---|
 | 248 |         if(result.inventoryGroups.size() > 4) | 
|---|
 | 249 |             result.inventoryGroups =  [[name:'More than 4']] | 
|---|
 | 250 |  | 
|---|
| [668] | 251 |         // Success. | 
|---|
 | 252 |         return result | 
|---|
 | 253 |  | 
|---|
| [671] | 254 |     } // getInventoryValueDetailed() | 
|---|
| [668] | 255 |  | 
|---|
| [676] | 256 |     /** | 
|---|
 | 257 |     * Get the data for the inventory overiew value. | 
|---|
 | 258 |     * @param params The request params, may contain params to specify the search. | 
|---|
 | 259 |     * @param locale The locale to use when generating result.message. | 
|---|
 | 260 |     */ | 
|---|
 | 261 |     def getInventoryValueOverview(params, locale) { | 
|---|
 | 262 |         def result = [:] | 
|---|
 | 263 |  | 
|---|
 | 264 |         result.inventoryItemCount = 0 | 
|---|
 | 265 |         result.inventoryItemTotalValue = new BigDecimal(0) | 
|---|
 | 266 |         result.currency = null | 
|---|
 | 267 |         result.errorMessage = null | 
|---|
 | 268 |         result.summaryOfCalculationMethod = "This report does not convert between different currency.\n" | 
|---|
 | 269 |         result.summaryOfCalculationMethod += "Therefore all item's are checked to ensure that currency is the same." | 
|---|
 | 270 |  | 
|---|
 | 271 |         result.site = Site.get(params.site.id.toLong()) | 
|---|
 | 272 |  | 
|---|
| [690] | 273 |         result.inventoryTypes = [] | 
|---|
 | 274 |         if(params.inventoryTypes instanceof String) | 
|---|
 | 275 |             result.inventoryTypes << InventoryType.get(params.inventoryTypes.toInteger()) | 
|---|
 | 276 |         else if(params.inventoryTypes) | 
|---|
| [676] | 277 |             result.inventoryTypes = params.inventoryTypes.collect { InventoryType.get(it.toInteger()) } | 
|---|
 | 278 |         else | 
|---|
 | 279 |             result.inventoryTypes = InventoryType.findAllByIsActive(true, [max:254, sort:'name']) | 
|---|
 | 280 |  | 
|---|
| [690] | 281 |         result.inventoryGroups = [] | 
|---|
 | 282 |         if(params.inventoryGroups instanceof String) | 
|---|
 | 283 |             result.inventoryGroups << InventoryGroup.get(params.inventoryGroups.toInteger()) | 
|---|
 | 284 |         else if(params.inventoryGroups) | 
|---|
| [676] | 285 |             result.inventoryGroups = params.inventoryGroups.collect { InventoryGroup.get(it.toInteger()) } | 
|---|
 | 286 |         else | 
|---|
 | 287 |             result.inventoryGroups = InventoryGroup.findAllByIsActive(true, [max:254, sort:'name']) | 
|---|
 | 288 |  | 
|---|
 | 289 |         def fail = { Map m -> | 
|---|
 | 290 |             result.error = [ code: m.code, args: m.args ] | 
|---|
 | 291 |             result.errorMessage = g.message(result.error) | 
|---|
 | 292 |             result.currency = null | 
|---|
 | 293 |             //result.inventoryItemTotalValue = new BigDecimal(0) | 
|---|
 | 294 |             return result | 
|---|
 | 295 |         } | 
|---|
 | 296 |  | 
|---|
 | 297 |         // Base query. | 
|---|
 | 298 |         def q = new HqlBuilder().query { | 
|---|
 | 299 |             select '' | 
|---|
 | 300 |             from 'InventoryItem as inventoryItem', | 
|---|
 | 301 |                     'left join inventoryItem.inventoryLocation as inventoryLocation', | 
|---|
 | 302 |                     'left join inventoryLocation.inventoryStore as inventoryStore' | 
|---|
 | 303 |             where 'inventoryItem.isActive = true' | 
|---|
 | 304 |                 namedParams.siteId = result.site.id | 
|---|
 | 305 |                 and 'inventoryStore.site.id = :siteId' | 
|---|
| [677] | 306 |                 namedParams.groupIds = result.inventoryGroups.collect {it.id} | 
|---|
 | 307 |                 and 'inventoryItem.inventoryGroup.id in(:groupIds)' | 
|---|
 | 308 |                 namedParams.typeIds = result.inventoryTypes.collect {it.id} | 
|---|
 | 309 |                 and 'inventoryItem.inventoryType.id in(:typeIds)' | 
|---|
| [676] | 310 |         } | 
|---|
 | 311 |         def baseWhereLogic = new ArrayList(q.whereClauseTerms) | 
|---|
 | 312 |  | 
|---|
 | 313 |         // Count the inventoryItems. | 
|---|
 | 314 |         q.select = 'count(distinct inventoryItem)' | 
|---|
| [677] | 315 |         result.inventoryItemCount = InventoryItem.executeQuery(q.query, q.namedParams)[0] | 
|---|
| [676] | 316 |  | 
|---|
 | 317 |         // Get the first currency found on this site. | 
|---|
 | 318 |         q.paginateParams.max = 1 | 
|---|
 | 319 |         q.select = 'inventoryItem.estimatedUnitPriceCurrency' | 
|---|
 | 320 |         result.currency = InventoryItem.executeQuery(q.query, q.namedParams, q.paginateParams)[0] | 
|---|
 | 321 |  | 
|---|
 | 322 |         // Count the distinct currency found. | 
|---|
 | 323 |         q.select = 'count(distinct inventoryItem.estimatedUnitPriceCurrency)' | 
|---|
 | 324 |         def currencyCount = InventoryItem.executeQuery(q.query, q.namedParams)[0] | 
|---|
 | 325 |  | 
|---|
 | 326 |         // Get total value. | 
|---|
 | 327 |         q.select = 'sum (inventoryItem.estimatedUnitPriceAmount * inventoryItem.unitsInStock)' | 
|---|
 | 328 |         result.inventoryItemTotalValue = InventoryItem.executeQuery(q.query, q.namedParams)[0] | 
|---|
 | 329 |  | 
|---|
 | 330 |         // Get values for each group. | 
|---|
 | 331 |         def tempGroups = [] | 
|---|
 | 332 |         result.inventoryGroups.each() { group -> | 
|---|
| [677] | 333 |             q.namedParams.groupIds = [group.id] | 
|---|
| [676] | 334 |             def groupValue = InventoryItem.executeQuery(q.query, q.namedParams)[0] ?: 0 | 
|---|
 | 335 |             tempGroups << [name: group.name, value: groupValue] | 
|---|
 | 336 |         } | 
|---|
| [677] | 337 |         q.namedParams.groupIds = result.inventoryGroups.collect {it.id} // reset. | 
|---|
| [676] | 338 |         result.inventoryGroups = tempGroups | 
|---|
 | 339 |  | 
|---|
 | 340 |         // Get values for each type. | 
|---|
 | 341 |         def tempTypes = [] | 
|---|
 | 342 |         result.inventoryTypes.each() { type -> | 
|---|
| [677] | 343 |             q.namedParams.typeIds = [type.id] | 
|---|
| [676] | 344 |             def typeValue = InventoryItem.executeQuery(q.query, q.namedParams)[0] ?: 0 | 
|---|
 | 345 |             tempTypes << [name: type.name, value: typeValue] | 
|---|
 | 346 |         } | 
|---|
 | 347 |         result.inventoryTypes = tempTypes | 
|---|
 | 348 |  | 
|---|
| [677] | 349 |         /// @todo: This protects against a bug in the report layout, remove when solved. | 
|---|
 | 350 |         if(result.inventoryTypes.size() > 4) | 
|---|
 | 351 |             result.inventoryTypes = [[name:'More than 4', value: 0]] | 
|---|
 | 352 |  | 
|---|
 | 353 |         if(result.inventoryGroups.size() > 4) | 
|---|
 | 354 |             result.inventoryGroups =  [[name:'More than 4', value: 0]] | 
|---|
 | 355 |  | 
|---|
 | 356 |         // Check all currency is the same. | 
|---|
| [690] | 357 |         if(currencyCount > 1) | 
|---|
| [676] | 358 |             fail(code:'report.error.multiple.currency.found') // No return, populate errors but continue report. | 
|---|
 | 359 |  | 
|---|
 | 360 |         // Success. | 
|---|
 | 361 |         return result | 
|---|
 | 362 |  | 
|---|
 | 363 |     } // getInventoryValueOverview() | 
|---|
 | 364 |  | 
|---|
| [546] | 365 | } // end class | 
|---|