| /* |
| 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.io.OutputStream; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| 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 javolution.util.FastSet; |
| |
| import org.ofbiz.base.util.Debug; |
| import org.ofbiz.base.util.GeneralException; |
| import org.ofbiz.base.util.StringUtil; |
| import org.ofbiz.base.util.UtilDateTime; |
| import org.ofbiz.base.util.UtilFormatOut; |
| import org.ofbiz.base.util.UtilGenerics; |
| 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.base.util.collections.MapStack; |
| 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.order.order.OrderReadHelper; |
| import org.ofbiz.party.party.PartyWorker; |
| 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.ofbiz.widget.fo.FoFormRenderer; |
| import org.ofbiz.widget.html.HtmlScreenRenderer; |
| import org.ofbiz.widget.screen.ScreenRenderer; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| |
| |
| public class OagisShipmentServices { |
| |
| public static final String module = OagisShipmentServices.class.getName(); |
| |
| protected static final HtmlScreenRenderer htmlScreenRenderer = new HtmlScreenRenderer(); |
| protected static final FoFormRenderer foFormRenderer = new FoFormRenderer(); |
| |
| public static final Set<String> invalidShipmentStatusSet = UtilMisc.toSet("SHIPMENT_CANCELLED", "SHIPMENT_PICKED", "SHIPMENT_PACKED", |
| "SHIPMENT_SHIPPED", "SHIPMENT_DELIVERED"); |
| |
| public static final String resource = "OagisUiLabels"; |
| |
| public static final String certAlias = UtilProperties.getPropertyValue("oagis.properties", "auth.client.certificate.alias"); |
| public static final String basicAuthUsername = UtilProperties.getPropertyValue("oagis.properties", "auth.basic.username"); |
| public static final String basicAuthPassword = UtilProperties.getPropertyValue("oagis.properties", "auth.basic.password"); |
| |
| public static final String oagisMainNamespacePrefix = "n"; |
| public static final String oagisSegmentsNamespacePrefix = "os"; |
| public static final String oagisFieldsNamespacePrefix = "of"; |
| |
| public static Map<String, Object> oagisReceiveShowShipment(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(); |
| Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); |
| 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 with userLoginId system: "+e.toString(); |
| Debug.logError(e, errMsg, module); |
| } |
| |
| Element showShipmentElement = doc.getDocumentElement(); |
| showShipmentElement.normalize(); |
| |
| Element controlAreaElement = UtilXml.firstChildElement(showShipmentElement, "os:CNTROLAREA"); // os |
| Element bsrElement = UtilXml.firstChildElement(controlAreaElement, "os:BSR"); // os |
| String bsrVerb = UtilXml.childElementValue(bsrElement, "of:VERB"); // of |
| String bsrNoun = UtilXml.childElementValue(bsrElement, "of:NOUN"); // of |
| String bsrRevision = UtilXml.childElementValue(bsrElement, "of:REVISION"); // of |
| Map<String, Object> oagisMsgInfoCtx = UtilMisc.toMap("bsrVerb", (Object) bsrVerb, "bsrNoun", bsrNoun, "bsrRevision", bsrRevision); |
| |
| Element senderElement = UtilXml.firstChildElement(controlAreaElement, "os:SENDER"); // os |
| String logicalId = UtilXml.childElementValue(senderElement, "of:LOGICALID"); // of |
| String component = UtilXml.childElementValue(senderElement, "of:COMPONENT"); // of |
| String task = UtilXml.childElementValue(senderElement, "of:TASK"); // of |
| String referenceId = UtilXml.childElementValue(senderElement, "of:REFERENCEID"); // of |
| String confirmation = UtilXml.childElementValue(senderElement, "of:CONFIRMATION"); // of |
| String authId = UtilXml.childElementValue(senderElement, "of:AUTHID"); // of |
| |
| String sentDate = UtilXml.childElementValue(controlAreaElement, "os:DATETIMEISO"); |
| Timestamp sentTimestamp = OagisServices.parseIsoDateString(sentDate, errorMapList); |
| |
| Element dataAreaElement = UtilXml.firstChildElement(showShipmentElement, "ns:DATAAREA"); // n |
| Element daShowShipmentElement = UtilXml.firstChildElement(dataAreaElement, "ns:SHOW_SHIPMENT"); // n |
| Element shipmentElement = UtilXml.firstChildElement(daShowShipmentElement, "ns:SHIPMENT"); // n |
| String shipmentId = UtilXml.childElementValue(shipmentElement, "of:DOCUMENTID"); // of |
| |
| Map<String, String> omiPkMap = UtilMisc.toMap("logicalId", logicalId, "component", component, "task", task, "referenceId", referenceId); |
| |
| // always log this to make messages easier to find |
| Debug.logInfo("Processing oagisReceiveShowShipment for shipmentId [" + shipmentId + "] 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 shipment ID [" + shipmentId + "] 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 shipmentId [" + shipmentId + "] 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", shipmentId, "omiPkMap", omiPkMap), locale)); |
| } |
| } |
| |
| oagisMsgInfoCtx.putAll(omiPkMap); |
| oagisMsgInfoCtx.put("confirmation", confirmation); |
| oagisMsgInfoCtx.put("authId", authId); |
| oagisMsgInfoCtx.put("outgoingMessage", "N"); |
| oagisMsgInfoCtx.put("receivedDate", nowTimestamp); |
| oagisMsgInfoCtx.put("sentDate", sentTimestamp); |
| oagisMsgInfoCtx.put("shipmentId", shipmentId); |
| oagisMsgInfoCtx.put("userLogin", userLogin); |
| oagisMsgInfoCtx.put("processingStatusId", "OAGMP_RECEIVED"); |
| if (OagisServices.debugSaveXmlIn) { |
| try { |
| oagisMsgInfoCtx.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", oagisMsgInfoCtx, 60, true); |
| } else { |
| dispatcher.runSync("createOagisMessageInfo", oagisMsgInfoCtx, 60, true); |
| } |
| /* running async for better error handling |
| if (ServiceUtil.isError(oagisMsgInfoResult)) { |
| String errMsg = ServiceUtil.getErrorMessage(oagisMsgInfoResult); |
| // errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "CreateOagisMessageInfoServiceError")); |
| Debug.logError(errMsg, module); |
| } |
| */ |
| } catch (GenericServiceException e) { |
| String errMsg = "Error creating 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); |
| } |
| |
| GenericValue shipment = null; |
| try { |
| shipment = delegator.findByPrimaryKey("Shipment", UtilMisc.toMap("shipmentId", shipmentId)); |
| } catch (GenericEntityException e) { |
| String errMsg = "Error getting Shipment from database for ID [" + shipmentId + "]: " + e.toString(); |
| Debug.logInfo(e, errMsg, module); |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "GenericEntityException")); |
| } |
| |
| if (shipment == null) { |
| String errMsg = "Could not find Shipment ID [" + shipmentId + "]"; |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "ShipmentIdNotValid")); |
| } else { |
| if (invalidShipmentStatusSet.contains(shipment.get("statusId"))) { |
| String errMsg = "Shipment with ID [" + shipmentId + "] is in a status [" + shipment.get("statusId") + "] that means it has been or is being shipped, so this Show Shipment message may be a duplicate."; |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "ShipmentInBadStatus")); |
| } |
| } |
| |
| List<? extends Element> shipUnitElementList = UtilXml.childElementList(daShowShipmentElement, "ns:SHIPUNIT"); // n |
| if (errorMapList.size() == 0 && UtilValidate.isNotEmpty(shipUnitElementList)) { |
| try { |
| String shipGroupSeqId = shipment.getString("primaryShipGroupSeqId"); |
| String originFacilityId = shipment.getString("originFacilityId"); |
| |
| Element shipUnitFirstElement = shipUnitElementList.get(0); |
| String trackingNum = UtilXml.childElementValue(shipUnitFirstElement, "of:TRACKINGID"); // of |
| String carrierCode = UtilXml.childElementValue(shipUnitFirstElement, "of:CARRIER"); // of |
| if (UtilValidate.isNotEmpty(carrierCode)) { |
| String carrierPartyId = null; |
| if (carrierCode.startsWith("F") || carrierCode.startsWith("f")) { |
| carrierPartyId = "FEDEX"; |
| } else if (carrierCode.startsWith("U")|| carrierCode.startsWith("u")) { |
| carrierPartyId = "UPS"; |
| } |
| Map<String, Object> resultMap = dispatcher.runSync("updateShipmentRouteSegment", UtilMisc.<String, Object>toMap("shipmentId", shipmentId, |
| "shipmentRouteSegmentId", "00001", "carrierPartyId", carrierPartyId, "trackingIdNumber", trackingNum, "userLogin", userLogin)); |
| if (ServiceUtil.isError(resultMap)) { |
| String errMsg = ServiceUtil.getErrorMessage(resultMap); |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "updateShipmentRouteSegmentError")); |
| Debug.logError(errMsg, module); |
| } |
| } |
| |
| // TODO: looks like we may have multiple shipunits for a single productId, AND with things split into |
| //multiple package we may need to sort by the packages by productId to avoid deadlocks, just like we |
| //do with INVITEMs... note sure exactly how that will work |
| |
| for (Element shipUnitElement : shipUnitElementList) { |
| String shipmentPackageSeqId = UtilXml.childElementValue(shipUnitElement, "of:SHPUNITSEQ"); // of |
| List<? extends Element> invItemElementList = UtilXml.childElementList(shipUnitElement, "ns:INVITEM"); //n |
| if (UtilValidate.isNotEmpty(invItemElementList)) { |
| |
| // sort the INVITEM elements by ITEM so that all shipments are processed in the same order, avoids deadlocking problems we've seen with concurrently processed orders |
| List<Map<String, Object>> invitemMapList = FastList.newInstance(); |
| boolean foundBadProductId = false; |
| for (Element invItemElement : invItemElementList) { |
| String productId = UtilXml.childElementValue(invItemElement, "of:ITEM"); // of |
| |
| // 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); |
| foundBadProductId = true; |
| continue; |
| } |
| |
| Map<String, Object> invitemMap = FastMap.newInstance(); |
| invitemMap.put("productId", productId); |
| // support multiple INVITEM elements for a given productId |
| UtilMisc.addToListInMap(invItemElement, invitemMap, "invItemElementList"); |
| invitemMapList.add(invitemMap); |
| } |
| |
| if (foundBadProductId) { |
| continue; |
| } |
| |
| invitemMapList = UtilGenerics.cast( |
| UtilMisc.sortMaps(UtilGenerics.<List<Map<Object, Object>>>cast(invitemMapList), UtilMisc.toList("productId"))); |
| |
| for (Map<String, Object> invitemMap : invitemMapList) { |
| List<Element> localInvItemElementList = UtilGenerics.checkList(invitemMap.get("invItemElementList"), Element.class); |
| |
| for (Element invItemElement : localInvItemElementList) { |
| String productId = UtilXml.childElementValue(invItemElement, "of:ITEM"); // of |
| |
| // this is based on the SHPUNIT which is basically a box/package, but we'll try to find the item with it if applicable |
| String possibleShipmentItemSeqId = null; |
| if (UtilValidate.isNotEmpty(shipmentPackageSeqId)) { |
| possibleShipmentItemSeqId = UtilFormatOut.formatPaddedNumber(Long.parseLong(shipmentPackageSeqId), 5); |
| } |
| |
| Element quantityElement = UtilXml.firstChildElement(invItemElement, "os:QUANTITY"); // os |
| String quantityValueStr = UtilXml.childElementValue(quantityElement, "of:VALUE"); // os |
| // TODO: <of:NUMOFDEC>0</of:NUMOFDEC> should always be 0, but might want to add code to check |
| Integer messageQuantity = Integer.valueOf(quantityValueStr); |
| |
| // do a few things to try to find the ShipmentItem corresponding to the INVITEM |
| List<GenericValue> shipmentItemList = null; |
| |
| // try getting it by the unit number, which is bogus but can be what some try IFF there is only one INVITEM in the SHPUNIT |
| if (invitemMapList.size() == 1 && localInvItemElementList.size() == 1 && UtilValidate.isNotEmpty(possibleShipmentItemSeqId)) { |
| GenericValue shipmentItem = delegator.findByPrimaryKey("ShipmentItem", UtilMisc.toMap("shipmentId", shipmentId, "shipmentItemSeqId", possibleShipmentItemSeqId)); |
| if (shipmentItem != null && !productId.equals(shipmentItem.getString("productId"))) { |
| // found an item, but it was for the wrong Product! |
| shipmentItem = null; |
| } |
| if (shipmentItem != null) { |
| Debug.logInfo("For Shipment [" + shipmentId + "] found ShipmentItem based on Package/Unit ID, possibleShipmentItemSeqId is [" + possibleShipmentItemSeqId + "]", module); |
| shipmentItemList = UtilMisc.toList(shipmentItem); |
| } |
| } |
| |
| if (UtilValidate.isEmpty(shipmentItemList)) { |
| shipmentItemList = delegator.findByAnd("ShipmentItem", UtilMisc.toMap("shipmentId", shipmentId, "productId",productId)); |
| if (UtilValidate.isEmpty(shipmentItemList)) { |
| String errMsg = "Could not find Shipment Item for Shipment with ID [" + shipmentId + "] and Product with ID [" + productId + "]."; |
| errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "ShipmentItemForProductNotFound", "description", errMsg)); |
| Debug.logError(errMsg, module); |
| continue; |
| } |
| |
| // try to isolate it to one item, ie find the first in the list that matches the quantity |
| //AND that has not already been used/issued |
| for (GenericValue shipmentItem : shipmentItemList) { |
| if (messageQuantity.intValue() == shipmentItem.getDouble("quantity").intValue()) { |
| // see if there is an ItemIssuance for this ShipmentItem, ie has already had inventory issued to it |
| //if so then move on, this isn't the ShipmentItem you want |
| List<GenericValue> itemIssuanceList = delegator.findByAnd("ItemIssuance", |
| UtilMisc.toMap("shipmentId", shipmentId, "shipmentItemSeqId", shipmentItem.get("shipmentItemSeqId"))); |
| if (itemIssuanceList.size() == 0) { |
| // found a match, set the list to be a new list with just this item and then break |
| shipmentItemList = UtilMisc.toList(shipmentItem); |
| break; |
| } |
| } |
| } |
| } |
| |
| // TODO: if there is more than one shipmentItem, what to do? split quantity somehow? |
| // for now just get the first item, the other scenario is not yet supported |
| if (shipmentItemList.size() > 1) { |
| String errMsg = "Could not find single Shipment Item for Shipment with ID [" + shipmentId + "] and Product with ID [" + productId + "], found [" + shipmentItemList.size() + "] and could not narrow down to one."; |
| errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "SingleShipmentItemForProductNotFound", "description", errMsg)); |
| Debug.logError(errMsg, module); |
| continue; |
| } |
| GenericValue shipmentItem = shipmentItemList.get(0); |
| |
| String shipmentItemSeqId = shipmentItem.getString("shipmentItemSeqId"); |
| GenericValue orderShipment = EntityUtil.getFirst(delegator.findByAnd("OrderShipment", UtilMisc.toMap("shipmentId", shipmentId, "shipmentItemSeqId", shipmentItemSeqId))); |
| if (orderShipment == null) { |
| String errMsg = "Could not find Order-Shipment record for ShipmentItem with ID [" + shipmentId + "] and Item Seq-ID [" + shipmentItemSeqId + "]."; |
| errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "OrderShipmentNotFound", "description", errMsg)); |
| Debug.logError(errMsg, module); |
| continue; |
| } |
| |
| String orderId = orderShipment.getString("orderId"); |
| String orderItemSeqId = orderShipment.getString("orderItemSeqId"); |
| GenericValue product = delegator.findByPrimaryKey("Product",UtilMisc.toMap("productId", productId)); |
| String requireInventory = product.getString("requireInventory"); |
| if (requireInventory == null) { |
| requireInventory = "N"; |
| } |
| |
| // NOTE: there could be more than one reservation record for a given shipment item? for example if there wasn't enough quantity in one inventory item and reservations on two were needed |
| List<GenericValue> orderItemShipGrpInvReservationList = delegator.findByAnd("OrderItemShipGrpInvRes", |
| UtilMisc.toMap("orderId", orderId, "orderItemSeqId", orderItemSeqId,"shipGroupSeqId",shipGroupSeqId)); |
| |
| // find the total quantity for all reservations |
| int totalReserved = 0; |
| for (GenericValue orderItemShipGrpInvReservation : orderItemShipGrpInvReservationList) { |
| if (orderItemShipGrpInvReservation.getDouble("quantity") != null) { |
| totalReserved += orderItemShipGrpInvReservation.getDouble("quantity").doubleValue(); |
| } |
| } |
| |
| List<String> serialNumberList = FastList.newInstance(); |
| List<? extends Element> invDetailElementList = UtilXml.childElementList(invItemElement, "ns:INVDETAIL"); //n |
| for (Element invDetailElement : invDetailElementList) { |
| String serialNumber = UtilXml.childElementValue(invDetailElement, "of:SERIALNUM"); // os |
| if (UtilValidate.isNotEmpty(serialNumber)) { |
| serialNumberList.add(serialNumber); |
| } |
| } |
| |
| // do some validations |
| if (UtilValidate.isNotEmpty(serialNumberList)) { |
| if (messageQuantity.intValue() != serialNumberList.size()) { |
| String errMsg = "Error: the quantity in the message [" + messageQuantity.intValue() + "] did not match the number of serial numbers passed [" + serialNumberList.size() + "] for ShipmentItem with ID [" + shipmentId + "] and Item Seq-ID [" + shipmentItemSeqId + "]."; |
| errorMapList.add(UtilMisc.<String, String>toMap("reasonCode", "QuantitySerialMismatch", "description", errMsg)); |
| Debug.logInfo(errMsg, module); |
| continue; |
| } |
| } |
| |
| // because there may be more than one ShipmentItem for an OrderItem allow there to be more inventory reservations for the |
| //OrderItem than there is quantity on the current ShipmentItem |
| if (totalReserved < messageQuantity.intValue()) { |
| String errMsg = "Inventory reservation quantity [" + totalReserved + "] was less than the message quantity [" + messageQuantity.intValue() + "] so cannot receive against reservations for ShipmentItem with ID [" + shipmentId + ":" + shipmentItemSeqId + "], and OrderItem [" + orderShipment.getString("orderId") + ":" + orderShipment.getString("orderItemSeqId") + "]"; |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumbersMissing")); |
| Debug.logInfo(errMsg, module); |
| continue; |
| } |
| |
| // just receive quantity for this ShipmentItem |
| int quantityLeft; |
| int shipmentItemQuantity = shipmentItem.getDouble("quantity").intValue(); |
| if (shipmentItemQuantity <= messageQuantity.intValue()) { |
| quantityLeft = shipmentItemQuantity; |
| } else { |
| quantityLeft = messageQuantity.intValue(); |
| } |
| |
| |
| for (GenericValue orderItemShipGrpInvReservation : orderItemShipGrpInvReservationList) { |
| if (quantityLeft <= 0) { |
| break; |
| } |
| int currentInvResQuantity = orderItemShipGrpInvReservation.getDouble("quantity").intValue(); |
| |
| int quantityToUse; |
| if (quantityLeft > currentInvResQuantity) { |
| quantityToUse = currentInvResQuantity; |
| quantityLeft -= currentInvResQuantity; |
| } else { |
| quantityToUse = quantityLeft; |
| quantityLeft = 0; |
| } |
| |
| Map<String, Object> isitspastCtx = UtilMisc.toMap("orderId", (Object) orderId, "shipGroupSeqId", shipGroupSeqId, |
| "orderItemSeqId", orderItemSeqId); |
| isitspastCtx.put("productId", productId); |
| isitspastCtx.put("reservedDatetime", orderItemShipGrpInvReservation.get("reservedDatetime")); |
| isitspastCtx.put("requireInventory", requireInventory); |
| isitspastCtx.put("reserveOrderEnumId", orderItemShipGrpInvReservation.get("reserveOrderEnumId")); |
| isitspastCtx.put("sequenceId", orderItemShipGrpInvReservation.get("sequenceId")); |
| isitspastCtx.put("originFacilityId", originFacilityId); |
| isitspastCtx.put("userLogin", userLogin); |
| isitspastCtx.put("trackingNum", trackingNum); |
| isitspastCtx.put("inventoryItemId", orderItemShipGrpInvReservation.get("inventoryItemId")); |
| isitspastCtx.put("shipmentId", shipmentId); |
| isitspastCtx.put("shipmentPackageSeqId", shipmentPackageSeqId); |
| isitspastCtx.put("promisedDatetime", orderItemShipGrpInvReservation.get("promisedDatetime")); |
| |
| if (UtilValidate.isNotEmpty(serialNumberList)) { |
| for (int i = 0; i < quantityToUse; i++) { |
| String serialNumber = serialNumberList.get(i); |
| |
| if (OagisServices.requireSerialNumberExist != null) { |
| // according to requireSerialNumberExist make sure serialNumber does or does not exist in database, add an error message as needed |
| Set<String> productIdSet = ProductWorker.getRefurbishedProductIdSet(productId, delegator); |
| productIdSet.add(productId); |
| |
| EntityCondition bySerialNumberCondition = EntityCondition.makeCondition(EntityCondition.makeCondition("serialNumber", EntityOperator.EQUALS, serialNumber), |
| EntityOperator.AND, EntityCondition.makeCondition("productId", EntityOperator.IN, productIdSet)); |
| List<GenericValue> inventoryItemsBySerialNumber = delegator.findList("InventoryItem", bySerialNumberCondition, null, null, null, false); |
| if (OagisServices.requireSerialNumberExist.booleanValue()) { |
| if (inventoryItemsBySerialNumber.size() == 0) { |
| String errMsg = "Referenced serial numbers must already exist, but serial number [" + serialNumber + "] was not found. Product ID(s) considered are: " + productIdSet; |
| 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 [" + serialNumber + "] already exists. Product ID(s) considered are: " + productIdSet; |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SerialNumberRequiredNotExistButFound")); |
| continue; |
| } |
| } |
| } |
| |
| isitspastCtx.put("serialNumber", serialNumber); |
| isitspastCtx.put("quantity", new Double (1)); |
| isitspastCtx.put("inventoryItemId", orderItemShipGrpInvReservation.get("inventoryItemId")); |
| isitspastCtx.remove("itemIssuanceId"); |
| Map<String, Object> resultMap = dispatcher.runSync("issueSerializedInvToShipmentPackageAndSetTracking", isitspastCtx); |
| if (ServiceUtil.isError(resultMap)) { |
| String errMsg = ServiceUtil.getErrorMessage(resultMap); |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "IssueSerializedInvServiceError")); |
| Debug.logError(errMsg, module); |
| } |
| } |
| } else { |
| isitspastCtx.put("quantity", new Double(quantityToUse)); |
| // NOTE: this same service is called for non-serialized inventory in spite of the name it is made to handle it |
| Map<String, Object> resultMap = dispatcher.runSync("issueSerializedInvToShipmentPackageAndSetTracking", isitspastCtx); |
| if (ServiceUtil.isError(resultMap)) { |
| String errMsg = ServiceUtil.getErrorMessage(resultMap); |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "IssueSerializedInvServiceError")); |
| Debug.logError(errMsg, module); |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (errorMapList.size() == 0) { |
| // NOTTODOLATER: to support mulitple and partial Show Shipment messages per shipment: |
| //check here if the entire shipment has been issues, ie there should be sufficient |
| //ItemIssuance quantities for the ShipmentItem quantities |
| // NOTE ON THIS DEJ20070906: this is actually really bad because it implies the shipment |
| //has been split and that isn't really allowed; maybe better to return an error! |
| |
| List<GenericValue> shipmentItemList = delegator.findByAnd("ShipmentItem", UtilMisc.toMap("shipmentId", shipmentId)); |
| for (GenericValue shipmentItem : shipmentItemList) { |
| int shipmentItemQuantity = shipmentItem.getDouble("quantity").intValue(); |
| |
| int totalItemIssuanceQuantity = 0; |
| List<GenericValue> itemIssuanceList = delegator.findByAnd("ItemIssuance", UtilMisc.toMap("shipmentId", shipmentId, "shipmentItemSeqId", |
| shipmentItem.get("shipmentItemSeqId"))); |
| for (GenericValue itemIssuance : itemIssuanceList) { |
| totalItemIssuanceQuantity += itemIssuance.getDouble("quantity").intValue(); |
| } |
| |
| if (shipmentItemQuantity > totalItemIssuanceQuantity) { |
| String errMsg = "ShipmentItem [" + shipmentId + ":" + shipmentItem.get("shipmentItemSeqId") + "] was not completely fulfilled; shipment item quantity was [" + shipmentItemQuantity + "], but total fulfilled is only [" + totalItemIssuanceQuantity + "]"; |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "ShipmentItemNotCompletelyFulfilled")); |
| Debug.logError(errMsg, module); |
| } |
| } |
| } |
| |
| if (errorMapList.size() == 0) { |
| Map<String, Object> resultMap = dispatcher.runSync("setShipmentStatusPackedAndShipped", |
| UtilMisc.toMap("shipmentId", shipmentId, "userLogin", userLogin)); |
| if (ServiceUtil.isError(resultMap)) { |
| String errMsg = ServiceUtil.getErrorMessage(resultMap); |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SetShipmentStatusPackedAndShippedError")); |
| Debug.logError(errMsg, module); |
| } |
| } |
| } catch (Throwable t) { |
| String errMsg = UtilProperties.getMessage(resource, "OagisErrorMessageShowShipment", UtilMisc.toMap("shipmentId", shipmentId, "omiPkMap", omiPkMap), locale); |
| errorMapList.add(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SystemError")); |
| |
| try { |
| oagisMsgInfoCtx.put("processingStatusId", "OAGMP_SYS_ERROR"); |
| dispatcher.runSync("updateOagisMessageInfo", oagisMsgInfoCtx, 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 + t.toString()); |
| } |
| } |
| |
| Map<String, Object> result = FastMap.newInstance(); |
| result.putAll(omiPkMap); |
| result.put("userLogin", userLogin); |
| |
| if (errorMapList.size() > 0) { |
| try { |
| oagisMsgInfoCtx.put("processingStatusId", "OAGMP_PROC_ERROR"); |
| dispatcher.runSync("updateOagisMessageInfo", oagisMsgInfoCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString(); |
| Debug.logError(e, errMsg, module); |
| } |
| |
| try { |
| // 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); |
| 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(omiPkMap); |
| sendConfirmBodCtx.put("errorMapList", errorMapList); |
| sendConfirmBodCtx.put("userLogin", userLogin); |
| // NOTE: this is different for each service, should be shipmentId or returnId or PO orderId or etc |
| 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 |
| String errMsg = UtilProperties.getMessage(resource, "OagisErrorBusinessLevel", UtilMisc.toMap("errorString", ""), locale) + errorMapList.get(0).toString(); |
| 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 { |
| try { |
| oagisMsgInfoCtx.put("processingStatusId", "OAGMP_PROC_SUCCESS"); |
| dispatcher.runSync("updateOagisMessageInfo", oagisMsgInfoCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = "Error updating OagisMessageInfo for the Incoming Message: " + e.toString(); |
| Debug.logError(e, errMsg, module); |
| } |
| } |
| |
| result.putAll(ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisServiceCompletedSuccessfully", locale))); |
| return result; |
| } |
| |
| public static Map<String, Object> oagisSendProcessShipmentsFromBackOrderSet(DispatchContext ctx, Map<String, Object> context) { |
| LocalDispatcher dispatcher = ctx.getDispatcher(); |
| Locale locale = (Locale) context.get("locale"); |
| Set<String> noLongerOnBackOrderIdSet = UtilGenerics.checkSet(context.get("noLongerOnBackOrderIdSet"), String.class); |
| Debug.logInfo("Running oagisSendProcessShipmentsFromBackOrderSet with noLongerOnBackOrderIdSet=" + noLongerOnBackOrderIdSet, module); |
| if (UtilValidate.isEmpty(noLongerOnBackOrderIdSet)) { |
| return ServiceUtil.returnSuccess(); |
| } |
| |
| try { |
| for (String orderId : noLongerOnBackOrderIdSet) { |
| dispatcher.runAsync("oagisSendProcessShipment", UtilMisc.toMap("orderId", orderId), true); |
| } |
| } catch (GenericServiceException e) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisErrorOagisSendProcessShipment", UtilMisc.toMap("errorString", e.toString()), locale)); |
| } |
| |
| return ServiceUtil.returnSuccess(); |
| } |
| |
| public static Map<String, Object> oagisSendProcessShipment(DispatchContext ctx, Map<String, Object> context) { |
| LocalDispatcher dispatcher = ctx.getDispatcher(); |
| Delegator delegator = ctx.getDelegator(); |
| String orderId = (String) context.get("orderId"); |
| Locale locale = (Locale) context.get("locale"); |
| |
| // Check if order is not on back order before processing shipment |
| try { |
| Map<String, Object> checkOrderResp = dispatcher.runSync("checkOrderIsOnBackOrder", UtilMisc.toMap("orderId", orderId)); |
| if (((Boolean) checkOrderResp.get("isBackOrder")).booleanValue()) { |
| Debug.logWarning("Order [" + orderId + "] is on back order, cannot Process Shipment", module); |
| return ServiceUtil.returnSuccess(); |
| } |
| } catch (GenericServiceException e) { |
| Debug.logError(e, module); |
| return ServiceUtil.returnError(e.getMessage()); |
| } |
| |
| String sendToUrl = (String) context.get("sendToUrl"); |
| if (UtilValidate.isEmpty(sendToUrl)) { |
| sendToUrl = UtilProperties.getPropertyValue("oagis.properties", "url.send.processShipment"); |
| } |
| |
| String saveToFilename = (String) context.get("saveToFilename"); |
| if (UtilValidate.isEmpty(saveToFilename)) { |
| String saveToFilenameBase = UtilProperties.getPropertyValue("oagis.properties", "test.save.outgoing.filename.base", ""); |
| if (UtilValidate.isNotEmpty(saveToFilenameBase)) { |
| saveToFilename = saveToFilenameBase + "ProcessShipment" + orderId + ".xml"; |
| } |
| } |
| String saveToDirectory = (String) context.get("saveToDirectory"); |
| if (UtilValidate.isEmpty(saveToDirectory)) { |
| saveToDirectory = UtilProperties.getPropertyValue("oagis.properties", "test.save.outgoing.directory"); |
| } |
| |
| OutputStream out = (OutputStream) context.get("outputStream"); |
| |
| if (Debug.infoOn()) Debug.logInfo("Call to oagisSendProcessShipment for orderId [" + orderId + "], sendToUrl=[" + sendToUrl + "], saveToDirectory=[" + saveToDirectory + "], saveToFilename=[" + saveToFilename + "]", module); |
| |
| Map<String, Object> result = ServiceUtil.returnSuccess(); |
| MapStack<String> bodyParameters = MapStack.create(); |
| bodyParameters.put("orderId", orderId); |
| |
| // the userLogin passed in will usually be the customer, so don't use it; use the system user instead |
| GenericValue userLogin = null; |
| try { |
| userLogin = delegator.findByPrimaryKey("UserLogin", UtilMisc.toMap("userLoginId", "system")); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting userLogin", module); |
| } |
| |
| GenericValue orderHeader = null; |
| GenericValue orderItemShipGroup = null; |
| |
| String logicalId = UtilProperties.getPropertyValue("oagis.properties", "CNTROLAREA.SENDER.LOGICALID"); |
| String referenceId = null; |
| String task = "SHIPREQUEST"; // Actual value of task is "SHIPREQUEST" which is more than 10 char, need this in the db so it will match Confirm BODs, etc |
| String component = "INVENTORY"; |
| Map<String, String> omiPkMap = null; |
| |
| String shipmentId = null; |
| |
| try { |
| // see if there are any OagisMessageInfo for this order that are in the OAGMP_OGEN_SUCCESS or OAGMP_SENT statuses, if so don't send again; these need to be manually reviewed before resending to avoid accidental duplicate messages |
| List<GenericValue> previousOagisMessageInfoList = delegator.findByAnd("OagisMessageInfo", |
| UtilMisc.toMap("orderId", orderId, "task", task, "component", component)); |
| if (EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_OGEN_SUCCESS")).size() > 0) { |
| // this isn't really an error, just a failed constraint so return success |
| return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisFoundExistingMessage", UtilMisc.toMap("orderId", orderId), locale) + EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_OGEN_SUCCESS"))); |
| } |
| if (EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_SENT")).size() > 0) { |
| // this isn't really an error, just a failed constraint so return success |
| return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisFoundExistingMessageSent", UtilMisc.toMap("orderId", orderId), locale) + EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_SENT"))); |
| } |
| |
| orderHeader = delegator.findByPrimaryKey("OrderHeader", UtilMisc.toMap("orderId", orderId)); |
| if (orderHeader == null) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisOrderIdNotFound", UtilMisc.toMap("orderId", orderId), locale)); |
| } |
| |
| List<String> validStores = StringUtil.split(UtilProperties.getPropertyValue("oagis.properties", "Oagis.Order.ValidProductStores"), ","); |
| if (UtilValidate.isNotEmpty(validStores)) { |
| if (!validStores.contains(orderHeader.getString("productStoreId"))) { |
| return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisOrderIdNotValidStore", UtilMisc.toMap("orderId", orderId), locale)); |
| } |
| } |
| String orderStatusId = orderHeader.getString("statusId"); |
| if (!"ORDER_APPROVED".equals(orderStatusId)) { |
| return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisOrderIdNotInApprovedStatus", UtilMisc.toMap("orderId", orderId, "orderStatusId", orderStatusId), locale)); |
| } |
| if (!"SALES_ORDER".equals(orderHeader.getString("orderTypeId"))) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisOrderIdNotASalesOrder", UtilMisc.toMap("orderId", orderId), locale)); |
| } |
| |
| // first check some things... |
| OrderReadHelper orderReadHelper = new OrderReadHelper(orderHeader); |
| // before doing or saving anything see if any OrderItems are Products with isPhysical=Y |
| if (!orderReadHelper.hasPhysicalProductItems()) { |
| // no need to process shipment, return success |
| return ServiceUtil.returnSuccess(); |
| } |
| if (!orderReadHelper.hasShippingAddress()) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisOrderIdWithoutShippingAddress", UtilMisc.toMap("orderId", orderId), locale)); |
| } |
| |
| // check payment authorization |
| Map<String, Object> authServiceContext = FastMap.newInstance(); |
| authServiceContext.put("orderId", orderId); |
| authServiceContext.put("userLogin", userLogin); |
| authServiceContext.put("reAuth", true); |
| Map<String, Object> authResult = dispatcher.runSync("authOrderPayments", authServiceContext); |
| if (!authResult.get("processResult").equals("APPROVED")) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisOrderIdPaymentNotAuthorized", locale)); |
| } |
| |
| referenceId = delegator.getNextSeqId("OagisMessageInfo"); |
| omiPkMap = UtilMisc.toMap("logicalId", logicalId, "component", component, "task", task, "referenceId", referenceId); |
| |
| String authId = UtilProperties.getPropertyValue("oagis.properties", "CNTROLAREA.SENDER.AUTHID"); |
| Timestamp timestamp = UtilDateTime.nowTimestamp(); |
| String sentDate = OagisServices.isoDateFormat.format(timestamp); |
| |
| bodyParameters.putAll(omiPkMap); |
| bodyParameters.put("authId", authId); |
| bodyParameters.put("sentDate", sentDate); |
| |
| // prepare map to Create Oagis Message Info |
| try { |
| Map<String, Object> comiCtx = FastMap.newInstance(); |
| comiCtx.putAll(omiPkMap); |
| comiCtx.put("processingStatusId", "OAGMP_TRIGGERED"); |
| comiCtx.put("outgoingMessage", "Y"); |
| comiCtx.put("confirmation", "1"); |
| comiCtx.put("bsrVerb", "PROCESS"); |
| comiCtx.put("bsrNoun", "SHIPMENT"); |
| comiCtx.put("bsrRevision", "001"); |
| comiCtx.put("orderId", orderId); |
| comiCtx.put("sentDate", timestamp); |
| comiCtx.put("authId", authId); |
| comiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("createOagisMessageInfo", comiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| if (Debug.infoOn()) Debug.logInfo("Saved OagisMessageInfo for oagisSendProcessShipment message for orderId [" + orderId + "]", module); |
| |
| // check to see if there is already a Shipment for this order |
| EntityCondition findShipmentCondition = EntityCondition.makeCondition(UtilMisc.toList( |
| EntityCondition.makeCondition("primaryOrderId", EntityOperator.EQUALS, orderId), |
| EntityCondition.makeCondition("statusId", EntityOperator.NOT_EQUAL, "SHIPMENT_CANCELLED") |
| ), EntityOperator.AND); |
| List<GenericValue> shipmentList = delegator.findList("Shipment", findShipmentCondition, null, null, null, false); |
| GenericValue shipment = EntityUtil.getFirst(shipmentList); |
| |
| if (shipment != null) { |
| // if picked, packed, shipped, delivered then complain, no reason to process the shipment! |
| String statusId = shipment.getString("statusId"); |
| if ("SHIPMENT_PICKED".equals(statusId) || "SHIPMENT_PACKED".equals(statusId) || "SHIPMENT_SHIPPED".equals(statusId) || "SHIPMENT_DELIVERED".equals(statusId)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisOrderIdWithShipment", UtilMisc.toMap("statusId", statusId), locale)); |
| } |
| shipmentId = shipment.getString("shipmentId"); |
| } else { |
| Map<String, Object> cospResult= dispatcher.runSync("createOrderShipmentPlan", UtilMisc.<String, Object>toMap("orderId", orderId, "userLogin", userLogin)); |
| shipmentId = (String) cospResult.get("shipmentId"); |
| shipment = delegator.findByPrimaryKey("Shipment", UtilMisc.toMap("shipmentId", shipmentId)); |
| } |
| |
| bodyParameters.put("shipment", shipment); |
| List<GenericValue> shipmentItems = delegator.findByAnd("ShipmentItem", UtilMisc.toMap("shipmentId", shipmentId)); |
| bodyParameters.put("shipmentItems", shipmentItems); |
| |
| GenericValue address = EntityUtil.getFirst(orderReadHelper.getShippingLocations()); |
| bodyParameters.put("address", address); |
| String emailString = orderReadHelper.getOrderEmailString(); |
| bodyParameters.put("emailString", emailString); |
| String contactMechId = shipment.getString("destinationTelecomNumberId"); |
| |
| GenericValue telecomNumber = delegator.findByPrimaryKey("TelecomNumber", UtilMisc.toMap("contactMechId", contactMechId)); |
| if (telecomNumber == null) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisOrderIdNotTelecomNumberFound", UtilMisc.toMap("orderId", orderId), locale)); |
| } |
| bodyParameters.put("telecomNumber", telecomNumber); |
| |
| orderItemShipGroup = EntityUtil.getFirst(delegator.findByAnd("OrderItemShipGroup", UtilMisc.toMap("orderId", orderId))); |
| bodyParameters.put("orderItemShipGroup", orderItemShipGroup); |
| Set<String> correspondingPoIdSet = FastSet.newInstance(); |
| |
| List<GenericValue> orderItems = orderReadHelper.getOrderItems(); |
| for (GenericValue orderItem : orderItems) { |
| String correspondingPoId = orderItem.getString("correspondingPoId"); |
| if (correspondingPoId != null) { |
| correspondingPoIdSet.add(correspondingPoId); |
| } |
| } |
| bodyParameters.put("correspondingPoIdSet", correspondingPoIdSet); |
| if (orderHeader.get("externalId") != null) { |
| Set<String> externalIdSet = FastSet.newInstance(); |
| externalIdSet.add(orderHeader.getString("externalId")); |
| bodyParameters.put("externalIdSet", externalIdSet); |
| } |
| // Check if order was a return replacement order (associated with return) |
| GenericValue returnItemResponse = EntityUtil.getFirst(delegator.findByAnd("ReturnItemResponse", UtilMisc.toMap("replacementOrderId", orderId))); |
| if (returnItemResponse != null) { |
| boolean includeReturnLabel = false; |
| |
| // Get the associated return Id (replaceReturnId) |
| String returnItemResponseId = returnItemResponse.getString("returnItemResponseId"); |
| List<GenericValue> returnItemList = delegator.findByAnd("ReturnItem", UtilMisc.toMap("returnItemResponseId", returnItemResponseId)); |
| GenericValue firstReturnItem = EntityUtil.getFirst(returnItemList); |
| if (firstReturnItem != null) { |
| bodyParameters.put("replacementReturnId", firstReturnItem.getString("returnId")); |
| } else { |
| Debug.logWarning("Could not find a ReturnItem for returnItemResponseId [" + returnItemResponseId + "]; this really shouldn't happen but isn't a real error either. It means a ReturnItemResponse was created but not attached to any item!", module); |
| } |
| |
| // return label should only be sent when we want a return label to be included; this would be for a cross-ship replacement type ReturnItem |
| |
| // go through the returnItemList and if any are cross-ship replacement, then include a label (not for wait replacement in other words) |
| for (GenericValue returnItem : returnItemList) { |
| if ("RTN_CSREPLACE".equals(returnItem.getString("returnTypeId"))) { |
| includeReturnLabel = true; |
| } |
| } |
| |
| if (includeReturnLabel) { |
| bodyParameters.put("shipnotes", "RETURNLABEL"); |
| } |
| |
| } |
| // tracking shipper account, other Party info |
| String partyId = shipment.getString("partyIdTo"); |
| bodyParameters.put("partyNameView", delegator.findByPrimaryKey("PartyNameView", UtilMisc.toMap("partyId", partyId))); |
| List<GenericValue> partyCarrierAccounts = delegator.findByAnd("PartyCarrierAccount", UtilMisc.toMap("partyId", partyId)); |
| partyCarrierAccounts = EntityUtil.filterByDate(partyCarrierAccounts); |
| if (partyCarrierAccounts != null) { |
| for (GenericValue partyCarrierAccount : partyCarrierAccounts) { |
| String carrierPartyId = partyCarrierAccount.getString("carrierPartyId"); |
| if (carrierPartyId.equals(orderItemShipGroup.getString("carrierPartyId"))) { |
| String accountNumber = partyCarrierAccount.getString("accountNumber"); |
| bodyParameters.put("shipperId", accountNumber); |
| } |
| } |
| } |
| |
| bodyParameters.put("shipmentId", shipmentId); |
| bodyParameters.put("orderId", orderId); |
| bodyParameters.put("userLogin", userLogin); |
| |
| String bodyScreenUri = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Template.ProcessShipment"); |
| String outText = null; |
| Writer writer = new StringWriter(); |
| ScreenRenderer screens = new ScreenRenderer(writer, bodyParameters, htmlScreenRenderer); |
| screens.render(bodyScreenUri); |
| writer.close(); |
| outText = writer.toString(); |
| if (Debug.infoOn()) Debug.logInfo("Finished rendering oagisSendProcessShipment message for orderId [" + orderId + "]", module); |
| |
| try { |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_OGEN_SUCCESS"); |
| uomiCtx.put("shipmentId", shipmentId); |
| uomiCtx.put("userLogin", userLogin); |
| if (OagisServices.debugSaveXmlOut) { |
| uomiCtx.put("fullMessageXml", outText); |
| } |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| |
| Map<String, Object> sendMessageReturn = OagisServices.sendMessageText(outText, out, sendToUrl, saveToDirectory, saveToFilename, locale); |
| if (sendMessageReturn != null && ServiceUtil.isError(sendMessageReturn)) { |
| try { |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_SEND_ERROR"); |
| uomiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| return sendMessageReturn; |
| } |
| |
| if (Debug.infoOn()) Debug.logInfo("Message send done for oagisSendProcessShipment for orderId [" + orderId + "], sendToUrl=[" + sendToUrl + "], saveToDirectory=[" + saveToDirectory + "], saveToFilename=[" + saveToFilename + "]", module); |
| try { |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_SENT"); |
| uomiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| } catch (Throwable t) { |
| String errMsg = UtilProperties.getMessage(resource, "OagisErrorProcessShipment", UtilMisc.toMap("orderId", orderId, "shipmentId", shipmentId, "omiPkMap", omiPkMap), locale) + t.toString(); |
| Debug.logError(t, errMsg, module); |
| |
| // if we have a referenceId and the omiPkMap not null, save the error status |
| if (omiPkMap != null) { |
| try { |
| // only do this if there is a record already in place |
| if (delegator.findByPrimaryKey("OagisMessageInfo", omiPkMap) == null) { |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_SYS_ERROR"); |
| uomiCtx.put("bsrVerb", "PROCESS"); |
| uomiCtx.put("bsrNoun", "SHIPMENT"); |
| uomiCtx.put("orderId", orderId); |
| uomiCtx.put("shipmentId", shipmentId); |
| uomiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| |
| List<Map<String, String>> errorMapList = UtilMisc.toList(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SystemError")); |
| Map<String, Object> saveErrorMapListCtx = FastMap.newInstance(); |
| saveErrorMapListCtx.putAll(omiPkMap); |
| saveErrorMapListCtx.put("errorMapList", errorMapList); |
| saveErrorMapListCtx.put("userLogin", userLogin); |
| dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true); |
| } catch (GeneralException e) { |
| String errMsg2 = "Error saving message error info: " + e.toString(); |
| Debug.logError(e, errMsg2, module); |
| } |
| } |
| |
| return ServiceUtil.returnError(errMsg); |
| } |
| return result; |
| } |
| |
| public static Map<String, Object> oagisSendReceiveDelivery(DispatchContext dctx, Map<String, Object> context) { |
| LocalDispatcher dispatcher = dctx.getDispatcher(); |
| Delegator delegator = dctx.getDelegator(); |
| String returnId = (String) context.get("returnId"); |
| Locale locale = (Locale) context.get("locale"); |
| String sendToUrl = (String) context.get("sendToUrl"); |
| if (UtilValidate.isEmpty(sendToUrl)) { |
| sendToUrl = UtilProperties.getPropertyValue("oagis.properties", "url.send.receiveDelivery"); |
| } |
| |
| String saveToFilename = (String) context.get("saveToFilename"); |
| if (UtilValidate.isEmpty(saveToFilename)) { |
| String saveToFilenameBase = UtilProperties.getPropertyValue("oagis.properties", "test.save.outgoing.filename.base", ""); |
| if (UtilValidate.isNotEmpty(saveToFilenameBase)) { |
| saveToFilename = saveToFilenameBase + "ReceiveDelivery" + returnId + ".xml"; |
| } |
| } |
| String saveToDirectory = (String) context.get("saveToDirectory"); |
| if (UtilValidate.isEmpty(saveToDirectory)) { |
| saveToDirectory = UtilProperties.getPropertyValue("oagis.properties", "test.save.outgoing.directory"); |
| } |
| |
| GenericValue userLogin = null; |
| try { |
| userLogin = delegator.findByPrimaryKey("UserLogin", UtilMisc.toMap("userLoginId", "system")); |
| } catch (GenericEntityException e) { |
| Debug.logError(e, "Error getting system userLogin", module); |
| } |
| |
| OutputStream out = (OutputStream) context.get("outputStream"); |
| |
| Map<String, Object> result = ServiceUtil.returnSuccess(); |
| MapStack<String> bodyParameters = MapStack.create(); |
| |
| String orderId = null; |
| |
| String referenceId = null; |
| String task = "RMA"; // Actual value of task is "SHIPREQUEST" which is more than 10 char, need this in the db so it will match Confirm BODs, etc |
| String component = "INVENTORY"; |
| Map<String, String> omiPkMap = null; |
| |
| try { |
| // see if there are any OagisMessageInfo for this order that are in the OAGMP_OGEN_SUCCESS or OAGMP_SENT statuses, if so don't send again; these need to be manually reviewed before resending to avoid accidental duplicate messages |
| List<GenericValue> previousOagisMessageInfoList = delegator.findByAnd("OagisMessageInfo", |
| UtilMisc.toMap("returnId", returnId, "task", task, "component", component)); |
| if (EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_OGEN_SUCCESS")).size() > 0) { |
| // this isn't really an error, just a failed constraint so return success |
| return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisFoundExistingMessageForReturn", UtilMisc.toMap("returnId", returnId), locale) + EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_OGEN_SUCCESS"))); |
| } |
| if (EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_SENT")).size() > 0) { |
| // this isn't really an error, just a failed constraint so return success |
| return ServiceUtil.returnSuccess(UtilProperties.getMessage(resource, "OagisFoundExistingMessageForReturnSent", UtilMisc.toMap("returnId", returnId), locale) + EntityUtil.filterByAnd(previousOagisMessageInfoList, UtilMisc.toMap("processingStatusId", "OAGMP_SENT"))); |
| } |
| |
| GenericValue returnHeader = delegator.findByPrimaryKey("ReturnHeader", UtilMisc.toMap("returnId", returnId)); |
| if (returnHeader == null) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisReturnIdNotFound", UtilMisc.toMap("returnId", returnId), locale)); |
| } |
| String statusId = returnHeader.getString("statusId"); |
| if (!"RETURN_ACCEPTED".equals(statusId)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisReturnIdNotInAcceptedStatus", UtilMisc.toMap("returnId", returnId), locale)); |
| } |
| |
| List<GenericValue> returnItems = delegator.findByAnd("ReturnItem", UtilMisc.toMap("returnId", returnId)); |
| bodyParameters.put("returnItems", returnItems); |
| |
| orderId = EntityUtil.getFirst(returnItems).getString("orderId"); |
| |
| String logicalId = UtilProperties.getPropertyValue("oagis.properties", "CNTROLAREA.SENDER.LOGICALID"); |
| String authId = UtilProperties.getPropertyValue("oagis.properties", "CNTROLAREA.SENDER.AUTHID"); |
| |
| referenceId = delegator.getNextSeqId("OagisMessageInfo"); |
| omiPkMap = UtilMisc.toMap("logicalId", logicalId, "component", component, "task", task, "referenceId", referenceId); |
| |
| Timestamp nowTimestamp = UtilDateTime.nowTimestamp(); |
| String sentDate = OagisServices.isoDateFormat.format(nowTimestamp); |
| |
| bodyParameters.putAll(omiPkMap); |
| bodyParameters.put("authId", authId); |
| bodyParameters.put("sentDate", sentDate); |
| |
| // prepare map to Create Oagis Message Info |
| try { |
| Map<String, Object> comiCtx = FastMap.newInstance(); |
| comiCtx.putAll(omiPkMap); |
| comiCtx.put("outgoingMessage", "Y"); |
| comiCtx.put("confirmation", "1"); |
| comiCtx.put("bsrVerb", "RECEIVE"); |
| comiCtx.put("bsrNoun", "DELIVERY"); |
| comiCtx.put("bsrRevision", "001"); |
| comiCtx.put("returnId", returnId); |
| comiCtx.put("orderId", orderId); |
| comiCtx.put("authId", authId); |
| comiCtx.put("sentDate", nowTimestamp); |
| comiCtx.put("processingStatusId", "OAGMP_TRIGGERED"); |
| comiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("createOagisMessageInfo", comiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| |
| GenericValue orderHeader = delegator.findByPrimaryKey("OrderHeader", UtilMisc.toMap("orderId", orderId)); |
| if (orderHeader == null) { |
| return ServiceUtil.returnError(UtilProperties.getMessage(resource, "OagisReturnIdNotValid", UtilMisc.toMap("orderId", orderId), locale)); |
| } |
| |
| String partyId = returnHeader.getString("fromPartyId"); |
| GenericValue postalAddress = delegator.findByPrimaryKey("PostalAddress", UtilMisc.toMap("contactMechId", returnHeader.getString("originContactMechId"))); |
| bodyParameters.put("postalAddress", postalAddress); |
| bodyParameters.put("partyNameView", delegator.findByPrimaryKey("PartyNameView", UtilMisc.toMap("partyId", partyId))); |
| |
| // calculate total qty of return items in a shipping unit received, order associated with return |
| double totalQty = 0.0; |
| Map<String, List<String>> serialNumberListByReturnItemSeqIdMap = FastMap.newInstance(); |
| bodyParameters.put("serialNumberListByReturnItemSeqIdMap", serialNumberListByReturnItemSeqIdMap); |
| for (GenericValue returnItem : returnItems) { |
| double itemQty = returnItem.getDouble("returnQuantity").doubleValue(); |
| totalQty += itemQty; |
| |
| // for each ReturnItem also get serial numbers using ItemIssuanceAndInventoryItem |
| // NOTE: technically if the ReturnItem.quantity != OrderItem.quantity then we don't know which serial number is being returned, so rather than guessing we will send it only in that case |
| GenericValue orderItem = returnItem.getRelatedOne("OrderItem"); |
| if (orderItem != null) { |
| if (orderItem.getDouble("quantity").doubleValue() == itemQty) { |
| List<GenericValue> itemIssuanceAndInventoryItemList = delegator.findByAnd("ItemIssuanceAndInventoryItem", |
| UtilMisc.toMap("orderId", orderItem.get("orderId"), "orderItemSeqId", orderItem.get("orderItemSeqId"), |
| "inventoryItemTypeId", "SERIALIZED_INV_ITEM")); |
| if (itemIssuanceAndInventoryItemList.size() == itemQty) { |
| List<String> serialNumberList = FastList.newInstance(); |
| serialNumberListByReturnItemSeqIdMap.put(returnItem.getString("returnItemSeqId"), serialNumberList); |
| for (GenericValue itemIssuanceAndInventoryItem : itemIssuanceAndInventoryItemList) { |
| serialNumberList.add(itemIssuanceAndInventoryItem.getString("serialNumber")); |
| } |
| } else { |
| // TODO: again a quantity mismatch, whatever to do? |
| // just logging this as info because the product may not be serialized or have serialized inventory |
| Debug.logInfo("Number of serial numbers [" + itemIssuanceAndInventoryItemList.size() + "] did not match quantity [" + itemQty + "] for return item: " + returnItem.getPrimaryKey() + "; may not be a serialized inventory product", module); |
| } |
| } else { |
| // TODO: we don't know which serial numbers are returned, should we throw an error? probably not, just do what we can |
| Debug.logWarning("Could not get matching serial numbers because order item quantity [" + orderItem.getDouble("quantity") + "] did not match quantity [" + itemQty + "] for return item: " + returnItem.getPrimaryKey(), module); |
| } |
| } |
| } |
| bodyParameters.put("totalQty", new Double(totalQty)); |
| |
| String emailString = PartyWorker.findPartyLatestContactMech(partyId, "EMAIL_ADDRESS", delegator).getString("infoString"); |
| bodyParameters.put("emailString", emailString); |
| |
| GenericValue telecomNumber = PartyWorker.findPartyLatestTelecomNumber(partyId, delegator); |
| bodyParameters.put("telecomNumber", telecomNumber); |
| |
| String entryDate = OagisServices.isoDateFormat.format(returnHeader.getTimestamp("entryDate")); |
| bodyParameters.put("entryDate", entryDate); |
| |
| bodyParameters.put("returnId", returnId); |
| |
| String bodyScreenUri = UtilProperties.getPropertyValue("oagis.properties", "Oagis.Template.ReceiveDelivery"); |
| Writer writer = new StringWriter(); |
| ScreenRenderer screens = new ScreenRenderer(writer, bodyParameters, htmlScreenRenderer); |
| screens.render(bodyScreenUri); |
| writer.close(); |
| String outText = writer.toString(); |
| |
| try { |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_OGEN_SUCCESS"); |
| uomiCtx.put("userLogin", userLogin); |
| if (OagisServices.debugSaveXmlOut) { |
| uomiCtx.put("fullMessageXml", outText); |
| } |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| |
| Map<String, Object> sendMessageReturn = OagisServices.sendMessageText(outText, out, sendToUrl, saveToDirectory, saveToFilename, locale); |
| if (sendMessageReturn != null && ServiceUtil.isError(sendMessageReturn)) { |
| try { |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_SEND_ERROR"); |
| uomiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| return sendMessageReturn; |
| } |
| |
| try { |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_SENT"); |
| uomiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| } catch (GenericServiceException e) { |
| String errMsg = UtilProperties.getMessage(ServiceUtil.resource, "OagisErrorInCreatingDataForOagisMessageInfoEntity", (Locale) context.get("locale")); |
| Debug.logError(e, errMsg, module); |
| } |
| } catch (Throwable t) { |
| String errMsg = UtilProperties.getMessage(resource, "OagisErrorReceivingDeliveryMessageReturn", UtilMisc.toMap("returnId", returnId, "orderId", orderId, "omiPkMap", omiPkMap), locale); |
| Debug.logError(t, errMsg, module); |
| |
| // if we have a referenceId and the omiPkMap not null, save the error status |
| if (omiPkMap != null) { |
| try { |
| // only do this if there is a record already in place |
| if (delegator.findByPrimaryKey("OagisMessageInfo", omiPkMap) == null) { |
| return ServiceUtil.returnError(errMsg); |
| } |
| |
| Map<String, Object> uomiCtx = FastMap.newInstance(); |
| uomiCtx.putAll(omiPkMap); |
| uomiCtx.put("processingStatusId", "OAGMP_SYS_ERROR"); |
| uomiCtx.put("bsrVerb", "RECEIVE"); |
| uomiCtx.put("bsrNoun", "DELIVERY"); |
| uomiCtx.put("returnId", returnId); |
| uomiCtx.put("orderId", orderId); |
| uomiCtx.put("userLogin", userLogin); |
| dispatcher.runSync("updateOagisMessageInfo", uomiCtx, 60, true); |
| |
| List<Map<String, String>> errorMapList = UtilMisc.toList(UtilMisc.<String, String>toMap("description", errMsg, "reasonCode", "SystemError")); |
| Map<String, Object> saveErrorMapListCtx = FastMap.newInstance(); |
| saveErrorMapListCtx.putAll(omiPkMap); |
| saveErrorMapListCtx.put("errorMapList", errorMapList); |
| saveErrorMapListCtx.put("userLogin", userLogin); |
| dispatcher.runSync("createOagisMsgErrInfosFromErrMapList", saveErrorMapListCtx, 60, true); |
| } catch (GeneralException e) { |
| String errMsg2 = "Error saving message error info: " + e.toString(); |
| Debug.logError(e, errMsg2, module); |
| } |
| } |
| return ServiceUtil.returnError(errMsg + t.toString()); |
| } |
| return result; |
| } |
| } |