[ofbiz-framework] branch trunk updated: OFBIZ-11468 Improved: Convert ShipmentReceiptServices.xml mini lang to groovy (#150)

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[ofbiz-framework] branch trunk updated: OFBIZ-11468 Improved: Convert ShipmentReceiptServices.xml mini lang to groovy (#150)

jleroux@apache.org
This is an automated email from the ASF dual-hosted git repository.

jleroux pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 7335ce0  OFBIZ-11468 Improved: Convert ShipmentReceiptServices.xml mini lang to groovy (#150)
7335ce0 is described below

commit 7335ce021a3d6d241306087a4d0a97dcb8d08234
Author: wpaetzold <[hidden email]>
AuthorDate: Wed Jun 24 10:57:28 2020 +0200

    OFBIZ-11468 Improved: Convert ShipmentReceiptServices.xml mini lang to groovy (#150)
   
    * Improved: Convert ShipmentReceiptServices.xml mini lang to groovy
   
    (OFBIZ-11468)
   
    Also converted getTotalIssuedQuantityForOrderItem in IssuanceServices, because it is used in ShipmentReceiptServices and needed more return values.
   
    * split to long lines
   
    Co-authored-by: Wiebke Pätzold <[hidden email]>
---
 .../shipment/issuance/IssuanceServices.groovy      |  44 ++
 .../receipt/ShipmentReceiptServices.groovy         | 500 ++++++++++++++++++
 .../shipment/receipt/ShipmentReceiptServices.xml   | 577 ---------------------
 .../product/servicedef/services_shipment.xml       |  38 +-
 4 files changed, 570 insertions(+), 589 deletions(-)

diff --git a/applications/product/groovyScripts/shipment/issuance/IssuanceServices.groovy b/applications/product/groovyScripts/shipment/issuance/IssuanceServices.groovy
new file mode 100644
index 0000000..1c683e0
--- /dev/null
+++ b/applications/product/groovyScripts/shipment/issuance/IssuanceServices.groovy
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+import org.apache.ofbiz.entity.GenericValue
+
+/**
+ * Computes the total quantity assigned to shipment for a purchase order item
+ * @return
+ */
+def getTotalIssuedQuantityForOrderItem() {
+    Map result = success()
+    GenericValue orderItem = parameters.orderItem
+    BigDecimal totalIssuedQuantity = 0.0
+    List orderShipments = from("OrderShipment").where(orderId: orderItem.orderId, orderItemSeqId: orderItem.orderItemSeqId).queryList()
+    if (orderShipments) {
+        for (GenericValue orderShipment : orderShipments) {
+            totalIssuedQuantity +=  orderShipment.quantity
+        }
+    } else {
+        // This is here for backward compatibility only: ItemIssuances are no more created for purchase orders
+        List allItemIssuances = from("ItemIssuance").where(orderId: orderItem.orderId, orderItemSeqId: orderItem.orderItemSeqId).queryList()
+        for (GenericValue itemIssuance : allItemIssuances) {
+            totalIssuedQuantity += itemIssuance.quantity
+        }
+    }
+    result.totalIssuedQuantity = totalIssuedQuantity
+    return result
+}
diff --git a/applications/product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy b/applications/product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy
new file mode 100644
index 0000000..2646ed7
--- /dev/null
+++ b/applications/product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy
@@ -0,0 +1,500 @@
+/*
+ * 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.
+ */
+
+import java.math.RoundingMode
+import java.sql.Timestamp
+
+import org.apache.ofbiz.base.util.UtilDateTime
+import org.apache.ofbiz.base.util.UtilProperties
+import org.apache.ofbiz.entity.GenericValue
+import org.apache.ofbiz.entity.condition.EntityCondition
+
+/**
+ * Create a ShipmentReceipt
+ * @return
+ */
+def createShipmentReceipt() {
+    Map result = success()
+    GenericValue newEntity = makeValue("ShipmentReceipt")
+    newEntity.setNonPKFields(parameters)
+
+    String receiptId = delegator.getNextSeqId("ShipmentReceipt").toString()
+    newEntity.receiptId = receiptId
+    result.receiptId = receiptId
+
+    if (!newEntity.datetimeReceived) {
+        newEntity.datetimeReceived = UtilDateTime.nowTimestamp()
+    }
+    newEntity.receivedByUserLoginId = userLogin.userLoginId
+    newEntity.create()
+
+    if (parameters.inventoryItemDetailSeqId) {
+        GenericValue invDet = from("InventoryItemDetail")
+                .where(inventoryItemDetailSeqId: parameters.inventoryItemDetailSeqId, inventoryItemId: parameters.inventoryItemId)
+                .queryOne()
+        invDet.receiptId = receiptId
+        invDet.store()
+    }
+    Boolean affectAccounting = true
+
+    GenericValue product = from("Product").where(parameters).queryOne()
+    if (product.productTypeId == "SERVICE_PRODUCT"
+            || product.productTypeId == "ASSET_USAGE_OUT_IN"
+            || product.productTypeId == "AGGREGATEDSERV_CONF") {
+        affectAccounting = false
+    }
+    result.affectAccounting = affectAccounting
+    return result
+}
+
+/**
+ * Receive Inventory in new Inventory Item(s)
+ * @return success, inventoryItemId, successMessageList
+ */
+def receiveInventoryProduct () {
+    /*
+     * NOTES
+     *
+     * - for serialized items with a serial number passed in: the quantityAccepted _should_ always be 1
+     * - if the type is SERIALIZED_INV_ITEM but there is not serial number (which is weird...) we'll create a bunch of individual InventoryItems
+     * - DEJ20070822: something to consider for the future:
+     *  maybe instead of this funny looping maybe for serialized items we should only allow a quantity of 1, ie return an error if it is not 1
+     */
+    Map result = success()
+    List successMessageList =[]
+    String currentInventoryItemId
+    Double loops = 1.0
+    if (parameters.inventoryItemTypeId == "SERIALIZED_INV_ITEM") {
+        // if we are serialized and either a serialNumber or inventoyItemId is passed in and the quantityAccepted is greater than 1 then complain
+        if ((parameters.serialNumber || parameters.currentInventoryItemId) && (parameters.quantityAccepted > (BigDecimal) 1)) {
+            Map errorLog = [parameters: parameters]
+            return error(UtilProperties.getMessage("ProductUiLabels", "FacilityReceiveInventoryProduct", errorLog,  parameters.locale))
+            // before getting going, see if there are any validation issues so far
+        }
+        loops = parameters.quantityAccepted
+        parameters.quantityAccepted = (BigDecimal) 1
+    }
+    parameters.quantityOnHandDiff = parameters.quantityAccepted
+    parameters.availableToPromiseDiff = parameters.quantityAccepted
+
+    //Status for Non serialized and Serialized inventory are different, lets make sure correct status is stored in database
+    if (parameters.inventoryItemTypeId == "NON_SERIAL_INV_ITEM") {
+        if (parameters.statusId == "INV_DEFECTIVE") {
+            // This status may come from the Receive Return Screen
+            parameters.statusId = "INV_NS_DEFECTIVE"
+        } else if (parameters.statusId == "INV_ON_HOLD") {
+            parameters.statusId = "INV_NS_ON_HOLD"
+        } else if (parameters.statusId == "INV_RETURNED") {
+            parameters.statusId = "INV_NS_RETURNED"
+        } else {
+            // Any other status should be just set to null, if it is not a valid status for Non Serialized inventory
+            parameters.statusId = null
+        }
+    }
+
+    for (Double currentLoop = 0; currentLoop <= loops; currentLoop++) {
+        logInfo("receiveInventoryProduct Looping and creating inventory info - ${currentLoop}")
+
+        // if there is an inventoryItemId, update it (this will happen when receiving serialized inventory already in the system, like for returns); if not create one
+        Map serviceInMap = [:]
+        currentInventoryItemId = null
+
+        // Set supplier partyId, if inventory received by purchase order
+        if (parameters.orderId) {
+            GenericValue orderRole = from("OrderRole")
+                    .where(orderId: parameters.orderId, roleTypeId: "SUPPLIER_AGENT")
+                    .queryFirst()
+            if (orderRole) {
+                parameters.partyId = orderRole.partyId
+            }
+        }
+        if (!parameters.currentInventoryItemId) {
+            Map serviceResult = run service:"createInventoryItem", with: parameters
+            currentInventoryItemId = serviceResult.inventoryItemId
+        } else {
+            if (parameters.currentInventoryItemId) {
+                parameters.inventoryItemId = parameters.currentInventoryItemId
+            }
+            run service:"updateInventoryItem", with: parameters
+            currentInventoryItemId = parameters.currentInventoryItemId
+        }
+
+        // do this only for non-serialized inventory
+        if (parameters.inventoryItemTypeId != "SERIALIZED_INV_ITEM") {
+            serviceInMap = [:]
+            serviceInMap = parameters
+            serviceInMap.inventoryItemId = currentInventoryItemId
+            Map serviceCIID = run service:"createInventoryItemDetail", with: serviceInMap
+            parameters.inventoryItemDetailSeqId = serviceCIID.inventoryItemDetailSeqId
+        }
+        serviceInMap = [:]
+        serviceInMap = parameters
+        serviceInMap.inventoryItemId = currentInventoryItemId
+        run service:"createShipmentReceipt", with: serviceInMap
+
+        //update serialized items to AVAILABLE (only if this is not a return), which then triggers other SECA chains
+        if (parameters.inventoryItemTypeId == "SERIALIZED_INV_ITEM" && !parameters.returnId) {
+            // Retrieve the new inventoryItem
+            GenericValue inventoryItem = from("InventoryItem").where(inventoryItemId: currentInventoryItemId).queryOne()
+
+            // Don't reset the status if it's already set to INV_PROMISED or INV_ON_HOLD
+            if (inventoryItem.statusId != "INV_PROMISED" && inventoryItem.statusId != "INV_ON_HOLD") {
+                serviceInMap = [:]
+                serviceInMap.inventoryItemId = currentInventoryItemId
+                serviceInMap.statusId = "INV_AVAILABLE" // XXX set to returned instead
+                run service:"updateInventoryItem", with: serviceInMap
+            }
+        }
+        serviceInMap = [:]
+        serviceInMap = parameters
+        serviceInMap.inventoryItemId = currentInventoryItemId
+        run service:"balanceInventoryItems", with: serviceInMap
+
+        successMessageList << "Received ${parameters.quantityAccepted} of ${parameters.productId} in inventory item ${currentInventoryItemId}"
+    }
+    // return the last inventory item received
+    result.inventoryItemId = currentInventoryItemId
+    result.successMessageList = successMessageList
+
+    return result
+}
+
+/**
+ * Quick Receive Entire Return
+ * @return
+ */
+def quickReceiveReturn() {
+    Map result = success()
+    GenericValue returnHeader = from("ReturnHeader").where(returnId: parameters.returnId).queryOne()
+    if (returnHeader.needsInventoryReceive == "Y") {
+        // before receiving inventory, check to see if there is inventory information in this database
+        Long iiCount = from("InventoryItem").where(facilityId: returnHeader.destinationFacilityId).queryCount()
+
+        if (iiCount > (Integer) 0) {
+            // create a return shipment for this return
+            Map shipmentCtx = [returnId: parameters.returnId]
+            Map serviceCSFR = run service:"createShipmentForReturn", with: shipmentCtx
+            String shipmentId = serviceCSFR.shipmentId
+            logInfo("Created new shipment ${shipmentId}")
+
+            List returnItems = from("ReturnItem").where(returnId: returnHeader.returnId).queryList()
+
+            // if no inventory item type specified, get default from facility
+            if(!parameters.inventoryItemTypeId) {
+                GenericValue facility = delegator.getRelatedOne("Facility", returnHeader, false)
+                parameters.inventoryItemTypeId = facility.defaultInventoryItemTypeId ?: "NON_SERIAL_INV_ITEM"
+            }
+            Timestamp nowTimestamp = UtilDateTime.nowTimestamp()
+
+            Long returnItemCount = from("ReturnItem").where(returnId: returnHeader.returnId).queryCount()
+            Long nonProductItems =  (Long) 0
+
+            for (GenericValue returnItem : returnItems) {
+                // record this return item on the return shipment as well.  not sure if this is actually necessary...
+                Map shipItemCtx = [shipmentId: shipmentId, productId: returnItem.productId, quantity: returnItem.returnQuantity]
+                logInfo("calling create shipment item with ${shipItemCtx}")
+                Map serviceCSI = run service:"createShipmentItem", with: shipItemCtx
+                String shipmentItemSeqId = serviceCSI.shipmentItemSeqId
+            }
+            for (GenericValue returnItem : returnItems) {
+                Map receiveCtx = [:]
+                if (!returnItem.expectedItemStatus) {
+                    returnItem.expectedItemStatus = "INV_RETURNED"
+                }
+                GenericValue orderItem = delegator.getRelatedOne("OrderItem", returnItem, false)
+                if (orderItem?.productId) {
+                    Map costCtx = [returnItemSeqId: returnItem.returnItemSeqId, returnId: returnItem.returnId]
+                    Map serviceGRIIC = run service:"getReturnItemInitialCost", with: costCtx
+                    receiveCtx.unitCost = serviceGRIIC.initialItemCost
+
+                    // check if the items already have SERIALIZED inventory. If so, it still puts them back as SERIALIZED with status "Accepted."
+                    Long serializedItemCount = from("InventoryItem")
+                            .where(productId: returnItem.productId, facilityId: returnHeader.destinationFacilityId, inventoryItemTypeId: "SERIALIZED_INV_ITEM")
+                            .queryCount()
+                    Boolean setNonSerial = false
+                    if (parameters.inventoryItemTypeId == "NON_SERIAL_INV_ITEM") {
+                        if (serializedItemCount == 0) {
+                            parameters.inventoryItemTypeId = "NON_SERIAL_INV_ITEM"
+                            setNonSerial = true
+                        }
+                    }
+                    if (!setNonSerial) {
+                        parameters.inventoryItemTypeId = "SERIALIZED_INV_ITEM"
+                        returnItem.returnQuantity = (BigDecimal) 1
+                    }
+                    receiveCtx = [inventoryItemTypeId: parameters.inventoryItemTypeId,
+                        statusId: returnItem.expectedItemStatus,
+                        productId: returnItem.productId,
+                        returnItemSeqId: returnItem.returnItemSeqId,
+                        returnId: returnItem.returnId,
+                        quantityAccepted: returnItem.returnQuantity,
+                        facilityId: returnHeader.destinationFacilityId,
+                        shipmentId: shipmentId, // important: associate ShipmentReceipt with return shipment created
+                        comments: "Returned Item RA# ${returnItem.returnId}",
+                        datetimeReceived: nowTimestamp,
+                        quantityRejected: (BigDecimal) 0
+                    ]
+                    Map serviceResult = run service:"receiveInventoryProduct", with: receiveCtx
+                    result.successMessageList = serviceResult.successMessageList
+                } else {
+                    nonProductItems += (Long) 1
+                }
+            }
+            // now that the receive is done; set the need flag to N
+            returnHeader.refresh()
+            returnHeader.needsInventoryReceive = "N"
+            returnHeader.store()
+
+            // always check/update the ReturnHeader status, even though it might have been from the receiving above, just make sure
+            if (returnHeader.statusId != "RETURN_RECEIVED") {
+                Map retStCtx = [returnId: returnHeader.returnId, statusId: "RETURN_RECEIVED"]
+                run service:"updateReturnHeader", with: retStCtx
+            }
+        } else {
+            logInfo("Not receiving inventory for returnId ${returnHeader.returnId}, no inventory information available.")
+        }
+    }
+    return result
+}
+
+/**
+ * Issues order item quantity specified to the shipment, then receives inventory for that item and quantity
+ * @return
+ */
+def issueOrderItemToShipmentAndReceiveAgainstPO() {
+    Map result = success()
+    String shipmentItemSeqId
+    GenericValue shipmentItem
+    // get orderItem
+    GenericValue orderItem = from("OrderItem").where(parameters).queryOne()
+    // get orderItemShipGroupAssoc
+    GenericValue orderItemShipGroupAssoc = from("OrderItemShipGroupAssoc").where(parameters).queryOne()
+    // get shipment
+    GenericValue shipment = from("Shipment").where(parameters).queryOne()
+
+    // try to find an existing shipmentItem and attach to it, if none found create a new shipmentItem
+    // if there is NO productId on the orderItem, ALWAYS create a new shipmentItem
+    if (orderItem?.productId) {
+        EntityCondition condition = EntityCondition.makeCondition([
+            EntityCondition.makeCondition(productId: orderItem.productId),
+            EntityCondition.makeCondition(shipmentId: shipment.shipmentId)
+        ])
+        if (parameters.shipmentItemSeqId) {
+            condition = EntityCondition.makeCondition([
+                EntityCondition.makeCondition(shipmentItemSeqId: parameters.shipmentItemSeqId),
+                condition
+            ])
+        }
+        shipmentItem = from("ShipmentItem")
+                .where(condition)
+                .orderBy("shipmentItemSeqId")
+                .queryFirst()
+    }
+    if (!shipmentItem) {
+        Map shipmentItemCreate = [productId: orderItem.productId, shipmentId: parameters.shipmentId, quantity: parameters.quantity]
+        Map serviceResult = run service:"createShipmentItem", with: shipmentItemCreate
+        Map shipmentItemLookupPk = [shipmentItemSeqId: serviceResult.shipmentItemSeqId, shipmentId: parameters.shipmentId]
+        shipmentItem = from("ShipmentItem").where(shipmentItemLookupPk).queryOne()
+
+        // Create OrderShipment for this ShipmentItem
+        Map orderShipmentCreate =[quantity: parameters.quantity,
+            shipmentId: shipmentItem.shipmentId,
+            shipmentItemSeqId: shipmentItem.shipmentItemSeqId,
+            orderId: orderItem.orderId,
+            orderItemSeqId: orderItem.orderItemSeqId]
+        if (orderItemShipGroupAssoc) {
+            // If we have a ShipGroup Assoc for this Item to focus on, set that; this is mostly the case for purchase orders and such
+            orderShipmentCreate.shipGroupSeqId = orderItemShipGroupAssoc.shipGroupSeqId
+        }
+        run service:"createOrderShipment", with: orderShipmentCreate
+    } else {
+        Map inputMap = parameters
+        inputMap.orderItem = orderItem
+        Map serviceResult = run service:"getTotalIssuedQuantityForOrderItem", with: inputMap
+        BigDecimal totalIssuedQuantity = serviceResult.totalIssuedQuantity
+        BigDecimal receivedQuantity = getReceivedQuantityForOrderItem(orderItem)
+        receivedQuantity += parameters.quantity
+        GenericValue orderShipment = from("OrderShipment")
+                .where(orderId: orderItem.orderId,
+                orderItemSeqId: orderItem.orderItemSeqId,
+                shipmentId: shipmentItem.shipmentId,
+                shipmentItemSeqId: shipmentItem.shipmentItemSeqId,
+                shipGroupSeqId: orderItemShipGroupAssoc.shipGroupSeqId)
+                .queryFirst()
+        if (totalIssuedQuantity < receivedQuantity) {
+            BigDecimal quantityToAdd = receivedQuantity - totalIssuedQuantity
+            shipmentItem.quantity += quantityToAdd
+            shipmentItem.store()
+            shipmentItemSeqId = shipmentItem.shipmentItemSeqId
+
+            orderShipment.quantity = orderShipment.quantity + quantityToAdd
+            orderShipment.store()
+        }
+    }
+    // TODO: if we want to record the role of the facility operation we have to re-implement this using ShipmentReceiptRole
+    // <call-simple-method method-name="associateIssueRoles" xml-resource="component://product/minilang/shipment/issuance/IssuanceServices.xml"/>
+
+    Map receiveInventoryProductCtx = parameters
+    receiveInventoryProductCtx.shipmentItemSeqId = shipmentItemSeqId
+    Map serviceResult = run service:"receiveInventoryProduct", with:receiveInventoryProductCtx
+    result.inventoryItemId = serviceResult.inventoryItemId
+    result.successMessageList = serviceResult.successMessageList
+
+    return result
+}
+
+/**
+ * Computes the till now received quantity from all ShipmentReceipts
+ * @return
+ */
+def getReceivedQuantityForOrderItem (GenericValue orderItem) {
+    BigDecimal receivedQuantity = 0
+    List shipmentReceipts = from("ShipmentReceipt").where(orderId: orderItem.orderId, orderItemSeqId: orderItem.orderItemSeqId).queryList()
+    for (GenericValue shipmentReceipt : shipmentReceipts) {
+        receivedQuantity +=  shipmentReceipt.quantityAccepted
+    }
+    return receivedQuantity
+}
+
+/**
+ * Update issuance, shipment and order items if quantity received is higher than quantity on purchase order
+ * @return
+ */
+def updateIssuanceShipmentAndPoOnReceiveInventory() {
+    GenericValue orderItem = from("OrderItem").where(parameters).queryOne()
+    if (parameters.orderCurrencyUnitPrice) {
+        if (parameters.orderCurrencyUnitPrice != orderItem.unitPrice) {
+            orderItem.unitPrice = new BigDecimal (parameters.orderCurrencyUnitPrice)
+            orderItem.store()
+        }
+    } else {
+        if (parameters.unitCost != orderItem.unitPrice) {
+            orderItem.unitPrice = parameters.unitCost
+            orderItem.store()
+        }
+    }
+    BigDecimal receivedQuantity = getReceivedQuantityForOrderItem(orderItem)
+    if (orderItem.quantity < receivedQuantity) {
+        GenericValue orderItemShipGroupAssoc = from("OrderItemShipGroupAssoc")
+                .where(orderId: orderItem.orderId, orderItemSeqId: orderItem.orderItemSeqId)
+                .queryFirst()
+        BigDecimal quantityVariance = (receivedQuantity - orderItem.quantity).setScale(2, RoundingMode.HALF_UP)
+        BigDecimal oisgaQuantity = (orderItemShipGroupAssoc.quantity + quantityVariance).setScale(2, RoundingMode.HALF_UP)
+        orderItemShipGroupAssoc.quantity = oisgaQuantity
+        orderItem.quantity = receivedQuantity
+        orderItemShipGroupAssoc.store()
+        orderItem.store()
+    }
+    if (parameters.shipmentId) {
+        if (orderItem.productId) {
+            Map inputMap = parameters
+            inputMap.orderItem = orderItem
+            Map serviceResult = run service:"getTotalIssuedQuantityForOrderItem", with: inputMap
+            BigDecimal totalIssuedQuantity = serviceResult.totalIssuedQuantity
+            if (totalIssuedQuantity < receivedQuantity) {
+                BigDecimal quantityToAdd = receivedQuantity - totalIssuedQuantity
+                EntityCondition condition = EntityCondition.makeCondition([
+                    EntityCondition.makeCondition(productId: orderItem.productId),
+                    EntityCondition.makeCondition(shipmentId: parameters.shipmentId)
+                ])
+                if (parameters.shipmentItemSeqId) {
+                    condition = EntityCondition.makeCondition([
+                        EntityCondition.makeCondition(shipmentItemSeqId: parameters.shipmentItemSeqId),
+                        condition
+                    ])
+                }
+                GenericValue shipmentItem = from("ShipmentItem").where(condition).orderBy("shipmentItemSeqId").queryFirst()
+                if (shipmentItem) {
+                    shipmentItem.quantity += quantityToAdd
+                    shipmentItem.store()
+                    GenericValue orderShipment = from("OrderShipment")
+                            .where(orderId: parameters.orderId, orderItemSeqId: parameters.orderItemSeqId,
+                                shipmentId: parameters.shipmentId, shipmentItemSeqId: shipmentItem.shipmentItemSeqId)
+                            .queryFirst()
+                    if (orderShipment) {
+                        orderShipment.quantity += quantityToAdd
+                        orderShipment.store()
+                    }
+                }
+
+                // TODO: if we want to record the role of the facility operation we have to re-implement this using ShipmentReceiptRole
+                // <set field="itemIssuanceId" from-field="itemIssuance.itemIssuanceId"/>
+                // <call-simple-method method-name="associateIssueRoles" xml-resource="component://product/minilang/shipment/issuance/IssuanceServices.xml"/>
+            }
+        }
+    }
+    return success()
+}
+
+
+/**
+ * Cancel Received Items against a purchase order if received something incorrectly
+ * @return
+ */
+def cancelReceivedItems() {
+    // TODO: When items are received against a Purchase Order, service listed below changes certain things in the system. Changes done by these
+    // services also need to be reverted and missing logic can be added later.
+    // 1. addProductsBackToCategory
+    // 2. setUnitPriceAsLastPrice
+    // 3. createAcctgTransForShipmentReceipt
+    // 4. updateProductIfAvailableFromShipment
+
+    // update the accepted and received quantity to zero in ShipmentReceipt entity
+    GenericValue shipmentReceipt = from("ShipmentReceipt").where(parameters).queryOne()
+    shipmentReceipt.quantityAccepted = 0.0
+    shipmentReceipt.quantityRejected = 0.0
+    shipmentReceipt.store()
+
+    // create record for InventoryItemDetail entity
+    GenericValue inventoryItem = delegator.getRelatedOne("InventoryItem", shipmentReceipt, false)
+    Map inventoryItemDetailMap = [inventoryItemId: inventoryItem.inventoryItemId]
+    inventoryItemDetailMap.quantityOnHandDiff = inventoryItem.quantityOnHandTotal * (-1)
+    inventoryItemDetailMap.availableToPromiseDiff = inventoryItem.availableToPromiseTotal *(-1)
+    run service:"createInventoryItemDetail", with: inventoryItemDetailMap
+
+    // Balance the inventory item
+    Map balanceInventoryItemMap = [inventoryItemId: inventoryItem.inventoryItemId,
+        priorityOrderId: shipmentReceipt.orderId, priorityOrderItemSeqId: shipmentReceipt.orderItemSeqId]
+    run service:"balanceInventoryItems", with: balanceInventoryItemMap
+
+    // update the shipment status, if shipment was received
+    GenericValue shipment = delegator.getRelatedOne("Shipment", shipmentReceipt, false)
+    if (shipment?.statusId == "PURCH_SHIP_RECEIVED") {
+        Map shipmentStatusMap = [shipmentId : shipment.shipmentId, statusId: "PURCH_SHIP_SHIPPED"]
+        run service:"updateShipment", with: shipmentStatusMap
+    }
+    // change order item and order status
+    GenericValue orderItem = delegator.getRelatedOne("OrderItem", shipmentReceipt, false)
+    if (orderItem?.statusId == "ITEM_COMPLETED") {
+        // update the order item status
+        orderItem.statusId = "ITEM_APPROVED"
+        Map orderItemCtx = [:]
+        orderItemCtx << orderItem
+        orderItemCtx.fromStatusId = "ITEM_COMPLETED"
+        run service:"changeOrderItemStatus", with: orderItemCtx
+        GenericValue orderHeader = delegator.getRelatedOne("OrderHeader", orderItem, false)
+        // cancel the invoice
+        GenericValue orderItemBilling = from("OrderItemBilling").where(orderId: orderItem.orderId).queryFirst()
+        if (orderItemBilling) {
+            Map invoiceStatusMap = [invoiceId: orderItemBilling.invoiceId, statusId: "INVOICE_CANCELLED"]
+            run service:"setInvoiceStatus", with: invoiceStatusMap
+        }
+    }
+    return success()
+}
diff --git a/applications/product/minilang/shipment/receipt/ShipmentReceiptServices.xml b/applications/product/minilang/shipment/receipt/ShipmentReceiptServices.xml
deleted file mode 100644
index 4334dc5..0000000
--- a/applications/product/minilang/shipment/receipt/ShipmentReceiptServices.xml
+++ /dev/null
@@ -1,577 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!--
-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.
--->
-
-<simple-methods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xmlns="http://ofbiz.apache.org/Simple-Method" xsi:schemaLocation="http://ofbiz.apache.org/Simple-Method http://ofbiz.apache.org/dtds/simple-methods.xsd">
-    <simple-method method-name="createShipmentReceipt" short-description="Create a ShipmentReceipt">
-        <make-value entity-name="ShipmentReceipt" value-field="newEntity"/>
-        <set-nonpk-fields map="parameters" value-field="newEntity"/>
-
-        <sequenced-id sequence-name="ShipmentReceipt" field="receiptId"/>
-        <to-string field="receiptId"/>
-        <set field="newEntity.receiptId" from-field="receiptId"/>
-        <field-to-result field="receiptId" result-name="receiptId"/>
-
-        <if-empty field="newEntity.datetimeReceived">
-            <now-timestamp field="nowTimestamp"/>
-            <set field="newEntity.datetimeReceived" from-field="nowTimestamp"/>
-        </if-empty>
-
-        <set field="newEntity.receivedByUserLoginId" from-field="userLogin.userLoginId"/>
-        <create-value value-field="newEntity"/>
-
-        <if-not-empty field="parameters.inventoryItemDetailSeqId">
-            <entity-one entity-name="InventoryItemDetail" value-field="invDet">
-                <field-map field-name="inventoryItemDetailSeqId" from-field="parameters.inventoryItemDetailSeqId"/>
-                <field-map field-name="inventoryItemId" from-field="parameters.inventoryItemId"/>
-            </entity-one>
-            <set field="invDet.receiptId" from-field="receiptId"/>
-            <store-value value-field="invDet"/>
-        </if-not-empty>
-        <set field="affectAccounting" type="Boolean" value="true"/>
-        
-        <entity-one entity-name="Product" value-field="product"/>
-        <if>
-            <condition>
-                <or>
-                    <if-compare field="product.productTypeId" operator="equals" value="SERVICE_PRODUCT"/>
-                    <if-compare field="product.productTypeId" operator="equals" value="ASSET_USAGE_OUT_IN"/>
-                    <if-compare field="product.productTypeId" operator="equals" value="AGGREGATEDSERV_CONF"/>
-                </or>
-            </condition>
-            <then>
-                <set field="affectAccounting" type="Boolean" value="false"/>
-            </then>
-        </if>
-        <field-to-result field="affectAccounting" result-name="affectAccounting"/>
-    </simple-method>
-
-    <simple-method method-name="receiveInventoryProduct" short-description="Receive Inventory in new Inventory Item(s)">
-        <!-- NOTES
-            - for serialized items with a serial number passed in: the quantityAccepted _should_ always be 1
-            - if the type is SERIALIZED_INV_ITEM but there is not serial number (which is weird...) we'll create a bunch of individual InventoryItems
-            - DEJ20070822: something to consider for the future: maybe instead of this funny looping maybe for serialized items we should only allow a quantity of 1, ie return an error if it is not 1
-        -->
-        <set field="loops" value="1" type="Double"/>
-        <if-compare value="SERIALIZED_INV_ITEM" operator="equals" field="parameters.inventoryItemTypeId">
-            <!-- if we are serialized and either a serialNumber or inventoyItemId is passed in and the quantityAccepted is greater than 1 then complain -->
-            <if>
-                <condition>
-                    <and>
-                        <or>
-                            <not><if-empty field="parameters.serialNumber"/></not>
-                            <not><if-empty field="parameters.currentInventoryItemId"/></not>
-                        </or>
-                        <if-compare field="parameters.quantityAccepted" operator="greater" value="1" type="BigDecimal"/>
-                    </and>
-                </condition>
-                <then>
-                    <add-error>
-                        <fail-property resource="ProductUiLabels" property="FacilityReceiveInventoryProduct"/>
-                    </add-error>
-                </then>
-            </if>
-
-            <set field="loops" from-field="parameters.quantityAccepted"/>
-            <set field="parameters.quantityAccepted" value="1" type="BigDecimal"/>
-        </if-compare>
-
-        <set field="parameters.quantityOnHandDiff" from-field="parameters.quantityAccepted"/>
-        <set field="parameters.availableToPromiseDiff" from-field="parameters.quantityAccepted"/>
-
-        <!-- before getting going, see if there are any validation issues so far -->
-        <check-errors/>
-
-        <!-- Status for Non serialized and Serialized inventory are different, lets make sure correct status is stored in database -->
-        <if-compare field="parameters.inventoryItemTypeId" operator="equals" value="NON_SERIAL_INV_ITEM">
-            <if-compare field="parameters.statusId" operator="equals" value="INV_DEFECTIVE"><!-- This status may come from the Receive Return Screen -->
-                <set field="parameters.statusId" value="INV_NS_DEFECTIVE"/>
-            <else>
-                <if-compare field="parameters.statusId" operator="equals" value="INV_ON_HOLD">
-                    <set field="parameters.statusId" value="INV_NS_ON_HOLD"/>
-                <else>
-                    <if-compare field="parameters.statusId" operator="equals" value="INV_RETURNED">
-                        <set field="parameters.statusId" value="INV_NS_RETURNED"/>
-                    </if-compare>
-                </else>
-                </if-compare>
-            </else>
-            </if-compare>
-            <!-- Any other status should be just set to null, if it is not a valid status for Non Serialized inventory -->
-            <if>
-                <condition>
-                    <and>
-                        <not><if-compare field="parameters.statusId" operator="equals" value="INV_NS_DEFECTIVE"/></not>
-                        <not><if-compare field="parameters.statusId" operator="equals" value="INV_NS_ON_HOLD"/></not>
-                        <not><if-compare field="parameters.statusId" operator="equals" value="INV_NS_RETURNED"/></not>
-                    </and>
-                </condition>
-                <then>
-                    <set field="parameters.statusId" from-field="nullField"/>
-                </then>
-            </if>
-        </if-compare>
-
-        <loop count="${loops}" field="currentLoop">
-            <log level="info" message="receiveInventoryProduct Looping and creating inventory info - ${currentLoop}"/>
-
-            <!-- if there is an inventoryItemId, update it (this will happen when receiving serialized inventory already in the system, like for returns); if not create one -->
-            <clear-field field="serviceInMap"/>
-            <clear-field field="currentInventoryItemId"/>
-
-            <!-- Set supplier partyId, if inventory received by purchase order -->
-            <if-not-empty field="parameters.orderId">
-                <entity-and entity-name="OrderRole" list="orderRoles">
-                    <field-map field-name="orderId" from-field="parameters.orderId"/>
-                    <field-map field-name="roleTypeId" value="SUPPLIER_AGENT"/>
-                </entity-and>
-                <if-not-empty field="orderRoles">
-                    <first-from-list list="orderRoles" entry="orderRole"/>
-                    <set field="parameters.partyId" from-field="orderRole.partyId"/>
-                </if-not-empty>
-            </if-not-empty>
-
-            <if-empty field="parameters.currentInventoryItemId">
-                <set-service-fields service-name="createInventoryItem" map="parameters" to-map="serviceInMap"/>
-                <call-service service-name="createInventoryItem" in-map-name="serviceInMap">
-                    <result-to-field result-name="inventoryItemId" field="currentInventoryItemId"/>
-                </call-service>
-
-                <else>
-                    <if-not-empty field="parameters.currentInventoryItemId">
-                        <set field="parameters.inventoryItemId" from-field="parameters.currentInventoryItemId"/>
-                    </if-not-empty>
-                    <set-service-fields service-name="updateInventoryItem" map="parameters" to-map="serviceInMap"/>
-                    <call-service service-name="updateInventoryItem" in-map-name="serviceInMap"/>
-                    <set field="currentInventoryItemId" from-field="parameters.currentInventoryItemId"/>
-                </else>
-            </if-empty>
-
-            <!-- do this only for non-serialized inventory -->
-            <if-compare value="SERIALIZED_INV_ITEM" operator="not-equals" field="parameters.inventoryItemTypeId">
-                <clear-field field="serviceInMap"/>
-                <set-service-fields service-name="createInventoryItemDetail" map="parameters" to-map="serviceInMap"/>
-                <set field="serviceInMap.inventoryItemId" from-field="currentInventoryItemId"/>
-                <call-service service-name="createInventoryItemDetail" in-map-name="serviceInMap">
-                    <result-to-field result-name="inventoryItemDetailSeqId" field="parameters.inventoryItemDetailSeqId"/>
-                </call-service>
-            </if-compare>
-
-            <clear-field field="serviceInMap"/>
-            <set-service-fields service-name="createShipmentReceipt" map="parameters" to-map="serviceInMap"/>
-            <set field="serviceInMap.inventoryItemId" from-field="currentInventoryItemId"/>
-            <call-service service-name="createShipmentReceipt" in-map-name="serviceInMap"/>
-
-            <!-- update serialized items to AVAILABLE (only if this is not a return), which then triggers other SECA chains -->
-            <if>
-                <condition>
-                    <and>
-                        <if-compare value="SERIALIZED_INV_ITEM" operator="equals" field="parameters.inventoryItemTypeId"/>
-                        <if-empty field="parameters.returnId"/>
-                    </and>
-                </condition>
-                <then>
-                    <!-- Retrieve the new inventoryItem -->
-                    <entity-one entity-name="InventoryItem" value-field="inventoryItem">
-                        <field-map field-name="inventoryItemId" from-field="currentInventoryItemId"/>
-                    </entity-one>
-
-                    <!-- Don't reset the status if it's already set to INV_PROMISED or INV_ON_HOLD -->
-                    <if>
-                        <condition>
-                            <and>
-                                <if-compare value="INV_PROMISED" operator="not-equals" field="inventoryItem.statusId"/>
-                                <if-compare value="INV_ON_HOLD" operator="not-equals" field="inventoryItem.statusId"/>
-                            </and>
-                        </condition>
-                        <then>
-                            <clear-field field="serviceInMap"/>
-                            <set field="serviceInMap.inventoryItemId" from-field="currentInventoryItemId"/>
-                            <set field="serviceInMap.statusId" value="INV_AVAILABLE"/> <!-- XXX set to returned instead -->
-                            <call-service service-name="updateInventoryItem" in-map-name="serviceInMap"/>
-                        </then>
-                    </if>
-                </then>
-            </if>
-
-            <clear-field field="serviceInMap"/>
-            <set-service-fields service-name="balanceInventoryItems" map="parameters" to-map="serviceInMap"/>
-            <set field="serviceInMap.inventoryItemId" from-field="currentInventoryItemId"/>
-            <call-service service-name="balanceInventoryItems" in-map-name="serviceInMap"/>
-
-            <set field="successMessageList[]" value="Received ${parameters.quantityAccepted} of ${parameters.productId} in inventory item ${currentInventoryItemId}"/>
-        </loop>
-        <!-- return the last inventory item received -->
-        <field-to-result field="currentInventoryItemId" result-name="inventoryItemId"/>
-    </simple-method>
-
-    <simple-method method-name="quickReceiveReturn" short-description="Quick Receive Entire Return">
-        <entity-one entity-name="ReturnHeader" value-field="returnHeader">
-            <field-map field-name="returnId" from-field="parameters.returnId"/>
-        </entity-one>
-
-        <if-compare field="returnHeader.needsInventoryReceive" operator="equals" value="Y">
-            <!-- before receiving inventory, check to see if there is inventory information in this database -->
-            <entity-count entity-name="InventoryItem" count-field="iiCount">
-                <condition-expr field-name="facilityId" operator="equals" from-field="returnHeader.destinationFacilityId"/>
-            </entity-count>
-
-            <if-compare field="iiCount" operator="greater" value="0" type="Integer">
-                <!-- create a return shipment for this return -->
-                <set field="shipmentCtx.returnId" from-field="parameters.returnId"/>
-                <call-service service-name="createShipmentForReturn" in-map-name="shipmentCtx">
-                    <result-to-field result-name="shipmentId"/>
-                </call-service>
-                <log level="info" message="Created new shipment ${shipmentId}"/>
-
-                <entity-condition entity-name="ReturnItem" list="returnItems">
-                    <condition-expr field-name="returnId" operator="equals" from-field="returnHeader.returnId"/>
-                </entity-condition>
-
-                <!-- if no inventory item type specified, get default from facility -->
-                <if-empty field="parameters.inventoryItemTypeId">
-                    <get-related-one value-field="returnHeader" relation-name="Facility" to-value-field="facility"/>
-                    <set field="parameters.inventoryItemTypeId" from-field="facility.defaultInventoryItemTypeId" default-value="NON_SERIAL_INV_ITEM"/>
-                </if-empty>
-
-                <now-timestamp field="nowTimestamp"/>
-
-                <entity-count entity-name="ReturnItem" count-field="returnItemCount">
-                    <condition-expr field-name="returnId" operator="equals" from-field="returnHeader.returnId"/>
-                </entity-count>
-                <set field="nonProductItems" type="Long" value="0"/>
-
-                <iterate list="returnItems" entry="returnItem">
-                    <!-- record this return item on the return shipment as well.  not sure if this is actually necessary... -->
-                    <clear-field field="shipItemCtx"/>
-                    <set from-field="shipmentId" field="shipItemCtx.shipmentId"/>
-                    <set from-field="returnItem.productId" field="shipItemCtx.productId"/>
-                    <set from-field="returnItem.returnQuantity" field="shipItemCtx.quantity"/>
-                    <log level="info" message="calling create shipment item with ${shipItemCtx}"/>
-                    <call-service service-name="createShipmentItem" in-map-name="shipItemCtx">
-                        <result-to-field result-name="shipmentItemSeqId"/>
-                    </call-service>
-                </iterate>
-                <iterate list="returnItems" entry="returnItem">
-                    <clear-field field="receiveCtx"/>
-
-                    <if-empty field="returnItem.expectedItemStatus">
-                        <set value="INV_RETURNED" field="returnItem.expectedItemStatus" type="String"/>
-                    </if-empty>
-                    <get-related-one value-field="returnItem" relation-name="OrderItem" to-value-field="orderItem"/>
-                    <if-not-empty field="orderItem.productId">
-                        <set field="costCtx.returnItemSeqId" from-field="returnItem.returnItemSeqId"/>
-                        <set field="costCtx.returnId" from-field="returnItem.returnId"/>
-                        <call-service service-name="getReturnItemInitialCost" in-map-name="costCtx">
-                            <result-to-field result-name="initialItemCost" field="receiveCtx.unitCost"/>
-                        </call-service>
-                        <!--check if the items already have SERIALIZED inventory. If so, it still puts them back as SERIALIZED with status "Accepted."-->
-                        <entity-count entity-name="InventoryItem" count-field="serializedItemCount">
-                            <condition-list combine="and">
-                                <condition-expr field-name="productId" operator="equals" from-field="returnItem.productId"/>
-                                <condition-expr field-name="facilityId" operator="equals" from-field="returnHeader.destinationFacilityId"/>
-                                <condition-expr field-name="inventoryItemTypeId" operator="equals" value="SERIALIZED_INV_ITEM"/>
-                            </condition-list>
-                        </entity-count>                        
-                        <set field="setNonSerial" value="false"/>
-                        <if-compare field="parameters.inventoryItemTypeId" value="NON_SERIAL_INV_ITEM" operator="equals">
-                            <if-compare field="serializedItemCount" value="0" operator="equals">
-                                <set field="parameters.inventoryItemTypeId" value="NON_SERIAL_INV_ITEM"/>
-                                <set field="setNonSerial" value="true"/>
-                            </if-compare>
-                        </if-compare>
-                        <if-compare field="setNonSerial" value="false" operator="equals">
-                            <set field="parameters.inventoryItemTypeId" value="SERIALIZED_INV_ITEM"/>
-                            <set field="returnItem.returnQuantity" value="1" type="BigDecimal"/>
-                        </if-compare>
-
-                        <set from-field="parameters.inventoryItemTypeId" field="receiveCtx.inventoryItemTypeId"/>
-                        <set from-field="returnItem.expectedItemStatus" field="receiveCtx.statusId"/>
-                        <set from-field="returnItem.productId" field="receiveCtx.productId"/>
-                        <set from-field="returnItem.returnItemSeqId" field="receiveCtx.returnItemSeqId"/>
-                        <set from-field="returnItem.returnId" field="receiveCtx.returnId"/>
-                        <set from-field="returnItem.returnQuantity" field="receiveCtx.quantityAccepted"/>
-                        <set from-field="returnHeader.destinationFacilityId" field="receiveCtx.facilityId"/>
-                        <!-- important: associate ShipmentReceipt with return shipment created -->
-                        <set field="receiveCtx.shipmentId" from-field="shipmentId"/>
-
-                        <set field="receiveCtx.comments" value="Returned Item RA# ${returnItem.returnId}"/>
-                        <set field="receiveCtx.datetimeReceived" from-field="nowTimestamp"/>
-                        <set field="receiveCtx.quantityRejected" value="0" type="BigDecimal"/>
-
-                        <call-service service-name="receiveInventoryProduct" in-map-name="receiveCtx"/>
-                    <else>
-                        <calculate field="nonProductItems" type="Long">
-                            <calcop operator="add">
-                                <number value="1"/>
-                            </calcop>
-                        </calculate>
-                    </else>
-                    </if-not-empty>
-                </iterate>
-
-                <!-- now that the receive is done; set the need flag to N -->
-                <refresh-value value-field="returnHeader"/>
-                <set field="returnHeader.needsInventoryReceive" value="N"/>
-                <store-value value-field="returnHeader"/>
-
-                <!-- always check/update the ReturnHeader status, even though it might have been from the receiving above, just make sure -->
-                <if-compare field="returnHeader.statusId" operator="not-equals" value="RETURN_RECEIVED">
-                    <set field="retStCtx.returnId" from-field="returnHeader.returnId"/>
-                    <set field="retStCtx.statusId" value="RETURN_RECEIVED"/>
-                    <call-service service-name="updateReturnHeader" in-map-name="retStCtx"/>
-                </if-compare>
-            <else>
-                <log level="info" message="Not receiving inventory for returnId ${returnHeader.returnId}, no inventory information available."/>
-            </else>
-            </if-compare>
-        </if-compare>
-    </simple-method>
-    
-    <simple-method method-name="issueOrderItemToShipmentAndReceiveAgainstPO" short-description="Issues order item quantity specified to the shipment, then receives inventory for that item and quantity">
-
-        <!-- get orderItem -->
-        <entity-one entity-name="OrderItem" value-field="orderItem" auto-field-map="true"/>
-        <!-- get orderItemShipGroupAssoc -->
-        <entity-one entity-name="OrderItemShipGroupAssoc" value-field="orderItemShipGroupAssoc" auto-field-map="true"/>
-        <!-- get shipment -->
-        <entity-one entity-name="Shipment" value-field="shipment" auto-field-map="true"/>
-        
-        <!-- try to find an existing shipmentItem and attach to it, if none found create a new shipmentItem -->
-        <!-- if there is NO productId on the orderItem, ALWAYS create a new shipmentItem -->
-        <if-not-empty field="orderItem.productId">
-            <entity-condition entity-name="ShipmentItem" list="shipmentItems">
-                <condition-list combine="and">
-                    <condition-expr field-name="productId" from-field="orderItem.productId"/>
-                    <condition-expr field-name="shipmentId" from-field="shipment.shipmentId"/>
-                    <condition-expr field-name="shipmentItemSeqId" from-field="parameters.shipmentItemSeqId" ignore-if-empty="true"/>
-                </condition-list>
-                <order-by field-name="shipmentItemSeqId"/>
-            </entity-condition>
-            <first-from-list list="shipmentItems" entry="shipmentItem"/>
-        </if-not-empty>
-
-        <if-empty field="shipmentItem">
-            <set from-field="orderItem.productId" field="shipmentItemCreate.productId"/>
-            <set from-field="parameters.shipmentId" field="shipmentItemCreate.shipmentId"/>
-            <set from-field="parameters.quantity" field="shipmentItemCreate.quantity"/>
-            <call-service service-name="createShipmentItem" in-map-name="shipmentItemCreate">
-                <result-to-field result-name="shipmentItemSeqId" field="shipmentItemLookupPk.shipmentItemSeqId"/>
-            </call-service>
-            <set from-field="parameters.shipmentId" field="shipmentItemLookupPk.shipmentId"/>
-            <find-by-primary-key entity-name="ShipmentItem" map="shipmentItemLookupPk" value-field="shipmentItem"/>
-            
-            <!-- Create OrderShipment for this ShipmentItem -->
-            <set from-field="parameters.quantity" field="orderShipmentCreate.quantity"/>
-            <set from-field="shipmentItem.shipmentId" field="orderShipmentCreate.shipmentId"/>
-            <set from-field="shipmentItem.shipmentItemSeqId" field="orderShipmentCreate.shipmentItemSeqId"/>
-            <set from-field="orderItem.orderId" field="orderShipmentCreate.orderId"/>
-            <set from-field="orderItem.orderItemSeqId" field="orderShipmentCreate.orderItemSeqId"/>
-            
-            <if-not-empty field="orderItemShipGroupAssoc">
-                <!-- If we have a ShipGroup Assoc for this Item to focus on, set that; this is mostly the case for purchase orders and such -->
-                <set from-field="orderItemShipGroupAssoc.shipGroupSeqId" field="orderShipmentCreate.shipGroupSeqId"/>
-            </if-not-empty>
-            <call-service service-name="createOrderShipment" in-map-name="orderShipmentCreate"/>
-        <else>
-            <call-simple-method method-name="getTotalIssuedQuantityForOrderItem" xml-resource="component://product/minilang/shipment/issuance/IssuanceServices.xml"/>
-            <call-simple-method method-name="getReceivedQuantityForOrderItem"/>
-            <set field="receivedQuantity" value="${receivedQuantity$bigDecimal + parameters.quantity$bigDecimal}" type="BigDecimal"/>
-            <entity-and entity-name="OrderShipment" list="orderShipments">
-                <field-map field-name="orderId" from-field="orderItem.orderId"/>
-                <field-map field-name="orderItemSeqId" from-field="orderItem.orderItemSeqId"/>
-                <field-map field-name="shipmentId" from-field="shipmentItem.shipmentId"/>
-                <field-map field-name="shipmentItemSeqId" from-field="shipmentItem.shipmentItemSeqId"/>
-                <field-map field-name="shipGroupSeqId" from-field="orderItemShipGroupAssoc.shipGroupSeqId"/>
-            </entity-and>
-            <first-from-list list="orderShipments" entry="orderShipment"/>
-            <if-compare-field field="totalIssuedQuantity" operator="less" to-field="receivedQuantity" type="BigDecimal">
-                <set field="quantityToAdd" value="${receivedQuantity$bigDecimal - totalIssuedQuantity$bigDecimal}" type="BigDecimal"/>
-                <set field="shipmentItem.quantity" value="${shipmentItem.quantity$bigDecimal + quantityToAdd$bigDecimal}" type="BigDecimal"/>
-                <store-value value-field="shipmentItem"/>
-                <set field="shipmentItemSeqId" from-field="shipmentItem.shipmentItemSeqId"/>
-                
-                <set field="orderShipment.quantity" value="${orderShipment.quantity$bigDecimal + quantityToAdd$bigDecimal}" type="BigDecimal"/>
-                <store-value value-field="orderShipment"/>
-            </if-compare-field>
-        </else>
-        </if-empty>
-        <!--
-            TODO: if we want to record the role of the facility operation we have to re-implement this using ShipmentReceiptRole
-        <call-simple-method method-name="associateIssueRoles" xml-resource="component://product/minilang/shipment/issuance/IssuanceServices.xml"/>
-        -->
-
-        <set-service-fields service-name="receiveInventoryProduct" map="parameters" to-map="receiveInventoryProductCtx"/>
-        <set field="receiveInventoryProductCtx.shipmentItemSeqId" from-field="shipmentItemSeqId"/>
-        <call-service service-name="receiveInventoryProduct" in-map-name="receiveInventoryProductCtx">
-            <result-to-result result-name="inventoryItemId"/>
-        </call-service>
-    </simple-method>
-    
-    <simple-method method-name="getReceivedQuantityForOrderItem" short-description="Computes the till now received quantity from all ShipmentReceipts">
-        <set field="receivedQuantity" type="BigDecimal" value="0"/>
-        <entity-and entity-name="ShipmentReceipt" list="shipmentReceipts">
-            <field-map field-name="orderId" from-field="orderItem.orderId"/>
-            <field-map field-name="orderItemSeqId" from-field="orderItem.orderItemSeqId"/>
-        </entity-and>
-        <iterate list="shipmentReceipts" entry="shipmentReceipt">
-            <set field="receivedQuantity" value="${receivedQuantity$bigDecimal + shipmentReceipt.quantityAccepted$bigDecimal}" type="BigDecimal"/>
-        </iterate>
-    </simple-method>
-
-    <simple-method method-name="updateIssuanceShipmentAndPoOnReceiveInventory" short-description="Update issuance, shipment and order items if quantity received is higher than quantity on purchase order">
-        <entity-one entity-name="OrderItem" value-field="orderItem"/>
-        <if-not-empty field="parameters.orderCurrencyUnitPrice">
-            <if-compare-field field="parameters.orderCurrencyUnitPrice" operator="not-equals" to-field="orderItem.unitPrice" type="BigDecimal">
-                <set field="orderItem.unitPrice" from-field="parameters.orderCurrencyUnitPrice" type="BigDecimal"/>
-                <store-value value-field="orderItem"/>
-            </if-compare-field>
-        <else>
-            <if-compare-field field="parameters.unitCost" operator="not-equals" to-field="orderItem.unitPrice" type="BigDecimal">
-                <set field="orderItem.unitPrice" from-field="parameters.unitCost" type="BigDecimal"/>
-                <store-value value-field="orderItem"/>
-            </if-compare-field>
-        </else>
-        </if-not-empty>
-        <call-simple-method method-name="getReceivedQuantityForOrderItem"/>
-        <if-compare-field field="orderItem.quantity" operator="less" to-field="receivedQuantity" type="BigDecimal">
-            <entity-and entity-name="OrderItemShipGroupAssoc" list="orderItemShipGroupAssocs">
-                <field-map field-name="orderId" from-field="orderItem.orderId"/>
-                <field-map field-name="orderItemSeqId" from-field="orderItem.orderItemSeqId"/>
-            </entity-and>
-            <calculate field="quantityVariance" type="BigDecimal" decimal-scale="2" rounding-mode="HalfUp">
-                <calcop operator="subtract">
-                    <calcop operator="get" field="receivedQuantity"/>
-                    <calcop operator="get" field="orderItem.quantity"/>
-                </calcop>
-            </calculate>
-            <first-from-list list="orderItemShipGroupAssocs" entry="orderItemShipGroupAssoc"/>
-            <calculate field="oisgaQuantity" type="BigDecimal" decimal-scale="2" rounding-mode="HalfUp">
-                <calcop operator="add">
-                    <calcop operator="get" field="orderItemShipGroupAssoc.quantity"/>
-                    <calcop operator="get" field="quantityVariance"/>
-                </calcop>
-            </calculate>
-            <set field="orderItemShipGroupAssoc.quantity" from-field="oisgaQuantity"/>
-            <set field="orderItem.quantity" from-field="receivedQuantity"/>
-            <store-value value-field="orderItemShipGroupAssoc"/>
-            <store-value value-field="orderItem"/>
-        </if-compare-field>
-        <if-not-empty field="parameters.shipmentId">
-            <if-not-empty field="orderItem.productId">
-                <call-simple-method method-name="getTotalIssuedQuantityForOrderItem" xml-resource="component://product/minilang/shipment/issuance/IssuanceServices.xml"/>
-                <if-compare-field field="totalIssuedQuantity" operator="less" to-field="receivedQuantity" type="BigDecimal">
-                    <set field="quantityToAdd" value="${receivedQuantity$bigDecimal - totalIssuedQuantity$bigDecimal}" type="BigDecimal"/>
-                    <entity-condition entity-name="ShipmentItem" list="shipmentItems">
-                        <condition-list combine="and">
-                            <condition-expr field-name="productId" from-field="orderItem.productId"/>
-                            <condition-expr field-name="shipmentId" from-field="parameters.shipmentId"/>
-                            <condition-expr field-name="shipmentItemSeqId" from-field="parameters.shipmentItemSeqId" ignore-if-empty="true"/>
-                        </condition-list>
-                        <order-by field-name="shipmentItemSeqId"/>
-                    </entity-condition>
-                    <first-from-list list="shipmentItems" entry="shipmentItem"/>
-                    <set field="shipmentItem.quantity" value="${shipmentItem.quantity$bigDecimal + quantityToAdd$bigDecimal}" type="BigDecimal"/>
-                    <store-value value-field="shipmentItem"/>
-                    
-                    <entity-and entity-name="OrderShipment" list="orderShipments">
-                        <field-map field-name="orderId" from-field="parameters.orderId"/>
-                        <field-map field-name="orderItemSeqId" from-field="parameters.orderItemSeqId"/>
-                        <field-map field-name="shipmentId" from-field="parameters.shipmentId"/>
-                        <field-map field-name="shipmentItemSeqId" from-field="shipmentItem.shipmentItemSeqId"/>
-                    </entity-and>
-                    <first-from-list list="orderShipments" entry="orderShipment"/>
-                    <set field="orderShipment.quantity" value="${orderShipment.quantity$bigDecimal + quantityToAdd$bigDecimal}" type="BigDecimal"/>
-                    <store-value value-field="orderShipment"/>
-                    <!--
-                        TODO: if we want to record the role of the facility operation we have to re-implement this using ShipmentReceiptRole
-                    <set field="itemIssuanceId" from-field="itemIssuance.itemIssuanceId"/>
-                    <call-simple-method method-name="associateIssueRoles" xml-resource="component://product/minilang/shipment/issuance/IssuanceServices.xml"/>
-                    -->
-                </if-compare-field>
-            </if-not-empty>
-        </if-not-empty>
-    </simple-method>
-
-    <simple-method method-name="cancelReceivedItems" short-description="Cancel Received Items against a purchase order if received something incorrectly">
-        <!-- TODO: When items are received against a Purchase Order, service listed below changes certain things in the system. Changes done by these
-                   services also need to be reverted and missing logic can be added later.
-            1. addProductsBackToCategory
-            2. setUnitPriceAsLastPrice
-            3. createAcctgTransForShipmentReceipt
-            4. updateProductIfAvailableFromShipment
-         -->
-        <!-- update the accepted and received quantity to zero in ShipmentReceipt entity -->
-        <entity-one entity-name="ShipmentReceipt" value-field="shipmentReceipt"/>
-        <set field="shipmentReceipt.quantityAccepted" value="0" type="BigDecimal"/>
-        <set field="shipmentReceipt.quantityRejected" value="0" type="BigDecimal"/>
-        <store-value value-field="shipmentReceipt"/>
-
-        <!-- create record for InventoryItemDetail entity -->
-        <get-related-one value-field="shipmentReceipt" relation-name="InventoryItem" to-value-field="inventoryItem"/>
-        <set field="inventoryItemDetailMap.inventoryItemId" from-field="inventoryItem.inventoryItemId"/>
-        <calculate field="inventoryItemDetailMap.quantityOnHandDiff">
-            <calcop operator="multiply" field="inventoryItem.quantityOnHandTotal">
-                <number value="-1"/>
-            </calcop>
-        </calculate>
-        <calculate field="inventoryItemDetailMap.availableToPromiseDiff">
-            <calcop operator="multiply" field="inventoryItem.availableToPromiseTotal">
-                <number value="-1"/>
-            </calcop>
-        </calculate>
-        <call-service service-name="createInventoryItemDetail" in-map-name="inventoryItemDetailMap"/>
-        
-        <!-- Balance the inventory item -->
-        <set field="balanceInventoryItemMap.inventoryItemId" from-field="inventoryItem.inventoryItemId"/>
-        <set field="balanceInventoryItemMap.priorityOrderId" from-field="shipmentReceipt.orderId"/>
-        <set field="balanceInventoryItemMap.priorityOrderItemSeqId" from-field="shipmentReceipt.orderItemSeqId"/>
-        <call-service service-name="balanceInventoryItems" in-map-name="balanceInventoryItemMap"/>
-
-        <!-- update the shipment status, if shipment was received -->
-        <get-related-one value-field="shipmentReceipt" relation-name="Shipment" to-value-field="shipment"/>
-        <if-compare field="shipment.statusId" operator="equals" value="PURCH_SHIP_RECEIVED">
-            <set field="shipmentStatusMap.shipmentId" from-field="shipment.shipmentId"/>
-            <set field="shipmentStatusMap.statusId" value="PURCH_SHIP_SHIPPED"/>
-            <call-service service-name="updateShipment" in-map-name="shipmentStatusMap"/>
-        </if-compare>
-        
-        <!-- change order item and order status -->
-        <get-related-one value-field="shipmentReceipt" relation-name="OrderItem" to-value-field="orderItem"/>
-        <if-compare field="orderItem.statusId" operator="equals" value="ITEM_COMPLETED">
-            <!-- update the order item status -->
-            <set field="orderItem.statusId" value="ITEM_APPROVED"/>
-            <set-service-fields service-name="changeOrderItemStatus" map="orderItem" to-map="orderItemCtx"/>
-            <set field="orderItemCtx.fromStatusId" value="ITEM_COMPLETED"/>
-            <call-service service-name="changeOrderItemStatus" in-map-name="orderItemCtx"/>
-            <get-related-one value-field="orderItem" relation-name="OrderHeader" to-value-field="orderHeader"/>
-            <!-- cancel the invoice -->
-            <entity-and entity-name="OrderItemBilling" list="orderItemBillings">
-                <field-map field-name="orderId" from-field="orderItem.orderId"/>
-            </entity-and>
-            <if-not-empty field="orderItemBillings">
-                <first-from-list list="orderItemBillings" entry="orderItemBilling"/>
-                <set field="invoiceStatusMap.invoiceId" from-field="orderItemBilling.invoiceId"/>
-                <set field="invoiceStatusMap.statusId" value="INVOICE_CANCELLED"/>
-                <call-service service-name="setInvoiceStatus" in-map-name="invoiceStatusMap"/>
-            </if-not-empty>
-        </if-compare>
-    </simple-method>
-</simple-methods>
diff --git a/applications/product/servicedef/services_shipment.xml b/applications/product/servicedef/services_shipment.xml
index 799b377..89bea13 100644
--- a/applications/product/servicedef/services_shipment.xml
+++ b/applications/product/servicedef/services_shipment.xml
@@ -606,6 +606,13 @@ under the License.
         </attribute>
     </service>
 
+    <service name="getTotalIssuedQuantityForOrderItem" engine="groovy"
+            location="component://product/groovyScripts/shipment/issuance/IssuanceServices.groovy" invoke="getTotalIssuedQuantityForOrderItem" auth="true">
+        <description>Computes the total quantity assigned to shipment for a purchase order item</description>
+        <attribute name="orderItem" type="GenericValue" mode="IN" optional="false"/>
+        <attribute name="totalIssuedQuantity" type="BigDecimal" mode="OUT" optional="false"/>
+    </service>
+
     <!-- Pick Verify Services -->
     <service name="verifySingleItem" engine="java"
             location="org.apache.ofbiz.shipment.verify.VerifyPickServices" invoke="verifySingleItem" auth="true">
@@ -846,8 +853,8 @@ under the License.
         <override name="quantityAccepted" optional="false"/>
         <override name="quantityRejected" optional="false"/>
     </service>
-    <service name="createShipmentReceipt" engine="simple"
-            location="component://product/minilang/shipment/receipt/ShipmentReceiptServices.xml" invoke="createShipmentReceipt" auth="true">
+    <service name="createShipmentReceipt" engine="groovy"
+            location="component://product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy" invoke="createShipmentReceipt" auth="true">
         <description>Creates a ShipmentReceipt Record</description>
         <permission-service service-name="facilityGenericPermission" main-action="CREATE"/>
         <implements service="interfaceShipmentReceipt"/>
@@ -870,8 +877,8 @@ under the License.
         <attribute name="shipmentId" type="String" mode="IN" optional="false"/>
     </service>
 
-    <service name="receiveInventoryProduct" engine="simple" transaction-timeout="600"
-            location="component://product/minilang/shipment/receipt/ShipmentReceiptServices.xml" invoke="receiveInventoryProduct" auth="true">
+    <service name="receiveInventoryProduct" engine="groovy" transaction-timeout="600"
+            location="component://product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy" invoke="receiveInventoryProduct" auth="true">
         <description>Receive Inventory In Warehouse</description>
         <permission-service service-name="facilityGenericPermission" main-action="CREATE"/>
         <auto-attributes entity-name="InventoryItem" include="nonpk" mode="IN" optional="true">
@@ -893,8 +900,8 @@ under the License.
         <override name="facilityId" optional="false"/>
     </service>
 
-    <service name="issueOrderItemToShipmentAndReceiveAgainstPO" engine="simple" transaction-timeout="600"
-            location="component://product/minilang/shipment/receipt/ShipmentReceiptServices.xml" invoke="issueOrderItemToShipmentAndReceiveAgainstPO" auth="true">
+    <service name="issueOrderItemToShipmentAndReceiveAgainstPO" engine="groovy" transaction-timeout="600"
+            location="component://product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy" invoke="issueOrderItemToShipmentAndReceiveAgainstPO" auth="true">
         <description>Issues order item quantity specified to the shipment, then receives inventory for that item and quantity</description>
         <required-permissions join-type="AND">
             <permission-service service-name="checkCanChangeShipmentStatusPacked" main-action="CREATE"/>
@@ -904,14 +911,21 @@ under the License.
         <implements service="receiveInventoryProduct"/>
     </service>
 
-    <service name="quickReceiveReturn" engine="simple"
-            location="component://product/minilang/shipment/receipt/ShipmentReceiptServices.xml" invoke="quickReceiveReturn" auth="true">
+    <service name="quickReceiveReturn" engine="groovy"
+            location="component://product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy" invoke="quickReceiveReturn" auth="true">
         <permission-service service-name="facilityGenericPermission" main-action="CREATE"/>
         <attribute name="returnId" type="String" mode="IN" optional="false"/>
         <attribute name="inventoryItemTypeId" type="String" mode="IN" optional="true"/>
         <attribute name="statusId" type="String" mode="IN" optional="true"/>
     </service>
 
+    <service name="interfaceShipmentReceiptRole" engine="interface" location="" invoke="">
+        <description>Interface for ShipmentReceiptRole</description>
+        <attribute name="receiptId" type="String" mode="IN" optional="false"/>
+        <attribute name="partyId" type="String" mode="IN" optional="false"/>
+        <attribute name="roleTypeId" type="String" mode="IN" optional="false"/>
+    </service>
+
     <!-- Shipment Receipt Role Services -->
     <service name="createShipmentReceiptRole" default-entity-name="ShipmentReceiptRole" engine="entity-auto" invoke="create" auth="true">
         <description>Create a ShipmentReceipt Role entry</description>
@@ -997,8 +1011,8 @@ under the License.
         <implements service="calcShipmentEstimateInterface"/>
     </service>
 
-    <service name="cancelReceivedItems" engine="simple"
-            location="component://product/minilang/shipment/receipt/ShipmentReceiptServices.xml" invoke="cancelReceivedItems" auth="true">
+    <service name="cancelReceivedItems" engine="groovy"
+            location="component://product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy" invoke="cancelReceivedItems" auth="true">
         <description>Cancel Received Items against a purchase order if received something incorrectly</description>
         <attribute name="receiptId" type="String" mode="IN" optional="false">
      <type-validate>
@@ -1105,8 +1119,8 @@ under the License.
         <attribute name="messageWrapper" type="org.apache.ofbiz.service.mail.MimeMessageWrapper" mode="OUT" optional="true"/>
         <attribute name="communicationEventId" type="String" mode="OUT" optional="true"/>
     </service>
-    <service name="updateIssuanceShipmentAndPoOnReceiveInventory" engine="simple"
-            location="component://product/minilang/shipment/receipt/ShipmentReceiptServices.xml" invoke="updateIssuanceShipmentAndPoOnReceiveInventory">
+    <service name="updateIssuanceShipmentAndPoOnReceiveInventory" engine="groovy"
+            location="component://product/groovyScripts/shipment/receipt/ShipmentReceiptServices.groovy" invoke="updateIssuanceShipmentAndPoOnReceiveInventory">
         <description>Update issuance, shipment and order items if quantity received is higher than quantity on purchase order</description>
         <attribute name="orderId" type="String" mode="IN" optional="false">
      <type-validate>