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> |
Free forum by Nabble | Edit this page |