[ofbiz-framework] branch trunk updated: Improved: Convert CostService.xml minilang to groovy

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: Improved: Convert CostService.xml minilang to groovy

danwatford
This is an automated email from the ASF dual-hosted git repository.

danwatford 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 8a96462  Improved: Convert CostService.xml minilang to groovy
8a96462 is described below

commit 8a964625385a2fa097679446c5d6435265eb50b0
Author: SebastianEcomify <[hidden email]>
AuthorDate: Sun Feb 21 12:38:21 2021 +0100

    Improved: Convert CostService.xml minilang to groovy
   
    (OFBIZ-11595)
   
    Thanks: Sebastian Berg for implementation
---
 .../accounting/config/arithmetic.properties        |   1 +
 .../accounting/servicedef/services_cost.xml        |   8 +-
 .../groovyScripts/product/cost/CostServices.groovy | 512 +++++++++++++++++
 .../product/minilang/product/cost/CostServices.xml | 613 ---------------------
 applications/product/servicedef/services_cost.xml  |  32 +-
 5 files changed, 533 insertions(+), 633 deletions(-)

diff --git a/applications/accounting/config/arithmetic.properties b/applications/accounting/config/arithmetic.properties
index 5dea2b2..567ea63 100644
--- a/applications/accounting/config/arithmetic.properties
+++ b/applications/accounting/config/arithmetic.properties
@@ -41,6 +41,7 @@ order.rounding = ROUND_HALF_UP
 finaccount.decimals = 2
 finaccount.rounding = ROUND_HALF_UP
 finaccount.roundingSimpleMethod = HalfUp
+finaccount.roundingGroovyMethod = HALF_UP
 
 # Most companies would want their sales tax calculations ALWAYS to round up (ie, 100.081 becomes 100.09)
 # This could be ROUND_CEILING or ROUND_UP.  (The difference is that ROUND_CEILING rounds towards positive infinity,
diff --git a/applications/accounting/servicedef/services_cost.xml b/applications/accounting/servicedef/services_cost.xml
index 00ba413..ef02a17 100644
--- a/applications/accounting/servicedef/services_cost.xml
+++ b/applications/accounting/servicedef/services_cost.xml
@@ -81,8 +81,8 @@ under the License.
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
 
-    <service name="updateProductAverageCostOnReceiveInventory" default-entity-name="ProductAverageCost" engine="simple"
-            location="component://product/minilang/product/cost/CostServices.xml" invoke="updateProductAverageCostOnReceiveInventory" auth="true">
+    <service name="updateProductAverageCostOnReceiveInventory" default-entity-name="ProductAverageCost" engine="groovy"
+            location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="updateProductAverageCostOnReceiveInventory" auth="true">
         <description>Update a Product Average Cost record on receive inventory</description>
         <permission-service service-name="acctgCostPermissionCheck" main-action="UPDATE"/>
         <attribute name="facilityId" type="String" mode="IN" optional="false"/>
@@ -91,8 +91,8 @@ under the License.
         <attribute name="inventoryItemId" type="String" mode="IN" optional="false"/>
     </service>
 
-    <service name="getProductAverageCost" engine="simple"
-            location="component://product/minilang/product/cost/CostServices.xml" invoke="getProductAverageCost" auth="true">
+    <service name="getProductAverageCost" engine="groovy"
+            location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="getProductAverageCost" auth="true">
         <description>Get Average cost of a product</description>
         <attribute name="inventoryItem" type="org.apache.ofbiz.entity.GenericValue" mode="IN" optional="true"/>
         <attribute name="unitCost" type="BigDecimal" mode="OUT" optional="false"/>
