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 <[email protected]>
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>