| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| // This script can be used for testing right now but it should not be used for production because |
| // it does not work for marketing packages and more importantly, if there is a new product without any |
| // inventory items, it will not show up on the inventory report even if it had a ProductFacility record. |
| // These problems need to be addressed before this can be used in production. |
| |
| // This script counts the inventory in the facility using a complex and |
| // pretty efficient dynamic view entity. |
| // However, since the quantities are not computed using the inventory |
| // services (getInventoryAvailableByFacility and getMktgPackagesAvailable) |
| // there are some limitations: the virtual inventory of marketing packages |
| // is not computed; you can use the ViewFacilityInventoryByProduct.groovy if you |
| // need it (but it is slower than this one). |
| |
| import org.ofbiz.base.util.Debug |
| import org.ofbiz.base.util.ObjectType |
| import org.ofbiz.entity.* |
| import org.ofbiz.entity.condition.* |
| import org.ofbiz.entity.transaction.* |
| import org.ofbiz.entity.util.* |
| import org.ofbiz.entity.model.DynamicViewEntity |
| import org.ofbiz.entity.model.ModelKeyMap |
| import org.ofbiz.entity.model.ModelViewEntity.ComplexAlias |
| import org.ofbiz.entity.model.ModelViewEntity.ComplexAliasField |
| import org.ofbiz.entity.model.ModelViewEntity.ComplexAliasMember |
| import org.ofbiz.product.inventory.* |
| |
| action = request.getParameter("action"); |
| |
| searchParameterString = ""; |
| searchParameterString = "action=Y&facilityId=" + facilityId; |
| |
| offsetQOH = -1; |
| offsetATP = -1; |
| hasOffsetQOH = false; |
| hasOffsetATP = false; |
| |
| EntityListIterator prodsEli = null; |
| rows = [] as ArrayList; |
| |
| if (action) { |
| // ------------------------------ |
| prodView = new DynamicViewEntity(); |
| atpDiffComplexAlias = new ComplexAlias("-"); |
| |
| conditionMap = [facilityId : facilityId]; |
| |
| if (offsetQOHQty) { |
| try { |
| offsetQOH = Integer.parseInt(offsetQOHQty); |
| hasOffsetQOH = true; |
| searchParameterString = searchParameterString + "&offsetQOHQty=" + offsetQOH; |
| } catch (NumberFormatException nfe) { |
| } |
| } |
| if (offsetATPQty) { |
| try { |
| offsetATP = Integer.parseInt(offsetATPQty); |
| hasOffsetATP = true; |
| searchParameterString = searchParameterString + "&offsetATPQty=" + offsetATP; |
| } catch (NumberFormatException nfe) { |
| } |
| } |
| |
| prodView.addMemberEntity("PRFA", "ProductFacility"); |
| prodView.addAlias("PRFA", "productId", null, null, null, Boolean.TRUE, null); |
| prodView.addAlias("PRFA", "minimumStock", null, null, null, Boolean.TRUE, null); |
| prodView.addAlias("PRFA", "reorderQuantity", null, null, null, Boolean.TRUE, null); |
| prodView.addAlias("PRFA", "daysToShip", null, null, null, Boolean.TRUE, null); |
| prodView.addAlias("PRFA", "facilityId", null, null, null, Boolean.TRUE, null); |
| |
| prodView.addMemberEntity("PROD", "Product"); |
| prodView.addViewLink("PROD", "PRFA", Boolean.FALSE, ModelKeyMap.makeKeyMapList("productId")); |
| prodView.addAlias("PROD", "internalName", null, null, null, Boolean.TRUE, null); |
| prodView.addAlias("PROD", "isVirtual", null, null, null, Boolean.TRUE, null); |
| prodView.addAlias("PROD", "salesDiscontinuationDate", null, null, null, Boolean.TRUE, null); |
| if (productTypeId) { |
| prodView.addAlias("PROD", "productTypeId", null, null, null, Boolean.TRUE, null); |
| conditionMap.productTypeId = productTypeId; |
| searchParameterString = searchParameterString + "&productTypeId=" + productTypeId; |
| } |
| |
| prodView.addMemberEntity("IITE", "InventoryItem"); |
| prodView.addViewLink("PRFA", "IITE", Boolean.FALSE, ModelKeyMap.makeKeyMapList("productId", "productId", "facilityId", "facilityId")); |
| prodView.addAlias("IITE", "totalQuantityOnHandTotal", "quantityOnHandTotal", null, null, null, "sum"); |
| prodView.addAlias("IITE", "totalAvailableToPromiseTotal", "availableToPromiseTotal", null, null, null, "sum"); |
| qohDiffComplexAlias = new ComplexAlias("-"); |
| qohDiffComplexAlias.addComplexAliasMember(new ComplexAliasField("IITE", "quantityOnHandTotal", null, "sum")); |
| qohDiffComplexAlias.addComplexAliasMember(new ComplexAliasField("PRFA", "minimumStock", null, null)); |
| prodView.addAlias(null, "offsetQOHQtyAvailable", null, null, null, null, null, qohDiffComplexAlias); |
| atpDiffComplexAlias = new ComplexAlias("-"); |
| atpDiffComplexAlias.addComplexAliasMember(new ComplexAliasField("IITE", "availableToPromiseTotal", null, "sum")); |
| atpDiffComplexAlias.addComplexAliasMember(new ComplexAliasField("PRFA", "minimumStock", null, null)); |
| prodView.addAlias(null, "offsetATPQtyAvailable", null, null, null, null, null, atpDiffComplexAlias); |
| |
| if (searchInProductCategoryId) { |
| prodView.addMemberEntity("PRCA", "ProductCategoryMember"); |
| prodView.addViewLink("PRFA", "PRCA", Boolean.FALSE, ModelKeyMap.makeKeyMapList("productId")); |
| prodView.addAlias("PRCA", "productCategoryId", null, null, null, Boolean.TRUE, null); |
| conditionMap.productCategoryId = searchInProductCategoryId; |
| searchParameterString = searchParameterString + "&searchInProductCategoryId=" + searchInProductCategoryId; |
| } |
| |
| if (productSupplierId) { |
| prodView.addMemberEntity("SPPR", "SupplierProduct"); |
| prodView.addViewLink("PRFA", "SPPR", Boolean.FALSE, ModelKeyMap.makeKeyMapList("productId")); |
| prodView.addAlias("SPPR", "partyId", null, null, null, Boolean.TRUE, null); |
| conditionMap.partyId = productSupplierId; |
| searchParameterString = searchParameterString + "&productSupplierId=" + productSupplierId; |
| } |
| |
| // set distinct on so we only get one row per product |
| findOpts = new EntityFindOptions(true, EntityFindOptions.TYPE_SCROLL_INSENSITIVE, EntityFindOptions.CONCUR_READ_ONLY, true); |
| searchCondition = EntityCondition.makeCondition(conditionMap, EntityOperator.AND); |
| notVirtualCondition = EntityCondition.makeCondition(EntityCondition.makeCondition("isVirtual", EntityOperator.EQUALS, null), |
| EntityOperator.OR, |
| EntityCondition.makeCondition("isVirtual", EntityOperator.NOT_EQUAL, "Y")); |
| |
| whereConditionsList = [searchCondition, notVirtualCondition]; |
| // add the discontinuation date condition |
| if (productsSoldThruTimestamp) { |
| discontinuationDateCondition = EntityCondition.makeCondition( |
| [ |
| EntityCondition.makeCondition("salesDiscontinuationDate", EntityOperator.EQUALS, null), |
| EntityCondition.makeCondition("salesDiscontinuationDate", EntityOperator.GREATER_THAN,productsSoldThruTimestamp) |
| ], |
| EntityOperator.OR); |
| whereConditionsList.add(discontinuationDateCondition); |
| searchParameterString = searchParameterString + "&productsSoldThruTimestamp=" + productsSoldThruTimestamp; |
| } |
| |
| // add search on internal name |
| if (internalName) { |
| whereConditionsList.add(EntityCondition.makeCondition("internalName", EntityOperator.LIKE, "%" + internalName + "%")); |
| searchParameterString = searchParameterString + "&internalName=" + internalName; |
| } |
| |
| // add search on productId |
| if (productId) { |
| whereConditionsList.add(EntityCondition.makeCondition("productId", EntityOperator.LIKE, productId + "%")); |
| searchParameterString = searchParameterString + "&productId=" + productId; |
| } |
| |
| orderBy = []; |
| if (hasOffsetATP) { |
| orderBy.add("offsetATPQtyAvailable"); |
| } |
| if (hasOffsetQOH) { |
| orderBy.add("offsetQOHQtyAvailable"); |
| } |
| orderBy.add("productId"); |
| |
| // If the user has specified a number of months over which to sum usage quantities, define the correct timestamp |
| checkTime = null; |
| monthsInPastLimitStr = request.getParameter("monthsInPastLimit"); |
| if (monthsInPastLimitStr) { |
| try { |
| monthsInPastLimit = Integer.parseInt(monthsInPastLimitStr); |
| cal = UtilDateTime.toCalendar(null); |
| cal.add(Calendar.MONTH, 0 - monthsInPastLimit); |
| checkTime = UtilDateTime.toTimestamp(cal.getTime()); |
| searchParameterString += "&monthsInPastLimit=" + monthsInPastLimitStr; |
| } catch (Exception e) { |
| // Ignore |
| } |
| } |
| |
| if (checkTime) { |
| |
| // Construct a dynamic view entity to search against for sales usage quantities |
| salesUsageViewEntity = new DynamicViewEntity(); |
| salesUsageViewEntity.addMemberEntity("OI", "OrderItem"); |
| salesUsageViewEntity.addMemberEntity("OH", "OrderHeader"); |
| salesUsageViewEntity.addMemberEntity("ItIss", "ItemIssuance"); |
| salesUsageViewEntity.addMemberEntity("InvIt", "InventoryItem"); |
| salesUsageViewEntity.addViewLink("OI", "OH", false, ModelKeyMap.makeKeyMapList("orderId")); |
| salesUsageViewEntity.addViewLink("OI", "ItIss", false, ModelKeyMap.makeKeyMapList("orderId", "orderId", "orderItemSeqId", "orderItemSeqId")); |
| salesUsageViewEntity.addViewLink("ItIss", "InvIt", false, ModelKeyMap.makeKeyMapList("inventoryItemId")); |
| salesUsageViewEntity.addAlias("OI", "productId"); |
| salesUsageViewEntity.addAlias("OH", "statusId"); |
| salesUsageViewEntity.addAlias("OH", "orderTypeId"); |
| salesUsageViewEntity.addAlias("OH", "orderDate"); |
| salesUsageViewEntity.addAlias("ItIss", "inventoryItemId"); |
| salesUsageViewEntity.addAlias("ItIss", "quantity"); |
| salesUsageViewEntity.addAlias("InvIt", "facilityId"); |
| |
| // Construct a dynamic view entity to search against for production usage quantities |
| productionUsageViewEntity = new DynamicViewEntity(); |
| productionUsageViewEntity.addMemberEntity("WEIA", "WorkEffortInventoryAssign"); |
| productionUsageViewEntity.addMemberEntity("WE", "WorkEffort"); |
| productionUsageViewEntity.addMemberEntity("II", "InventoryItem"); |
| productionUsageViewEntity.addViewLink("WEIA", "WE", false, ModelKeyMap.makeKeyMapList("workEffortId")); |
| productionUsageViewEntity.addViewLink("WEIA", "II", false, ModelKeyMap.makeKeyMapList("inventoryItemId")); |
| productionUsageViewEntity.addAlias("WEIA", "quantity"); |
| productionUsageViewEntity.addAlias("WE", "actualCompletionDate"); |
| productionUsageViewEntity.addAlias("WE", "workEffortTypeId"); |
| productionUsageViewEntity.addAlias("II", "facilityId"); |
| productionUsageViewEntity.addAlias("II", "productId"); |
| } |
| |
| whereCondition = EntityCondition.makeCondition(whereConditionsList, EntityOperator.AND); |
| |
| beganTransaction = false; |
| List prods = null; |
| try { |
| beganTransaction = TransactionUtil.begin(); |
| |
| // get the indexes for the partial list |
| lowIndex = ((viewIndex.intValue() * viewSize.intValue()) + 1); |
| highIndex = (viewIndex.intValue() + 1) * viewSize.intValue(); |
| findOpts.setMaxRows(highIndex); |
| prodsEli = delegator.findListIteratorByCondition(prodView, whereCondition, null, null, orderBy, findOpts); |
| |
| // get the partial list for this page |
| prods = prodsEli.getPartialList(lowIndex, highIndex); |
| prodsIt = prods.iterator(); |
| while (prodsIt) { |
| oneProd = prodsIt.next(); |
| offsetQOHQtyAvailable = oneProd.getBigDecimal("offsetQOHQtyAvailable"); |
| offsetATPQtyAvailable = oneProd.getBigDecimal("offsetATPQtyAvailable"); |
| if (hasOffsetATP) { |
| if (offsetATPQtyAvailable && offsetATPQtyAvailable.doubleValue() > offsetATP) { |
| break; |
| } |
| } |
| if (hasOffsetQOH) { |
| if (offsetQOHQtyAvailable && offsetQOHQtyAvailable.doubleValue() > offsetQOH) { |
| break; |
| } |
| } |
| |
| oneInventory = [:]; |
| oneInventory.productId = oneProd.productId; |
| oneInventory.minimumStock = oneProd.getBigDecimal("minimumStock"); |
| oneInventory.reorderQuantity = oneProd.getBigDecimal("reorderQuantity"); |
| oneInventory.daysToShip = oneProd.getString("daysToShip"); |
| oneInventory.totalQuantityOnHand = oneProd.totalQuantityOnHandTotal; |
| oneInventory.totalAvailableToPromise = oneProd.totalAvailableToPromiseTotal; |
| oneInventory.offsetQOHQtyAvailable = offsetQOHQtyAvailable; |
| oneInventory.offsetATPQtyAvailable = offsetATPQtyAvailable; |
| oneInventory.quantityOnOrder = InventoryWorker.getOutstandingPurchasedQuantity(oneProd.productId, delegator); |
| |
| |
| if (checkTime) { |
| |
| // Make a query against the sales usage view entity |
| salesUsageIt = delegator.findListIteratorByCondition(salesUsageViewEntity, |
| EntityCondition.makeCondition( |
| [EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, facilityId), |
| EntityCondition.makeCondition("productId", EntityOperator.EQUALS, oneProd.productId), |
| EntityCondition.makeCondition("statusId", EntityOperator.IN, ['ORDER_COMPLETED', 'ORDER_APPROVED', 'ORDER_HELD']), |
| EntityCondition.makeCondition("orderTypeId", EntityOperator.EQUALS, "SALES_ORDER"), |
| EntityCondition.makeCondition("orderDate", EntityOperator.GREATER_THAN_EQUAL_TO, checkTime) |
| ], |
| EntityOperator.AND), null, null, null, null); |
| |
| // Sum the sales usage quantities found |
| salesUsageQuantity = 0; |
| salesUsageIt.each { salesUsageItem -> |
| if (salesUsageItem.quantity) { |
| try { |
| salesUsageQuantity += salesUsageItem.getDouble("quantity").doubleValue(); |
| } catch (Exception e) { |
| // Ignore |
| } |
| } |
| } |
| salesUsageIt.close(); |
| |
| // Make a query against the production usage view entity |
| productionUsageIt = delegator.findListIteratorByCondition(productionUsageViewEntity, |
| EntityCondition.makeCondition( |
| [EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, facilityId), |
| EntityCondition.makeCondition("productId", EntityOperator.EQUALS, oneProd.productId), |
| EntityCondition.makeCondition("workEffortTypeId", EntityOperator.EQUALS, "PROD_ORDER_TASK"), |
| EntityCondition.makeCondition("actualCompletionDate", EntityOperator.GREATER_THAN_EQUAL_TO, checkTime) |
| ], |
| EntityOperator.AND), null, null, null, null); |
| |
| // Sum the production usage quantities found |
| productionUsageQuantity = 0; |
| productionUsageIt.each { productionUsageItem -> |
| if (productionUsageItem.quantity) { |
| try { |
| productionUsageQuantity += productionUsageItem.getDouble("quantity").doubleValue(); |
| } catch (Exception e) { |
| // Ignore |
| } |
| } |
| } |
| productionUsageIt.close(); |
| oneInventory.usageQuantity = salesUsageQuantity + productionUsageQuantity; |
| } |
| rows.add(oneInventory); |
| } |
| if (rows.size() < viewSize.intValue()) { |
| productListSize = lowIndex + rows.size() - 1; |
| } else { |
| // attempt to get the full size |
| if (hasOffsetQOH || hasOffsetATP) { |
| rowProcessed = 0; |
| while (nextValue = prodsEli.next()) { |
| offsetQOHQtyAvailable = nextValue.getDouble("offsetQOHQtyAvailable"); |
| offsetATPQtyAvailable = nextValue.getDouble("offsetATPQtyAvailable"); |
| if (hasOffsetATP) { |
| if (offsetATPQtyAvailable && offsetATPQtyAvailable.doubleValue() > offsetATP) { |
| break; |
| } |
| } |
| if (hasOffsetQOH) { |
| if (offsetQOHQtyAvailable && offsetQOHQtyAvailable.doubleValue() > offsetQOH) { |
| break; |
| } |
| } |
| rowProcessed++; |
| } |
| productListSize = lowIndex + rows.size() + rowProcessed - 1; |
| } else { |
| productListSize = prodsEli.getResultsSizeAfterPartialList(); |
| } |
| } |
| prodsEli.close(); |
| if (highIndex > productListSize) { |
| highIndex = productListSize; |
| } |
| context.overrideListSize = productListSize; |
| context.highIndex = highIndex; |
| context.lowIndex = lowIndex; |
| |
| } catch (GenericEntityException e) { |
| errMsg = "Failure in operation, rolling back transaction"; |
| Debug.logError(e, errMsg, "ViewFacilityInventoryByProduct"); |
| try { |
| // only rollback the transaction if we started one... |
| TransactionUtil.rollback(beganTransaction, errMsg, e); |
| } catch (GenericEntityException e2) { |
| Debug.logError(e2, "Could not rollback transaction: " + e2.toString(), "ViewFacilityInventoryByProduct"); |
| } |
| // after rolling back, rethrow the exception |
| throw e; |
| } finally { |
| if (prodsEli != null) { |
| try { |
| prodsEli.close(); |
| } catch (Exception exc) {} |
| } |
| // only commit the transaction if we started one... this will throw an exception if it fails |
| TransactionUtil.commit(beganTransaction); |
| } |
| } |
| context.inventoryByProduct = rows; |
| context.searchParameterString = searchParameterString; |