Package org.ofbiz.oagis

Source Code of org.ofbiz.oagis.OagisInventoryServices

/*
* 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.ofbiz.oagis;

import java.io.IOException;
import java.sql.Timestamp;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javolution.util.FastList;
import javolution.util.FastMap;

import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.UtilDateTime;
import org.ofbiz.base.util.UtilFormatOut;
import org.ofbiz.base.util.UtilMisc;
import org.ofbiz.base.util.UtilProperties;
import org.ofbiz.base.util.UtilValidate;
import org.ofbiz.base.util.UtilXml;
import org.ofbiz.entity.Delegator;
import org.ofbiz.entity.GenericEntityException;
import org.ofbiz.entity.GenericValue;
import org.ofbiz.entity.condition.EntityCondition;
import org.ofbiz.entity.condition.EntityOperator;
import org.ofbiz.entity.transaction.GenericTransactionException;
import org.ofbiz.entity.transaction.TransactionUtil;
import org.ofbiz.entity.util.EntityUtil;
import org.ofbiz.product.product.ProductWorker;
import org.ofbiz.service.DispatchContext;
import org.ofbiz.service.GenericServiceException;
import org.ofbiz.service.LocalDispatcher;
import org.ofbiz.service.ServiceUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class OagisInventoryServices {

    public static final String module = OagisInventoryServices.class.getName();
    public static final String resource = "OagisUiLabels";
    public static final Double doubleZero = new Double(0.0);
    public static final Double doubleOne = new Double(1.0);
    public static final String syncInventoryFacilityId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.SyncInventoryFacilityId");

    public static Map<String, Object> oagisReceiveSyncInventory(DispatchContext ctx, Map<String, Object> context) {
        Document doc = (Document) context.get("document");
        boolean isErrorRetry = Boolean.TRUE.equals(context.get("isErrorRetry"));
        Delegator delegator = ctx.getDelegator();
        LocalDispatcher dispatcher = ctx.getDispatcher();
        Locale locale = (Locale) context.get("locale");
        List<Map<String, String>> errorMapList = FastList.newInstance();
        List<Map<String, Object>> inventoryMapList = FastList.newInstance();

        GenericValue userLogin = null;
        try {
            userLogin = delegator.findByPrimaryKey("UserLogin", UtilMisc.toMap("userLoginId", "system"));
        } catch (GenericEntityException e) {
            String errMsg = "Error Getting UserLogin: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        Element syncInventoryRootElement = doc.getDocumentElement();
        syncInventoryRootElement.normalize();
        Element docCtrlAreaElement = UtilXml.firstChildElement(syncInventoryRootElement, "os:CNTROLAREA");
        Element docBsrElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:BSR");
        Element docSenderElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:SENDER");

        String bsrVerb = UtilXml.childElementValue(docBsrElement, "of:VERB");
        String bsrNoun = UtilXml.childElementValue(docBsrElement, "of:NOUN");
        String bsrRevision = UtilXml.childElementValue(docBsrElement, "of:REVISION");


        String logicalId = UtilXml.childElementValue(docSenderElement, "of:LOGICALID");
        String component = UtilXml.childElementValue(docSenderElement, "of:COMPONENT");
        String task = UtilXml.childElementValue(docSenderElement, "of:TASK");
        String referenceId = UtilXml.childElementValue(docSenderElement, "of:REFERENCEID");
        String confirmation = UtilXml.childElementValue(docSenderElement, "of:CONFIRMATION");
        String authId = UtilXml.childElementValue(docSenderElement, "of:AUTHID");

        // create oagis message info
        Map<String, Object> comiCtx = FastMap.newInstance();
        comiCtx.put("logicalId", logicalId);
        comiCtx.put("component", component);
        comiCtx.put("task", task);
        comiCtx.put("referenceId", referenceId);
        comiCtx.put("confirmation", confirmation);
        comiCtx.put("authId", authId);
        comiCtx.put("bsrVerb", bsrVerb);
        comiCtx.put("bsrNoun", bsrNoun);
        comiCtx.put("bsrRevision", bsrRevision);
        comiCtx.put("receivedDate", UtilDateTime.nowTimestamp());
        comiCtx.put("outgoingMessage", "N");
        comiCtx.put("processingStatusId", "OAGMP_RECEIVED");
        comiCtx.put("userLogin", userLogin);
        if (OagisServices.debugSaveXmlIn) {
            try {
                comiCtx.put("fullMessageXml", UtilXml.writeXmlDocument(doc));
            } catch (IOException e) {
                // this is just for debug info, so just log and otherwise ignore error
                String errMsg = "Warning: error creating text from XML Document for saving to database: " + e.toString();
                Debug.logWarning(errMsg, module);
            }
        }
        try {
            if (isErrorRetry) {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } else {
                dispatcher.runSync("createOagisMessageInfo", comiCtx, 60, true);
            }
        } catch (GenericServiceException e) {
            String errMsg = "Error creating OagisMessageInfo for the Incoming Message: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        // data area elements
        List<? extends Element> dataAreaList = UtilXml.childElementList(syncInventoryRootElement, "ns:DATAAREA");
        if (UtilValidate.isNotEmpty(dataAreaList)) {
            try {
                for (Element dataAreaElement : dataAreaList) {
                    Element syncInventoryElement = UtilXml.firstChildElement(dataAreaElement, "ns:SYNC_INVENTORY");
                    Element inventoryElement = UtilXml.firstChildElement(syncInventoryElement, "ns:INVENTORY");

                    Element quantityElement = UtilXml.firstChildElement(inventoryElement, "os:QUANTITY");

                    String itemQtyStr = UtilXml.childElementValue(quantityElement, "of:VALUE");
                    double itemQty = Double.parseDouble(itemQtyStr);
                    /* TODO sign denoted whether quantity is accepted(+) or rejected(-), which plays role in receiving inventory
                     * In this message will it serve any purpose, since it is not handled.
                     */
                   
                    // TODOLATER: Not used now, Later we may need it
                    //String sign = UtilXml.childElementValue(quantityElement, "of:SIGN");
                    //String uom = UtilXml.childElementValue(quantityElement, "of:UOM");
                    String productId = UtilXml.childElementValue(inventoryElement, "of:ITEM");
                    String itemStatus = UtilXml.childElementValue(inventoryElement, "of:ITEMSTATUS");

                    // make sure productId is valid
                    GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
                    if (product == null) {
                        String errMsg = "Product with ID [" + productId + "] not found (invalid Product ID).";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ProductIdNotValid", "description", errMsg));
                        Debug.logError(errMsg, module);
                        continue;
                    }

                    // if anything but "NOTAVAILABLE" set to available
                    boolean isAvailable = !"NOTAVAILABLE".equals(itemStatus);
                    String statusId = "INV_AVAILABLE";
                    if (!isAvailable) {
                        statusId = "INV_ON_HOLD";
                    }

                    String snapshotDateStr = UtilXml.childElementValue(inventoryElement, "os:DATETIMEISO");
                    //Parse this into a valid Timestamp Object
                    Timestamp snapshotDate = OagisServices.parseIsoDateString(snapshotDateStr, errorMapList);

                    // get quantity on hand diff
                    double quantityOnHandTotal = 0.0;

                    // only if looking for available inventory find the non-serialized QOH total
                    if (isAvailable) {
                        EntityCondition condition = EntityCondition.makeCondition(UtilMisc.toList(
                                EntityCondition.makeCondition("effectiveDate", EntityOperator.LESS_THAN_EQUAL_TO, snapshotDate),
                                EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId),
                                EntityCondition.makeCondition("inventoryItemTypeId", EntityOperator.EQUALS, "NON_SERIAL_INV_ITEM"),
                                EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, syncInventoryFacilityId)), EntityOperator.AND);
                        List<GenericValue> invItemAndDetails = delegator.findList("InventoryItemDetailForSum", condition, UtilMisc.toSet("quantityOnHandSum"), null, null, false);
                        for (GenericValue inventoryItemDetailForSum : invItemAndDetails) {
                            quantityOnHandTotal += inventoryItemDetailForSum.getDouble("quantityOnHandSum").doubleValue();
                        }
                    }

                    // now regardless of AVAILABLE or NOTAVAILABLE check serialized inventory, just use the corresponding statusId as set above
                    EntityCondition serInvCondition = EntityCondition.makeCondition(UtilMisc.toList(
                            EntityCondition.makeCondition("statusDatetime", EntityOperator.LESS_THAN_EQUAL_TO, snapshotDate),
                            EntityCondition.makeCondition(EntityCondition.makeCondition("statusEndDatetime", EntityOperator.GREATER_THAN, snapshotDate), EntityOperator.OR, EntityCondition.makeCondition("statusEndDatetime", EntityOperator.EQUALS, null)),
                            EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId),
                            EntityCondition.makeCondition("statusId", EntityOperator.EQUALS, statusId),
                            EntityCondition.makeCondition("inventoryItemTypeId", EntityOperator.EQUALS, "SERIALIZED_INV_ITEM"),
                            EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, syncInventoryFacilityId)), EntityOperator.AND);
                    long invItemQuantCount = delegator.findCountByCondition("InventoryItemStatusForCount", serInvCondition, null, null);
                    quantityOnHandTotal += invItemQuantCount;

                    // check for mismatch in quantity
                    if (itemQty != quantityOnHandTotal) {
                        double quantityDiff = Math.abs((itemQty - quantityOnHandTotal));
                        inventoryMapList.add(UtilMisc.toMap("productId", (Object) productId, "statusId", statusId,
                                "quantityOnHandTotal", String.valueOf(quantityOnHandTotal), "quantityFromMessage", itemQtyStr,
                                "quantityDiff", String.valueOf(quantityDiff), "timestamp", snapshotDate));
                    }
                }
            } catch (Throwable t) {
                String errMsg = "Error processing Sync Inventory message: " + t.toString();
                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "Exception"));
                Debug.logInfo(t, errMsg, module);
            }
        }
        // send mail if mismatch(s) found
        if (errorMapList.size() == 0 && inventoryMapList.size() > 0) {
            try {
                // prepare information to send mail
                Map<String, Object> sendMap = FastMap.newInstance();

                String sendToEmail = UtilProperties.getPropertyValue("oagis.properties", "oagis.notification.email.sendTo");

                /* DEJ20070802 changed to get email address from properties file, should be way easier to manage
                // get facility email address
                List facilityContactMechs = null;
                GenericValue contactMech = null;
                try {
                    facilityContactMechs = delegator.findByAnd("FacilityContactMech", UtilMisc.toMap("facilityId", facilityId));
                } catch (GenericEntityException e) {
                    String errMsg = "Error Getting FacilityContactMech: " + e.toString();
                    errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "GenericEntityException", "description", errMsg));
                    Debug.logError(e, errMsg, module);
                }

                Iterator fcmIter  = facilityContactMechs.iterator();
                while (fcmIter.hasNext()) {
                    GenericValue facilityContactMech = (GenericValue) fcmIter.next();
                    String contactMechId = facilityContactMech.getString("contactMechId");
                    try {
                        contactMech = delegator.findByPrimaryKey("ContactMech", UtilMisc.toMap("contactMechId", contactMechId));
                    } catch (GenericEntityException e) {
                        String errMsg = "Error Getting ContactMech: " + e.toString();
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "GenericEntityException", "description", errMsg));
                        Debug.logError(e, errMsg, module);
                    }
                    String contactMechTypeId = contactMech.getString("contactMechTypeId");
                    if (contactMechTypeId.equals("EMAIL_ADDRESS")) {
                        String emailString = contactMech.getString("infoString");
                        sendMap.put("sendTo", emailString);
                    }
                }
                */

                if (UtilValidate.isNotEmpty(sendToEmail)) {
                    String productStoreId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.SyncInventoryProductStoreId");
                    GenericValue productStoreEmail = delegator.findByPrimaryKey("ProductStoreEmailSetting", UtilMisc.toMap("productStoreId", productStoreId, "emailType", "PRDS_OAGIS_CONFIRM"));
                    if (productStoreEmail != null) {
                        String bodyScreenLocation = productStoreEmail.getString("bodyScreenLocation");
                        sendMap.put("bodyScreenUri", bodyScreenLocation);
                    } else {
                        sendMap.put("bodyScreenUri", "component://oagis/widget/EmailOagisMessageScreens.xml#InventoryMismatchNotice");
                    }
                    if (locale == null) {
                        locale = Locale.getDefault();
                    }

                    sendMap.put("sendTo", sendToEmail);
                    sendMap.put("subject", productStoreEmail.getString("subject"));
                    sendMap.put("sendFrom", productStoreEmail.getString("fromAddress"));
                    sendMap.put("sendCc", productStoreEmail.getString("ccAddress"));
                    sendMap.put("sendBcc", productStoreEmail.getString("bccAddress"));
                    sendMap.put("contentType", productStoreEmail.getString("contentType"));

                    Map<String, Object> bodyParameters = UtilMisc.toMap("inventoryMapList", inventoryMapList, "locale", locale);
                    sendMap.put("bodyParameters", bodyParameters);
                    sendMap.put("userLogin", userLogin);

                    // send the notification
                    // run async so it will happen in the background AND so errors in sending won't mess this up
                    dispatcher.runAsync("sendMailFromScreen", sendMap, true);
                } else {
                    // no send to email address, just log to file
                    Debug.logImportant("No sendTo email address found in process oagisReceiveSyncInventory service: inventoryMapList: " + inventoryMapList, module);
                }
            } catch (Throwable t) {
                Debug.logInfo(t, "System Error processing Sync Inventory message: " + t.toString(), module);
                // in this case we don't want to return a Confirm BOD, so return an error now
                return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisErrorProcessingSyncInventory", UtilMisc.toMap("errorString", t.toString()), locale));
            }
        }

        Map<String, Object> result = FastMap.newInstance();
        result.put("logicalId", logicalId);
        result.put("component", component);
        result.put("task", task);
        result.put("referenceId", referenceId);
        result.put("userLogin", userLogin);

        if (errorMapList.size() > 0) {
            try {
                comiCtx.put("processingStatusId", "OAGMP_PROC_ERROR");
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            // call services createOagisMsgErrInfosFromErrMapList and for incoming messages oagisSendConfirmBod
            Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
            saveErrorMapListCtx.put("logicalId", logicalId);
            saveErrorMapListCtx.put("component", component);
            saveErrorMapListCtx.put("task", task);
            saveErrorMapListCtx.put("referenceId", referenceId);
            saveErrorMapListCtx.put("errorMapList", errorMapList);
            saveErrorMapListCtx.put("userLogin", userLogin);
            try {
                dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            try {
                Map<String, Object> sendConfirmBodCtx = FastMap.newInstance();
                sendConfirmBodCtx.putAll(saveErrorMapListCtx);
                // NOTE: this is different for each service, should be shipmentId or returnId or PO orderId or etc
                // for sync inventory no such ID: sendConfirmBodCtx.put("origRefId", shipmentId);

                // run async because this will send a message back to the other server and may take some time, and/or fail
                dispatcher.runAsync("oagisSendConfirmBod", sendConfirmBodCtx, null, true, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error sending Confirm BOD: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            // return success here so that the message won't be retried and the Confirm BOD, etc won't be sent multiple times
            result.putAll(ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisErrorProcessingMessage", locale)));
            return result;
        } else {
            try {
                comiCtx.put("processingStatusId", "OAGMP_PROC_SUCCESS");
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                // don't pass this back, nothing they can do about it: errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "GenericServiceException"));
                Debug.logError(e, errMsg, module);
            }
        }

        result.putAll(ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisServiceCompletedSuccessfully", locale)));
        return result;
    }

    public static Map<String, Object> oagisReceiveAcknowledgeDeliveryPo(DispatchContext ctx, Map<String, Object> context) {
        Document doc = (Document) context.get("document");
        boolean isErrorRetry = Boolean.TRUE.equals(context.get("isErrorRetry"));
        Locale locale = (Locale) context.get("locale");
        LocalDispatcher dispatcher = ctx.getDispatcher();
        Delegator delegator = ctx.getDelegator();
        List<Map<String, String>> errorMapList = FastList.newInstance();
        Map<String, Object> comiCtx = FastMap.newInstance();

        GenericValue userLogin = null;
        try {
            userLogin = delegator.findByPrimaryKey("UserLogin", UtilMisc.toMap("userLoginId", "system"));
        } catch (GenericEntityException e) {
            String errMsg = "Error Getting UserLogin: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        // parse the message
        Element receivePoElement = doc.getDocumentElement();
        receivePoElement.normalize();
        Element docCtrlAreaElement = UtilXml.firstChildElement(receivePoElement, "os:CNTROLAREA");

        Element docSenderElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:SENDER");
        Element docBsrElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:BSR");

        String bsrVerb = UtilXml.childElementValue(docBsrElement, "of:VERB");
        String bsrNoun = UtilXml.childElementValue(docBsrElement, "of:NOUN");
        String bsrRevision = UtilXml.childElementValue(docBsrElement, "of:REVISION");

        String logicalId = UtilXml.childElementValue(docSenderElement, "of:LOGICALID");
        String component = UtilXml.childElementValue(docSenderElement, "of:COMPONENT");
        String task = UtilXml.childElementValue(docSenderElement, "of:TASK"); // This field should be Not more then 10 char long
        String referenceId = UtilXml.childElementValue(docSenderElement, "of:REFERENCEID");
        String confirmation = UtilXml.childElementValue(docSenderElement, "of:CONFIRMATION");
        String authId = UtilXml.childElementValue(docSenderElement, "of:AUTHID");

        String sentDate = UtilXml.childElementValue(docCtrlAreaElement, "os:DATETIMEISO");
        Timestamp sentTimestamp = OagisServices.parseIsoDateString(sentDate, errorMapList);
        Timestamp timestamp = UtilDateTime.nowTimestamp();

        Map<String, Object> omiPkMap = UtilMisc.toMap("logicalId", (Object) logicalId, "component", component, "task", task, "referenceId", referenceId);

        // always log this to make messages easier to find
        Debug.logInfo("Processing oagisReceiveAcknowledgeDeliveryPo for message ID [" + omiPkMap + "]", module);

        // before getting into this check to see if we've tried once and had an error, if so set isErrorRetry even if it wasn't passed in
        GenericValue previousOagisMessageInfo = null;
        try {
            previousOagisMessageInfo = delegator.findByPrimaryKey("OagisMessageInfo", omiPkMap);
        } catch (GenericEntityException e) {
            String errMsg = "Error getting OagisMessageInfo from database for message ID [" + omiPkMap + "]: " + e.toString();
            Debug.logInfo(e, errMsg, module);
            // anything else to do about this? we don't really want to send the error back or anything...
        }

        if (previousOagisMessageInfo != null && !isErrorRetry) {
            if ("OAGMP_SYS_ERROR".equals(previousOagisMessageInfo.getString("processingStatusId"))) {
                isErrorRetry = true;
            } else {
                // message already in the db, but is not in a system error state...
                Debug.logError("Message received for message ID [" + omiPkMap + "] was already partially processed but is not in a system error state, needs manual review; message ID: " + omiPkMap, module);
                return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisErrorMessageAlreadyProcessed", UtilMisc.toMap("shipmentId", "", "omiPkMap", omiPkMap), locale));
            }
        }

        comiCtx.putAll(omiPkMap);
        comiCtx.put("authId", authId);
        comiCtx.put("receivedDate", timestamp);
        comiCtx.put("sentDate", sentTimestamp);
        comiCtx.put("outgoingMessage", "N");
        comiCtx.put("confirmation", confirmation);
        comiCtx.put("bsrVerb", bsrVerb);
        comiCtx.put("bsrNoun", bsrNoun);
        comiCtx.put("bsrRevision", bsrRevision);
        comiCtx.put("processingStatusId", "OAGMP_RECEIVED");
        comiCtx.put("userLogin", userLogin);
        if (OagisServices.debugSaveXmlIn) {
            try {
                comiCtx.put("fullMessageXml", UtilXml.writeXmlDocument(doc));
            } catch (IOException e) {
                // this is just for debug info, so just log and otherwise ignore error
                String errMsg = "Warning: error creating text from XML Document for saving to database: " + e.toString();
                Debug.logWarning(errMsg, module);
            }
        }
        try {
            if (isErrorRetry) {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } else {
                dispatcher.runSync("createOagisMessageInfo", comiCtx, 60, true);
            }
        } catch (GenericServiceException e) {
            String errMsg = "Error creating OagisMessageInfo for the Incoming Message: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        Element dataAreaElement = UtilXml.firstChildElement(receivePoElement, "ns:DATAAREA");
        Element acknowledgeDeliveryElement = UtilXml.firstChildElement(dataAreaElement, "ns:ACKNOWLEDGE_DELIVERY");

        String facilityId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.PoReceiptFacilityId");
        String orderId = null;
        // get RECEIPTLN elements from message
        List<? extends Element> acknowledgeElementList = UtilXml.childElementList(acknowledgeDeliveryElement, "ns:RECEIPTLN");
        if (UtilValidate.isNotEmpty(acknowledgeElementList)) {
            try {
                for (Element receiptLnElement : acknowledgeElementList) {
                    Map<String, Object> ripCtx = FastMap.newInstance();
                    Element qtyElement = UtilXml.firstChildElement(receiptLnElement, "os:QUANTITY");

                    String itemQtyStr = UtilXml.childElementValue(qtyElement, "of:VALUE");
                    double itemQty = Double.parseDouble(itemQtyStr);
                    String sign = UtilXml.childElementValue(qtyElement, "of:SIGN");

                    String productId = UtilXml.childElementValue(receiptLnElement, "of:ITEM");

                    // make sure productId is valid
                    GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
                    if (product == null) {
                        String errMsg = "Product with ID [" + productId + "] not found (invalid Product ID).";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ProductIdNotValid", "description", errMsg));
                        Debug.logError(errMsg, module);
                        continue;
                    }

                    Element documentRefElement = UtilXml.firstChildElement(receiptLnElement, "os:DOCUMNTREF");
                    orderId = UtilXml.childElementValue(documentRefElement, "of:DOCUMENTID");
                    String orderTypeId = UtilXml.childElementValue(documentRefElement, "of:DOCTYPE");
                    if (orderTypeId.equals("PO")) {
                        orderTypeId = "PURCHASE_ORDER";
                    }

                    String datetimeReceived = UtilXml.childElementValue(receiptLnElement, "os:DATETIMEISO");
                    Timestamp timestampItemReceived = OagisServices.parseIsoDateString(datetimeReceived, errorMapList);
                    ripCtx.put("datetimeReceived", timestampItemReceived);
                    // Check reference to PO number, if exists
                    GenericValue orderHeader = null;
                    if (orderId != null) {
                        List<GenericValue> toStore = FastList.newInstance();
                        orderHeader = delegator.findByPrimaryKey("OrderHeader", UtilMisc.toMap("orderId", orderId));
                        if (orderHeader != null) {
                            // Case : update the record
                            ripCtx.put("orderId", orderId);
                            comiCtx.put("orderId", orderId);
                            GenericValue orderItem = delegator.makeValue("OrderItem", UtilMisc.toMap("orderId", orderId, "productId",productId,"quantity",new Double(itemQtyStr)));
                            delegator.setNextSubSeqId(orderItem, "orderItemSeqId", 5, 1);
                            delegator.create(orderItem);
                            ripCtx.put("orderItemSeqId", orderItem.get("orderItemSeqId"));
                        } else {
                            // Case : New record entry when PO not exists in the Database
                            orderHeader =  delegator.makeValue("OrderHeader", UtilMisc.toMap("orderId", orderId, "orderTypeId",orderTypeId ,
                                    "orderDate", timestampItemReceived, "statusId", "ORDER_CREATED", "entryDate", UtilDateTime.nowTimestamp(),
                                    "productStoreId", UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.SyncInventoryProductStoreId","9001")));
                            toStore.add(orderHeader);
                            GenericValue orderItem = delegator.makeValue("OrderItem", UtilMisc.toMap("orderId", orderId,
                                    "orderItemSeqId", UtilFormatOut.formatPaddedNumber(1L, 5),
                                    "productId", productId, "quantity", new Double(itemQtyStr)));
                            toStore.add(orderItem);
                            delegator.storeAll(toStore);
                        }
                    }

                    /* NOTE DEJ20070813 this is only meant to be used in the Ack Delivery RMA message, so ignoring here and always settings status to AVAILABLE
                    // get inventory item status
                    String invItemStatus = UtilXml.childElementValue(receiptLnElement, "of:DISPOSITN");
                    if (invItemStatus.equals("ReceivedTOAvailable") || invItemStatus.equals("NotAvailableTOAvailable")) {
                        ripCtx.put("statusId","INV_AVAILABLE");
                    } else if (invItemStatus.equals("ReceivedTONotAvailable") || invItemStatus.equals("AvailableTONotAvailable")) {
                        ripCtx.put("statusId","INV_ON_HOLD");
                    }
                    */

                    ripCtx.put("statusId","INV_AVAILABLE");
                    ripCtx.put("inventoryItemTypeId", "NON_SERIAL_INV_ITEM");
                    ripCtx.put("productId", productId);
                    ripCtx.put("facilityId",facilityId);
                    ripCtx.put("userLogin", userLogin);

                    // sign handling for items
                    double quantityAccepted = 0.0;
                    double quantityRejected = 0.0;
                    if (sign.equals("+")) {
                        quantityAccepted = itemQty;
                        quantityRejected= 0.0;
                    } else {
                        quantityRejected = itemQty;
                        quantityAccepted = 0.0;
                    }
                    ripCtx.put("quantityAccepted", new Double(quantityAccepted));
                    ripCtx.put("quantityRejected", new Double(quantityRejected));
                    Map<String, Object> ripResult = dispatcher.runSync("receiveInventoryProduct", ripCtx);
                    if (ServiceUtil.isError(ripResult)) {
                        String errMsg = ServiceUtil.getErrorMessage(ripResult);
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ReceiveInventoryServiceError", "description", errMsg));
                    }
                }
            } catch (Throwable t) {
                String errMsg = UtilProperties.getMessage(resource, "OagisErrorDeliveryMessagePO", UtilMisc.toMap("omiPkMap", omiPkMap), locale) + t.toString();
                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SystemError"));

                try {
                    comiCtx.put("processingStatusId", "OAGMP_SYS_ERROR");
                    dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);

                    Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
                    saveErrorMapListCtx.putAll(omiPkMap);
                    saveErrorMapListCtx.put("errorMapList", errorMapList);
                    saveErrorMapListCtx.put("userLogin", userLogin);
                    dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
                } catch (GenericServiceException e) {
                    String errMsg2 = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                    Debug.logError(e, errMsg2, module);
                }

                Debug.logInfo(t, errMsg, module);
                // in this case we don't want to return a Confirm BOD, so return an error now
                return ServiceUtil.returnError(errMsg);
            }
        }

        Map<String, Object> result = FastMap.newInstance();
        result.put("logicalId", logicalId);
        result.put("component", component);
        result.put("task", task);
        result.put("referenceId", referenceId);
        result.put("userLogin", userLogin);

        if (errorMapList.size() > 0) {
            try {
                comiCtx.put("processingStatusId", "OAGMP_PROC_ERROR");
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            // call services createOagisMsgErrInfosFromErrMapList and for incoming messages oagisSendConfirmBod
            Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
            saveErrorMapListCtx.put("logicalId", logicalId);
            saveErrorMapListCtx.put("component", component);
            saveErrorMapListCtx.put("task", task);
            saveErrorMapListCtx.put("referenceId", referenceId);
            saveErrorMapListCtx.put("errorMapList", errorMapList);
            saveErrorMapListCtx.put("userLogin", userLogin);
            try {
                dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            try {
                Map<String, Object> sendConfirmBodCtx = FastMap.newInstance();
                sendConfirmBodCtx.putAll(saveErrorMapListCtx);
                // NOTE: this is different for each service, should be shipmentId or returnId or PO orderId or etc
                sendConfirmBodCtx.put("origRefId", orderId);

                // run async because this will send a message back to the other server and may take some time, and/or fail
                dispatcher.runAsync("oagisSendConfirmBod", sendConfirmBodCtx, null, true, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error sending Confirm BOD: " + e.toString();
                Debug.logError(e, errMsg, module);
            }
           
            // return success here so that the message won't be retried and the Confirm BOD, etc won't be sent multiple times
            String errMsg = UtilProperties.getMessage(resource, "OagisErrorBusinessLevel", UtilMisc.toMap("errorString", ""), locale) + errorMapList.get(0);
            result.putAll(ServiceUtil.returnSuccess(errMsg));

            // however, we still don't want to save the partial results, so set rollbackOnly
            try {
                TransactionUtil.setRollbackOnly(errMsg, null);
            } catch (GenericTransactionException e) {
                Debug.logError(e, "Error setting rollback only ", module);
            }

            return result;
        } else {
            comiCtx.put("processingStatusId", "OAGMP_PROC_SUCCESS");
            try {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                // don't pass this back, nothing they can do about it: errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "GenericServiceException"));
                Debug.logError(e, errMsg, module);
            }
        }

        result.putAll(ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisServiceCompletedSuccessfully", locale)));
        return result;
    }

    public static Map<String, Object> oagisReceiveAcknowledgeDeliveryRma(DispatchContext ctx, Map<String, Object> context) {
        Document doc = (Document) context.get("document");
        boolean isErrorRetry = Boolean.TRUE.equals(context.get("isErrorRetry"));
        Locale locale = (Locale) context.get("locale");
        LocalDispatcher dispatcher = ctx.getDispatcher();
        Delegator delegator = ctx.getDelegator();
        List<Map<String, String>> errorMapList = FastList.newInstance();

        GenericValue userLogin = null;
        try {
            userLogin = delegator.findByPrimaryKey("UserLogin", UtilMisc.toMap("userLoginId", "system"));
        } catch (GenericEntityException e) {
            Debug.logError(e, "Error Getting UserLogin: " + e.toString(), module);
        }

        // parse the message
        Element receiveRmaElement = doc.getDocumentElement();
        receiveRmaElement.normalize();
        Element docCtrlAreaElement = UtilXml.firstChildElement(receiveRmaElement, "os:CNTROLAREA");
        Element docSenderElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:SENDER");
        Element docBsrElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:BSR");

        String bsrVerb = UtilXml.childElementValue(docBsrElement, "of:VERB");
        String bsrNoun = UtilXml.childElementValue(docBsrElement, "of:NOUN");
        String bsrRevision = UtilXml.childElementValue(docBsrElement, "of:REVISION");

        String logicalId = UtilXml.childElementValue(docSenderElement, "of:LOGICALID");
        String component = UtilXml.childElementValue(docSenderElement, "of:COMPONENT");
        String task = UtilXml.childElementValue(docSenderElement, "of:TASK");
        String referenceId = UtilXml.childElementValue(docSenderElement, "of:REFERENCEID");
        String confirmation = UtilXml.childElementValue(docSenderElement, "of:CONFIRMATION");
        String authId = UtilXml.childElementValue(docSenderElement, "of:AUTHID");

        String sentDate = UtilXml.childElementValue(docCtrlAreaElement, "os:DATETIMEISO");
        Timestamp sentTimestamp = OagisServices.parseIsoDateString(sentDate, errorMapList);

        Element dataAreaElement = UtilXml.firstChildElement(receiveRmaElement, "ns:DATAAREA");
        Element acknowledgeDeliveryElement = UtilXml.firstChildElement(dataAreaElement, "ns:ACKNOWLEDGE_DELIVERY");

        // get the first returnId from the list so we at least have something in the info record
        Element firstReceiptlnElement = UtilXml.firstChildElement(acknowledgeDeliveryElement, "ns:RECEIPTLN");
        Element firstDocRefElement = UtilXml.firstChildElement(firstReceiptlnElement, "os:DOCUMNTREF");
        String firstReturnId = UtilXml.childElementValue(firstDocRefElement, "of:DOCUMENTID");

        String facilityId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.PoReceiptFacilityId");
        String locationSeqId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.ReturnReceiptLocationSeqId");

        Timestamp timestamp = UtilDateTime.nowTimestamp();
        Map<String, Object> comiCtx = FastMap.newInstance();

        Map<String, Object> omiPkMap = UtilMisc.toMap("logicalId", (Object) logicalId, "component", component, "task", task, "referenceId", referenceId);

        // always log this to make messages easier to find
        Debug.logInfo("Processing oagisReceiveAcknowledgeDeliveryRma for message ID [" + omiPkMap + "]", module);

        // before getting into this check to see if we've tried once and had an error, if so set isErrorRetry even if it wasn't passed in
        GenericValue previousOagisMessageInfo = null;
        try {
            previousOagisMessageInfo = delegator.findByPrimaryKey("OagisMessageInfo", omiPkMap);
        } catch (GenericEntityException e) {
            String errMsg = "Error getting OagisMessageInfo from database for message ID [" + omiPkMap + "]: " + e.toString();
            Debug.logInfo(e, errMsg, module);
            // anything else to do about this? we don't really want to send the error back or anything...
        }

        if (previousOagisMessageInfo != null && !isErrorRetry) {
            if ("OAGMP_SYS_ERROR".equals(previousOagisMessageInfo.getString("processingStatusId"))) {
                isErrorRetry = true;
            } else {
                // message already in the db, but is not in a system error state...
                Debug.logError("Message received for message ID [" + omiPkMap + "] was already partially processed but is not in a system error state, needs manual review; message ID: " + omiPkMap, module);
                return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisErrorMessageAlreadyProcessed", UtilMisc.toMap("shipmentId", "", "omiPkMap", omiPkMap), locale));
            }
        }

        comiCtx.putAll(omiPkMap);
        comiCtx.put("authId", authId);
        comiCtx.put("receivedDate", timestamp);
        comiCtx.put("sentDate", sentTimestamp);
        comiCtx.put("outgoingMessage", "N");
        comiCtx.put("confirmation", confirmation);
        comiCtx.put("bsrVerb", bsrVerb);
        comiCtx.put("bsrNoun", bsrNoun);
        comiCtx.put("bsrRevision", bsrRevision);
        comiCtx.put("processingStatusId", "OAGMP_RECEIVED");
        comiCtx.put("returnId", firstReturnId);
        comiCtx.put("userLogin", userLogin);
        if (OagisServices.debugSaveXmlIn) {
            try {
                comiCtx.put("fullMessageXml", UtilXml.writeXmlDocument(doc));
            } catch (IOException e) {
                // this is just for debug info, so just log and otherwise ignore error
                String errMsg = "Warning: error creating text from XML Document for saving to database: " + e.toString();
                Debug.logWarning(errMsg, module);
            }
        }
        try {
            if (isErrorRetry) {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } else {
                dispatcher.runSync("createOagisMessageInfo", comiCtx, 60, true);
            }
        } catch (GenericServiceException e) {
            String errMsg = "Error creating OagisMessageInfo for the Incoming Message: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        String lastReturnId = null;
        //String inventoryItemId = null;
        List<String> invItemIds = FastList.newInstance();
        // get RECEIPTLN elements from message
        List<? extends Element> receiptLineElementList = UtilXml.childElementList(acknowledgeDeliveryElement, "ns:RECEIPTLN");
        if (UtilValidate.isNotEmpty(receiptLineElementList)) {
            try {
                Map<String, String> processedStatusIdByReturnIdMap = FastMap.newInstance();

                for (Element receiptLnElement : receiptLineElementList) {
                    Map<String, Object> ripCtx = FastMap.newInstance();
                    Element qtyElement = UtilXml.firstChildElement(receiptLnElement, "os:QUANTITY");

                    String itemQtyStr = UtilXml.childElementValue(qtyElement, "of:VALUE");
                    double itemQty = Double.parseDouble(itemQtyStr);
                    String sign = UtilXml.childElementValue(qtyElement, "of:SIGN");

                    String productId = UtilXml.childElementValue(receiptLnElement, "of:ITEM");
                    if (UtilValidate.isEmpty(productId)) {
                        String errMsg = "Product ID Missing";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ProductIdMissing", "description", errMsg));
                        Debug.logError(errMsg, module);
                    }
                    // make sure productId is valid
                    GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
                    if (product == null) {
                        String errMsg = "Product with ID [" + productId + "] not found (invalid Product ID).";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ProductIdNotValid", "description", errMsg));
                        Debug.logError(errMsg, module);
                        continue;
                    }

                    Element documentRefElement = UtilXml.firstChildElement(receiptLnElement, "os:DOCUMNTREF");
                    String returnId = UtilXml.childElementValue(documentRefElement, "of:DOCUMENTID");
                    lastReturnId = returnId;
                    ripCtx.put("returnId", returnId);

                    String returnHeaderTypeId = UtilXml.childElementValue(documentRefElement, "of:DOCTYPE");
                    if (returnHeaderTypeId.equals("RMA")) {
                        returnHeaderTypeId = "CUSTOMER_RETURN";
                    }

                    String returnItemSeqId = UtilXml.childElementValue(documentRefElement, "of:LINENUM");
                    if (UtilValidate.isNotEmpty(returnItemSeqId)) {
                        // if there is a LINENUM/returnItemSeqId make sure it is valid
                        GenericValue returnItem = delegator.findByPrimaryKeyCache("ReturnItem", UtilMisc.toMap("returnId", returnId, "returnItemSeqId", returnItemSeqId));
                        if (returnItem == null) {
                            String errMsg = "Return Item with ID [" + returnId + ":" + returnItemSeqId + "] not found (invalid Return/Item ID Combination).";
                            errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ReturnAndItemIdNotValid", "description", errMsg));
                            Debug.logError(errMsg, module);
                            continue;
                        }
                    } else {
                        String errMsg = "No Return Item ID (LINENUM) found in DOCUMNTREF for Return [" + returnId + "]; this is a required field.";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ReturnItemIdLinenumMissing", "description", errMsg));
                        Debug.logError(errMsg, module);
                        continue;
                    }

                    // getting inventory item status
                    String invItemStatusId = null;
                    String disposition = UtilXml.childElementValue(receiptLnElement, "of:DISPOSITN");
                    if ("ReceivedTOAvailable".equals(disposition)) {
                        invItemStatusId = "INV_AVAILABLE";
                    } else if ("ReceivedTONotAvailable".equals(disposition)) {
                        invItemStatusId = "INV_ON_HOLD";
                    } else if ("NotAvailableTOAvailable".equals(disposition) || "AvailableTONotAvailable".equals(disposition)) {
                        // for RMA we should only get the ReceivedTO* DISPOSITN values; if we get something else we should return an error
                        String errMsg = "Got DISPOSITN value [" + disposition + "] that is not valid for RMA, only for status change.";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "DispositnNotValidForRMA", "description", errMsg));
                        continue;
                    }
                    ripCtx.put("statusId", invItemStatusId);

                    // TODOLATER: get the returnItem associated with the product received and update the receivedQuantity

                    String datetimeReceived = UtilXml.childElementValue(receiptLnElement, "os:DATETIMEISO");
                    Timestamp timestampItemReceived = OagisServices.parseIsoDateString(datetimeReceived, errorMapList);
                    ripCtx.put("datetimeReceived", timestampItemReceived);

                    GenericValue returnHeader = delegator.findByPrimaryKey("ReturnHeader", UtilMisc.toMap("returnId", returnId));

                    if (returnHeader != null) {
                        //getting ReturnHeader status
                        String statusId = returnHeader.get("statusId").toString();

                        // save this here so the status will be updated after all processed
                        processedStatusIdByReturnIdMap.put(returnId, statusId);

                        // getting the serial number(s)
                        List<String> serialNumsList = FastList.newInstance();
                        List<? extends Element> invDetailList = UtilXml.childElementList(receiptLnElement, "ns:INVDETAIL");
                        if (UtilValidate.isNotEmpty(invDetailList)) {
                            for (Element invDetailElement : invDetailList) {
                                String serialNumber = UtilXml.childElementValue(invDetailElement, "of:SERIALNUM");
                                if (UtilValidate.isNotEmpty(serialNumber)) {
                                    serialNumsList.add(serialNumber);
                                }
                            }

                            /* DEJ20070711 Commenting this out because it shouldn't happen, ie more likely the ITEM element will be filled
                             * than INVDETAIL->SERIALNUM, and this isn't a reliable way to look it up (may be more than 1 record for a given
                             * serialNumber for different products
                             // this is a Serialized Inventory Item. If the productId from the message is not valid then lets read it from InventoryItem in Ofbiz database.
                             if (productId == null || "".equals(productId)) {
                             try {
                                 GenericValue inventoryItem = EntityUtil.getFirst(delegator.findByAnd("InventoryItem", UtilMisc.toMap("serialNumber", serialNumber)));
                                 if (inventoryItem !=null) {
                                     productId = inventoryItem.getString("productId");
                                 }
                             } catch (GenericEntityException e) {
                                 String errMsg = "Error Getting Entity InventoryItem";
                                 Debug.logError(e, errMsg, module);
                            } */
                        }

                        //do some validations
                        Integer messageQuantity = Integer.valueOf(itemQtyStr);
                        if (UtilValidate.isNotEmpty(serialNumsList)) {
                            if (messageQuantity.intValue() != serialNumsList.size()) {
                                String errMsg = "Not enough serial numbers [" + serialNumsList.size() + "] for the quantity [" + messageQuantity.intValue() + "].";
                                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumbersMissing"));
                                Debug.logInfo(errMsg, module);
                                continue;
                            }
                        }

                        ripCtx.put("facilityId",facilityId);
                        ripCtx.put("locationSeqId", locationSeqId);
                        ripCtx.put("userLogin", userLogin);

                        // sign handling for items
                        double quantityAccepted = 0.0;
                        if (sign.equals("+")) {
                            quantityAccepted = itemQty;
                        } else {
                            quantityAccepted = 0.0;
                        }
                        if (quantityAccepted > 0) {
                            if (serialNumsList.size() > 0) {
                                String inventoryItemTypeId = "SERIALIZED_INV_ITEM";
                                ripCtx.put("inventoryItemTypeId", inventoryItemTypeId);

                                for (String serialNum : serialNumsList) {
                                    // also look at the productId, and associated refurb productId(s) (or other way around, we might get a refurb sku
                                    //and need to look up by the non-refurb sku); serialNumbers may not be unique globally, but should be per product
                                    Set<String> productIdSet = ProductWorker.getRefurbishedProductIdSet(productId, delegator);
                                    productIdSet.add(productId);

                                    EntityCondition bySerialNumberCondition = EntityCondition.makeCondition(EntityCondition.makeCondition("serialNumber", EntityOperator.EQUALS, serialNum),
                                            EntityOperator.AND, EntityCondition.makeCondition("productId", EntityOperator.IN, productIdSet));
                                    List<GenericValue> inventoryItemsBySerialNumber = delegator.findList("InventoryItem", bySerialNumberCondition, null, null, null, false);

                                    if (OagisServices.requireSerialNumberExist != null) {
                                        // according to requireSerialNumberExist make sure serialNumber does or does not exist in database, add an error message as needed
                                        if (OagisServices.requireSerialNumberExist.booleanValue()) {
                                            if (inventoryItemsBySerialNumber.size() == 0) {
                                                String errMsg = "Referenced serial numbers must already exist, but serial number [" + serialNum + "] was not found.";
                                                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumberRequiredButNotFound"));
                                                continue;
                                            }
                                        } else {
                                            if (inventoryItemsBySerialNumber.size() > 0) {
                                                String errMsg = "Referenced serial numbers must NOT already exist, but serial number [" + serialNum + "] already exists.";
                                                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumberRequiredNotExistButFound"));
                                                continue;
                                            }
                                        }
                                    }

                                    // TODOLATER: another fun thing to check: see if the serial number matches a serial number attached to the original return (if possible!)

                                    //clone the context as it may be changed in the call
                                    Map<String, Object> localRipCtx = FastMap.newInstance();
                                    localRipCtx.putAll(ripCtx);
                                    localRipCtx.put("quantityAccepted", new Double(1.0));
                                    // always set this to 0, if needed we'll handle the rejected quantity separately
                                    localRipCtx.put("quantityRejected", new Double(0.0));
                                    localRipCtx.put("serialNumber", serialNum);
                                    localRipCtx.put("productId", productId);
                                    localRipCtx.put("returnItemSeqId", returnItemSeqId);

                                    GenericValue inventoryItem = EntityUtil.getFirst(inventoryItemsBySerialNumber);
                                    if (inventoryItem != null) {
                                        localRipCtx.put("currentInventoryItemId", inventoryItem.getString("inventoryItemId"));
                                    }

                                    Map<String, Object> ripResult = dispatcher.runSync("receiveInventoryProduct", localRipCtx);
                                    if (ServiceUtil.isError(ripResult)) {
                                        String errMsg = ServiceUtil.getErrorMessage(ripResult);
                                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ReceiveInventoryServiceError", "description", errMsg));
                                    } else {
                                        invItemIds.add((String) ripResult.get("inventoryItemId"));
                                    }
                                }
                            } else {
                                String inventoryItemTypeId = "NON_SERIAL_INV_ITEM";
                                ripCtx.put("inventoryItemTypeId", inventoryItemTypeId);

                                // no serial numbers, just receive the quantity
                                // clone the context as it may be changted in the call
                                Map<String, Object> localRipCtx = FastMap.newInstance();
                                localRipCtx.putAll(ripCtx);
                                localRipCtx.put("quantityAccepted", new Double(quantityAccepted));
                                // always set this to 0, if needed we'll handle the rejected quantity separately
                                localRipCtx.put("quantityRejected", new Double(0.0));
                                localRipCtx.put("productId", productId);
                                localRipCtx.put("returnItemSeqId", returnItemSeqId);
                                String inventoryItemId = null;

                                Map<String, Object> ripResult = dispatcher.runSync("receiveInventoryProduct", localRipCtx);
                                if (ServiceUtil.isError(ripResult)) {
                                    String errMsg = ServiceUtil.getErrorMessage(ripResult);
                                    errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ReceiveInventoryServiceError", "description", errMsg));
                                }
                                inventoryItemId = (String) ripResult.get("inventoryItemId");

                                invItemIds.add(inventoryItemId);

                                if (("INV_ON_HOLD").equals(invItemStatusId)) {
                                    Map<String, Object> createPhysicalInvAndVarCtx = FastMap.newInstance();
                                    createPhysicalInvAndVarCtx.put("inventoryItemId", inventoryItemId);
                                    createPhysicalInvAndVarCtx.put("physicalInventoryDate", UtilDateTime.nowTimestamp());
                                    // NOTE DEJ20070815: calling damaged for now as the only option so that all will feed into a check/repair process and go into the ON_HOLD status; we should at some point change OFBiz so these can go into the ON_HOLD status without having to call them damaged
                                    createPhysicalInvAndVarCtx.put("generalComments", "Damaged, in repair");
                                    createPhysicalInvAndVarCtx.put("varianceReasonId", "VAR_DAMAGED");
                                    createPhysicalInvAndVarCtx.put("availableToPromiseVar", new Double(-quantityAccepted));
                                    createPhysicalInvAndVarCtx.put("quantityOnHandVar", new Double(0.0));
                                    createPhysicalInvAndVarCtx.put("userLogin", userLogin);
                                    Map<String, Object> cpivResult = dispatcher.runSync("createPhysicalInventoryAndVariance", createPhysicalInvAndVarCtx);
                                    if (ServiceUtil.isError(cpivResult)) {
                                        String errMsg = ServiceUtil.getErrorMessage(cpivResult);
                                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "CreatePhysicalInventoryAndVarianceServiceError", "description", errMsg));
                                    }
                                }
                            }
                        } else {
                            // TODOLATER: need to run service receiveInventoryProduct and updateInventoryItem when quantityRejected > 0
                            // NOTE DEJ20070711 this shouldn't happen for current needs, so save for later
                        }
                    } else {
                        String errMsg = "Return ID [" + returnId + "] Not Found";
                        Debug.logError(errMsg, module);
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ReturnIdNotFound", "description", errMsg));
                    }
                }

                for (Map.Entry<String, String> processedStatusIdByReturnIdEntry : processedStatusIdByReturnIdMap.entrySet()) {
                    String returnId = processedStatusIdByReturnIdEntry.getKey();
                    String statusId = processedStatusIdByReturnIdEntry.getValue();

                    if (UtilValidate.isNotEmpty(statusId) && statusId.equals("RETURN_ACCEPTED")) {
                        // check to see if all return items have been received, if so then set to received then completed

                        // NOTE: an alternative method would be to see if the total has been received for each
                        //ReturnItem (receivedQuantity vs returnQuantity), but we may have a hard time matching
                        //those up so that information is probably not as reliable; another note: as of 20070821
                        //the receipt processing code properly updates ReturnItem with receivedQuantity and status
                        //so we _could_ possibly move to that method if needed

                        // loop through ReturnItem records, get totals for each productId
                        Map<String, Double> returnQuantityByProductIdMap = FastMap.newInstance();
                        List<GenericValue> returnItemList = delegator.findByAnd("ReturnItem", UtilMisc.toMap("returnId", returnId));
                        for (GenericValue returnItem : returnItemList) {
                            String productId = returnItem.getString("productId");
                            Double returnQuantityDbl = returnItem.getDouble("returnQuantity");
                            if (UtilValidate.isNotEmpty(productId) && returnQuantityDbl != null) {
                                double newTotal = returnQuantityDbl.doubleValue();
                                Double existingTotal = returnQuantityByProductIdMap.get(productId);
                                if (existingTotal != null) newTotal += existingTotal.doubleValue();
                                returnQuantityByProductIdMap.put(productId, new Double(newTotal));
                            }
                        }

                        // set to true, if we find any that aren't fully received, will set to false
                        boolean fullReturnReceived = true;

                        // for each productId see if total received is equal to the total for that ID
                        for (Map.Entry<String, Double> returnQuantityByProductIdEntry : returnQuantityByProductIdMap.entrySet()) {
                            String productId = returnQuantityByProductIdEntry.getKey();
                            double returnQuantity = returnQuantityByProductIdEntry.getValue();

                            double receivedQuantity = 0;
                            // note no facilityId because we don't really care where the return items were received
                            List<GenericValue> shipmentReceiptList = delegator.findByAnd("ShipmentReceipt", UtilMisc.toMap("productId", productId, "returnId", returnId));
                            // NOTE only consider those with a quantityOnHandDiff > 0 so we just look at how many have been received, not what was actually done with them
                            for (GenericValue shipmentReceipt : shipmentReceiptList) {
                                Double quantityAccepted = shipmentReceipt.getDouble("quantityAccepted");
                                if (quantityAccepted != null && quantityAccepted.doubleValue() > 0) {
                                    receivedQuantity += quantityAccepted.doubleValue();
                                }
                            }

                            if (receivedQuantity < returnQuantity) {
                                fullReturnReceived = false;
                                break;
                            } else if (receivedQuantity > returnQuantity) {
                                // TODOLATER: we received MORE than expected... what to do about that?!?
                                String warnMsg = "Received more [" + receivedQuantity + "] than were expected on return [" + returnQuantity + "] for Return ID [" + returnId + "] and Product ID [" + productId + "]";
                                warnMsg = warnMsg + "; still completing return, but something should be done with these extras!";
                                Debug.logWarning(warnMsg, module);
                                // even with that, allow it to go through and complete the return
                            }
                        }

                        if (fullReturnReceived) {
                            dispatcher.runSync("updateReturnHeader", UtilMisc.<String, Object>toMap("statusId", "RETURN_RECEIVED", "returnId", returnId, "userLogin", userLogin));
                            dispatcher.runSync("updateReturnHeader", UtilMisc.<String, Object>toMap("statusId", "RETURN_COMPLETED", "returnId", returnId, "userLogin", userLogin));
                        }
                    }
                }
            } catch (Throwable t) {
                String errMsg = "System Error processing Acknowledge Delivery RMA message for message [" + omiPkMap + "]: " + t.toString();
                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SystemError"));

                try {
                    comiCtx.put("processingStatusId", "OAGMP_SYS_ERROR");
                    dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);

                    Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
                    saveErrorMapListCtx.putAll(omiPkMap);
                    saveErrorMapListCtx.put("errorMapList", errorMapList);
                    saveErrorMapListCtx.put("userLogin", userLogin);
                    dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
                } catch (GenericServiceException e) {
                    String errMsg2 = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                    Debug.logError(e, errMsg2, module);
                }

                Debug.logInfo(t, errMsg, module);
                // in this case we don't want to return a Confirm BOD, so return an error now
                return ServiceUtil.returnError(errMsg);
            }
        }

        Map<String, Object> result = FastMap.newInstance();
        result.put("logicalId", logicalId);
        result.put("component", component);
        result.put("task", task);
        result.put("referenceId", referenceId);
        result.put("userLogin", userLogin);

        if (errorMapList.size() > 0) {
            try {
                comiCtx.put("processingStatusId", "OAGMP_PROC_ERROR");
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            // call services createOagisMsgErrInfosFromErrMapList and for incoming messages oagisSendConfirmBod
            Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
            saveErrorMapListCtx.put("logicalId", logicalId);
            saveErrorMapListCtx.put("component", component);
            saveErrorMapListCtx.put("task", task);
            saveErrorMapListCtx.put("referenceId", referenceId);
            saveErrorMapListCtx.put("errorMapList", errorMapList);
            saveErrorMapListCtx.put("userLogin", userLogin);
            try {
                dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            try {
                Map<String, Object> sendConfirmBodCtx = FastMap.newInstance();
                sendConfirmBodCtx.putAll(saveErrorMapListCtx);
                // NOTE: this is different for each service, should be shipmentId or returnId or PO orderId or etc
                // TODO: unfortunately there could be multiple returnIds for the message, so what to do...?
                sendConfirmBodCtx.put("origRefId", lastReturnId);

                // run async because this will send a message back to the other server and may take some time, and/or fail
                dispatcher.runAsync("oagisSendConfirmBod", sendConfirmBodCtx, null, true, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error sending Confirm BOD: " + e.toString();
                Debug.logError(e, errMsg, module);
            }
            String errMsg = "Found business level errors in message processing, not saving results; first error is: " + errorMapList.get(0);

            // return success here so that the message won't be retried and the Confirm BOD, etc won't be sent multiple times
            result.putAll(ServiceUtil.returnSuccess(errMsg));

            // however, we still don't want to save the partial results, so set rollbackOnly
            try {
                TransactionUtil.setRollbackOnly(errMsg, null);
            } catch (GenericTransactionException e) {
                Debug.logError(e, "Error setting rollback only ", module);
            }

            return result;
        } else {
            comiCtx.put("processingStatusId", "OAGMP_PROC_SUCCESS");
            try {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                // don't pass this back, nothing they can do about it: errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "GenericServiceException"));
                Debug.logError(e, errMsg, module);
            }
        }

        result.putAll(ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisServiceCompletedSuccessfully", locale)));
        result.put("inventoryItemIdList", invItemIds);
        return result;
    }

    public static Map<String, Object> oagisReceiveAcknowledgeDeliveryStatus(DispatchContext ctx, Map<String, Object> context) {
        Document doc = (Document) context.get("document");
        boolean isErrorRetry = Boolean.TRUE.equals(context.get("isErrorRetry"));
        Locale locale = (Locale) context.get("locale");
        LocalDispatcher dispatcher = ctx.getDispatcher();
        Delegator delegator = ctx.getDelegator();
        List<Map<String, String>> errorMapList = FastList.newInstance();

        GenericValue userLogin = null;
        try {
            userLogin = delegator.findByPrimaryKey("UserLogin", UtilMisc.toMap("userLoginId", "system"));
        } catch (GenericEntityException e) {
            String errMsg = "Error Getting UserLogin: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        // parse the message
        Element receiveStatusElement = doc.getDocumentElement();
        receiveStatusElement.normalize();
        Element docCtrlAreaElement = UtilXml.firstChildElement(receiveStatusElement, "os:CNTROLAREA");
        Element docSenderElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:SENDER");
        Element docBsrElement = UtilXml.firstChildElement(docCtrlAreaElement, "os:BSR");

        String bsrVerb = UtilXml.childElementValue(docBsrElement, "of:VERB");
        String bsrNoun = UtilXml.childElementValue(docBsrElement, "of:NOUN");
        String bsrRevision = UtilXml.childElementValue(docBsrElement, "of:REVISION");

        String logicalId = UtilXml.childElementValue(docSenderElement, "of:LOGICALID");
        String component = UtilXml.childElementValue(docSenderElement, "of:COMPONENT");
        String task = UtilXml.childElementValue(docSenderElement, "of:TASK");
        String referenceId = UtilXml.childElementValue(docSenderElement, "of:REFERENCEID");
        String confirmation = UtilXml.childElementValue(docSenderElement, "of:CONFIRMATION");
        String authId = UtilXml.childElementValue(docSenderElement, "of:AUTHID");

        String sentDate = UtilXml.childElementValue(docCtrlAreaElement, "os:DATETIMEISO");
        Timestamp sentTimestamp = OagisServices.parseIsoDateString(sentDate, errorMapList);

        Element dataAreaElement = UtilXml.firstChildElement(receiveStatusElement, "ns:DATAAREA");
        Element acknowledgeDeliveryElement = UtilXml.firstChildElement(dataAreaElement, "ns:ACKNOWLEDGE_DELIVERY");

        String facilityId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.PoReceiptFacilityId");
        String locationSeqId = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Warehouse.ReturnReceiptLocationSeqId");

        Timestamp timestamp = UtilDateTime.nowTimestamp();
        Map<String, Object> comiCtx = FastMap.newInstance();

        Map<String, Object> omiPkMap = UtilMisc.toMap("logicalId", (Object) logicalId, "component", component, "task", task, "referenceId", referenceId);

        // always log this to make messages easier to find
        Debug.logInfo("Processing oagisReceiveAcknowledgeDeliveryStatus for message ID [" + omiPkMap + "]", module);

        // before getting into this check to see if we've tried once and had an error, if so set isErrorRetry even if it wasn't passed in
        GenericValue previousOagisMessageInfo = null;
        try {
            previousOagisMessageInfo = delegator.findByPrimaryKey("OagisMessageInfo", omiPkMap);
        } catch (GenericEntityException e) {
            String errMsg = "Error getting OagisMessageInfo from database for message ID [" + omiPkMap + "]: " + e.toString();
            Debug.logInfo(e, errMsg, module);
            // anything else to do about this? we don't really want to send the error back or anything...
        }

        if (previousOagisMessageInfo != null && !isErrorRetry) {
            if ("OAGMP_SYS_ERROR".equals(previousOagisMessageInfo.getString("processingStatusId"))) {
                isErrorRetry = true;
            } else {
                // message already in the db, but is not in a system error state...
                Debug.logError("Message received for message ID [" + omiPkMap + "] was already partially processed but is not in a system error state, needs manual review; message ID: " + omiPkMap, module);
                return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisErrorMessageAlreadyProcessed", UtilMisc.toMap("shipmentId", "", "omiPkMap", omiPkMap), locale));
            }
        }

        comiCtx.putAll(omiPkMap);
        comiCtx.put("authId", authId);
        comiCtx.put("receivedDate", timestamp);
        comiCtx.put("sentDate", sentTimestamp);
        comiCtx.put("outgoingMessage", "N");
        comiCtx.put("confirmation", confirmation);
        comiCtx.put("bsrVerb", bsrVerb);
        comiCtx.put("bsrNoun", bsrNoun);
        comiCtx.put("bsrRevision", bsrRevision);
        comiCtx.put("processingStatusId", "OAGMP_RECEIVED");
        comiCtx.put("userLogin", userLogin);
        if (OagisServices.debugSaveXmlIn) {
            try {
                comiCtx.put("fullMessageXml", UtilXml.writeXmlDocument(doc));
            } catch (IOException e) {
                // this is just for debug info, so just log and otherwise ignore error
                String errMsg = "Warning: error creating text from XML Document for saving to database: " + e.toString();
                Debug.logWarning(errMsg, module);
            }
        }
        try {
            if (isErrorRetry) {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } else {
                dispatcher.runSync("createOagisMessageInfo", comiCtx, 60, true);
            }
        } catch (GenericServiceException e) {
            String errMsg = "Error creating OagisMessageInfo for the Incoming Message: " + e.toString();
            Debug.logError(e, errMsg, module);
        }

        //String inventoryItemId = null;
        List<String> invItemIds = FastList.newInstance();
        // get RECEIPTLN elements from message
        List<? extends Element> receiptLineElementList = UtilXml.childElementList(acknowledgeDeliveryElement, "ns:RECEIPTLN");
        if (UtilValidate.isNotEmpty(receiptLineElementList)) {
            try {
                for (Element receiptLnElement : receiptLineElementList) {
                    Map<String, Object> uiiCtx = FastMap.newInstance();
                    Element qtyElement = UtilXml.firstChildElement(receiptLnElement, "os:QUANTITY");

                    String itemQtyStr = UtilXml.childElementValue(qtyElement, "of:VALUE");
                    String sign = UtilXml.childElementValue(qtyElement, "of:SIGN");

                    String productId = UtilXml.childElementValue(receiptLnElement, "of:ITEM");
                    if (UtilValidate.isEmpty(productId)) {
                        String errMsg = "Product ID Missing";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ProductIdMissing", "description", errMsg));
                        Debug.logError(errMsg, module);
                    }
                    // make sure productId is valid
                    GenericValue product = delegator.findByPrimaryKeyCache("Product", UtilMisc.toMap("productId", productId));
                    if (product == null) {
                        String errMsg = "Product with ID [" + productId + "] not found (invalid Product ID).";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ProductIdNotValid", "description", errMsg));
                        Debug.logError(errMsg, module);
                        continue;
                    }

                    /* no place to put this in the InventoryItem update
                    String datetimeReceived = UtilXml.childElementValue(receiptLnElement, "os:DATETIMEISO");
                    Timestamp timestampItemReceived = OagisServices.parseIsoDateString(datetimeReceived, errorMapList);
                    uiiCtx.put("datetimeReceived", timestampItemReceived);
                    */

                    String invItemStatusId = null;
                    String reqFromItemStatusId = null;
                    String disposition = UtilXml.childElementValue(receiptLnElement, "of:DISPOSITN");
                    if ("NotAvailableTOAvailable".equals(disposition)) {
                        invItemStatusId = "INV_AVAILABLE";
                        reqFromItemStatusId = "INV_ON_HOLD";
                    } else if ("AvailableTONotAvailable".equals(disposition)) {
                        invItemStatusId = "INV_ON_HOLD";
                        reqFromItemStatusId = "INV_AVAILABLE";
                    } else if ("ReceivedTOAvailable".equals(disposition) || "ReceivedTONotAvailable".equals(disposition)) {
                        String errMsg = "Got DISPOSITN value [" + disposition + "] that is not valid for Status Change, only for RMA/return.";
                        errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "DispositnNotValidForStatusChange", "description", errMsg));
                        continue;
                    }

                    uiiCtx.put("statusId", invItemStatusId);

                    // geting the serial number(s)
                    List<String> serialNumsList = FastList.newInstance();
                    List<? extends Element> invDetailList = UtilXml.childElementList(receiptLnElement, "ns:INVDETAIL");
                    if (UtilValidate.isNotEmpty(invDetailList)) {
                        for (Element invDetailElement : invDetailList) {
                            String serialNumber = UtilXml.childElementValue(invDetailElement, "of:SERIALNUM");
                            if (UtilValidate.isNotEmpty(serialNumber)) {
                                serialNumsList.add(serialNumber);
                            }
                        }
                    }

                    //do some validations
                    Integer messageQuantity = Integer.valueOf(itemQtyStr);
                    if (UtilValidate.isNotEmpty(serialNumsList)) {
                        if (messageQuantity.intValue() != serialNumsList.size()) {
                            String errMsg = "Not enough serial numbers [" + serialNumsList.size() + "] for the quantity [" + messageQuantity.intValue() + "].";
                            errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumbersMissing"));
                            Debug.logInfo(errMsg, module);
                            continue;
                        }
                    }

                    uiiCtx.put("facilityId",facilityId);
                    uiiCtx.put("locationSeqId", locationSeqId);
                    uiiCtx.put("userLogin", userLogin);

                    // sign handling for items
                    if (!"+".equals(sign)) {
                        String errMsg = "Got a sign [" + sign + "] that was not plus (+), this is not valid for a Status Change operation.";
                        errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SignNotPlusForStatusChange"));
                        continue;
                    }

                    if (serialNumsList.size() > 0) {
                        String inventoryItemTypeId = "SERIALIZED_INV_ITEM";
                        uiiCtx.put("inventoryItemTypeId", inventoryItemTypeId);

                        for (String serialNum : serialNumsList) {
                            // also look at the productId, and associated refurb productId(s) (or other way around, we might get a refurb sku
                            //and need to look up by the non-refurb sku); serialNumbers may not be unique globally, but should be per product
                            Set<String> productIdSet = ProductWorker.getRefurbishedProductIdSet(productId, delegator);
                            productIdSet.add(productId);

                            EntityCondition bySerialNumberCondition = EntityCondition.makeCondition(EntityCondition.makeCondition("serialNumber", EntityOperator.EQUALS, serialNum),
                                    EntityOperator.AND, EntityCondition.makeCondition("productId", EntityOperator.IN, productIdSet));
                            List<GenericValue> inventoryItemsBySerialNumber = delegator.findList("InventoryItem", bySerialNumberCondition, null, null, null, false);

                            // this is a status update, so referenced serial number MUST already exist
                            if (inventoryItemsBySerialNumber.size() == 0) {
                                String errMsg = "Referenced serial numbers must already exist, but serial number [" + serialNum + "] was not found.";
                                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumberRequiredButNotFound"));
                                continue;
                            }

                            GenericValue inventoryItem = EntityUtil.getFirst(inventoryItemsBySerialNumber);
                            if (UtilValidate.isNotEmpty(reqFromItemStatusId) && !reqFromItemStatusId.equals(inventoryItem.getString("statusId"))) {
                                String errMsg = "Referenced serial number [" + serialNum + "] has status [" + inventoryItem.getString("statusId") + "] but we were expecting [" + reqFromItemStatusId + "]; this may mean the Acknowledge Delivery RMA message has not yet come in for this item.";
                                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumberRequiredButNotFound"));
                                continue;
                            }

                            Map<String, Object> updateInvItmMap = FastMap.newInstance();
                            updateInvItmMap.put("inventoryItemId", inventoryItem.getString("inventoryItemId"));
                            updateInvItmMap.put("userLogin", userLogin);
                            updateInvItmMap.put("statusId", invItemStatusId);
                            String inventoryItemProductId = inventoryItem.getString("productId");
                            if (!inventoryItemProductId.equals(productId)) {
                                // got a new productId for the serial number; this may happen for refurbishment, etc
                                updateInvItmMap.put("productId",productId);
                            }
                            dispatcher.runSync("updateInventoryItem", updateInvItmMap);
                            invItemIds.add(inventoryItem.getString("inventoryItemId"));
                        }
                    } else {
                        String inventoryItemTypeId = "NON_SERIAL_INV_ITEM";
                        uiiCtx.put("inventoryItemTypeId", inventoryItemTypeId);

                        // TODO: later somehow do status changes for non-serialized inventory

                        // for now just return an error message
                        String errMsg = "No serial numbers were included in the message and right now this is not supported";
                        errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "NoSerialNumbersInMessage"));
                    }
                }
            } catch (Throwable t) {
                String errMsg = "System Error processing Acknowledge Delivery Status message for message [" + omiPkMap + "]: " + t.toString();
                errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SystemError"));

                try {
                    comiCtx.put("processingStatusId", "OAGMP_SYS_ERROR");
                    dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);

                    Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
                    saveErrorMapListCtx.putAll(omiPkMap);
                    saveErrorMapListCtx.put("errorMapList", errorMapList);
                    saveErrorMapListCtx.put("userLogin", userLogin);
                    dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
                } catch (GenericServiceException e) {
                    String errMsg2 = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                    Debug.logError(e, errMsg2, module);
                }

                Debug.logInfo(t, errMsg, module);
                // in this case we don't want to return a Confirm BOD, so return an error now
                return ServiceUtil.returnError(errMsg);
            }
        }

        Map<String, Object> result = FastMap.newInstance();
        result.putAll(omiPkMap);
        result.put("userLogin", userLogin);

        if (errorMapList.size() > 0) {
            try {
                comiCtx.put("processingStatusId", "OAGMP_PROC_ERROR");
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            // call services createOagisMsgErrInfosFromErrMapList and for incoming messages oagisSendConfirmBod
            Map<String, Object> saveErrorMapListCtx = FastMap.newInstance();
            saveErrorMapListCtx.putAll(omiPkMap);
            saveErrorMapListCtx.put("errorMapList", errorMapList);
            saveErrorMapListCtx.put("userLogin", userLogin);
            try {
                dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            try {
                Map<String, Object> sendConfirmBodCtx = FastMap.newInstance();
                sendConfirmBodCtx.putAll(saveErrorMapListCtx);

                // run async because this will send a message back to the other server and may take some time, and/or fail
                dispatcher.runAsync("oagisSendConfirmBod", sendConfirmBodCtx, null, true, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error sending Confirm BOD: " + e.toString();
                Debug.logError(e, errMsg, module);
            }

            String errMsg = "Found business level errors in message processing, not saving results; first error is: " + errorMapList.get(0);

            // return success here so that the message won't be retried and the Confirm BOD, etc won't be sent multiple times
            result.putAll(ServiceUtil.returnSuccess(errMsg));

            // however, we still don't want to save the partial results, so set rollbackOnly
            try {
                TransactionUtil.setRollbackOnly(errMsg, null);
            } catch (GenericTransactionException e) {
                Debug.logError(e, "Error setting rollback only ", module);
            }

            return result;
        } else {
            comiCtx.put("processingStatusId", "OAGMP_PROC_SUCCESS");
            try {
                dispatcher.runSync("updateOagisMessageInfo", comiCtx, 60, true);
            } catch (GenericServiceException e) {
                String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString();
                // don't pass this back, nothing they can do about it: errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "GenericServiceException"));
                Debug.logError(e, errMsg, module);
            }
        }

        result.putAll(ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisServiceCompletedSuccessfully", locale)));
        result.put("inventoryItemIdList", invItemIds);
        return result;
    }
}
TOP

Related Classes of org.ofbiz.oagis.OagisInventoryServices

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.