diff --git a/applications/product/groovyScripts/product/cost/CostServices.groovy b/applications/product/groovyScripts/product/cost/CostServices.groovy
new file mode 100644
index 0000000..53a9b4f
--- /dev/null
+++ b/applications/product/groovyScripts/product/cost/CostServices.groovy
@@ -0,0 +1,512 @@
+/*
+ * 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 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
+import org.apache.ofbiz.entity.condition.EntityOperator
+import org.apache.ofbiz.entity.util.EntityUtil
+import org.apache.ofbiz.service.ServiceUtil
+
+
+/**
+ * Cancels CostComponents
+ * @return
+ */
+def cancelCostComponents() {
+    Map costsAndMap = [:]
+    if (parameters.costComponentId) {
+        costsAndMap.costComponentId = parameters.costComponentId
+    }
+    if (parameters.productId) {
+        costsAndMap.productId = parameters.productId
+    }
+    if (parameters.costUomId) {
+        costsAndMap.costUomId = parameters.costUomId
+    }
+    if (parameters.costComponentTypeId) {
+        costsAndMap.costComponentTypeId = parameters.costComponentTypeId
+    }
+    List existingCosts = from("CostComponent").where(costsAndMap).filterByDate().queryList()
+    for (GenericValue existingCost : existingCosts) {
+        existingCost.thruDate = UtilDateTime.nowTimestamp()
+        existingCost.store()
+    }
+    return success()
+}
+
+/**
+ * Create a CostComponent and cancel the existing ones
+ * @return
+ */
+def recreateCostComponent() {
+    Map result = success()
+    // The existing costs of the same type are expired
+    Map costsAndMap = [:]
+    if (parameters.productId) {
+        costsAndMap.productId = parameters.productId
+    }
+    if (parameters.costUomId) {
+        costsAndMap.costUomId = parameters.costUomId
+    }
+    if (parameters.costComponentTypeId) {
+        costsAndMap.costComponentTypeId = parameters.costComponentTypeId
+    }
+    List existingCosts = from("CostComponent").where(costsAndMap).filterByDate().queryList()
+    for (GenericValue existingCost : existingCosts) {
+        existingCost.thruDate = UtilDateTime.nowTimestamp()
+        existingCost.store()
+    }
+    // The new cost is created
+    GenericValue newEntity = makeValue("CostComponent")
+    newEntity.setNonPKFields(parameters)
+    newEntity.costComponentId = delegator.getNextSeqId("CostComponent")
+    if (!newEntity.fromDate) {
+        newEntity.fromDate = UtilDateTime.nowTimestamp()
+    }
+    newEntity.create()
+    result.costComponentId = newEntity.costComponentId
+    return result
+}
+
+// Services to get the product and tasks costs
+
+/**
+ * Gets the product's costs (from CostComponent or ProductPrice)
+ * @return
+ */
+def getProductCost() {
+    Map result = success()
+    Map inputMap
+    String inputString = "${parameters.costComponentTypePrefix}_%"
+    EntityCondition condition = EntityCondition.makeCondition(
+            EntityCondition.makeCondition("productId", parameters.productId),
+            EntityCondition.makeCondition("costUomId", parameters.currencyUomId),
+            EntityCondition.makeCondition("costComponentTypeId", EntityOperator.LIKE , inputString)
+            )
+    List costComponents = from("CostComponent").where(condition).filterByDate().queryList()
+    BigDecimal productCost = (BigDecimal) 0
+    for (GenericValue costComponent : costComponents) {
+        productCost += costComponent.cost
+        // set field="productCost" value="${costComponent.cost + productCost}" type="BigDecimal"/
+    }
+    productCost = productCost.setScale(6)
+    // if the cost is zero, and the product is a variant, get the cost of the virtual
+    if (productCost == (BigDecimal) 0) {
+        GenericValue product = from("Product").where(parameters).queryOne()
+        Map assocAndMap = [productIdTo: product.productId, productAssocTypeId: "PRODUCT_VARIANT"]
+        GenericValue virtualAssoc = from("ProductAssoc").where(assocAndMap).filterByDate().queryFirst()
+        if (virtualAssoc) {
+            inputMap = [productId: virtualAssoc.productId, currencyUomId: parameters.currencyUomId, costComponentTypePrefix: parameters.costComponentTypePrefix]
+            Map serviceResult = run service: "getProductCost", with: inputMap
+            productCost = serviceResult.productCost
+        }
+    }
+    // if the cost is zero, get the purchase cost from the SupplierProduct
+    if (productCost == (BigDecimal) 0) {
+        List orderByList = [
+            "+supplierPrefOrderId",
+            "+lastPrice"
+        ]
+        Map costsAndMap = [productId: parameters.productId, currencyUomId: parameters.currencyUomId]
+        List priceCosts = from("SupplierProduct").where(costsAndMap).orderBy(orderByList).queryList()
+        priceCosts = EntityUtil.filterByDate(priceCosts, UtilDateTime.nowTimestamp(), "availableFromDate", "availableThruDate", true)
+        if (priceCosts) {
+            GenericValue priceCost = priceCosts.get(0)
+            if (priceCost.lastPrice) {
+                productCost = priceCost.lastPrice
+            }
+        }
+        // if the cost is zero, get the purchase cost from the SupplierProduct in a different currency and try to convert
+        if (productCost == (BigDecimal) 0) {
+            costsAndMap = [productId: parameters.productId]
+            priceCosts = from("SupplierProduct").where(costsAndMap).orderBy(orderByList).queryList()
+            priceCosts = EntityUtil.filterByDate(priceCosts, UtilDateTime.nowTimestamp(), "avalableFromDate", "availableThruDate", true)
+            if (priceCosts) {
+                GenericValue priceCost = priceCosts.get(0)
+                if (priceCost.lastPrice) {
+                    // we try to convert the lastPrice to the desired currency
+                    inputMap = [originalValue: priceCost.lastPrice, uomId: priceCost.currencyUomId, uomIdTo: parameters.currencyUomId]
+                    Map serviceResultCU
+                    try {
+                        serviceResultCU = dispatcher.runSync("convertUom", inputMap, 7200, true)
+                    } catch (Exception e) {
+                        serviceResultCU = ServiceUtil.returnError(e.toString())
+                    }
+                    productCost = serviceResultCU.convertedValue
+                    // if currency conversion fails then a 0 cost will be returned
+                    if (!productCost) {
+                        logWarning("Currency conversion failed for ProductCost lookup; unable to convert from ${priceCost.currencyUomId} to ${parameters.currencyUomId}")
+                        productCost = (BigDecimal) 0
+                    }
+                }
+            }
+        }
+    }
+    //    <if-compare field="productCost" operator="equals" value="0" type="BigDecimal">
+    //        <clear-field field="costsAndMap"/>
+    //        <set from-field="parameters.productId" field="costsAndMap.productId"/>
+    //        <set from-field="parameters.currencyUomId" field="costsAndMap.currencyUomId"/>
+    //        <set from-field="parameters.productPriceTypeId" field="costsAndMap.productPriceTypeId"/>
+    //        <find-by-and entity-name="ProductPrice" map="costsAndMap" list="priceCosts"/>
+    //        <filter-list-by-date list="priceCosts"/>
+    //        <first-from-list list="priceCosts" entry="priceCost"/>
+    //        <if-not-empty field="priceCost.price">
+    //            <set from-field="priceCost.price" field="productCost"/>
+    //        </if-not-empty>
+    //    </if-compare>
+    result.productCost = productCost
+    return result
+}
+
+/**
+ * Gets the production run task's costs
+ * @return
+ */
+def getTaskCost() {
+    Map result = success()
+    Map costsByType = [:]
+    GenericValue setupCost
+    GenericValue usageCost
+    // First of all, the estimated task time is computed
+    Map inputMap = parameters
+    inputMap.taskId = parameters.workEffortId
+    Map serviceResult = run service: "getEstimatedTaskTime", with: inputMap
+    Long totalEstimatedTaskTime = serviceResult.estimatedTaskTime
+    BigDecimal setupTime = serviceResult.setupTime
+    BigDecimal estimatedTaskTime = totalEstimatedTaskTime - setupTime
+    estimatedTaskTime = estimatedTaskTime.setScale(6)
+
+    GenericValue task = from("WorkEffort").where(parameters).queryOne()
+    if (task) {
+        GenericValue fixedAsset = delegator.getRelatedOne("FixedAsset", task, false)
+        Map costsAndMap = [amountUomId: parameters.currencyUomId, fixedAssetStdCostTypeId: "SETUP_COST"]
+        List setupCosts = delegator.getRelated("FixedAssetStdCost", costsAndMap, null, fixedAsset, false)
+        setupCosts = EntityUtil.filterByDate(setupCosts)
+        // <filter-list-by-and list-name="costs" map-name="costsAndMap"/>
+        setupCost = setupCosts.get(0)
+        costsAndMap.fixedAssetStdCostTypeId = "USAGE_COST"
+        List usageCosts = delegator.getRelated("FixedAssetStdCost", costsAndMap, null, fixedAsset, false)
+        usageCosts = EntityUtil.filterByDate(usageCosts)
+        usageCost = usageCosts.get(0)
+    }
+    BigDecimal taskCost = (estimatedTaskTime * (usageCost ? usageCost.amount : 0)) + (setupTime * (setupCost ? setupCost.amount : 0))
+    taskCost = taskCost.setScale(6)
+
+    // Time is converted from milliseconds to hours
+    taskCost /= 3600000
+    taskCost = taskCost.setScale(6)
+
+    // Now compute the costs derived from CostComponentCalc records associated with the task
+    List weccs = delegator.getRelated("WorkEffortCostCalc", null, null, task, false)
+    weccs = EntityUtil.filterByDate(weccs)
+    for (GenericValue wecc : weccs) {
+        GenericValue costComponentCalc = delegator.getRelatedOne("CostComponentCalc", wecc, false)
+        GenericValue customMethod = delegator.getRelatedOne("CustomMethod", costComponentCalc, false)
+        if (!customMethod) {
+            if (costComponentCalc.perMilliSecond) {
+                if (costComponentCalc.perMilliSecond != (BigDecimal) 0) {
+                    BigDecimal totalCostComponentTime = totalEstimatedTaskTime / costComponentCalc.perMilliSecond
+                    totalCostComponentTime = totalCostComponentTime.setScale(6)
+                    BigDecimal totalCostComponentCost = totalCostComponentTime * costComponentCalc.variableCost
+                    totalCostComponentCost += costComponentCalc.fixedCost
+                    totalCostComponentCost = totalCostComponentCost.setScale(6)
+                    costsByType."${wecc.costComponentTypeId}" = totalCostComponentCost
+                }
+            }
+        } else {
+            // FIXME: formulas are still not supported for standard costs
+        }
+    }
+    result.taskCost = taskCost
+    result.costsByType = costsByType
+    return result
+}
+
+// services to automatically generate cost information
+
+/**
+ * Calculates estimated costs for all the products
+ * @return
+ */
+def calculateAllProductsCosts() {
+    // filter-by-date="true"
+    List products = from("Product").orderBy("-billOfMaterialLevel").select("productId").queryList()
+    Map inMap = [currencyUomId: parameters.currencyUomId, costComponentTypePrefix: parameters.costComponentTypePrefix]
+    for (GenericValue product : products) {
+        inMap.productId = product.productId
+        run service: "calculateProductCosts", with: inMap
+    }
+    return success()
+}
+
+/**
+ * Calculates the product's cost
+ * @return
+ */
+def calculateProductCosts() {
+    Map result = success()
+    Map totalCostsByType = [:]
+    BigDecimal totalProductCost = (BigDecimal) 0
+    BigDecimal totalTaskCost = (BigDecimal) 0
+    BigDecimal totalOtherTaskCost = (BigDecimal) 0
+    // the existing costs are expired
+    Map cancelMap = [costComponentTypeId: (String) "${parameters.costComponentTypePrefix}_ROUTE_COST", productId: parameters.productId, costUomId: parameters.currencyUomId]
+    run service: "cancelCostComponents", with: cancelMap
+    cancelMap.costComponentTypeId = (String) "${parameters.costComponentTypePrefix}_MAT_COST"
+    run service: "cancelCostComponents", with: cancelMap
+    // calculate the total materials' cost
+    Map callSvcMap = [productId: parameters.productId]
+    Map serviceResult = run service: "getManufacturingComponents", with: callSvcMap
+    List componentsMap = serviceResult.componentsMap
+    if (componentsMap) {
+        for (Map componentMap : componentsMap) {
+            GenericValue product = componentMap.product
+            Map inputMap = [productId: product.productId, currencyUomId: parameters.currencyUomId, costComponentTypePrefix: parameters.costComponentTypePrefix]
+            Map serviceResultGPC = run service: "getProductCost", with: inputMap
+            BigDecimal productCost = serviceResultGPC.productCost
+            totalProductCost += componentMap.quantity * productCost
+            totalProductCost = totalProductCost.setScale(6)
+        }
+    } else {
+        Map inputMap = [productId: parameters.productId, currencyUomId: parameters.currencyUomId, costComponentTypePrefix: parameters.costComponentTypePrefix]
+        Map serviceResultGPC = run service: "getProductCost", with: inputMap
+        BigDecimal productCost = serviceResultGPC.productCost
+        totalProductCost += productCost
+        totalProductCost = totalProductCost.setScale(6)
+    }
+    // calculate the total tasks' cost
+    callSvcMap.ignoreDefaultRouting = "Y"
+    Map serviceResultGPR = run service: "getProductRouting", with: callSvcMap
+    List tasks = serviceResultGPR.tasks
+    GenericValue routing = serviceResultGPR.routing
+    for (GenericValue task : tasks) {
+        callSvcMap = [workEffortId: task.workEffortIdTo, currencyUomId: parameters.currencyUomId, productId: parameters.productId, routingId: routing.workEffortId]
+        Map serviceResultGTC = run service: "getTaskCost", with: callSvcMap
+        BigDecimal taskCost = serviceResultGTC.taskCost
+        Map costsByType = serviceResultGTC.costsByType
+        totalTaskCost += taskCost
+        totalTaskCost = totalTaskCost.setScale(6)
+        for (Map.Entry entry : costsByType.entrySet()) {
+            if (totalCostsByType."${entry.key}") {
+                totalCostsByType."${entry.key}" = entry.value + totalCostByType."${entry.key}"
+            } else {
+                totalCostsByType."${entry.key}" = entry.value
+            }
+            totalOtherTaskCost += entry.value
+            totalOtherTaskCost = totalOtherTaskCost.setScale(6)
+            totalCostsByType."${entry.key}" = (totalCostsByType."${entry.key}").setScale(6)
+        }
+    }
+    BigDecimal totalCost = totalTaskCost + totalProductCost + totalOtherTaskCost
+    totalCost = totalCost.setScale(6)
+
+    // The CostComponent records are created.
+    if (totalTaskCost > (BigDecimal) 0) {
+        callSvcMap = [costComponentTypeId: (String) "${parameters.costComponentTypePrefix}_ROUTE_COST", productId: parameters.productId, costUomId: parameters.currencyUomId, cost: totalTaskCost]
+        run service: "recreateCostComponent", with: callSvcMap
+    }
+    if (totalProductCost > (BigDecimal) 0) {
+        callSvcMap = [costComponentTypeId: (String) "${parameters.costComponentTypePrefix}_MAT_COST", productId: parameters.productId, costUomId: parameters.currencyUomId, cost: totalProductCost]
+        run service: "recreateCostComponent", with: callSvcMap
+    }
+    for (Map.Entry entry : totalCostsByType.entrySet()) {
+        String costType = entry.getKey()
+        BigDecimal totalCostAmount = entry.getValue()
+        callSvcMap = [costComponentTypeId: "${parameters.costComponentTypePrefix}_${costType}", productId: parameters.productId, costUomId: parameters.currencyUomId, cost: totalCostAmount]
+        run service: "recreateCostComponent", with: callSvcMap
+    }
+    // Now compute the costs derived from CostComponentCalc records associated with the product
+    List productCostComponentCalcs = from("ProductCostComponentCalc").where(productId: parameters.productId).filterByDate().orderBy("sequenceNum").queryList()
+    for (GenericValue productCostComponentCalc : productCostComponentCalcs) {
+        GenericValue costComponentCalc = delegator.getRelatedOne("CostComponentCalc", productCostComponentCalc, false)
+        GenericValue customMethod = delegator.getRelatedOne("CustomMethod", costComponentCalc, false)
+        if (!customMethod) {
+            // TODO: not supported for CostComponentCalc entries directly associated to a product
+            logWarning("Unable to create cost component for cost component calc with id [${costComponentCalc.costComponentCalcId}] because customMethod is not set")
+        } else {
+            Map customMethodParameters = [productCostComponentCalc: productCostComponentCalc, costComponentCalc: costComponentCalc, currencyUomId: parameters.currencyUomId, costComponentTypePrefix: parameters.costComponentTypePrefix, baseCost: totalCost]
+            Map serviceResultCM = run service: "${customMethod.customMethodName}", with: customMethodParameters
+            BigDecimal productCostAdjustment = serviceResultCM.productCostAdjustment
+            callSvcMap = [costComponentTypeId: (String) "${parameters.costComponentTypePrefix}_${productCostComponentCalc.costComponentTypeId}", productId: productCostComponentCalc.productId, costUomId: parameters.currencyUomId, cost: productCostAdjustment]
+            run service: "recreateCostComponent", with: callSvcMap
+            // set field="totalCost" value="${totalCost + productCostAdjustment}" type="BigDecimal"/
+            totalCost += productCostAdjustment
+            totalCost = totalCost.setScale(6)
+        }
+    }
+    result.totalCost = totalCost
+    return result
+}
+
+/**
+ * Calculate inventory average cost for a product
+ * @return
+ */
+def calculateProductAverageCost() {
+    Map result = success()
+    EntityCondition condition = EntityCondition.makeCondition(
+            EntityCondition.makeCondition("productId", parameters.productId),
+            EntityCondition.makeCondition("unitCost", EntityOperator.NOT_EQUAL, null)
+            )
+    if (parameters.facilityId) {
+        condition = EntityCondition.makeCondition(condition, EntityCondition.makeCondition("facilityId", parameters.facilityId))
+    }
+    if (parameters.ownerPartyId) {
+        condition = EntityCondition.makeCondition(condition, EntityCondition.makeCondition("ownerPartyId", parameters.ownerPartyId))
+    }
+
+    List inventoryItems = from("InventoryItem").where(condition).select("quantityOnHandTotal", "unitCost", "currencyUomId").queryList()
+    BigDecimal totalQuantityOnHand = (BigDecimal) 0
+    BigDecimal totalInventoryCost = (BigDecimal) 0
+    BigDecimal absValOfTotalQOH = (BigDecimal) 0
+    BigDecimal absValOfTotalInvCost = (BigDecimal) 0
+    Boolean differentCurrencies = false
+    String currencyUomId
+    for (GenericValue inventoryItem : inventoryItems) {
+        totalQuantityOnHand += inventoryItem.quantityOnHandTotal
+        if (!currencyUomId) {
+            currencyUomId = inventoryItem.currencyUomId
+        }
+        if (!differentCurrencies) {
+            if (currencyUomId == inventoryItem.currencyUomId) {
+                totalInventoryCost += (inventoryItem.unitCost * inventoryItem.quantityOnHandTotal)
+
+                // calculation of absolute values of QOH and total inventory cost
+                if (inventoryItem.quantityOnHandTotal < (BigDecimal) 0) {
+                    absValOfTotalQOH = absValOfTotalQOH - inventoryItem.quantityOnHandTotal
+                    absValOfTotalInvCost = absValOfTotalInvCost + (-1 * inventoryItem.quantityOnHandTotal * inventoryItem.unitCost)
+                } else {
+                    absValOfTotalQOH += inventoryItem.quantityOnHandTotal
+                    absValOfTotalInvCost = absValOfTotalInvCost + (inventoryItem.quantityOnHandTotal * inventoryItem.unitCost)
+                }
+            } else {
+                differentCurrencies = true
+            }
+        }
+    }
+    BigDecimal productAverageCost = (BigDecimal) 0
+    if (absValOfTotalQOH != (BigDecimal) 0) {
+        productAverageCost = absValOfTotalInvCost / absValOfTotalQOH
+    }
+    result.totalQuantityOnHand = totalQuantityOnHand.setScale(2, RoundingMode.HALF_EVEN)
+    if (!differentCurrencies) {
+        result.totalInventoryCost = totalInventoryCost.setScale(2, RoundingMode.HALF_EVEN)
+        result.productAverageCost = productAverageCost.setScale(2, RoundingMode.HALF_EVEN)
+        result.currencyUomId = currencyUomId
+    }
+    return result
+}
+
+/**
+ * Update a Product Average Cost record on receive inventory
+ * @return
+ */
+def updateProductAverageCostOnReceiveInventory() {
+    GenericValue inventoryItem = from("InventoryItem").where(parameters).queryOne()
+    String organizationPartyId = inventoryItem?.ownerPartyId
+    if (!organizationPartyId) {
+        GenericValue facility = from("Facility").where(parameters).queryOne()
+        organizationPartyId = facility?.ownerPartyId
+        if (!organizationPartyId) {
+            GenericValue productStore = delegator.getRelatedOne("ProductStore", facility, false)
+            organizationPartyId = productStore?.ownerPartyId
+            if (!organizationPartyId) {
+                String errorMessage = UtilProperties.getMessage("ProductUiLabels", "ProductOwnerPartyIsMissing", locale)
+                logError(errorMessage)
+                return error(errorMessage)
+            }
+        }
+    }
+    GenericValue productAverageCost = from("ProductAverageCost").where(productId: parameters.productId, facilityId: parameters.facilityId, productAverageCostTypeId: "SIMPLE_AVG_COST", organizationPartyId: organizationPartyId).filterByDate().queryFirst()
+    // <log level="always" message="In updateProductAverageCostOnReceiveInventory found productAverageCost: ${productAverageCost}"/>
+    Map productAverageCostMap = parameters
+    productAverageCostMap.productAverageCostTypeId = "SIMPLE_AVG_COST"
+    productAverageCostMap.organizationPartyId = organizationPartyId
+    Map updateProductAverageCostMap = [:]
+    if (!productAverageCost) {
+        productAverageCostMap.averageCost = inventoryItem.unitCost
+    } else {
+        // Expire existing one and calculate average cost
+        updateProductAverageCostMap << productAverageCost
+        updateProductAverageCostMap.thurDate = UtilDateTime.nowTimestamp()
+        run service: "updateProductAverageCost", with: updateProductAverageCostMap
+
+        Map serviceInMap = [productId: parameters.productId, facilityId: parameters.facilityId]
+        Map serviceResultGIABF = run service: "getInventoryAvailableByFacility", with: serviceInMap
+        BigDecimal quantityOnHandTotal = serviceResultGIABF.quantityOnHandTotal
+        BigDecimal oldProductQuantity = quantityOnHandTotal - parameters.quantityAccepted
+        BigDecimal averageCost = ((productAverageCost.averageCost * oldProductQuantity) + (inventoryItem.unitCost * parameters.quantityAccepted))/(quantityOnHandTotal)
+        int roundingDecimal = UtilProperties.getPropertyAsInteger("arithmetic", "finaccout.decimals", 2)
+        String roundingMode = UtilProperties.getPropertyValue("arithmetic", "finaccount.roundingGroovyMethod", "HALF_UP")
+        averageCost = averageCost.setScale(roundingDecimal, RoundingMode."${roundingMode}")
+        productAverageCostMap.averageCost = averageCost
+        productAverageCostMap.fromDate = UtilDateTime.nowTimestamp()
+    }
+    // <log level="info" message="In updateProductAverageCostOnReceiveInventory creating new average cost with productAverageCostMap: ${productAverageCostMap}"/>
+    run service: "createProductAverageCost", with: productAverageCostMap
+    logInfo("For facilityId ${parameters.facilityId}, Average cost of product ${parameters.productId} is set from  ${updateProductAverageCostMap.averageCost} to ${productAverageCostMap.averageCost}")
+    return success()
+}
+
+// Service to get the average cost of product
+def getProductAverageCost() {
+    Map result = success()
+    GenericValue productAverageCost
+    BigDecimal unitCost
+    GenericValue inventoryItem = parameters.inventoryItem
+    Map getPartyAcctgPrefMap = [organizationPartyId: inventoryItem.ownerPartyId]
+    Map serviceResult = run service: "getPartyAccountingPreferences", with: getPartyAcctgPrefMap
+    GenericValue partyAccountingPreference = serviceResult.partyAccountingPreference
+    if (partyAccountingPreference.cogsMethodId == "COGS_AVG_COST") {
+        // TODO: handle productAverageCostTypeId for WEIGHTED_AVG_COST and MOVING_AVG_COST
+        productAverageCost = from("ProductAverageCost")
+                .where(productAverageCostTypeId: "SIMPLE_AVG_COST", organizationPartyId: inventoryItem.ownerPartyId, productId: inventoryItem.productId, facilityId: inventoryItem.facilityId)
+                .filterByDate()
+                .queryFirst()
+    }
+    if (productAverageCost) {
+        unitCost = productAverageCost.averageCost
+    } else {
+        unitCost = inventoryItem.unitCost
+    }
+    result.unitCost = unitCost
+    return result
+}
+
+/**
+ * Formula that creates a cost component equal to a percentage of total product cost
+ * @return
+ */
+def productCostPercentageFormula() {
+    Map result = success()
+    GenericValue productCostComponentCalc = parameters.productCostComponentCalc
+    GenericValue costComponentCalc = parameters.costComponentCalc
+    Map inputMap = [productId: productCostComponentCalc.productId, currencyUomId: parameters.currencyUomId, costComponentTypePrefix: parameters.costComponentTypePrefix]
+    Map serviceResult = run service: "getProductCost", with: inputMap
+    BigDecimal productCost = serviceResult.productCost
+    // set field="productCostAdjustment" value="${parameters.baseCost * costComponentCalc.fixedCost}" type="BigDecimal"/
+    BigDecimal productCostAdjustment = costComponentCalc.fixedCost * parameters.baseCost
+    productCostAdjustment = productCostAdjustment.setScale(6)
+    result.productCostAdjustment = productCostAdjustment
+    return result
+}
diff --git a/applications/product/minilang/product/cost/CostServices.xml b/applications/product/minilang/product/cost/CostServices.xml
deleted file mode 100644
index 7951f18..0000000
--- a/applications/product/minilang/product/cost/CostServices.xml
+++ /dev/null
@@ -1,613 +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">
-    <!-- CostComponent services -->
-    <simple-method method-name="cancelCostComponents" short-description="Cancels CostComponents">
-        <set from-field="parameters.costComponentId" field="costsAndMap.costComponentId"/>
-        <set from-field="parameters.productId" field="costsAndMap.productId"/>
-        <set from-field="parameters.costUomId" field="costsAndMap.costUomId"/>
-        <set from-field="parameters.costComponentTypeId" field="costsAndMap.costComponentTypeId"/>
-        <find-by-and entity-name="CostComponent" map="costsAndMap" list="existingCosts"/>
-        <filter-list-by-date list="existingCosts"/>
-        <iterate list="existingCosts" entry="existingCost">
-            <now-timestamp field="existingCost.thruDate"/>
-            <store-value value-field="existingCost"/>
-        </iterate>
-    </simple-method>
-    <simple-method method-name="recreateCostComponent" short-description="Create a CostComponent and cancel the existing ones">
-        <!-- The existing costs of the same type are expired -->
-        <set from-field="parameters.productId" field="costsAndMap.productId"/>
-        <set from-field="parameters.costUomId" field="costsAndMap.costUomId"/>
-        <set from-field="parameters.costComponentTypeId" field="costsAndMap.costComponentTypeId"/>
-        <find-by-and entity-name="CostComponent" map="costsAndMap" list="existingCosts"/>
-        <filter-list-by-date list="existingCosts"/>
-        <iterate list="existingCosts" entry="existingCost">
-            <now-timestamp field="existingCost.thruDate"/>
-            <store-value value-field="existingCost"/>
-        </iterate>
-        <!-- The new cost is created -->
-        <make-value entity-name="CostComponent" value-field="newEntity"/>
-        <set-nonpk-fields map="parameters" value-field="newEntity"/>
-        <sequenced-id sequence-name="CostComponent" field="newEntity.costComponentId"/>
-        <if-empty field="newEntity.fromDate">
-            <now-timestamp field="newEntity.fromDate"/>
-        </if-empty>
-        <create-value value-field="newEntity"/>
-        <field-to-result field="newEntity.costComponentId" result-name="costComponentId"/>
-    </simple-method>
-
-    <!-- Services to get the product and tasks costs -->
-    <simple-method method-name="getProductCost" short-description="Gets the product's costs (from CostComponent or ProductPrice)">
-        <entity-condition entity-name="CostComponent" list="costComponents" filter-by-date="true">
-            <condition-list>
-                <condition-expr field-name="productId" operator="equals" from-field="parameters.productId"/>
-                <condition-expr field-name="costUomId" operator="equals" from-field="parameters.currencyUomId"/>
-                <condition-expr field-name="costComponentTypeId" operator="like" value="${parameters.costComponentTypePrefix}_%"/>
-            </condition-list>
-        </entity-condition>
-        <set field="productCost" value="0" type="BigDecimal"/>
-        <iterate list="costComponents" entry="costComponent">
-            <calculate field="productCost" decimal-scale="6">
-                <calcop operator="add" field="costComponent.cost">
-                    <calcop operator="get" field="productCost"/>
-                </calcop>
-            </calculate>
-            <!--set field="productCost" value="${costComponent.cost + productCost}" type="BigDecimal"/-->
-        </iterate>
-        <!-- if the cost is zero, and the product is a variant, get the cost of the virtual -->
-        <if-compare field="productCost" operator="equals" value="0" type="BigDecimal">
-            <entity-one entity-name="Product" value-field="product"/>
-            <set from-field="product.productId" field="assocAndMap.productIdTo"/>
-            <set value="PRODUCT_VARIANT" field="assocAndMap.productAssocTypeId"/>
-            <find-by-and entity-name="ProductAssoc" map="assocAndMap" list="virtualAssocs"/>
-            <filter-list-by-date list="virtualAssocs"/>
-            <first-from-list list="virtualAssocs" entry="virtualAssoc"/>
-            <if-not-empty field="virtualAssoc">
-                <set from-field="virtualAssoc.productId" field="inputMap.productId"/>
-                <set from-field="parameters.currencyUomId" field="inputMap.currencyUomId"/>
-                <set from-field="parameters.costComponentTypePrefix" field="inputMap.costComponentTypePrefix"/>
-                <call-service service-name="getProductCost" in-map-name="inputMap">
-                    <result-to-field result-name="productCost"/>
-                </call-service>
-            </if-not-empty>
-        </if-compare>
-        <!-- if the cost is zero, get the purchase cost from the SupplierProduct -->
-        <if-compare field="productCost" operator="equals" value="0" type="BigDecimal">
-            <set field="orderByList[]" value="+supplierPrefOrderId"/>
-            <set field="orderByList[]" value="+lastPrice"/>
-            <clear-field field="costsAndMap"/>
-            <set from-field="parameters.productId" field="costsAndMap.productId"/>
-            <set from-field="parameters.currencyUomId" field="costsAndMap.currencyUomId"/>
-            <find-by-and entity-name="SupplierProduct" map="costsAndMap" list="priceCosts" order-by-list="orderByList"/>
-            <filter-list-by-date list="priceCosts" from-field-name="availableFromDate" thru-field-name="availableThruDate"/>
-            <first-from-list list="priceCosts" entry="priceCost"/>
-            <if-not-empty field="priceCost.lastPrice">
-                <set from-field="priceCost.lastPrice" field="productCost"/>
-            </if-not-empty>
-            <!-- if the cost is zero, get the purchase cost from the SupplierProduct
-                 in a different currency and try to convert
-            -->
-            <if-compare field="productCost" operator="equals" value="0" type="BigDecimal">
-                <clear-field field="costsAndMap"/>
-                <set from-field="parameters.productId" field="costsAndMap.productId"/>
-                <find-by-and entity-name="SupplierProduct" map="costsAndMap" list="priceCosts" order-by-list="orderByList"/>
-                <filter-list-by-date list="priceCosts" from-field-name="availableFromDate" thru-field-name="availableThruDate"/>
-                <first-from-list list="priceCosts" entry="priceCost"/>
-                <if-not-empty field="priceCost.lastPrice">
-                    <!-- we try to convert the lastPrice to the desired currency -->
-                    <clear-field field="inputMap"/>
-                    <set from-field="priceCost.lastPrice" field="inputMap.originalValue"/>
-                    <set from-field="priceCost.currencyUomId" field="inputMap.uomId"/>
-                    <set from-field="parameters.currencyUomId" field="inputMap.uomIdTo"/>
-
-                    <call-service service-name="convertUom" in-map-name="inputMap" require-new-transaction="true" break-on-error="false">
-                        <result-to-field result-name="convertedValue" field="productCost"/>
-                    </call-service>
-
-                    <!-- if currency conversion fails then a 0 cost will be returned -->
-                    <if-empty field="productCost">
-                        <log level="warning" message="Currency conversion failed for ProductCost lookup; unable to convert from ${priceCost.currencyUomId} to ${parameters.currencyUomId}"/>
-                        <set field="productCost" value="0" type="BigDecimal"/>
-                    </if-empty>
-                </if-not-empty>
-            </if-compare>
-        </if-compare>
-        <!--
-        <if-compare field="productCost" operator="equals" value="0" type="BigDecimal">
-            <clear-field field="costsAndMap"/>
-            <set from-field="parameters.productId" field="costsAndMap.productId"/>
-            <set from-field="parameters.currencyUomId" field="costsAndMap.currencyUomId"/>
-            <set from-field="parameters.productPriceTypeId" field="costsAndMap.productPriceTypeId"/>
-            <find-by-and entity-name="ProductPrice" map="costsAndMap" list="priceCosts"/>
-            <filter-list-by-date list="priceCosts"/>
-            <first-from-list list="priceCosts" entry="priceCost"/>
-            <if-not-empty field="priceCost.price">
-                <set from-field="priceCost.price" field="productCost"/>
-            </if-not-empty>
-        </if-compare>
-        -->
-        <field-to-result field="productCost"/>
-    </simple-method>
-    <simple-method method-name="getTaskCost" short-description="Gets the production run task's costs">
-        <!-- First of all, the estimated task time is computed -->
-        <set-service-fields service-name="getEstimatedTaskTime" map="parameters" to-map="inputMap"/>
-        <set from-field="parameters.workEffortId" field="inputMap.taskId"/>
-        <call-service service-name="getEstimatedTaskTime" in-map-name="inputMap">
-            <result-to-field result-name="estimatedTaskTime" field="totalEstimatedTaskTime"/>
-            <result-to-field result-name="setupTime"/>
-        </call-service>
-
-        <calculate field="estimatedTaskTime" decimal-scale="6">
-            <calcop operator="subtract" field="totalEstimatedTaskTime">
-                <calcop operator="get" field="setupTime"/>
-            </calcop>
-        </calculate>
-
-        <entity-one entity-name="WorkEffort" value-field="task"/>
-        <if-not-empty field="task">
-            <get-related-one value-field="task" relation-name="FixedAsset" to-value-field="fixedAsset"/>
-            <set from-field="parameters.currencyUomId" field="costsAndMap.amountUomId"/>
-            <set value="SETUP_COST" field="costsAndMap.fixedAssetStdCostTypeId"/>
-            <get-related value-field="fixedAsset" relation-name="FixedAssetStdCost" map="costsAndMap" list="setupCosts"/>
-            <filter-list-by-date list="setupCosts"/>
-            <!--<filter-list-by-and list-name="costs" map-name="costsAndMap"/>-->
-            <first-from-list list="setupCosts" entry="setupCost"/>
-            <set value="USAGE_COST" field="costsAndMap.fixedAssetStdCostTypeId"/>
-            <get-related value-field="fixedAsset" relation-name="FixedAssetStdCost" map="costsAndMap" list="usageCosts"/>
-            <filter-list-by-date list="usageCosts"/>
-            <first-from-list list="usageCosts" entry="usageCost"/>
-        </if-not-empty>
-        <calculate field="taskCost" decimal-scale="6">
-            <calcop operator="add">
-                <calcop operator="multiply" field="estimatedTaskTime">
-                    <calcop operator="get" field="usageCost.amount"/>
-                </calcop>
-                <calcop operator="multiply" field="setupTime">
-                    <calcop operator="get" field="setupCost.amount"/>
-                </calcop>
-            </calcop>
-        </calculate>
-
-        <!-- Time is converted from milliseconds to hours -->
-        <calculate field="taskCost" decimal-scale="6">
-            <calcop operator="divide" field="taskCost">
-                <number value="3600000"/>
-            </calcop>
-        </calculate>
-
-        <!-- Now compute the costs derived from CostComponentCalc records associated with the task -->
-        <get-related relation-name="WorkEffortCostCalc" list="weccs" value-field="task"/>
-        <filter-list-by-date list="weccs"/>
-        <iterate list="weccs" entry="wecc">
-            <clear-field field="totalCostComponentCost"/>
-            <clear-field field="totalCostComponentTime"/>
-            <get-related-one relation-name="CostComponentCalc" to-value-field="costComponentCalc" value-field="wecc"/>
-            <get-related-one relation-name="CustomMethod" to-value-field="customMethod" value-field="costComponentCalc"/>
-            <if-empty field="customMethod">
-                <if-not-empty field="costComponentCalc.perMilliSecond">
-                    <if-compare operator="not-equals" value="0" field="costComponentCalc.perMilliSecond" type="BigDecimal">
-                        <calculate field="totalCostComponentTime" decimal-scale="6">
-                            <calcop operator="divide" field="totalEstimatedTaskTime">
-                                <calcop operator="get" field="costComponentCalc.perMilliSecond"/>
-                            </calcop>
-                        </calculate>
-                        <calculate field="totalCostComponentCost" decimal-scale="6">
-                            <calcop operator="multiply" field="totalCostComponentTime">
-                                <calcop operator="get" field="costComponentCalc.variableCost"/>
-                            </calcop>
-                        </calculate>
-                        <calculate field="totalCostComponentCost" decimal-scale="6">
-                            <calcop operator="add" field="totalCostComponentCost">
-                                <calcop operator="get" field="costComponentCalc.fixedCost"/>
-                            </calcop>
-                        </calculate>
-                        <set field="costsByType.${wecc.costComponentTypeId}" from-field="totalCostComponentCost"/>
-                    </if-compare>
-                </if-not-empty>
-            <else>
-                <!-- FIXME: formulas are still not supported for standard costs -->
-            </else>
-            </if-empty>
-        </iterate>
-        <field-to-result field="taskCost"/>
-        <field-to-result field="costsByType"/>
-    </simple-method>
-
-    <!-- services to automatically generate cost information -->
-    <simple-method method-name="calculateAllProductsCosts" short-description="Calculates estimated costs for all the products">
-        <!--filter-by-date="true"-->
-        <entity-condition entity-name="Product" list="products">
-            <select-field field-name="productId"/>
-            <order-by field-name="-billOfMaterialLevel"/>
-        </entity-condition>
-        <set from-field="parameters.currencyUomId" field="inMap.currencyUomId"/>
-        <set from-field="parameters.costComponentTypePrefix" field="inMap.costComponentTypePrefix"/>
-        <iterate list="products" entry="product">
-            <set from-field="product.productId" field="inMap.productId"/>
-            <call-service service-name="calculateProductCosts" in-map-name="inMap"/>
-        </iterate>
-    </simple-method>
-    <simple-method method-name="calculateProductCosts" short-description="Calculates the product's cost">
-        <!-- the existing costs are expired -->
-        <set value="${parameters.costComponentTypePrefix}_ROUTE_COST" field="cancelMap.costComponentTypeId"/>
-        <set from-field="parameters.productId" field="cancelMap.productId"/>
-        <set from-field="parameters.currencyUomId" field="cancelMap.costUomId"/>
-        <call-service service-name="cancelCostComponents" in-map-name="cancelMap"/>
-        <set value="${parameters.costComponentTypePrefix}_MAT_COST" field="cancelMap.costComponentTypeId"/>
-        <call-service service-name="cancelCostComponents" in-map-name="cancelMap"/>
-        <!-- calculate the total materials' cost -->
-        <set from-field="parameters.productId" field="callSvcMap.productId"/>
-        <call-service service-name="getManufacturingComponents" in-map-name="callSvcMap">
-            <result-to-field result-name="componentsMap"/>
-        </call-service>
-        <if-not-empty field="componentsMap">
-            <iterate list="componentsMap" entry="componentMap">
-                <clear-field field="inputMap"/>
-                <set field="product" from-field="componentMap.product"/>
-                <set field="inputMap.productId" from-field="product.productId"/>
-                <set field="inputMap.currencyUomId" from-field="parameters.currencyUomId"/>
-                <set field="inputMap.costComponentTypePrefix" from-field="parameters.costComponentTypePrefix"/>
-                <call-service service-name="getProductCost" in-map-name="inputMap">
-                    <result-to-field result-name="productCost"/>
-                </call-service>
-                <calculate field="totalProductsCost" decimal-scale="6">
-                    <calcop operator="add" field="totalProductsCost">
-                        <calcop operator="multiply" field="componentMap.quantity">
-                            <calcop operator="get" field="productCost"/>
-                        </calcop>
-                    </calcop>
-                </calculate>
-            </iterate>
-            <else>
-                <clear-field field="inputMap"/>
-                <set field="inputMap.productId" from-field="parameters.productId"/>
-                <set field="inputMap.currencyUomId" from-field="parameters.currencyUomId"/>
-                <set field="inputMap.costComponentTypePrefix" from-field="parameters.costComponentTypePrefix"/>
-                <call-service service-name="getProductCost" in-map-name="inputMap">
-                    <result-to-field result-name="productCost"/>
-                </call-service>
-                <calculate field="totalProductsCost" decimal-scale="6">
-                    <calcop operator="get" field="productCost"/>
-                </calculate>
-            </else>
-        </if-not-empty>
-        <!-- calculate the total tasks' cost -->
-        <set field="callSvcMap.ignoreDefaultRouting" value="Y"/>
-        <call-service service-name="getProductRouting" in-map-name="callSvcMap">
-            <result-to-field result-name="tasks"/>
-            <result-to-field result-name="routing"/>
-        </call-service>
-        <iterate list="tasks" entry="task">
-            <clear-field field="callSvcMap"/>
-            <set from-field="task.workEffortIdTo" field="callSvcMap.workEffortId"/>
-            <set from-field="parameters.currencyUomId" field="callSvcMap.currencyUomId"/>
-            <set from-field="parameters.productId" field="callSvcMap.productId"/>
-            <set from-field="routing.workEffortId" field="callSvcMap.routingId"/>
-            <call-service service-name="getTaskCost" in-map-name="callSvcMap">
-                <result-to-field result-name="taskCost" field="taskCost"/>
-                <result-to-field result-name="costsByType" field="costsByType"/>
-            </call-service>
-            <calculate field="totalTaskCost" decimal-scale="6">
-                <calcop operator="add" field="totalTaskCost">
-                    <calcop operator="get" field="taskCost"/>
-                </calcop>
-            </calculate>
-            <iterate-map map="costsByType" key="costType" value="costAmount">
-                <if-not-empty field="totalCostsByType.${costType}">
-                    <calculate field="totalCostsByType.${costType}" decimal-scale="6">
-                        <calcop operator="add" field="costAmount">
-                            <calcop operator="get" field="totalCostsByType.${costType}"/>
-                        </calcop>
-                    </calculate>
-                <else>
-                    <set field="totalCostsByType.${costType}" from-field="costAmount"/>
-                </else>
-                </if-not-empty>
-                <calculate field="totalOtherTaskCost" decimal-scale="6">
-                    <calcop operator="add" field="totalOtherTaskCost">
-                        <calcop operator="get" field="costAmount"/>
-                    </calcop>
-                </calculate>
-            </iterate-map>
-        </iterate>
-
-        <calculate field="totalCost" decimal-scale="6">
-            <calcop operator="add" field="totalTaskCost">
-                <calcop operator="get" field="totalProductsCost"/>
-                <calcop operator="get" field="totalOtherTaskCost"/>
-            </calcop>
-        </calculate>
-
-        <!-- The CostComponent records are created. -->
-        <if-not-empty field="totalTaskCost">
-            <if-compare field="totalTaskCost" operator="greater" value="0" type="BigDecimal">
-                <clear-field field="callSvcMap"/>
-                <set value="${parameters.costComponentTypePrefix}_ROUTE_COST" field="callSvcMap.costComponentTypeId"/>
-                <set from-field="parameters.productId" field="callSvcMap.productId"/>
-                <set from-field="parameters.currencyUomId" field="callSvcMap.costUomId"/>
-                <set from-field="totalTaskCost" field="callSvcMap.cost"/>
-                <call-service service-name="recreateCostComponent" in-map-name="callSvcMap"/>
-            </if-compare>
-        </if-not-empty>
-        <if-not-empty field="totalProductsCost">
-            <if-compare field="totalProductsCost" operator="greater" value="0" type="BigDecimal">
-                <clear-field field="callSvcMap"/>
-                <set value="${parameters.costComponentTypePrefix}_MAT_COST" field="callSvcMap.costComponentTypeId"/>
-                <set from-field="parameters.productId" field="callSvcMap.productId"/>
-                <set from-field="parameters.currencyUomId" field="callSvcMap.costUomId"/>
-                <set from-field="totalProductsCost" field="callSvcMap.cost"/>
-                <call-service service-name="recreateCostComponent" in-map-name="callSvcMap"/>
-            </if-compare>
-        </if-not-empty>
-        <iterate-map map="totalCostsByType" key="costType" value="totalCostAmount">
-            <clear-field field="callSvcMap"/>
-            <set value="${parameters.costComponentTypePrefix}_${costType}" field="callSvcMap.costComponentTypeId"/>
-            <set from-field="parameters.productId" field="callSvcMap.productId"/>
-            <set from-field="parameters.currencyUomId" field="callSvcMap.costUomId"/>
-            <set from-field="totalCostAmount" field="callSvcMap.cost"/>
-            <call-service service-name="recreateCostComponent" in-map-name="callSvcMap"/>
-        </iterate-map>
-
-        <!-- Now compute the costs derived from CostComponentCalc records associated with the product -->
-        <entity-condition entity-name="ProductCostComponentCalc" list="productCostComponentCalcs" filter-by-date="true">
-            <condition-expr field-name="productId" from-field="parameters.productId"/>
-            <order-by field-name="sequenceNum"/>
-        </entity-condition>
-        <iterate list="productCostComponentCalcs" entry="productCostComponentCalc">
-            <get-related-one relation-name="CostComponentCalc" to-value-field="costComponentCalc" value-field="productCostComponentCalc"/>
-            <get-related-one relation-name="CustomMethod" to-value-field="customMethod" value-field="costComponentCalc"/>
-            <if-empty field="customMethod">
-                <!-- TODO: not supported for CostComponentCalc entries directly associated to a product -->
-                <log level="warning" message="Unable to create cost component for cost component calc with id [${costComponentCalc.costComponentCalcId}] because customMethod is not set"/>
-            <else>
-                <clear-field field="customMethodParameters"/>
-                <set field="customMethodParameters.productCostComponentCalc" from-field="productCostComponentCalc"/>
-                <set field="customMethodParameters.costComponentCalc" from-field="costComponentCalc"/>
-                <set from-field="parameters.currencyUomId" field="customMethodParameters.currencyUomId"/>
-                <set from-field="parameters.costComponentTypePrefix" field="customMethodParameters.costComponentTypePrefix"/>
-                <set from-field="totalCost" field="customMethodParameters.baseCost"/>
-                <call-service service-name="${customMethod.customMethodName}" in-map-name="customMethodParameters">
-                    <result-to-field result-name="productCostAdjustment"/>
-                </call-service>
-                <clear-field field="callSvcMap"/>
-                <set value="${parameters.costComponentTypePrefix}_${productCostComponentCalc.costComponentTypeId}" field="callSvcMap.costComponentTypeId"/>
-                <set from-field="productCostComponentCalc.productId" field="callSvcMap.productId"/>
-                <set from-field="parameters.currencyUomId" field="callSvcMap.costUomId"/>
-                <set from-field="productCostAdjustment" field="callSvcMap.cost"/>
-                <call-service service-name="recreateCostComponent" in-map-name="callSvcMap"/>
-                <!--set field="totalCost" value="${totalCost + productCostAdjustment}" type="BigDecimal"/-->
-                <calculate field="totalCost" decimal-scale="6">
-                    <calcop operator="add" field="totalCost">
-                        <calcop operator="get" field="productCostAdjustment"/>
-                    </calcop>
-                </calculate>
-            </else>
-            </if-empty>
-        </iterate>
-        
-        <field-to-result field="totalCost"/>
-    </simple-method>
-    <simple-method method-name="calculateProductAverageCost" short-description="Calculate inventory average cost for a product">
-        <entity-condition entity-name="InventoryItem" list="inventoryItems">
-            <condition-list>
-                <condition-expr field-name="productId" from-field="parameters.productId"/>
-                <condition-expr field-name="facilityId" from-field="parameters.facilityId" ignore-if-empty="true"/>
-                <condition-expr field-name="ownerPartyId" from-field="parameters.ownerPartyId" ignore-if-empty="true"/>
-                <condition-expr field-name="unitCost" operator="not-equals" from-field="nullField"/>
-            </condition-list>
-            <select-field field-name="quantityOnHandTotal"/>
-            <select-field field-name="unitCost"/>
-            <select-field field-name="currencyUomId"/>
-        </entity-condition>
-        <set field="totalQuantityOnHand" type="BigDecimal" value="0"/>
-        <set field="totalInventoryCost" type="BigDecimal" value="0"/>
-        <set field="absValOfTotalQOH" type="BigDecimal" value="0"/>
-        <set field="absValOfTotalInvCost" type="BigDecimal" value="0"/>
-        <set field="differentCurrencies" type="Boolean" value="false"/>
-        <iterate list="inventoryItems" entry="inventoryItem">
-            <calculate field="totalQuantityOnHand">
-                <calcop operator="add" >
-                    <calcop operator="get" field="totalQuantityOnHand"/>
-                    <calcop operator="get" field="inventoryItem.quantityOnHandTotal"/>
-                </calcop>
-            </calculate>
-
-            <if-empty field="currencyUomId">
-                <set field="currencyUomId" from-field="inventoryItem.currencyUomId"/>
-            </if-empty>
-            <if-compare field="differentCurrencies" operator="equals" value="false" type="Boolean">
-                <if-compare-field field="inventoryItem.currencyUomId" operator="equals" to-field="currencyUomId">
-                    <calculate field="totalInventoryCost" type="BigDecimal">
-                        <calcop operator="add">
-                            <calcop operator="get" field="totalInventoryCost"/>
-                            <calcop operator="multiply">
-                                <calcop operator="get" field="inventoryItem.quantityOnHandTotal"/>
-                                <calcop operator="get" field="inventoryItem.unitCost"/>
-                            </calcop>
-                        </calcop>
-                    </calculate>
-
-                    <!-- calculation of absolute values of QOH and total inventory cost -->
-                    <if-compare field="inventoryItem.quantityOnHandTotal" operator="less" value="0">
-                        <calculate field="absValOfTotalQOH">
-                            <calcop operator="add">
-                                <calcop operator="get" field="absValOfTotalQOH"/>
-                                <calcop operator="negative" field="inventoryItem.quantityOnHandTotal"/>
-                            </calcop>
-                        </calculate>
-                        <calculate field="absValOfTotalInvCost" type="BigDecimal">
-                            <calcop operator="add">
-                                <calcop operator="get" field="absValOfTotalInvCost"/>
-                                <calcop operator="multiply">
-                                    <calcop operator="negative" field="inventoryItem.quantityOnHandTotal"/>
-                                    <calcop operator="get" field="inventoryItem.unitCost"/>
-                                </calcop>
-                            </calcop>
-                        </calculate>
-                        <else>
-                        <calculate field="absValOfTotalQOH">
-                            <calcop operator="add" >
-                                <calcop operator="get" field="absValOfTotalQOH"/>
-                                <calcop operator="get" field="inventoryItem.quantityOnHandTotal"/>
-                            </calcop>
-                        </calculate>
-                        <calculate field="absValOfTotalInvCost" type="BigDecimal">
-                            <calcop operator="add">
-                                <calcop operator="get" field="absValOfTotalInvCost"/>
-                                <calcop operator="multiply">
-                                    <calcop operator="get" field="inventoryItem.quantityOnHandTotal"/>
-                                    <calcop operator="get" field="inventoryItem.unitCost"/>
-                                </calcop>
-                            </calcop>
-                        </calculate>
-                        </else>
-                    </if-compare>
-                <else>
-                    <set field="differentCurrencies" type="Boolean" value="true"/>
-                </else>
-                </if-compare-field>
-            </if-compare>
-        </iterate>
-
-        <if-compare field="absValOfTotalQOH" operator="not-equals" value="0" type="BigDecimal">
-            <calculate field="productAverageCost" type="BigDecimal">
-                <calcop operator="divide">
-                    <calcop operator="get" field="absValOfTotalInvCost"/>
-                    <calcop operator="get" field="absValOfTotalQOH"/>
-                </calcop>
-            </calculate>
-        <else>
-            <set field="productAverageCost" type="BigDecimal" value="0"/>
-        </else>
-        </if-compare>
-        <field-to-result field="totalQuantityOnHand"/>
-        <if-compare field="differentCurrencies" operator="equals" value="false" type="Boolean">
-            <field-to-result field="totalInventoryCost"/>
-            <field-to-result field="productAverageCost"/>
-            <field-to-result field="currencyUomId"/>
-        </if-compare>
-    </simple-method>
-
-    <simple-method method-name="updateProductAverageCostOnReceiveInventory" short-description="Update a Product Average Cost record on receive inventory">
-        <entity-one entity-name="InventoryItem" value-field="inventoryItem"/>
-        <set field="organizationPartyId" from-field="inventoryItem.ownerPartyId"/>
-        <if-empty field="organizationPartyId">
-            <entity-one entity-name="Facility" value-field="facility" auto-field-map="true"/>
-            <set field="organizationPartyId" from-field="facility.ownerPartyId"/>
-            <if-empty field="organizationPartyId">
-                <get-related-one relation-name="ProductStore" to-value-field="productStore" value-field="facility"/>
-                <set field="organizationPartyId" from-field="productStore.ownerPartyId"/>
-                <if-empty field="organizationPartyId">
-                    <add-error error-list-name="error_list">
-                        <fail-property resource="ProductUiLabels" property="ProductOwnerPartyIsMissing"/>
-                    </add-error>
-                </if-empty>
-                <check-errors/>
-            </if-empty>
-        </if-empty>
-
-        <entity-and entity-name="ProductAverageCost" list="productAverageCostList" filter-by-date="true">
-            <field-map field-name="productId" from-field="parameters.productId"/>
-            <field-map field-name="facilityId" from-field="parameters.facilityId"/>
-            <field-map field-name="productAverageCostTypeId" value="SIMPLE_AVG_COST"/>
-            <field-map field-name="organizationPartyId"/>
-        </entity-and>
-        <first-from-list list="productAverageCostList" entry="productAverageCost"/>
-        
-        <!-- <log level="always" message="In updateProductAverageCostOnReceiveInventory found productAverageCost: ${productAverageCost}"/> -->
-
-        <set-service-fields service-name="createProductAverageCost" map="parameters" to-map="productAverageCostMap"/>
-        <set field="productAverageCostMap.productAverageCostTypeId" value="SIMPLE_AVG_COST"/>
-        <set field="productAverageCostMap.organizationPartyId" from-field="organizationPartyId"/>
-        <if-empty field="productAverageCost">
-            <set field="productAverageCostMap.averageCost" from-field="inventoryItem.unitCost"/>
-        <else>
-            <!-- Expire existing one and calculate average cost -->
-            <set-service-fields service-name="updateProductAverageCost" map="productAverageCost" to-map="updateProductAverageCostMap"/>
-            <now-timestamp field="updateProductAverageCostMap.thruDate"/>
-            <call-service service-name="updateProductAverageCost" in-map-name="updateProductAverageCostMap"/>
-            
-            <set field="serviceInMap.productId" from-field="parameters.productId"/>
-            <set field="serviceInMap.facilityId" from-field="parameters.facilityId"/>
-            <call-service service-name="getInventoryAvailableByFacility" in-map-name="serviceInMap">
-                <result-to-field result-name="quantityOnHandTotal"/>
-            </call-service>
-            
-            <set field="oldProductQuantity" value="${quantityOnHandTotal - parameters.quantityAccepted}" type="BigDecimal"/>
-            <set field="productAverageCostMap.averageCost" value="${((productAverageCost.averageCost * oldProductQuantity) + (inventoryItem.unitCost * parameters.quantityAccepted))/(quantityOnHandTotal)}" type="BigDecimal"/>
-            <property-to-field resource="arithmetic" property="finaccount.decimals" field="roundingDecimals" default="2"/>
-            <property-to-field resource="arithmetic" property="finaccount.roundingSimpleMethod" field="roundingMode" default="HalfUp"/>
-            <calculate field="productAverageCostMap.averageCost" type="BigDecimal" decimal-scale="${roundingDecimals}" rounding-mode="${roundingMode}">
-                <calcop operator="get" field="productAverageCostMap.averageCost"/>
-            </calculate>
-
-            <now-timestamp field="productAverageCostMap.fromDate"/>
-        </else>
-        </if-empty>
-        
-        <!-- <log level="info" message="In updateProductAverageCostOnReceiveInventory creating new average cost with productAverageCostMap: ${productAverageCostMap}"/> -->
-        <call-service service-name="createProductAverageCost" in-map-name="productAverageCostMap"/>
-        <log level="info" message="For facilityId ${parameters.facilityId}, Average cost of product ${parameters.productId} is set from  ${updateProductAverageCostMap.averageCost} to ${productAverageCostMap.averageCost}"/>
-    </simple-method>
-
-    <simple-method method-name="getProductAverageCost" short-description="Service to get the average cost of product">
-        <set field="inventoryItem" from-field="parameters.inventoryItem"/>
-        <set field="getPartyAcctgPrefMap.organizationPartyId" from-field="inventoryItem.ownerPartyId"/>
-        <call-service service-name="getPartyAccountingPreferences" in-map-name="getPartyAcctgPrefMap">
-            <result-to-field result-name="partyAccountingPreference"/>
-        </call-service>
-        <if-compare field="partyAccountingPreference.cogsMethodId" operator="equals" value="COGS_AVG_COST">
-            <entity-and entity-name="ProductAverageCost" list="productAverageCostList" filter-by-date="true">
-                <field-map field-name="productAverageCostTypeId" value="SIMPLE_AVG_COST"/> <!-- TODO: handle for WEIGHTED_AVG_COST and MOVING_AVG_COST -->
-                <field-map field-name="organizationPartyId" from-field="inventoryItem.ownerPartyId"/>
-                <field-map field-name="productId" from-field="inventoryItem.productId"/>
-                <field-map field-name="facilityId" from-field="inventoryItem.facilityId"/>
-            </entity-and>
-            <first-from-list list="productAverageCostList" entry="productAverageCost"/>
-        </if-compare>
-        <if-not-empty field="productAverageCost">
-            <set field="unitCost" from-field="productAverageCost.averageCost" type="BigDecimal"/>
-        <else>
-            <set field="unitCost" from-field="inventoryItem.unitCost" type="BigDecimal"/>
-        </else>
-        </if-not-empty>
-        <field-to-result field="unitCost"/>
-    </simple-method>
-
-    <simple-method method-name="productCostPercentageFormula" short-description="Formula that creates a cost component equal to a percentage of total product cost">
-        <set field="productCostComponentCalc" from-field="parameters.productCostComponentCalc"/>
-        <set field="costComponentCalc" from-field="parameters.costComponentCalc"/>
-        <set field="inputMap.productId" from-field="productCostComponentCalc.productId"/>
-        <set field="inputMap.currencyUomId" from-field="parameters.currencyUomId"/>
-        <set field="inputMap.costComponentTypePrefix" from-field="parameters.costComponentTypePrefix"/>
-        <call-service service-name="getProductCost" in-map-name="inputMap">
-            <result-to-field result-name="productCost"/>
-        </call-service>
-        <!--set field="productCostAdjustment" value="${parameters.baseCost * costComponentCalc.fixedCost}" type="BigDecimal"/-->
-        <calculate field="productCostAdjustment" type="BigDecimal" decimal-scale="6">
-            <calcop operator="multiply" field="costComponentCalc.fixedCost">
-                <calcop operator="get" field="parameters.baseCost"/>
-            </calcop>
-        </calculate>
-        <field-to-result field="productCostAdjustment"/>
-    </simple-method>
-</simple-methods>
diff --git a/applications/product/servicedef/services_cost.xml b/applications/product/servicedef/services_cost.xml
index 4bfad3b..8b1d5fb 100644
--- a/applications/product/servicedef/services_cost.xml
+++ b/applications/product/servicedef/services_cost.xml
@@ -39,14 +39,14 @@ under the License.
         <description>Delete a CostComponent</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
