blob: ee5a3db78b1c20f3fd800bd2acc34e320355489f [file] [log] [blame]
/*
* 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.apache.ofbiz.base.util.Debug
import org.apache.ofbiz.entity.*
import org.apache.ofbiz.entity.condition.*
import org.apache.ofbiz.entity.transaction.*
import org.apache.ofbiz.entity.util.*
import org.apache.ofbiz.entity.model.DynamicViewEntity
import org.apache.ofbiz.entity.model.ModelKeyMap
import org.apache.ofbiz.entity.model.ModelViewEntity.ComplexAlias
import org.apache.ofbiz.entity.model.ModelViewEntity.ComplexAliasField
import org.apache.ofbiz.product.inventory.*
action = request.getParameter("action")
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
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()
prodsEli = from(prodView).where(whereCondition).orderBy(orderBy).cursorScrollInsensitive().maxRows(highIndex).distinct().queryIterator()
// 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 = from(salesUsageViewEntity)
.where(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))
.queryIterator()
// 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 = from(productionUsageViewEntity)
.where(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))
.queryIterator()
// 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