blob: 5aa74914cf651493cccb0b51559c728c341574b8 [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.
*******************************************************************************/
package org.apache.ofbiz.product.inventory;
import java.math.BigDecimal;
import java.math.MathContext;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.UtilDateTime;
import org.apache.ofbiz.base.util.UtilGenerics;
import org.apache.ofbiz.base.util.UtilMisc;
import org.apache.ofbiz.base.util.UtilProperties;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.entity.Delegator;
import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.entity.GenericValue;
import org.apache.ofbiz.entity.condition.EntityCondition;
import org.apache.ofbiz.entity.condition.EntityExpr;
import org.apache.ofbiz.entity.condition.EntityOperator;
import org.apache.ofbiz.entity.model.DynamicViewEntity;
import org.apache.ofbiz.entity.model.ModelKeyMap;
import org.apache.ofbiz.entity.util.EntityListIterator;
import org.apache.ofbiz.entity.util.EntityQuery;
import org.apache.ofbiz.entity.util.EntityTypeUtil;
import org.apache.ofbiz.service.DispatchContext;
import org.apache.ofbiz.service.GenericServiceException;
import org.apache.ofbiz.service.LocalDispatcher;
import org.apache.ofbiz.service.ServiceUtil;
import com.ibm.icu.util.Calendar;
/**
* Inventory Services
*/
public class InventoryServices {
public final static String module = InventoryServices.class.getName();
public static final String resource = "ProductUiLabels";
public static final MathContext generalRounding = new MathContext(10);
public static Map<String, Object> prepareInventoryTransfer(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
String inventoryItemId = (String) context.get("inventoryItemId");
BigDecimal xferQty = (BigDecimal) context.get("xferQty");
GenericValue inventoryItem = null;
GenericValue newItem = null;
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
try {
inventoryItem = EntityQuery.use(delegator).from("InventoryItem").where("inventoryItemId", inventoryItemId).queryOne();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductNotFindInventoryItemWithId", locale) + inventoryItemId);
}
if (inventoryItem == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductNotFindInventoryItemWithId", locale) + inventoryItemId);
}
try {
Map<String, Object> results = ServiceUtil.returnSuccess();
String inventoryType = inventoryItem.getString("inventoryItemTypeId");
if (inventoryType.equals("NON_SERIAL_INV_ITEM")) {
BigDecimal atp = inventoryItem.getBigDecimal("availableToPromiseTotal");
BigDecimal qoh = inventoryItem.getBigDecimal("quantityOnHandTotal");
if (atp == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemATPNotAvailable",
UtilMisc.toMap("inventoryItemId", inventoryItem.getString("inventoryItemId")), locale));
}
if (qoh == null) {
qoh = atp;
}
// first make sure we have enough to cover the request transfer amount
if (xferQty.compareTo(atp) > 0) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemATPIsNotSufficient",
UtilMisc.toMap("inventoryItemId", inventoryItem.getString("inventoryItemId"),
"atp", atp, "xferQty", xferQty), locale));
}
/*
* atp < qoh - split and save the qoh - atp
* xferQty < atp - split and save atp - xferQty
* atp < qoh && xferQty < atp - split and save qoh - atp + atp - xferQty
*/
// at this point we have already made sure that the xferQty is less than or equals to the atp, so if less that just create a new inventory record for the quantity to be moved
// NOTE: atp should always be <= qoh, so if xfer < atp, then xfer < qoh, so no need to check/handle that
// however, if atp < qoh && atp == xferQty, then we still need to split; oh, but no need to check atp == xferQty in the second part because if it isn't greater and isn't less, then it is equal
if (xferQty.compareTo(atp) < 0 || atp.compareTo(qoh) < 0) {
BigDecimal negXferQty = xferQty.negate();
// NOTE: new inventory items should always be created calling the
// createInventoryItem service because in this way we are sure
// that all the relevant fields are filled with default values.
// However, the code here should work fine because all the values
// for the new inventory item are inerited from the existing item.
newItem = GenericValue.create(inventoryItem);
newItem.set("availableToPromiseTotal", BigDecimal.ZERO);
newItem.set("quantityOnHandTotal", BigDecimal.ZERO);
delegator.createSetNextSeqId(newItem);
results.put("inventoryItemId", newItem.get("inventoryItemId"));
// TODO: how do we get this here: "inventoryTransferId", inventoryTransferId
Map<String, Object> createNewDetailMap = UtilMisc.toMap("availableToPromiseDiff", xferQty, "quantityOnHandDiff", xferQty, "accountingQuantityDiff", xferQty,
"inventoryItemId", newItem.get("inventoryItemId"), "userLogin", userLogin);
Map<String, Object> createUpdateDetailMap = UtilMisc.toMap("availableToPromiseDiff", negXferQty, "quantityOnHandDiff", negXferQty, "accountingQuantityDiff", negXferQty,
"inventoryItemId", inventoryItem.get("inventoryItemId"), "userLogin", userLogin);
try {
Map<String, Object> resultNew = dctx.getDispatcher().runSync("createInventoryItemDetail", createNewDetailMap);
if (ServiceUtil.isError(resultNew)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", ""), locale), null, null, resultNew);
}
Map<String, Object> resultUpdate = dctx.getDispatcher().runSync("createInventoryItemDetail", createUpdateDetailMap);
if (ServiceUtil.isError(resultUpdate)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", ""), locale), null, null, resultUpdate);
}
} catch (GenericServiceException e1) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", e1.getMessage()), locale));
}
} else {
results.put("inventoryItemId", inventoryItem.get("inventoryItemId"));
}
} else if (inventoryType.equals("SERIALIZED_INV_ITEM")) {
if (!"INV_AVAILABLE".equals(inventoryItem.getString("statusId"))) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductSerializedInventoryNotAvailable", locale));
}
}
// setup values so that no one will grab the inventory during the move
// if newItem is not null, it is the item to be moved, otherwise the original inventoryItem is the one to be moved
if (inventoryType.equals("NON_SERIAL_INV_ITEM")) {
// set the transfered inventory item's atp to 0 and the qoh to the xferQty; at this point atp and qoh will always be the same, so we can safely zero the atp for now
GenericValue inventoryItemToClear = newItem == null ? inventoryItem : newItem;
inventoryItemToClear.refresh();
BigDecimal atp = inventoryItemToClear.get("availableToPromiseTotal") == null ? BigDecimal.ZERO : inventoryItemToClear.getBigDecimal("availableToPromiseTotal");
if (atp.compareTo(BigDecimal.ZERO) != 0) {
Map<String, Object> createDetailMap = UtilMisc.toMap("availableToPromiseDiff", atp.negate(),
"inventoryItemId", inventoryItemToClear.get("inventoryItemId"), "userLogin", userLogin);
try {
Map<String, Object> result = dctx.getDispatcher().runSync("createInventoryItemDetail", createDetailMap);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", ""), locale), null, null, result);
}
} catch (GenericServiceException e1) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", e1.getMessage()), locale));
}
}
} else if (inventoryType.equals("SERIALIZED_INV_ITEM")) {
// set the status to avoid re-moving or something
if (newItem != null) {
newItem.refresh();
newItem.set("statusId", "INV_BEING_TRANSFERED");
newItem.store();
results.put("inventoryItemId", newItem.get("inventoryItemId"));
} else {
inventoryItem.refresh();
inventoryItem.set("statusId", "INV_BEING_TRANSFERED");
inventoryItem.store();
results.put("inventoryItemId", inventoryItem.get("inventoryItemId"));
}
}
return results;
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemStoreProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
}
public static Map<String, Object> completeInventoryTransfer(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
String inventoryTransferId = (String) context.get("inventoryTransferId");
Timestamp receiveDate = (Timestamp) context.get("receiveDate");
GenericValue inventoryTransfer = null;
GenericValue inventoryItem = null;
GenericValue destinationFacility = null;
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
try {
inventoryTransfer = EntityQuery.use(delegator).from("InventoryTransfer").where("inventoryTransferId", inventoryTransferId).queryOne();
inventoryItem = inventoryTransfer.getRelatedOne("InventoryItem", false);
destinationFacility = inventoryTransfer.getRelatedOne("ToFacility", false);
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemLookupProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
if (inventoryTransfer == null || inventoryItem == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemLookupProblem",
UtilMisc.toMap("errorString", ""), locale));
}
String inventoryType = inventoryItem.getString("inventoryItemTypeId");
// set the fields on the transfer record
if (inventoryTransfer.get("receiveDate") == null) {
if (receiveDate != null) {
inventoryTransfer.set("receiveDate", receiveDate);
} else {
inventoryTransfer.set("receiveDate", UtilDateTime.nowTimestamp());
}
}
if (inventoryType.equals("NON_SERIAL_INV_ITEM")) {
// add an adjusting InventoryItemDetail so set ATP back to QOH: ATP = ATP + (QOH - ATP), diff = QOH - ATP
BigDecimal atp = inventoryItem.get("availableToPromiseTotal") == null ? BigDecimal.ZERO : inventoryItem.getBigDecimal("availableToPromiseTotal");
BigDecimal qoh = inventoryItem.get("quantityOnHandTotal") == null ? BigDecimal.ZERO : inventoryItem.getBigDecimal("quantityOnHandTotal");
Map<String, Object> createDetailMap = UtilMisc.toMap("availableToPromiseDiff", qoh.subtract(atp),
"inventoryItemId", inventoryItem.get("inventoryItemId"), "userLogin", userLogin);
try {
Map<String, Object> result = dctx.getDispatcher().runSync("createInventoryItemDetail", createDetailMap);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", ""), locale), null, null, result);
}
} catch (GenericServiceException e1) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", e1.getMessage()), locale));
}
try {
inventoryItem.refresh();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemRefreshProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
}
// set the fields on the item
Map<String, Object> updateInventoryItemMap = UtilMisc.toMap("inventoryItemId", inventoryItem.getString("inventoryItemId"),
"facilityId", inventoryTransfer.get("facilityIdTo"),
"containerId", inventoryTransfer.get("containerIdTo"),
"locationSeqId", inventoryTransfer.get("locationSeqIdTo"),
"userLogin", userLogin);
// for serialized items, automatically make them available
if (inventoryType.equals("SERIALIZED_INV_ITEM")) {
updateInventoryItemMap.put("statusId", "INV_AVAILABLE");
}
// if the destination facility's owner is different
// from the inventory item's ownwer,
// the inventory item is assigned to the new owner.
if (destinationFacility != null && destinationFacility.get("ownerPartyId") != null) {
String fromPartyId = inventoryItem.getString("ownerPartyId");
String toPartyId = destinationFacility.getString("ownerPartyId");
if (fromPartyId == null || !fromPartyId.equals(toPartyId)) {
updateInventoryItemMap.put("ownerPartyId", toPartyId);
}
}
try {
Map<String, Object> result = dctx.getDispatcher().runSync("updateInventoryItem", updateInventoryItemMap);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemStoreProblem",
UtilMisc.toMap("errorString", ""), locale), null, null, result);
}
} catch (GenericServiceException exc) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemStoreProblem",
UtilMisc.toMap("errorString", exc.getMessage()), locale));
}
// set the inventory transfer record to complete
inventoryTransfer.set("statusId", "IXF_COMPLETE");
// store the entities
try {
inventoryTransfer.store();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemStoreProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
return ServiceUtil.returnSuccess();
}
public static Map<String, Object> cancelInventoryTransfer(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
String inventoryTransferId = (String) context.get("inventoryTransferId");
GenericValue inventoryTransfer = null;
GenericValue inventoryItem = null;
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
try {
inventoryTransfer = EntityQuery.use(delegator).from("InventoryTransfer").where("inventoryTransferId", inventoryTransferId).queryOne();
if (UtilValidate.isEmpty(inventoryTransfer)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemTransferNotFound",
UtilMisc.toMap("inventoryTransferId", inventoryTransferId), locale));
}
inventoryItem = inventoryTransfer.getRelatedOne("InventoryItem", false);
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemLookupProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
if (inventoryTransfer == null || inventoryItem == null) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemLookupProblem",
UtilMisc.toMap("errorString", ""), locale));
}
String inventoryType = inventoryItem.getString("inventoryItemTypeId");
// re-set the fields on the item
if (inventoryType.equals("NON_SERIAL_INV_ITEM")) {
// add an adjusting InventoryItemDetail so set ATP back to QOH: ATP = ATP + (QOH - ATP), diff = QOH - ATP
BigDecimal atp = inventoryItem.get("availableToPromiseTotal") == null ? BigDecimal.ZERO : inventoryItem.getBigDecimal("availableToPromiseTotal");
BigDecimal qoh = inventoryItem.get("quantityOnHandTotal") == null ? BigDecimal.ZERO : inventoryItem.getBigDecimal("quantityOnHandTotal");
Map<String, Object> createDetailMap = UtilMisc.toMap("availableToPromiseDiff", qoh.subtract(atp),
"inventoryItemId", inventoryItem.get("inventoryItemId"),
"userLogin", userLogin);
try {
Map<String, Object> result = dctx.getDispatcher().runSync("createInventoryItemDetail", createDetailMap);
if (ServiceUtil.isError(result)) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", ""), locale), null, null, result);
}
} catch (GenericServiceException e1) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemDetailCreateProblem",
UtilMisc.toMap("errorString", e1.getMessage()), locale));
}
} else if (inventoryType.equals("SERIALIZED_INV_ITEM")) {
inventoryItem.set("statusId", "INV_AVAILABLE");
// store the entity
try {
inventoryItem.store();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemStoreProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
}
// set the inventory transfer record to complete
inventoryTransfer.set("statusId", "IXF_CANCELLED");
// store the entities
try {
inventoryTransfer.store();
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryItemStoreProblem",
UtilMisc.toMap("errorString", e.getMessage()), locale));
}
return ServiceUtil.returnSuccess();
}
/** In spite of the generic name this does the very specific task of checking availability of all back-ordered items and sends notices, etc */
public static Map<String, Object> checkInventoryAvailability(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
GenericValue userLogin = (GenericValue) context.get("userLogin");
Locale locale = (Locale) context.get("locale");
Map<String, Map<String, Timestamp>> ordersToUpdate = new HashMap<String, Map<String,Timestamp>>();
Map<String, Map<String, Timestamp>> ordersToCancel = new HashMap<String, Map<String,Timestamp>>();
// find all inventory items w/ a negative ATP
List<GenericValue> inventoryItems = null;
try {
inventoryItems = EntityQuery.use(delegator).from("InventoryItem").where(EntityCondition.makeCondition("availableToPromiseTotal", EntityOperator.LESS_THAN, BigDecimal.ZERO)).queryList();
} catch (GenericEntityException e) {
Debug.logError(e, "Trouble getting inventory items", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductPriceCannotRetrieveInventoryItem", locale));
}
if (inventoryItems == null) {
Debug.logInfo("No items out of stock; no backorders to worry about", module);
return ServiceUtil.returnSuccess();
}
Debug.logInfo("OOS Inventory Items: " + inventoryItems.size(), module);
for (GenericValue inventoryItem: inventoryItems) {
// get the incomming shipment information for the item
List<GenericValue> shipmentAndItems = null;
try {
List<EntityExpr> exprs = new ArrayList<EntityExpr>();
exprs.add(EntityCondition.makeCondition("productId", EntityOperator.EQUALS, inventoryItem.get("productId")));
exprs.add(EntityCondition.makeCondition("destinationFacilityId", EntityOperator.EQUALS, inventoryItem.get("facilityId")));
exprs.add(EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "SHIPMENT_DELIVERED"));
exprs.add(EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "SHIPMENT_CANCELLED"));
shipmentAndItems = EntityQuery.use(delegator).from("ShipmentAndItem").where(EntityCondition.makeCondition(exprs, EntityOperator.AND)).orderBy("estimatedArrivalDate").queryList();
} catch (GenericEntityException e) {
Debug.logError(e, "Problem getting ShipmentAndItem records", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductPriceCannotRetrieveShipmentAndItem", locale));
}
// get the reservations in order of newest first
List<GenericValue> reservations = null;
try {
reservations = inventoryItem.getRelated("OrderItemShipGrpInvRes", null, UtilMisc.toList("-reservedDatetime"), false);
} catch (GenericEntityException e) {
Debug.logError(e, "Problem getting related reservations", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductPriceCannotRetrieveRelativeReservation", locale));
}
if (reservations == null) {
Debug.logWarning("No outstanding reservations for this inventory item, why is it negative then?", module);
continue;
}
Debug.logInfo("Reservations for item: " + reservations.size(), module);
// available at the time of order
BigDecimal availableBeforeReserved = inventoryItem.getBigDecimal("availableToPromiseTotal");
// go through all the reservations in order
for (GenericValue reservation: reservations) {
String orderId = reservation.getString("orderId");
String orderItemSeqId = reservation.getString("orderItemSeqId");
Timestamp promisedDate = reservation.getTimestamp("promisedDatetime");
Timestamp currentPromiseDate = reservation.getTimestamp("currentPromisedDate");
Timestamp actualPromiseDate = currentPromiseDate;
if (actualPromiseDate == null) {
if (promisedDate != null) {
actualPromiseDate = promisedDate;
} else {
// fall back if there is no promised date stored
actualPromiseDate = reservation.getTimestamp("reservedDatetime");
}
}
Debug.logInfo("Promised Date: " + actualPromiseDate, module);
// find the next possible ship date
Timestamp nextShipDate = null;
BigDecimal availableAtTime = BigDecimal.ZERO;
for (GenericValue shipmentItem: shipmentAndItems) {
availableAtTime = availableAtTime.add(shipmentItem.getBigDecimal("quantity"));
if (availableAtTime.compareTo(availableBeforeReserved) >= 0) {
nextShipDate = shipmentItem.getTimestamp("estimatedArrivalDate");
break;
}
}
Debug.logInfo("Next Ship Date: " + nextShipDate, module);
// create a modified promise date (promise date - 1 day)
Calendar pCal = Calendar.getInstance();
pCal.setTimeInMillis(actualPromiseDate.getTime());
pCal.add(Calendar.DAY_OF_YEAR, -1);
Timestamp modifiedPromisedDate = new Timestamp(pCal.getTimeInMillis());
Timestamp now = UtilDateTime.nowTimestamp();
Debug.logInfo("Promised Date + 1: " + modifiedPromisedDate, module);
Debug.logInfo("Now: " + now, module);
// check the promised date vs the next ship date
if (nextShipDate == null || nextShipDate.after(actualPromiseDate)) {
if (nextShipDate == null && modifiedPromisedDate.after(now)) {
// do nothing; we are okay to assume it will be shipped on time
Debug.logInfo("No ship date known yet, but promised date hasn't approached, assuming it will be here on time", module);
} else {
// we cannot ship by the promised date; need to notify the customer
Debug.logInfo("We won't ship on time, getting notification info", module);
Map<String, Timestamp> notifyItems = ordersToUpdate.get(orderId);
if (notifyItems == null) {
notifyItems = new HashMap<String, Timestamp>();
}
notifyItems.put(orderItemSeqId, nextShipDate);
ordersToUpdate.put(orderId, notifyItems);
// need to know if nextShipDate is more then 30 days after promised
Calendar sCal = Calendar.getInstance();
sCal.setTimeInMillis(actualPromiseDate.getTime());
sCal.add(Calendar.DAY_OF_YEAR, 30);
Timestamp farPastPromised = new Timestamp(sCal.getTimeInMillis());
// check to see if this is >30 days or second run, if so flag to cancel
boolean needToCancel = false;
if (nextShipDate == null || nextShipDate.after(farPastPromised)) {
// we cannot ship until >30 days after promised; using cancel rule
Debug.logInfo("Ship date is >30 past the promised date", module);
needToCancel = true;
} else if (currentPromiseDate != null && actualPromiseDate.equals(currentPromiseDate)) {
// this is the second notification; using cancel rule
needToCancel = true;
}
// add the info to the cancel map if we need to schedule a cancel
if (needToCancel) {
// queue the item to be cancelled
Debug.logInfo("Flagging the item to auto-cancel", module);
Map<String, Timestamp> cancelItems = ordersToCancel.get(orderId);
if (cancelItems == null) {
cancelItems = new HashMap<String, Timestamp>();
}
cancelItems.put(orderItemSeqId, farPastPromised);
ordersToCancel.put(orderId, cancelItems);
}
// store the updated promiseDate as the nextShipDate
try {
reservation.set("currentPromisedDate", nextShipDate);
reservation.store();
} catch (GenericEntityException e) {
Debug.logError(e, "Problem storing reservation : " + reservation, module);
}
}
}
// subtract our qty from reserved to get the next value
availableBeforeReserved = availableBeforeReserved.subtract(reservation.getBigDecimal("quantity"));
}
}
// all items to cancel will also be in the notify list so start with that
List<String> ordersToNotify = new LinkedList<String>();
for (Map.Entry<String, Map<String, Timestamp>> entry: ordersToUpdate.entrySet()) {
String orderId = entry.getKey();
Map<String, Timestamp> backOrderedItems = entry.getValue();
Map<String, Timestamp> cancelItems = ordersToCancel.get(orderId);
boolean cancelAll = false;
Timestamp cancelAllTime = null;
List<GenericValue> orderItemShipGroups = null;
try {
orderItemShipGroups= EntityQuery.use(delegator).from("OrderItemShipGroup").where("orderId", orderId).queryList();
} catch (GenericEntityException e) {
Debug.logError(e, "Cannot get OrderItemShipGroups from orderId" + orderId, module);
}
for (GenericValue orderItemShipGroup: orderItemShipGroups) {
List<GenericValue> orderItems = new LinkedList<GenericValue>();
List<GenericValue> orderItemShipGroupAssoc = null;
try {
orderItemShipGroupAssoc = EntityQuery.use(delegator).from("OrderItemShipGroupAssoc").where("shipGroupSeqId", orderItemShipGroup.get("shipGroupSeqId"), "orderId", orderId).queryList();
for (GenericValue assoc: orderItemShipGroupAssoc) {
GenericValue orderItem = assoc.getRelatedOne("OrderItem", false);
if (orderItem != null) {
orderItems.add(orderItem);
}
}
} catch (GenericEntityException e) {
Debug.logError(e, "Problem fetching OrderItemShipGroupAssoc", module);
}
/* Check the split preference. */
boolean maySplit = false;
if (orderItemShipGroup != null && orderItemShipGroup.get("maySplit") != null) {
maySplit = orderItemShipGroup.getBoolean("maySplit").booleanValue();
}
/* Figure out if we must cancel all items. */
if (!maySplit && cancelItems != null) {
cancelAll = true;
Set<String> cancelSet = cancelItems.keySet();
cancelAllTime = cancelItems.get(cancelSet.iterator().next());
}
// if there are none to cancel just create an empty map
if (cancelItems == null) {
cancelItems = new HashMap<String, Timestamp>();
}
if (orderItems != null) {
List<GenericValue> toBeStored = new LinkedList<GenericValue>();
for (GenericValue orderItem: orderItems) {
String orderItemSeqId = orderItem.getString("orderItemSeqId");
Timestamp shipDate = backOrderedItems.get(orderItemSeqId);
Timestamp cancelDate = cancelItems.get(orderItemSeqId);
Timestamp currentCancelDate = orderItem.getTimestamp("autoCancelDate");
Debug.logInfo("OI: " + orderId + " SEQID: "+ orderItemSeqId + " cancelAll: " + cancelAll + " cancelDate: " + cancelDate, module);
if (backOrderedItems.containsKey(orderItemSeqId)) {
orderItem.set("estimatedShipDate", shipDate);
if (currentCancelDate == null) {
if (cancelAll || cancelDate != null) {
if (orderItem.get("dontCancelSetUserLogin") == null && orderItem.get("dontCancelSetDate") == null) {
if (cancelAllTime != null) {
orderItem.set("autoCancelDate", cancelAllTime);
} else {
orderItem.set("autoCancelDate", cancelDate);
}
}
}
// only notify orders which have not already sent the final notice
ordersToNotify.add(orderId);
}
toBeStored.add(orderItem);
}
}
if (toBeStored.size() > 0) {
try {
delegator.storeAll(toBeStored);
} catch (GenericEntityException e) {
Debug.logError(e, "Problem storing order items", module);
}
}
}
}
}
// send off a notification for each order
for (String orderId: ordersToNotify) {
try {
dispatcher.runAsync("sendOrderBackorderNotification", UtilMisc.<String, Object>toMap("orderId", orderId, "userLogin", userLogin));
} catch (GenericServiceException e) {
Debug.logError(e, "Problems sending off the notification", module);
continue;
}
}
return ServiceUtil.returnSuccess();
}
/**
* Get Inventory Available for a Product based on the list of associated products. The final ATP and QOH will
* be the minimum of all the associated products' inventory divided by their ProductAssoc.quantity
* */
public static Map<String, Object> getProductInventoryAvailableFromAssocProducts(DispatchContext dctx, Map<String, ? extends Object> context) {
LocalDispatcher dispatcher = dctx.getDispatcher();
List<GenericValue> productAssocList = UtilGenerics.checkList(context.get("assocProducts"));
String facilityId = (String)context.get("facilityId");
String statusId = (String)context.get("statusId");
BigDecimal availableToPromiseTotal = BigDecimal.ZERO;
BigDecimal quantityOnHandTotal = BigDecimal.ZERO;
if (UtilValidate.isNotEmpty(productAssocList)) {
// minimum QOH and ATP encountered
BigDecimal minQuantityOnHandTotal = null;
BigDecimal minAvailableToPromiseTotal = null;
// loop through each associated product.
for (GenericValue productAssoc: productAssocList) {
String productIdTo = productAssoc.getString("productIdTo");
BigDecimal assocQuantity = productAssoc.getBigDecimal("quantity");
// if there is no quantity for the associated product in ProductAssoc entity, default it to 1.0
if (assocQuantity == null) {
Debug.logWarning("ProductAssoc from [" + productAssoc.getString("productId") + "] to [" + productAssoc.getString("productIdTo") + "] has no quantity, assuming 1.0", module);
assocQuantity = BigDecimal.ONE;
}
// figure out the inventory available for this associated product
Map<String, Object> resultOutput = null;
try {
Map<String, String> inputMap = UtilMisc.toMap("productId", productIdTo, "statusId", statusId);
if (facilityId != null) {
inputMap.put("facilityId", facilityId);
resultOutput = dispatcher.runSync("getInventoryAvailableByFacility", inputMap);
} else {
resultOutput = dispatcher.runSync("getProductInventoryAvailable", inputMap);
}
} catch (GenericServiceException e) {
Debug.logError(e, "Problems getting inventory available by facility", module);
return ServiceUtil.returnError(e.getMessage());
}
// Figure out what the QOH and ATP inventory would be with this associated product
BigDecimal currentQuantityOnHandTotal = (BigDecimal) resultOutput.get("quantityOnHandTotal");
BigDecimal currentAvailableToPromiseTotal = (BigDecimal) resultOutput.get("availableToPromiseTotal");
BigDecimal tmpQuantityOnHandTotal = currentQuantityOnHandTotal.divideToIntegralValue(assocQuantity, generalRounding);
BigDecimal tmpAvailableToPromiseTotal = currentAvailableToPromiseTotal.divideToIntegralValue(assocQuantity, generalRounding);
// reset the minimum QOH and ATP quantities if those quantities for this product are less
if (minQuantityOnHandTotal == null || tmpQuantityOnHandTotal.compareTo(minQuantityOnHandTotal) < 0) {
minQuantityOnHandTotal = tmpQuantityOnHandTotal;
}
if (minAvailableToPromiseTotal == null || tmpAvailableToPromiseTotal.compareTo(minAvailableToPromiseTotal) < 0) {
minAvailableToPromiseTotal = tmpAvailableToPromiseTotal;
}
if (Debug.verboseOn()) {
Debug.logVerbose("productIdTo = " + productIdTo + " assocQuantity = " + assocQuantity + "current QOH " + currentQuantityOnHandTotal +
"currentATP = " + currentAvailableToPromiseTotal + " minQOH = " + minQuantityOnHandTotal + " minATP = " + minAvailableToPromiseTotal, module);
}
}
// the final QOH and ATP quantities are the minimum of all the products
quantityOnHandTotal = minQuantityOnHandTotal;
availableToPromiseTotal = minAvailableToPromiseTotal;
}
Map<String, Object> result = ServiceUtil.returnSuccess();
result.put("availableToPromiseTotal", availableToPromiseTotal);
result.put("quantityOnHandTotal", quantityOnHandTotal);
return result;
}
public static Map<String, Object> getProductInventorySummaryForItems(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
List<GenericValue> orderItems = UtilGenerics.checkList(context.get("orderItems"));
String facilityId = (String) context.get("facilityId");
Locale locale = (Locale) context.get("locale");
Map<String, BigDecimal> atpMap = new HashMap<String, BigDecimal>();
Map<String, BigDecimal> qohMap = new HashMap<String, BigDecimal>();
Map<String, BigDecimal> mktgPkgAtpMap = new HashMap<String, BigDecimal>();
Map<String, BigDecimal> mktgPkgQohMap = new HashMap<String, BigDecimal>();
Map<String, Object> results = ServiceUtil.returnSuccess();
// get a list of all available facilities for looping
List<GenericValue> facilities = null;
try {
if (facilityId != null) {
facilities = EntityQuery.use(delegator).from("Facility").where("facilityId", facilityId).queryList();
} else {
facilities = EntityQuery.use(delegator).from("Facility").queryList();
}
} catch (GenericEntityException e) {
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductErrorFacilityIdNotFound",
UtilMisc.toMap("facilityId", facilityId), locale));
}
// loop through all the order items
for (GenericValue orderItem: orderItems) {
String productId = orderItem.getString("productId");
if ((productId == null) || productId.equals("")) continue;
GenericValue product = null;
try {
product = orderItem.getRelatedOne("Product", true);
} catch (GenericEntityException e) {
Debug.logError(e, "Couldn't get product.", module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductProductNotFound", locale) + productId);
}
BigDecimal atp = BigDecimal.ZERO;
BigDecimal qoh = BigDecimal.ZERO;
BigDecimal mktgPkgAtp = BigDecimal.ZERO;
BigDecimal mktgPkgQoh = BigDecimal.ZERO;
// loop through all the facilities
for (GenericValue facility: facilities) {
Map<String, Object> invResult = null;
Map<String, Object> mktgPkgInvResult = null;
// get both the real ATP/QOH available and the quantities available from marketing packages
try {
if (EntityTypeUtil.hasParentType(delegator, "ProductType", "productTypeId", product.getString("productTypeId"), "parentTypeId", "MARKETING_PKG")) {
mktgPkgInvResult = dispatcher.runSync("getMktgPackagesAvailable", UtilMisc.toMap("productId", productId, "facilityId", facility.getString("facilityId")));
}
invResult = dispatcher.runSync("getInventoryAvailableByFacility", UtilMisc.toMap("productId", productId, "facilityId", facility.getString("facilityId")));
} catch (GenericServiceException e) {
Debug.logError(e, "Could not find inventory for facility " + facility.getString("facilityId"), module);
return ServiceUtil.returnError(UtilProperties.getMessage(resource,
"ProductInventoryNotAvailableForFacility",
UtilMisc.toMap("facilityId", facility.getString("facilityId")), locale));
}
// add the results for this facility to the ATP/QOH counter for all facilities
if (!ServiceUtil.isError(invResult)) {
BigDecimal fatp = (BigDecimal) invResult.get("availableToPromiseTotal");
BigDecimal fqoh = (BigDecimal) invResult.get("quantityOnHandTotal");
if (fatp != null) atp = atp.add(fatp);
if (fqoh != null) qoh = qoh.add(fqoh);
}
if (EntityTypeUtil.hasParentType(delegator, "ProductType", "productTypeId", product.getString("productTypeId"), "parentTypeId", "MARKETING_PKG") && !ServiceUtil.isError(mktgPkgInvResult)) {
BigDecimal fatp = (BigDecimal) mktgPkgInvResult.get("availableToPromiseTotal");
BigDecimal fqoh = (BigDecimal) mktgPkgInvResult.get("quantityOnHandTotal");
if (fatp != null) mktgPkgAtp = mktgPkgAtp.add(fatp);
if (fqoh != null) mktgPkgQoh = mktgPkgQoh.add(fqoh);
}
}
atpMap.put(productId, atp);
qohMap.put(productId, qoh);
mktgPkgAtpMap.put(productId, mktgPkgAtp);
mktgPkgQohMap.put(productId, mktgPkgQoh);
}
results.put("availableToPromiseMap", atpMap);
results.put("quantityOnHandMap", qohMap);
results.put("mktgPkgATPMap", mktgPkgAtpMap);
results.put("mktgPkgQOHMap", mktgPkgQohMap);
return results;
}
public static Map<String, Object> getProductInventoryAndFacilitySummary(DispatchContext dctx, Map<String, ? extends Object> context) {
Delegator delegator = dctx.getDelegator();
LocalDispatcher dispatcher = dctx.getDispatcher();
Timestamp checkTime = (Timestamp)context.get("checkTime");
String facilityId = (String)context.get("facilityId");
String productId = (String)context.get("productId");
BigDecimal minimumStock = (BigDecimal)context.get("minimumStock");
String statusId = (String)context.get("statusId");
Map<String, Object> result = new HashMap<String, Object>();
Map<String, Object> resultOutput = new HashMap<String, Object>();
Map<String, String> contextInput = UtilMisc.toMap("productId", productId, "facilityId", facilityId, "statusId", statusId);
GenericValue product = null;
try {
product = EntityQuery.use(delegator).from("Product").where("productId", productId).queryOne();
} catch (GenericEntityException e) {
e.printStackTrace();
}
if (EntityTypeUtil.hasParentType(delegator, "ProductType", "productTypeId", product.getString("productTypeId"), "parentTypeId", "MARKETING_PKG")) {
try {
resultOutput = dispatcher.runSync("getMktgPackagesAvailable", contextInput);
} catch (GenericServiceException e) {
e.printStackTrace();
}
} else {
try {
resultOutput = dispatcher.runSync("getInventoryAvailableByFacility", contextInput);
} catch (GenericServiceException e) {
e.printStackTrace();
}
}
// filter for quantities
minimumStock = minimumStock != null ? minimumStock : BigDecimal.ZERO;
BigDecimal quantityOnHandTotal = BigDecimal.ZERO;
if (resultOutput.get("quantityOnHandTotal") != null) {
quantityOnHandTotal = (BigDecimal)resultOutput.get("quantityOnHandTotal");
}
BigDecimal offsetQOHQtyAvailable = quantityOnHandTotal.subtract(minimumStock);
BigDecimal availableToPromiseTotal = BigDecimal.ZERO;
if (resultOutput.get("availableToPromiseTotal") != null) {
availableToPromiseTotal = (BigDecimal)resultOutput.get("availableToPromiseTotal");
}
BigDecimal offsetATPQtyAvailable = availableToPromiseTotal.subtract(minimumStock);
BigDecimal quantityOnOrder = InventoryWorker.getOutstandingPurchasedQuantity(productId, delegator);
result.put("totalQuantityOnHand", resultOutput.get("quantityOnHandTotal"));
result.put("totalAvailableToPromise", resultOutput.get("availableToPromiseTotal"));
result.put("quantityOnOrder", quantityOnOrder);
result.put("quantityUomId", product.getString("quantityUomId"));
result.put("offsetQOHQtyAvailable", offsetQOHQtyAvailable);
result.put("offsetATPQtyAvailable", offsetATPQtyAvailable);
List<GenericValue> productPrices = null;
try {
productPrices = EntityQuery.use(delegator).from("ProductPrice").where("productId",productId).orderBy("-fromDate").cache(true).queryList();
} catch (GenericEntityException e) {
e.printStackTrace();
}
//change this for product price
for (GenericValue onePrice: productPrices) {
if (onePrice.getString("productPriceTypeId").equals("DEFAULT_PRICE")) { //defaultPrice
result.put("defaultPrice", onePrice.getBigDecimal("price"));
} else if (onePrice.getString("productPriceTypeId").equals("WHOLESALE_PRICE")) {//
result.put("wholeSalePrice", onePrice.getBigDecimal("price"));
} else if (onePrice.getString("productPriceTypeId").equals("LIST_PRICE")) {//listPrice
result.put("listPrice", onePrice.getBigDecimal("price"));
} else {
result.put("defaultPrice", onePrice.getBigDecimal("price"));
result.put("listPrice", onePrice.getBigDecimal("price"));
result.put("wholeSalePrice", onePrice.getBigDecimal("price"));
}
}
DynamicViewEntity salesUsageViewEntity = new DynamicViewEntity();
DynamicViewEntity productionUsageViewEntity = new DynamicViewEntity();
if (! UtilValidate.isEmpty(checkTime)) {
// Construct a dynamic view entity to search against for sales usage quantities
salesUsageViewEntity.addMemberEntity("OI", "OrderItem");
salesUsageViewEntity.addMemberEntity("OH", "OrderHeader");
salesUsageViewEntity.addMemberEntity("ItIss", "ItemIssuance");
salesUsageViewEntity.addMemberEntity("InvIt", "InventoryItem");
salesUsageViewEntity.addViewLink("OI", "OH", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("orderId"));
salesUsageViewEntity.addViewLink("OI", "ItIss", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("orderId", "orderId", "orderItemSeqId", "orderItemSeqId"));
salesUsageViewEntity.addViewLink("ItIss", "InvIt", Boolean.valueOf(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.addMemberEntity("WEIA", "WorkEffortInventoryAssign");
productionUsageViewEntity.addMemberEntity("WE", "WorkEffort");
productionUsageViewEntity.addMemberEntity("II", "InventoryItem");
productionUsageViewEntity.addViewLink("WEIA", "WE", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("workEffortId"));
productionUsageViewEntity.addViewLink("WEIA", "II", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("inventoryItemId"));
productionUsageViewEntity.addAlias("WEIA", "quantity");
productionUsageViewEntity.addAlias("WE", "actualCompletionDate");
productionUsageViewEntity.addAlias("WE", "workEffortTypeId");
productionUsageViewEntity.addAlias("II", "facilityId");
productionUsageViewEntity.addAlias("II", "productId");
}
if (! UtilValidate.isEmpty(checkTime)) {
// Make a query against the sales usage view entity
EntityListIterator salesUsageIt = null;
try {
EntityCondition cond = EntityCondition.makeCondition(
UtilMisc.toList(
EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, facilityId),
EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId),
EntityCondition.makeCondition("statusId", EntityOperator.IN, UtilMisc.toList("ORDER_COMPLETED", "ORDER_APPROVED", "ORDER_HELD")),
EntityCondition.makeCondition("orderTypeId", EntityOperator.EQUALS, "SALES_ORDER"),
EntityCondition.makeCondition("orderDate", EntityOperator.GREATER_THAN_EQUAL_TO, checkTime)
),
EntityOperator.AND);
salesUsageIt = EntityQuery.use(delegator).from(salesUsageViewEntity).where(cond).queryIterator();
} catch (GenericEntityException e2) {
e2.printStackTrace();
}
// Sum the sales usage quantities found
BigDecimal salesUsageQuantity = BigDecimal.ZERO;
GenericValue salesUsageItem = null;
while ((salesUsageItem = salesUsageIt.next()) != null) {
if (salesUsageItem.get("quantity") != null) {
try {
salesUsageQuantity = salesUsageQuantity.add(salesUsageItem.getBigDecimal("quantity"));
} catch (Exception e) {
// Ignore
}
}
}
try {
salesUsageIt.close();
} catch (GenericEntityException e2) {
e2.printStackTrace();
}
// Make a query against the production usage view entity
EntityListIterator productionUsageIt = null;
try {
EntityCondition conditions = EntityCondition.makeCondition(
UtilMisc.toList(
EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, facilityId),
EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId),
EntityCondition.makeCondition("workEffortTypeId", EntityOperator.EQUALS, "PROD_ORDER_TASK"),
EntityCondition.makeCondition("actualCompletionDate", EntityOperator.GREATER_THAN_EQUAL_TO, checkTime)
),
EntityOperator.AND);
productionUsageIt = EntityQuery.use(delegator).from(productionUsageViewEntity).where(conditions).queryIterator();
} catch (GenericEntityException e1) {
e1.printStackTrace();
}
// Sum the production usage quantities found
BigDecimal productionUsageQuantity = BigDecimal.ZERO;
GenericValue productionUsageItem = null;
while ((productionUsageItem = productionUsageIt.next()) != null) {
if (productionUsageItem.get("quantity") != null) {
try {
productionUsageQuantity = productionUsageQuantity.add(productionUsageItem.getBigDecimal("quantity"));
} catch (Exception e) {
// Ignore
}
}
}
try {
productionUsageIt.close();
} catch (GenericEntityException e) {
e.printStackTrace();
}
result.put("usageQuantity", salesUsageQuantity.add(productionUsageQuantity));
}
return result;
}
}