-    <service name="recreateCostComponent" default-entity-name="CostComponent" engine="simple"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="recreateCostComponent" auth="true">
+    <service name="recreateCostComponent" default-entity-name="CostComponent" engine="groovy"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="recreateCostComponent" auth="true">
         <description>Create a CostComponent and cancel the existing ones</description>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
         <auto-attributes include="pk" mode="OUT" optional="false"/>
     </service>
-    <service name="cancelCostComponents" engine="simple"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="cancelCostComponents" auth="true">
+    <service name="cancelCostComponents" engine="groovy"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="cancelCostComponents" auth="true">
         <description>Cancels CostComponent</description>
         <attribute mode="IN" name="costComponentId" optional="true" type="String"/>
         <attribute mode="IN" name="productId" optional="true" type="String"/>
@@ -67,8 +67,8 @@ under the License.
         <description>Delete a Example</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
-    <service name="getProductCost" engine="simple" auth="true"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="getProductCost">
+    <service name="getProductCost" engine="groovy" auth="true"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="getProductCost">
         <description>Gets the product's costs from CostComponent entries</description>
         <attribute mode="IN" name="productId" optional="false" type="String"/>
         <attribute mode="IN" name="currencyUomId" optional="false" type="String"/>
@@ -79,8 +79,8 @@ under the License.
      </type-validate>
         </attribute>
     </service>
-    <service name="getTaskCost" engine="simple" auth="true"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="getTaskCost">
+    <service name="getTaskCost" engine="groovy" auth="true"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="getTaskCost">
         <description>Gets the production run task's costs</description>
         <attribute mode="IN" name="workEffortId" optional="false" type="String"/>
         <attribute mode="IN" name="currencyUomId" optional="false" type="String"/>
@@ -93,22 +93,22 @@ under the License.
         </attribute>
         <attribute mode="OUT" name="costsByType" type="Map" optional="true"/>
     </service>
-    <service name="calculateProductCosts" engine="simple" auth="true"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="calculateProductCosts">
+    <service name="calculateProductCosts" engine="groovy" auth="true"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="calculateProductCosts">
         <description>Calculates the product's costs. If the product does not have cost component defined, will use the BOM to calculate the cost.</description>
         <attribute mode="IN" name="productId" optional="false" type="String"/>
         <attribute mode="IN" name="currencyUomId" optional="false" type="String"/>
         <attribute mode="IN" name="costComponentTypePrefix" optional="false" type="String"/>
         <attribute mode="OUT" name="totalCost" type="BigDecimal"/>
     </service>
-    <service name="calculateAllProductsCosts" engine="simple" auth="true" transaction-timeout="7200"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="calculateAllProductsCosts">
+    <service name="calculateAllProductsCosts" engine="groovy" auth="true" transaction-timeout="7200"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="calculateAllProductsCosts">
         <description>Calculates estimated costs for all the products</description>
         <attribute mode="IN" name="currencyUomId" optional="false" type="String"/>
         <attribute mode="IN" name="costComponentTypePrefix" optional="false" type="String"/>
     </service>
-    <service name="calculateProductAverageCost" engine="simple" auth="true"
-            location="component://product/minilang/product/cost/CostServices.xml" invoke="calculateProductAverageCost">
+    <service name="calculateProductAverageCost" engine="groovy" auth="true"
+            location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="calculateProductAverageCost">
         <description>Calculate inventory average cost for a product</description>
         <attribute name="productId" type="String" mode="IN"/>
         <attribute name="facilityId" type="String" mode="IN" optional="true"/>
@@ -128,8 +128,8 @@ under the License.
         <attribute name="baseCost" type="BigDecimal" mode="IN" optional="false"/>
         <attribute name="productCostAdjustment" type="BigDecimal" mode="OUT" optional="false"/>
     </service>
-    <service name="productCostPercentageFormula" engine="simple"
-                location="component://product/minilang/product/cost/CostServices.xml" invoke="productCostPercentageFormula" auth="true">
+    <service name="productCostPercentageFormula" engine="groovy"
+                location="component://product/groovyScripts/product/cost/CostServices.groovy" invoke="productCostPercentageFormula" auth="true">
         <description>Formula that creates a cost component equal to a percentage of total product cost</description>
         <implements service="productCostCalcInterface"/>
     </service>