Michael, I have lot of improvement to share with your commit (groovy
syntax, code simplification)

Do you prefer keep the hand, or I commit directly ?

You can find some diff on my starting work hereĀ  [1]

Cheers,

Nicolas

[1]
https://github.com/nmalin/ApacheOFBiz/compare/ProductService.groovy?expand=1

On 21/02/2020 17:51, mbr...@apache.org wrote:
> This is an automated email from the ASF dual-hosted git repository.
>
> mbrohl 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 e0a26fc  Improved: Convert ProductServices.xml mini lang to groovy 
> (OFBIZ-10231)
> e0a26fc is described below
>
> commit e0a26fce43eec7c84d87c0d5055ff0a87f2af796
> Author: Michael Brohl <michael.br...@ecomify.de>
> AuthorDate: Fri Feb 21 16:59:37 2020 +0100
>
>     Improved: Convert ProductServices.xml mini lang to groovy
>     (OFBIZ-10231)
>     
>     Thanks Dennis Balkir for reporting and Sebastian Berg for the 
> implementation.
> ---
>  .../product/product/ProductServices.groovy         | 1095 
> ++++++++++++++++++++
>  .../minilang/product/product/ProductServices.xml   | 1051 -------------------
>  applications/product/servicedef/services.xml       |  112 +-
>  3 files changed, 1151 insertions(+), 1107 deletions(-)
>
> diff --git 
> a/applications/product/groovyScripts/product/product/ProductServices.groovy 
> b/applications/product/groovyScripts/product/product/ProductServices.groovy
> new file mode 100644
> index 0000000..b4be894
> --- /dev/null
> +++ 
> b/applications/product/groovyScripts/product/product/ProductServices.groovy
> @@ -0,0 +1,1095 @@
> +/*
> +  * 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.sql.Timestamp
> +
> +import org.apache.ofbiz.base.util.UtilDateTime
> +import org.apache.ofbiz.base.util.UtilProperties
> +import org.apache.ofbiz.base.util.UtilValidate
> +import org.apache.ofbiz.entity.GenericValue
> +import org.apache.ofbiz.entity.serialize.XmlSerializer
> +import org.apache.ofbiz.entity.util.EntityUtil
> +import org.apache.ofbiz.product.product.KeywordIndex
> +import org.apache.ofbiz.product.product.ProductWorker
> +import org.apache.ofbiz.service.ServiceUtil
> +
> +
> +
> + module = "ProductServices.groovy" // this is used for logging
> +
> + /**
> +  * Create a Product
> +  */
> + def createProduct() {
> +    Map result = success()
> +    if (!(security.hasEntityPermission("CATALOG", "_CREATE", 
> parameters.userLogin)
> +        || security.hasEntityPermission("CATALOG_ROLE", "_CREATE", 
> parameters.userLogin))) {
> +            return error(UtilProperties.getMessage("ProductUiLabels", 
> "ProductCatalogCreatePermissionError", parameters.locale))
> +    }
> +
> +    GenericValue newEntity = makeValue("Product")
> +    newEntity.setNonPKFields(parameters)
> +    
> +    newEntity.productId = parameters.productId
> +
> +    if (UtilValidate.isEmpty(newEntity.productId)) {
> +        newEntity.productId = delegator.getNextSeqId("Product")
> +    } else {
> +        String errorMessage = 
> UtilValidate.checkValidDatabaseId(newEntity.productId)
> +        if(errorMessage != null) {
> +            logError(errorMessage)
> +            return error(errorMessage)
> +        }
> +        GenericValue dummyProduct = findOne("Product", ["productId": 
> parameters.productId], false)
> +        if (UtilValidate.isNotEmpty(dummyProduct)) {
> +             errorMessage = UtilProperties.getMessage("CommonErrorUiLabels", 
> CommonErrorDuplicateKey, parameters.locale)
> +            logError(errorMessage)
> +            return error(errorMessage)
> +        }
> +    }
> +    result.productId = newEntity.productId
> +    
> +    Timestamp nowTimestamp = UtilDateTime.nowTimestamp()
> +    
> +    newEntity.createdDate = nowTimestamp
> +    newEntity.lastModifiedDate = nowTimestamp
> +    newEntity.lastModifiedByUserLogin = userLogin.userLoginId
> +    newEntity.createdByUserLogin = userLogin.userLoginId
> +    
> +    if (UtilValidate.isEmpty(newEntity.isVariant)) {
> +        newEntity.isVariant = "N"
> +    }
> +    if (UtilValidate.isEmpty(newEntity.isVirtual)) {
> +        newEntity.isVirtual = "N"
> +    }
> +    if (UtilValidate.isEmpty(newEntity.billOfMaterialLevel)) {
> +        newEntity.billOfMaterialLevel = (Long) 0
> +    }
> +    
> +    newEntity.create()
> +    
> +    /*
> +     *  if setting the primaryProductCategoryId create a member entity too 
> +     *  THIS IS REMOVED BECAUSE IT CAUSES PROBLEMS FOR WORKING ON PRODUCTION 
> SITES
> +     *  <if-not-empty field="newEntity.primaryProductCategoryId">
> +     *  <make-value entity-name="ProductCategoryMember" 
> value-field="newMember"/>
> +     *  <set from-field="productId" map-name="newEntity" 
> to-field-name="productId" to-map-name="newMember"/>
> +     *  <set from-field="primaryProductCategoryId" map-name="newEntity" 
> to-field-name="productCategoryId" to-map-name="newMember"/>
> +     *  <now-timestamp field="nowStamp"/>
> +     *  <set from-field="nowStamp" field="newMember.fromDate"/>
> +     *  <create-value value-field="newMember"/>
> +     *   </if-not-empty>
> +     */
> +
> +    // if the user has the role limited position, add this product to the 
> limit category/ies
> +
> +
> +    if (security.hasEntityPermission("CATALOG_ROLE","_CREATE", 
> parameters.userLogin)) {
> +        List productCategoryRoles = 
> from("ProductCategoryRole").where("partyId": userLogin.partyId, "roleTypeId": 
> "LTD_ADMIN").queryList()
> +        
> +        for (GenericValue productCategoryRole : productCategoryRoles) {
> +            // add this new product to the category
> +            GenericValue newLimitMember = makeValue("ProductCategoryMember")
> +            newLimitMember.productId = newEntity.productId
> +            newLimitMember.productCateogryId = 
> productCategoryRole.productCategoryId
> +            newLimitMember.fromDate = nowTimestamp
> +            newLimitMember.create()
> +        }
> +    }
> +
> +    return result
> +} 
> +
> +/**
> + * Update a product
> + */
> +def updateProduct() {
> +    Map res = checkProductRelatedPermission("updateProduct", "UPDATE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    GenericValue lookedUpValue = findOne("Product", ["productId": 
> parameters.productId], false)
> +    // save this value before overwriting it so we can compare it later
> +    Map saveIdMap = ["primaryProductCategoryId": 
> lookedUpValue.primaryProductCategoryId]
> +    
> +    lookedUpValue.setNonPKFields(parameters)
> +    lookedUpValue.lastModifiedDate = UtilDateTime.nowTimestamp()
> +    lookedUpValue.lastModifiedByUserLogin = userLogin.userLoginId 
> +    lookedUpValue.store()
> +
> +    return success()
> + }
> +
> + /**
> +  * Update a Product Name from quick admin
> +  */
> +def updateProductQuickAdminName() {
> +    Map res = checkProductRelatedPermission("updateQuickAdminName", "UPDATE")
> +
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    
> +    GenericValue lookedUpValue = findOne("Product", ["productId": 
> parameters.productId], false)
> +    lookedUpValue.productName = parameters.productName
> +    if ("Y".equals(lookedUpValue.isVirtual)) {
> +        lookedUpValue.internalName = lookedUpValue.productName
> +    }
> +    
> +    lookedUpValue.lastModifiedDate = UtilDateTime.nowTimestamp();
> +    lookedUpValue.lastModifiedByUserLogin = userLogin.userLoginId
> +    
> +    lookedUpValue.store()
> +    
> +    if ("Y".equals(lookedUpValue.isVirtual)) {
> +        // get all variant products, to update their productNames
> +        Map variantProductAssocMap = ["productId": parameters.productId, 
> "productAssocTypeId": "PRODUCT_VARIANT"]
> +        
> +        // get all productAssocs, then get the actual product to update
> +        List variantProductAssocs = 
> from("ProductAssoc").where(variantProductAssocMap).queryList()
> +        variantProductAssocs = EntityUtil.filterByDate(variantProductAssocs)
> +        for(GenericValue variantProductAssoc : variantProductAssocs) {
> +            GenericValue variantProduct = null
> +            variantProduct = findOne("Product", ["productId": 
> variantProductAssoc.productIdTo], false)
> +            
> +            variantProduct.productName = parameters.productName
> +            variantProduct.lastModifiedDate = UtilDateTime.nowTimestamp()
> +            variantProduct.lastModifiedByUserLogin = userLogin.userLoginId
> +            variantProduct.store()
> +        }
> +    }
> +    return success()
> +}
> +
> +/**
> + * Duplicate a Product
> + */
> +def duplicateProduct() {
> +    String callingMethodName = "duplicateProduct"
> +    String checkAction = "CREATE"
> +    Map res = checkProductRelatedPermission(callingMethodName, checkAction)
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    checkAction = "DELETE"
> +    res = checkProductRelatedPermission(callingMethodName, checkAction)
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    GenericValue dummyProduct = findOne("Product", ["productId": 
> parameters.productId], false)
> +    if (UtilValidate.isNotEmpty(dummyProduct)) {
> +        String errorMessage = 
> UtilProperties.getMessage("CommonErrorUiLabels", CommonErrorDuplicateKey, 
> parameters.locale)
> +        logError(errorMessage)
> +        return error(errorMessage)
> +    }
> +    
> +    // look up the old product and clone it
> +    GenericValue oldProduct = findOne("Product", ["productId": 
> parameters.oldProductId], false)
> +    GenericValue newProduct = oldProduct.clone()
> +    
> +    // set the productId, and write it to the datasource
> +    newProduct.productId = parameters.productId
> +    
> +    // if requested, set the new internalName field
> +    if (UtilValidate.isNotEmpty(parameters.newInternalName)) {
> +        newProduct.internalName = parameters.newInternalName
> +    }
> +    
> +    // if requested, set the new productName field
> +    if (UtilValidate.isNotEmpty(parameters.newProductName)) {
> +        newProduct.productName = parameters.newProductName
> +    }
> +    
> +    // if requested, set the new description field
> +    if (UtilValidate.isNotEmpty(parameters.newDescription)) {
> +        newProduct.description = parameters.newDescription
> +    }
> +    
> +    // if requested, set the new longDescription field
> +    if (UtilValidate.isNotEmpty(parameters.newLongDescription)) {
> +        newProduct.longDescription = parameters.newLongDescription
> +    }
> +    
> +    newProduct.create()
> +    
> +    // set up entity filter
> +    Map productFindContext = ["productId": parameters.oldProductId]
> +    Map reverseProductFindContext = ["productIdTo": parameters.oldProductId]
> +    
> +    // if requested, duplicate related data as well
> +    if (UtilValidate.isNotEmpty(parameters.duplicatePrices)) {
> +        List foundValues = 
> from("ProductPrice").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateIDs)) {
> +        List foundValues = 
> from("GoodIdentification").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateContent)) {
> +        List foundValues = 
> from("ProductContent").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateCategoryMembers)) {
> +        List foundValues = 
> from("ProductCategoryMember").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateAssocs)) {
> +        List foundValues = 
> from("ProductAssoc").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            newTempValue.create()
> +        }
> +        
> +        // small difference here, also do the reverse assocs...
> +        foundValues = from("ProductAssoc").where("productIdTo": 
> parameters.oldProductId).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productIdTo = parameters.productId
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateAttributes)) {
> +        List foundValues = 
> from("ProductAttribute").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateFeatureAppls)) {
> +        List foundValues = 
> from("ProductFeatureAppl").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            newTempValue.create()
> +        }
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.duplicateInventoryItems)) {
> +        List foundValues = 
> from("InventoryItem").where(productFindContext).queryList()
> +        for (GenericValue foundValue : foundValues) {
> +            /*
> +             *      NOTE: new inventory items should always be created 
> calling the
> +             *            createInventoryItem service because in this way we 
> are sure
> +             *            that all the relevant fields are filled with 
> default values.
> +             *            However, the code here should work fine because 
> all the values
> +             *            for the new inventory item are inerited from the 
> existing item.
> +             *      TODO: is this code correct? What is the meaning of 
> duplicating inventory items?
> +             *            What about the InventoryItemDetail entries?
> +             */
> +            GenericValue newTempValue = foundValue.clone()
> +            newTempValue.productId = parameters.productId
> +            // this one is slightly different because it needs a new 
> sequenced inventoryItemId
> +            newTempValue.inventoryItemId = 
> delegator.getNextSeqId("InventoryItem")
> +            newTempValue.create()
> +        }
> +    }
> +    
> +    // if requested, remove related data as well
> +    if (UtilValidate.isNotEmpty(parameters.removePrices)) {
> +        delegator.removeByAnd("ProductPrice", productFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeIDs)) {
> +        delegator.removeByAnd("GoodIdentification", productFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeContent)) {
> +        delegator.removeByAnd("ProductContent", productFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeCategoryMembers)) {
> +        delegator.removeByAnd("ProductCategoryMember", productFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeAssocs)) {
> +        delegator.removeByAnd("ProductAssoc", productFindContext)
> +        // small difference here, also do the reverse assocs...
> +        delegator.removeByAnd("ProductAssoc", reverseProductFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeAttributes)) {
> +        delegator.removeByAnd("ProductAttribute", productFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeFeatureAppls)) {
> +        delegator.removeByAnd("ProductFeatureAppl", productFindContext)
> +    }
> +    if (UtilValidate.isNotEmpty(parameters.removeInventoryItems)) {
> +        delegator.removeByAnd("InventoryItem", productFindContext)
> +    }
> +    return success()
> +}
> +
> +// Product Keyword Services
> +
> +/**
> + * induce all the keywords of a product
> + */
> +def forceIndexProductKeywords() {
> +    GenericValue product = findOne("Product", [productId: 
> parameters.productId], false)
> +    KeywordIndex.forceIndexKeywords(product)
> +    return success()
> +}
> +
> +/**
> + * delete all the keywords of a produc
> + */
> +def deleteProductKeywords() {
> +    GenericValue product = findOne("Product", [productId: 
> parameters.productId], false)
> +    delegator.removeRelated("ProductKeyword", product)
> +    return success()
> +}
> +
> +/**
> + * Index the Keywords for a Product
> + */
> +def indexProductKeywords() {
> +    //this service is meant to be called from an entity ECA for entities 
> that include a productId
> +    //if it is the Product entity itself triggering this action, then a 
> [productInstance] parameter
> +    //will be passed and we can save a few cycles looking that up
> +    GenericValue productInstance = parameters.productInstance
> +    if (productInstance == null) {
> +        Map findProductMap = [productId: parameters.productId]
> +        productInstance = findOne("Product", findProductMap, false)
> +    }
> +    //induce keywords if autoCreateKeywords is empty or Y
> +    if (UtilValidate.isEmpty(productInstance.autoCreateKeywords) || 
> "Y".equals(productInstance.autoCreateKeywords)) {
> +        KeywordIndex.indexKeywords(productInstance)
> +    }
> +    return success()
> +}
> +
> +/**
> + *  Discontinue Product Sales
> + *  set sales discontinuation date to now
> + */
> +def discontinueProductSales() {
> +    // set sales discontinuation date to now 
> +    Timestamp nowTimestamp = UtilDateTime.nowTimestamp()
> +    GenericValue product = findOne("Product", parameters, false)
> +    product.salesDiscontinuationDate = nowTimestamp
> +    product.store()
> +    
> +    // expire product from all categories
> +    List productCategoryMembers = 
> delegator.getRelated("ProductCategoryMember", null, null, product, false)
> +    for (GenericValue productCategoryMember : productCategoryMembers) {
> +        if (UtilValidate.isEmpty(productCategoryMember.thruDate)) {
> +            productCategoryMember.thruDate = UtilDateTime.nowTimestamp()
> +            productCategoryMember.store()
> +        }
> +    }
> +    // expire product from all associations going to it
> +    List assocProductAssocs = delegator.getRelated("AssocProductAssoc", 
> null, null, product, false)
> +    for (GenericValue assocProductAssoc : assocProductAssocs) {
> +        if (UtilValidate.isEmpty(assocProductAssoc.thruDate)) {
> +            assocProductAssoc.thruDate = UtilDateTime.nowTimestamp()
> +            assocProductAssoc.store()
> +        }
> +    }
> +    return success()
> +}
> +
> +
> +def countProductView() {
> +    if (UtilValidate.isEmpty(parameters.weight)) {
> +        parameters.weight = (Long) 1
> +    }
> +    GenericValue productCalculatedInfo = findOne("ProductCalculatedInfo", 
> ["productId": parameters.productId], false)
> +    if (UtilValidate.isEmpty(productCalculatedInfo)) {
> +        // go ahead and create it
> +        productCalculatedInfo = makeValue("ProductCalculatedInfo")
> +        productCalculatedInfo.productId = parameters.productId
> +        productCalculatedInfo.totalTimesViewed = parameters.weight
> +        productCalculatedInfo.create()
> +    } else {
> +        productCalculatedInfo.totalTimesViewed = 
> productCalculatedInfo.totalTimesViewed + parameters.weight
> +        productCalculatedInfo.store()
> +    }
> +    
> +    // do the same for the virtual product...
> +    GenericValue product = findOne("Product", ["productId": 
> parameters.productId], true)
> +    ProductWorker productWorker = new ProductWorker()
> +    String virtualProductId = productWorker.getVariantVirtualId(product)
> +    if (UtilValidate.isNotEmpty(virtualProductId)) {
> +        Map callSubMap = ["productId": virtualProductId, "weight": 
> parameters.weight]
> +        run service: "countProductView", with: callSubMap
> +    }
> +    return success()
> +    
> +}
> +
> +/**
> + * Create a ProductReview
> + */
> +def createProductReview() {
> +    GenericValue newEntity = makeValue("ProductReview", parameters)
> +    newEntity.userLoginId = userLogin.userLoginId
> +    newEntity.statusId = "PRR_PENDING"
> +    
> +    // code to check for auto-approved reviews (store setting)
> +    GenericValue productStore = findOne("ProductStore", ["productStoreId": 
> parameters.productStoreId], false)
> +    
> +    if (!UtilValidate.isEmpty(productStore)) {
> +        if ("Y".equals(productStore.autoApproveReviews)) {
> +            newEntity.statusId = "PRR_APPROVED"
> +        }
> +    }
> +    
> +    // create the new ProductReview
> +    newEntity.productReviewId = delegator.getNextSeqId("ProductReview")
> +    Map result = success()
> +    result.productReviewId = newEntity.productReviewId
> +    
> +    if (UtilValidate.isEmpty(newEntity.postedDateTime)) {
> +        newEntity.postedDateTime = UtilDateTime.nowTimestamp()
> +    }
> +    
> +    newEntity.create()
> +    
> +    String productId = newEntity.productId
> +    String successMessage = UtilProperties.getMessage("ProductUiLabels", 
> "ProductCreateProductReviewSuccess", parameters.locale)
> +    updateProductWithReviewRatingAvg(productId)
> +    
> +    return result
> +}
> +
> +/**
> + *  Update ProductReview
> + */
> +def updateProductReview() {
> +    Map res = checkProductRelatedPermission("updateProductReview", "UPDATE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    
> +    GenericValue lookupPKMap = makeValue("ProductReview")
> +    lookupPKMap.setPKFields(parameters)
> +    GenericValue lookedUpValue = findOne("ProductReview", lookupPKMap, false)
> +    lookupPKMap.setNonPKFields(parameters)
> +    lookupPKMap.store()
> +    
> +    String productId = lookedUpValue.productId
> +    updateProductWithReviewRatingAvg(productId)
> +    
> +    return success()
> +}
> +
> +/**
> + * change the product review Status
> + */
> +def setProductReviewStatus(){
> +    Map res = checkProductRelatedPermission("setProductReviewStatus", 
> "UPDATE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    
> +    GenericValue productReview = findOne("ProductReview", parameters, false)
> +    if (UtilValidate.isNotEmpty(productReview)) {
> +        if (!productReview.statusId.equals(parameters.statusId)) {
> +            GenericValue statusChange = from("StatusValidChange")
> +                .where("statusId", productReview.statusId, "statusIdTo", 
> parameters.statusId)
> +                .queryOne()
> +            if (UtilValidate.isEmpty(statusChange)) {
> +                String msg = "Status is not a valid change: from " + 
> productReview.statusId + " to " + parameters.statusId
> +                logError(msg)
> +                String errorMessage = 
> UtilProperties.getMessage("ProductErrorUiLabels", 
> ProductReviewErrorCouldNotChangeOrderStatusFromTo, parameters.locale)
> +                logError(errorMessage)
> +                return error(errorMessage)
> +            }
> +        }
> +    }
> +    
> +    productReview.statusId = parameters.statusId
> +    productReview.store()
> +    Map result = success()
> +    result.productReviewId = productReview.productReviewId
> +    
> +    return result
> +}
> +
> +/**
> + * Update Product with new Review Rating Avg
> + * this method is meant to be called in-line and depends in a productId 
> parameter
> + */
> +def updateProductWithReviewRatingAvg(String productId) {
> +    ProductWorker productWorker = new ProductWorker()
> +    BigDecimal averageCustomerRating = 
> productWorker.getAverageProductRating(delegator, productId)
> +    logInfo("Got new average customer rating "+ averageCustomerRating)
> +    
> +    if (averageCustomerRating == 0) {
> +        return success()
> +    }
> +    
> +    // update the review average on the ProductCalculatedInfo entity
> +    GenericValue productCalculatedInfo = findOne("ProductCalculatedInfo", 
> parameters, false)
> +    if (UtilValidate.isEmpty(productCalculatedInfo)) {
> +        // go ahead and create it
> +        productCalculatedInfo = makeValue("ProductCalculatedInfo")
> +        productCalculatedInfo.productId = productId
> +        productCalculatedInfo.averageCustomerRating = averageCustomerRating
> +        productCalculatedInfo.create()
> +    } else {
> +        productCalculatedInfo.averageCustomerRating = averageCustomerRating
> +        productCalculatedInfo.store()
> +    }
> +    
> +    return success()
> +}
> +
> +/**
> + * Updates the Product's Variants
> + */
> +def copyToProductVariants() {
> +    String callingMethodName = "copyToProductVariants"
> +    String checkAction = "CREATE"
> +    Map res = checkProductRelatedPermission(callingMethodName, checkAction)
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    checkAction = "DELETE"
> +    res = checkProductRelatedPermission(callingMethodName, checkAction)
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    
> +    Map productFindContext = ["productId": parameters.virtualProductId]
> +    GenericValue oldProduct = findOne("Product", productFindContext, false)
> +    
> +    Map variantsFindContext = ["productId": parameters.virtualProductId, 
> "productAssocTypeId": "PRODUCT_VARIANT"]
> +
> +    List variants = 
> from("ProductAssoc").where(variantsFindContext).filterByDate().queryList()
> +    List foundVariantValues = []
> +    List foundValues = []
> +    for (GenericValue newProduct : variants) {
> +        Map productVariantContext = ["productId": newProduct.productIdTo]
> +        // if requested, duplicate related data
> +        if (UtilValidate.isNotEmpty(parameters.duplicatePrices)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("ProductPrice").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("ProductPrice").where(productFindContext).queryList()
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +            }
> +        }
> +        if (UtilValidate.isNotEmpty(parameters.duplicateIDs)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("GoodIdentification").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("GoodIdentification").where(productFindContext).queryList() 
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +                }
> +            
> +        }
> +        if (UtilValidate.isNotEmpty(parameters.duplicateContent)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("ProductContent").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("ProductContent").where(productFindContext).queryList()
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +            }
> +        }
> +        if (UtilValidate.isNotEmpty(parameters.duplicateCategoryMembers)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("ProductCategoryMember").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("ProductCategoryMember").where(productFindContext).queryList()
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +            }
> +        }
> +        if (UtilValidate.isNotEmpty(parameters.duplicateAttributes)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("ProductAttribute").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("ProductAttribute").where(productFindContext).queryList()
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +            }
> +        }
> +        if (UtilValidate.isNotEmpty(parameters.duplicateFacilities)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("ProductFacility").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("ProductFacility").where(productFindContext).queryList()
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +            }
> +        }
> +        if (UtilValidate.isNotEmpty(parameters.duplicateLocations)) {
> +            if (UtilValidate.isNotEmpty(parameters.removeBefore)) {
> +                foundVariantValues = 
> from("ProductFacilityLocation").where(productVariantContext).queryList()
> +                for (GenericValue foundVariantValue : foundVariantValues) {
> +                    foundVariantValue.remove()
> +                }
> +            }
> +            foundValues = 
> from("ProductFacilityLocation").where(productFindContext).queryList()
> +            for (GenericValue foundValue : foundValues) {
> +                GenericValue newTempValue = foundValue.clone()
> +                newTempValue.productId = newProduct.productIdTo
> +                newTempValue.create()
> +            }
> +        }
> +    }
> +    return success()
> +}
> +
> +/**
> + * Check Product Related Permission
> + * a method to centralize product security code, meant to be called in-line 
> with
> + * call-simple-method, and the checkAction and callingMethodName attributes 
> should be in the method context
> + */
> +def checkProductRelatedPermission (String callingMethodName, String 
> checkAction){
> +    if (UtilValidate.isEmpty(callingMethodName)) {
> +        callingMethodName = UtilProperties.getMessage("CommonUiLabels", 
> "CommonPermissionThisOperation", parameters.locale)
> +    }
> +    if (UtilValidate.isEmpty(checkAction)) {
> +        checkAction = "UPDATE"
> +    }
> +    List roleCategories = []
> +    // find all role-categories that this product is a member of
> +    if (!security.hasEntityPermission("CATALOG", "_${checkAction}", 
> parameters.userLogin)) {
> +        Map lookupRoleCategoriesMap = ["productId": parameters.productId, 
> "partyId": userLogin.partyId, "roleTypeId": "LTD_ADMIN"]
> +        roleCategories = 
> from("ProductCategoryMemberAndRole").where(lookupRoleCategoriesMap).filterByDate("roleFromDate",
>  "roleThruDate").queryList()
> +    }
> +    
> +    if (! ((security.hasEntityPermission("CATALOG", "_${checkAction}", 
> parameters.userLogin)) 
> +        || (security.hasEntityPermission("CATALOG_ROLE", "_${checkAction}", 
> parameters.userLogin) && !UtilValidate.isEmpty(roleCategories)) 
> +        || (!UtilValidate.isEmpty(parameters.alternatePermissionRoot) && 
> security.hasEntityPermission(parameters.alternatePermissionRoot, checkAction, 
> parameters.userLogin)))) {
> +            String checkActionLabel = "ProductCatalog" + 
> checkAction.charAt(0) + checkAction.substring(1).toLowerCase() + 
> "PermissionError"
> +            String resourceDescription = callingMethodName
> +            
> +            String errorMessage = 
> UtilProperties.getMessage("ProductUiLabels", checkActionLabel, 
> parameters.locale)
> +            logError(errorMessage)
> +            return error(errorMessage)
> +        }
> +    return success()
> +}
> +
> +/**
> + * Main permission logic
> + */
> +def productGenericPermission(){
> +    String mainAction = parameters.mainAction
> +    Map result = success()
> +    if (UtilValidate.isEmpty(mainAction)) {
> +        String errorMessage = UtilProperties.getMessage("ProductUiLabels", 
> "ProductMissingMainActionInPermissionService", parameters.locale)
> +        logError(errorMessage)
> +        return error(errorMessage)
> +    }
> +    Map res = checkProductRelatedPermission(parameters.resourceDescription, 
> parameters.mainAction)
> +    if (!ServiceUtil.isSuccess(res)) {
> +        String failMessage = UtilProperties.getMessage("ProductUiLabels", 
> "ProductPermissionError", parameters.locale)
> +        Boolean hasPermission = false
> +        result = fail(failMessage)
> +        result.hasPermission = hasPermission
> +    } else {
> +        Boolean hasPermission = true
> +        result.hasPermission = hasPermission
> +    }
> +    return result
> +}
> +
> +/**
> + * product price permission logic
> + */
> +def productPriceGenericPermission(){
> +    String mainAction = parameters.mainAction
> +    if (UtilValidate.isEmpty(mainAction)) {
> +        String errorMessage = UtilProperties.getMessage("ProductUiLabels", 
> "ProductMissingMainActionInPermissionService", parameters.locale)
> +        logError(errorMessage)
> +        return error(errorMessage)
> +    }
> +    Map result = success()
> +    if (!security.hasEntityPermission("CATALOG_PRICE_MAINT", null, 
> parameters.userLogin)) {
> +        String errorMessage = UtilProperties.getMessage("ProductUiLabels", 
> "ProductPriceMaintPermissionError", parameters.locale)
> +        logError(errorMessage)
> +        result = error(errorMessage)
> +    }
> +    Map res = checkProductRelatedPermission(null, null)
> +    if (ServiceUtil.isSuccess(result) && ServiceUtil.isSuccess(res)) {
> +        result.hasPermission = true
> +    } else {
> +        String failMessage = UtilProperties.getMessage("ProductUiLabels", 
> "ProductPermissionError", parameters.locale)
> +        result = fail(failMessage)
> +        result.hasPermission = false
> +    }
> +    return result
> +}
> +
> +/**
> + * ================================================================
> + * ProductRole Services
> + * ================================================================
> + */
> +
> +
> +/**
> + * Add Party to Product
> + */
> +def addPartyToProduct(){
> +    Map result = checkProductRelatedPermission("addPartyToProduct", "CREATE")
> +    if (!ServiceUtil.isSuccess(result)) {
> +        return result
> +    }
> +    GenericValue newEntity = makeValue("ProductRole", parameters)
> +    
> +    if (UtilValidate.isEmpty(newEntity.fromDate)) {
> +        newEntity.fromDate = UtilDateTime.nowTimestamp()
> +    }
> +    newEntity.create()
> +    return success()
> +}
> +
> +/**
> + * Update Party to Product
> + */
> +def updatePartyToProduct(){
> +    Map result = checkProductRelatedPermission("updatePartyToProduct", 
> "UPDATE")
> +    if (!ServiceUtil.isSuccess(result)) {
> +        return result
> +    }
> +    GenericValue lookupPKMap = makeValue("ProductRole")
> +    lookupPKMap.setPKFields(parameters)
> +    GenericValue lookedUpValue = findOne("ProductRole", lookupPKMap, false)
> +    lookedUpValue.setNonPKFields(parameters)
> +    lookedUpValue.store()
> +    return success()
> +}
> +
> +/**
> + * Remove Party From Product
> + */
> +def removePartyFromProduct(){
> +    Map res = checkProductRelatedPermission("removePartyFromProduct", 
> "DELETE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    Map lookupPKMap = makeValue("ProductRole")
> +    lookupPKMap.setPKFields(parameters)
> +    GenericValue lookedUpValue = findOne("ProductRole", lookupPKMap, false)
> +    lookedUpValue.remove()
> +    
> +    return success()
> +}
> +
> +// ProductCategoryGlAccount methods
> + /**
> +  * Create a ProductCategoryGlAccount
> +  */
> +def createProductCategoryGlAccount(){
> +    Map res = 
> checkProductRelatedPermission("createProductCategoryGlAccount", "CREATE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    
> +    GenericValue newEntity = makeValue("ProductCategoryGlAccount", 
> parameters)
> +    newEntity.create()
> +    
> +    return success()
> +}
> +
> +/**
> + * Update a ProductCategoryGlAccount
> + */
> +def updateProductCategoryGlAccount(){
> +    Map res = 
> checkProductRelatedPermission("updateProductCategoryGlAccount", "UPDATE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    
> +    GenericValue lookedUpValue = findOne("ProductCategoryGlAccount", 
> parameters, false)
> +    lookedUpValue.setNonPKFields(parameters)
> +    lookedUpValue.store()
> +    
> +    return success()
> +}
> +
> +/**
> + * Delete a ProductCategoryGlAccount
> + */
> +def deleteProductCategoryGlAccount(){
> +    Map res = checkProductRelatedPermission("deleteProductCategorGLAccount", 
> "DELETE")
> +    if (!ServiceUtil.isSuccess(res)) {
> +        return res
> +    }
> +    GenericValue lookedUpValue = findOne("ProductCategoryGlAccount", 
> parameters, false)
> +    lookedUpValue.remove()
> +    
> +    return success()
> +}
> +
> +// Product GroupOrder Services -->
> +
> +/**
> + * Create ProductGroupOrder
> + */
> +def createProductGroupOrder(){
> +    GenericValue newEntity = makeValue("ProductGroupOrder")
> +    delegator.setNextSubSeqId(newEntity, "groupOrderId", 5, 1)
> +    Map result = success()
> +    result.groupOrderId = newEntity.groupOrderId
> +    newEntity.setNonPKFields(parameters)
> +    newEntity.create()
> +    
> +    return result
> +}
> +
> +/**
> + * Update ProductGroupOrder
> + */
> +def updateProductGroupOrder(){
> +    GenericValue productGroupOrder = findOne("ProductGroupOrder", 
> ["groupOrderId": parameters.groupOrderId], false)
> +    productGroupOrder.setNonPKFields(parameters)
> +    productGroupOrder.store()
> +    
> +    if ("GO_CREATED".equals(productGroupOrder.statusId)) {
> +        GenericValue jobSandbox = findOne("JobSandbox", ["jobId": 
> productGroupOrder.jobId], false)
> +        if (UtilValidate.isNotEmpty(jobSandbox)) {
> +            jobSandbox.runTime = parameters.thruDate
> +            jobSandbox.store()
> +        }
> +    }
> +    return success()
> +}
> +
> +/**
> + * Delete ProductGroupOrder
> + */
> +def deleteProductGroupOrder(){
> +    List orderItemGroupOrders = 
> from("OrderItemGroupOrder").where("groupOrderId": 
> parameters.groupOrderId).queryList()
> +    for (GenericValue orderItemGroupOrder : orderItemGroupOrders) {
> +        orderItemGroupOrder.remove()
> +    }
> +    GenericValue productGroupOrder = findOne("ProductGroupOrder", 
> ["groupOrderId": parameters.groupOrderId], false)
> +    if (UtilValidate.isEmpty(productGroupOrder)) {
> +        return error("Entity value not found with name: " + 
> productGroupOrder)
> +    }
> +    productGroupOrder.remove()
> +    
> +    GenericValue jobSandbox = findOne("JobSandbox", ["jobId": 
> productGroupOrder.jobId], false)
> +    if (UtilValidate.isEmpty(jobSandbox)) {
> +        return error("Entity value not found with name: " + jobSandbox)
> +    }
> +    jobSandbox.remove()
> +    
> +    List jobSandboxList = from("JobSandbox").where("runtimeDataId": 
> jobSandbox.runtimeDataId).queryList()
> +    for (GenericValue jobSandboxRelatedRuntimeData : jobSandboxList) {
> +        jobSandboxRelatedRuntimeData.remove()
> +    }
> +    
> +    GenericValue runtimeData = findOne("RuntimeData", ["runtimeDataId": 
> jobSandbox.runtimeDataId], false)
> +    if (UtilValidate.isEmpty(runtimeData)) {
> +        return error("Entity value not found with name: " + runtimeData)
> +    }
> +    runtimeData.remove()
> +    
> +    return success()
> +}
> +
> +/**
> + * Create ProductGroupOrder
> + */
> +def createJobForProductGroupOrder(){
> +    GenericValue productGroupOrder = findOne("ProductGroupOrder", 
> ["groupOrderId": parameters.groupOrderId], false)
> +    if (UtilValidate.isEmpty(productGroupOrder.jobId)) {
> +        // Create RuntimeData For ProductGroupOrder
> +        Map runtimeDataMap = ["groupOrderId": parameters.groupOrderId]
> +        XmlSerializer xmlSerializer = new XmlSerializer()
> +        String runtimeInfo = xmlSerializer.serialize(runtimeDataMap)
> +        
> +        GenericValue runtimeData = makeValue("RuntimeData")
> +        runtimeData.runtimeDataId = delegator.getNextSeqId("RuntimeData")
> +        String runtimeDataId = runtimeData.runtimeDataId
> +        runtimeData.runtimeInfo = runtimeInfo
> +        runtimeData.create()
> +        
> +        // Create Job For ProductGroupOrder
> +        // FIXME: Jobs should not be manually created
> +        GenericValue jobSandbox = makeValue("JobSandbox")
> +        jobSandbox.jobId = delegator.getNextSeqId("JobSandbox")
> +        String jobId = jobSandbox.jobId
> +        jobSandbox.jobName = "Check ProductGroupOrder Expired"
> +        jobSandbox.runTime = parameters.thruDate
> +        jobSandbox.poolId = "pool"
> +        jobSandbox.statusId = "SERVICE_PENDING"
> +        jobSandbox.serviceName = "checkProductGroupOrderExpired"
> +        jobSandbox.runAsUser = "system"
> +        jobSandbox.runtimeDataId = runtimeDataId
> +        jobSandbox.maxRecurrenceCount = (Long) 1
> +        jobSandbox.priority = (Long) 50
> +        jobSandbox.create()
> +        
> +        productGroupOrder.jobId = jobId
> +        productGroupOrder.store()
> +    }
> +    return success()
> +}
> +
> +/**
> + * Check OrderItem For ProductGroupOrder
> + */
> +def checkOrderItemForProductGroupOrder(){
> +    List orderItems = from("OrderItem").where("orderId": 
> parameters.orderId).queryList()
> +    for (GenericValue orderItem : orderItems) {
> +        String productId = orderItem.productId
> +        GenericValue product = findOne("Product", ["productId": 
> orderItem.productId], false)
> +        if ("Y".equals(product.isVariant)) {
> +            List variantProductAssocs = 
> from("ProductAssoc").where("productIdTo": orderItem.productId, 
> "productAssocTypeId": "PRODUCT_VARIANT").queryList()
> +            variantProductAssocs = 
> EntityUtil.filterByDate(variantProductAssocs)
> +            GenericValue variantProductAssoc = variantProductAssocs.get(0)
> +            productId = variantProductAssoc.productId
> +        }
> +        List productGroupOrders = 
> from("ProductGroupOrder").where("productId": productId).queryList()
> +        if (UtilValidate.isNotEmpty(productGroupOrders)) {
> +            productGroupOrders = EntityUtil.filterByDate(productGroupOrders)
> +            GenericValue productGroupOrder = productGroupOrders.get(0)
> +            if (UtilValidate.isEmpty(productGroupOrder.soldOrderQty)) {
> +                productGroupOrder.soldOrderQty = orderItem.quantity
> +            } else {
> +            productGroupOrder.soldOrderQty = productGroupOrder.soldOrderQty 
> + orderItem.quantity
> +            }
> +            productGroupOrder.store()
> +            
> +            Map createOrderItemGroupOrderMap = ["orderId": 
> orderItem.orderId, "orderItemSeqId": orderItem.orderItemSeqId, 
> "groupOrderId": productGroupOrder.groupOrderId]
> +            
> +            run service: "createOrderItemGroupOrder", with: 
> createOrderItemGroupOrderMap
> +        }
> +    }
> +    return success()
> +}
> +
> +/**
> + * Cancle OrderItemGroupOrder
> + */
> +def cancleOrderItemGroupOrder(){
> +    List orderItems = []
> +    if (UtilValidate.isNotEmpty(parameters.orderItemSeqId)) {
> +        orderItems = from("OrderItem")
> +            .where("orderId", parameters.orderId, "orderItemSeqId", 
> parameters.orderItemSeqId)
> +            .queryList()
> +    } else {
> +        orderItems = from("OrderItem")
> +            .where("orderId", parameters.orderId)
> +            .queryList()
> +    }
> +    for(GenericValue orderItem : orderItems) {
> +        List orderItemGroupOrders = from("OrderItemGroupOrder")
> +            .where("orderId", orderItem.orderId, "orderItemSeqId", 
> orderItem.orderItemSeqId)
> +            .queryList()
> +        if (UtilValidate.isNotEmpty(orderItemGroupOrders)) {
> +            GenericValue orderItemGroupOrder = orderItemGroupOrders.get(0)
> +            GenericValue productGroupOrder = findOne("ProductGroupOrder", 
> [groupOrderId: orderItemGroupOrder.groupOrderId], false)
> +            
> +            if (UtilValidate.isNotEmpty(productGroupOrder)) {
> +                if ("GO_CREATED".equals(productGroupOrder.statusId)) {
> +                    if ("ITEM_CANCELLED".equals(orderItem.statusId)) {
> +                        BigDecimal cancelQuantity
> +                        if 
> (UtilValidate.isNotEmpty(orderItem.cancelQuantity)) {
> +                            cancelQuantity = orderItem.cancelQuantity
> +                        } else {
> +                            cancelQuantity = orderItem.quantity
> +                        }
> +                        productGroupOrder.soldOrderQty = 
> productGroupOrder.soldOrderQty - cancelQuantity
> +                    }
> +                    productGroupOrder.store()
> +                    orderItemGroupOrder.remove()
> +                }
> +            }
> +        }
> +    }
> +    return success()
> +}
> +
> +/**
> + * Check ProductGroupOrder Expired
> + */
> +def checkProductGroupOrderExpired(){
> +    GenericValue productGroupOrder = findOne("ProductGroupOrder", 
> parameters, false)
> +    if (UtilValidate.isNotEmpty(productGroupOrder)) {
> +        String groupOrderStatusId
> +        String newItemStatusId
> +        if (productGroupOrder.soldOrderQty >= productGroupOrder.reqOrderQty) 
> {
> +            newItemStatusId = "ITEM_APPROVED"
> +            groupOrderStatusId = "GO_SUCCESS"
> +        } else {
> +            newItemStatusId = "ITEM_CANCELLED"
> +            groupOrderStatusId = "GO_CANCELLED"
> +        }
> +    Map updateProductGroupOrderMap = [:]
> +    updateProductGroupOrderMap.groupOrderId = productGroupOrder.groupOrderId
> +    updateProductGroupOrderMap.statusId = groupOrderStatusId
> +    run service: "updateProductGroupOrder", with: updateProductGroupOrderMap
> +    
> +    List orderItemGroupOrders = from("OrderItemGroupOrder")
> +        .where("groupOrderId", productGroupOrder.groupOrderId)
> +        .queryList()
> +    for(GenericValue orderItemGroupOrder : orderItemGroupOrders) {
> +        Map changeOrderItemStatusMap = ["orderId": 
> orderItemGroupOrder.orderId, "orderItemSeqId": 
> orderItemGroupOrder.orderItemSeqId, "statusId": newItemStatusId]
> +        run service: "changeOrderItemStatus", with: changeOrderItemStatusMap
> +    }
> +    return success()
> +    }
> +}
> +
> diff --git 
> a/applications/product/minilang/product/product/ProductServices.xml 
> b/applications/product/minilang/product/product/ProductServices.xml
> deleted file mode 100644
> index b331b32..0000000
> --- a/applications/product/minilang/product/product/ProductServices.xml
> +++ /dev/null
> @@ -1,1051 +0,0 @@
> -<?xml version="1.0" encoding="UTF-8"?>
> -<!--
> -Licensed to the Apache Software Foundation (ASF) under one
> -or more contributor license agreements.  See the NOTICE file
> -distributed with this work for additional information
> -regarding copyright ownership.  The ASF licenses this file
> -to you under the Apache License, Version 2.0 (the
> -"License"); you may not use this file except in compliance
> -with the License.  You may obtain a copy of the License at
> -
> -http://www.apache.org/licenses/LICENSE-2.0
> -
> -Unless required by applicable law or agreed to in writing,
> -software distributed under the License is distributed on an
> -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
> -KIND, either express or implied.  See the License for the
> -specific language governing permissions and limitations
> -under the License.
> --->
> -
> -<simple-methods xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
> -        xmlns="http://ofbiz.apache.org/Simple-Method"; 
> xsi:schemaLocation="http://ofbiz.apache.org/Simple-Method 
> http://ofbiz.apache.org/dtds/simple-methods.xsd";>
> -    <simple-method method-name="createProduct" short-description="Create a 
> Product">
> -        <check-permission permission="CATALOG" action="_CREATE">
> -            <alt-permission permission="CATALOG_ROLE" action="_CREATE"/>
> -            <fail-property resource="ProductUiLabels" 
> property="ProductCatalogCreatePermissionError"/>
> -        </check-permission>
> -        <check-errors/>
> -
> -        <make-value entity-name="Product" value-field="newEntity"/>
> -        <set-nonpk-fields map="parameters" value-field="newEntity"/>
> -
> -        <set from-field="parameters.productId" field="newEntity.productId"/>
> -        <if-empty field="newEntity.productId">
> -            <sequenced-id sequence-name="Product" 
> field="newEntity.productId"/>
> -        <else>
> -            <check-id field="newEntity.productId"/>
> -            <check-errors />
> -            <entity-one entity-name="Product" 
> value-field="dummyProduct"><field-map field-name="productId" 
> from-field="parameters.productId"/></entity-one>
> -            <if-not-empty field="dummyProduct">
> -                <add-error ><fail-property resource="CommonErrorUiLabels" 
> property="CommonErrorDuplicateKey" /></add-error>
> -            </if-not-empty>
> -            <check-errors />
> -        </else>
> -        </if-empty>
> -        <field-to-result field="newEntity.productId" 
> result-name="productId"/>
> -
> -        <now-timestamp field="nowTimestamp"/>
> -        <set from-field="nowTimestamp" field="newEntity.createdDate"/>
> -        <set from-field="nowTimestamp" field="newEntity.lastModifiedDate"/>
> -        <set from-field="userLogin.userLoginId" 
> field="newEntity.lastModifiedByUserLogin"/>
> -        <set from-field="userLogin.userLoginId" 
> field="newEntity.createdByUserLogin"/>
> -        <if-empty field="newEntity.isVariant">
> -            <set field="newEntity.isVariant" value="N"/>
> -        </if-empty>
> -        <if-empty field="newEntity.isVirtual">
> -            <set field="newEntity.isVirtual" value="N"/>
> -        </if-empty>
> -        <if-empty field="newEntity.billOfMaterialLevel">
> -            <set field="newEntity.billOfMaterialLevel" value="0" 
> type="Long"/>
> -        </if-empty>
> -
> -        <create-value value-field="newEntity"/>
> -
> -        <!-- if setting the primaryProductCategoryId create a member entity 
> too -->
> -        <!-- THIS IS REMOVED BECAUSE IT CAUSES PROBLEMS FOR WORKING ON 
> PRODUCTION SITES
> -        <if-not-empty field="newEntity.primaryProductCategoryId">
> -            <make-value entity-name="ProductCategoryMember" 
> value-field="newMember"/>
> -            <set from-field="productId" map-name="newEntity" 
> to-field-name="productId" to-map-name="newMember"/>
> -            <set from-field="primaryProductCategoryId" map-name="newEntity" 
> to-field-name="productCategoryId" to-map-name="newMember"/>
> -            <now-timestamp field="nowStamp"/>
> -            <set from-field="nowStamp" field="newMember.fromDate"/>
> -            <create-value value-field="newMember"/>
> -        </if-not-empty>
> -        -->
> -
> -        <!-- if the user has the role limited position, add this product to 
> the limit category/ies -->
> -        <if-has-permission permission="CATALOG_ROLE" action="_CREATE">
> -            <entity-and entity-name="ProductCategoryRole" 
> list="productCategoryRoles" filter-by-date="true">
> -                <field-map field-name="partyId" 
> from-field="userLogin.partyId"/>
> -                <field-map field-name="roleTypeId" value="LTD_ADMIN"/>
> -            </entity-and>
> -
> -            <iterate list="productCategoryRoles" entry="productCategoryRole">
> -                <!-- add this new product to the category -->
> -                <make-value entity-name="ProductCategoryMember" 
> value-field="newLimitMember"/>
> -                <set from-field="newEntity.productId" 
> field="newLimitMember.productId"/>
> -                <set from-field="productCategoryRole.productCategoryId" 
> field="newLimitMember.productCategoryId"/>
> -                <set from-field="nowTimestamp" 
> field="newLimitMember.fromDate"/>
> -                <create-value value-field="newLimitMember"/>
> -            </iterate>
> -        </if-has-permission>
> -    </simple-method>
> -    <simple-method method-name="updateProduct" short-description="Update a 
> Product">
> -        <set value="updateProduct" field="callingMethodName"/>
> -        <set value="UPDATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <entity-one entity-name="Product" value-field="lookedUpValue"/>
> -        <!-- save this value before overwriting it so we can compare it 
> later -->
> -        <set from-field="lookedUpValue.primaryProductCategoryId" 
> field="saveIdMap.primaryProductCategoryId"/>
> -        <set-nonpk-fields map="parameters" value-field="lookedUpValue"/>
> -
> -        <now-timestamp field="lookedUpValue.lastModifiedDate"/>
> -        <set from-field="userLogin.userLoginId" 
> field="lookedUpValue.lastModifiedByUserLogin"/>
> -
> -        <store-value value-field="lookedUpValue"/>
> -
> -        <!-- if setting the primaryParentCategoryId, create a rollup entity 
> too -->
> -        <!-- THIS IS REMOVED BECAUSE IT CAUSES PROBLEMS FOR WORKING ON 
> PRODUCTION SITES
> -        <if-not-empty field="lookedUpValue.primaryProductCategoryId">
> -            <if-compare-field to-field="saveIdMap.primaryProductCategoryId" 
> field="lookedUpValue.primaryProductCategoryId" operator="equals">
> -                <make-value entity-name="ProductCategoryMember" 
> value-field="newMember"/>
> -                <set from-field="productId" map-name="newEntity" 
> to-field-name="productId" to-map-name="newMember"/>
> -                <set from-field="primaryProductCategoryId" 
> map-name="newEntity" to-field-name="productCategoryId" 
> to-map-name="newMember"/>
> -                <now-timestamp field="newMember.fromDate"/>
> -                <create-value value-field="newMember"/>
> -            </if-compare-field>
> -        </if-not-empty>
> -        -->
> -    </simple-method>
> -
> -    <!-- update the name of a product - handles real , virtual and variant 
> products -->
> -    <simple-method method-name="updateProductQuickAdminName" 
> short-description="Update a Product Name from quick admin">
> -        <set value="updateProductQuickAdminName" field="callingMethodName"/>
> -        <set value="UPDATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <entity-one entity-name="Product" value-field="lookedUpValue"/>
> -        <set from-field="parameters.productName" 
> field="lookedUpValue.productName"/>
> -        <if-compare field="lookedUpValue.isVirtual" operator="equals" 
> value="Y">
> -            <set from-field="lookedUpValue.productName" 
> field="lookedUpValue.internalName"/>
> -        </if-compare>
> -
> -        <now-timestamp field="lookedUpValue.lastModifiedDate"/>
> -        <set from-field="userLogin.userLoginId" 
> field="lookedUpValue.lastModifiedByUserLogin"/>
> -
> -        <store-value value-field="lookedUpValue"/>
> -
> -        <if-compare field="lookedUpValue.isVirtual" operator="equals" 
> value="Y">
> -            <!-- get all variant products, to update their productNames -->
> -            <set from-field="parameters.productId" 
> field="variantProductAssocMap.productId"/>
> -            <set value="PRODUCT_VARIANT" 
> field="variantProductAssocMap.productAssocTypeId"/>
> -
> -            <!-- get all productAssocs, then get the actual product to 
> update -->
> -            <find-by-and entity-name="ProductAssoc" 
> map="variantProductAssocMap" list="variantProductAssocs"/>
> -            <filter-list-by-date list="variantProductAssocs"/>
> -            <iterate list="variantProductAssocs" entry="variantProductAssoc">
> -                <clear-field field="variantProduct"/>
> -                <entity-one entity-name="Product" 
> value-field="variantProduct" auto-field-map="false">
> -                    <field-map field-name="productId" 
> from-field="variantProductAssoc.productIdTo"/>
> -                </entity-one>
> -
> -                <set from-field="parameters.productName" 
> field="variantProduct.productName"/>
> -                <now-timestamp field="variantProduct.lastModifiedDate"/>
> -                <set from-field="userLogin.userLoginId" 
> field="variantProduct.lastModifiedByUserLogin"/>
> -                <store-value value-field="variantProduct"/>
> -            </iterate>
> -        </if-compare>
> -    </simple-method>
> -
> -    <simple-method method-name="duplicateProduct" 
> short-description="Duplicate a Product">
> -        <set value="duplicateProduct" field="callingMethodName"/>
> -        <set value="CREATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <set value="DELETE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <entity-one entity-name="Product" value-field="dummyProduct">
> -            <field-map field-name="productId" 
> from-field="parameters.productId"/>
> -        </entity-one>
> -        <if-not-empty field="dummyProduct">
> -            <add-error ><fail-property resource="CommonErrorUiLabels" 
> property="CommonErrorDuplicateKey" /></add-error>
> -        </if-not-empty>
> -        <check-errors/>
> -
> -        <!-- look up the old product and clone it -->
> -        <entity-one entity-name="Product" value-field="oldProduct" 
> auto-field-map="false">
> -            <field-map field-name="productId" 
> from-field="parameters.oldProductId"/>
> -        </entity-one>
> -        <clone-value value-field="oldProduct" new-value-field="newProduct"/>
> -
> -        <!-- set the productId, and write it to the datasource -->
> -        <set from-field="parameters.productId" field="newProduct.productId"/>
> -
> -        <!-- if requested, set the new internalName field -->
> -        <if-not-empty field="parameters.newInternalName">
> -            <set from-field="parameters.newInternalName" 
> field="newProduct.internalName"/>
> -        </if-not-empty>
> -
> -        <!-- if requested, set the new productName field -->
> -        <if-not-empty field="parameters.newProductName">
> -            <set from-field="parameters.newProductName" 
> field="newProduct.productName"/>
> -        </if-not-empty>
> -
> -        <!-- if requested, set the new description field -->
> -        <if-not-empty field="parameters.newDescription">
> -            <set from-field="parameters.newDescription" 
> field="newProduct.description"/>
> -        </if-not-empty>
> -
> -        <!-- if requested, set the new longDescription field -->
> -        <if-not-empty field="parameters.newLongDescription">
> -            <set from-field="parameters.newLongDescription" 
> field="newProduct.longDescription"/>
> -        </if-not-empty>
> -
> -        <create-value value-field="newProduct"/>
> -
> -        <!-- set up entity filter -->
> -        <set field="productFindContext.productId" 
> from-field="parameters.oldProductId"/>
> -        <set field="reverseProductFindContext.productIdTo" 
> from-field="parameters.oldProductId"/>
> -
> -        <!-- if requested, duplicate related data as well -->
> -        <if-not-empty field="parameters.duplicatePrices">
> -            <find-by-and entity-name="ProductPrice" map="productFindContext" 
> list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateIDs">
> -            <find-by-and entity-name="GoodIdentification" 
> map="productFindContext" list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateContent">
> -            <find-by-and entity-name="ProductContent" 
> map="productFindContext" list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateCategoryMembers">
> -            <find-by-and entity-name="ProductCategoryMember" 
> map="productFindContext" list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <!-- Clone Content -->
> -                <sequenced-id sequence-name="Content" field="newContentId"/>
> -                <entity-one entity-name="Content" value-field="oldContent" 
> use-cache="false">
> -                    <field-map field-name="contentId" 
> value="${newTempValue.contentId}"/>
> -                </entity-one>
> -                <if-not-empty field="oldContent">
> -                    <clone-value value-field="oldContent" 
> new-value-field="clonedContent"/>
> -                    <set from-field="newContentId" 
> field="clonedContent.contentId"/>
> -                    <set from-field="newContentId" 
> field="newTempValue.contentId"/>
> -                    <!-- Clone DataResource -->
> -                    <entity-one entity-name="DataResource" 
> value-field="oldDataResource" use-cache="false">
> -                        <field-map field-name="dataResourceId" 
> value="${clonedContent.dataResourceId}"/>
> -                    </entity-one>
> -                    <if-not-empty field="oldDataResource">
> -                        <sequenced-id sequence-name="DataResource" 
> field="newDataResourceId"/>
> -                        <clone-value new-value-field="clonedDataresource" 
> value-field="oldDataResource"/>
> -                        <set from-field="newDataResourceId" 
> field="clonedDataresource.dataResourceId"/>
> -                        <!-- set new data resource id in cloned content -->
> -                        <set from-field="newDataResourceId" 
> field="clonedContent.dataResourceId"/>
> -                        <create-value value-field="clonedDataresource"/>
> -                        <!-- Clone Electronic Text if exists -->
> -                        <get-related-one value-field="oldDataResource" 
> relation-name="ElectronicText" to-value-field="oldElectronicText"/>
> -                        <if-not-empty field="oldElectronicText">
> -                            <clone-value value-field="oldElectronicText" 
> new-value-field="clonedElectronicText"/>
> -                            <set from-field="newDataResourceId" 
> field="clonedElectronicText.dataResourceId"/>
> -                            <create-value 
> value-field="clonedElectronicText"/>
> -                        </if-not-empty>
> -                    </if-not-empty>
> -                    <create-value value-field="clonedContent"/>
> -                </if-not-empty>
> -                <!-- End Clone Contet -->
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateAssocs">
> -            <find-by-and entity-name="ProductAssoc" map="productFindContext" 
> list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -
> -            <!-- small difference here, also do the reverse assocs... -->
> -            <entity-and entity-name="ProductAssoc" list="foundValues">
> -                <field-map field-name="productIdTo" 
> from-field="parameters.oldProductId"/>
> -            </entity-and>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productIdTo"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateAttributes">
> -            <find-by-and entity-name="ProductAttribute" 
> map="productFindContext" list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateFeatureAppls">
> -            <find-by-and entity-name="ProductFeatureAppl" 
> map="productFindContext" list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.duplicateInventoryItems">
> -            <find-by-and entity-name="InventoryItem" 
> map="productFindContext" list="foundValues"/>
> -            <iterate list="foundValues" entry="foundValue">
> -                <!--
> -                    NOTE: new inventory items should always be created 
> calling the
> -                          createInventoryItem service because in this way we 
> are sure
> -                          that all the relevant fields are filled with 
> default values.
> -                          However, the code here should work fine because 
> all the values
> -                          for the new inventory item are inerited from the 
> existing item.
> -                    TODO: is this code correct? What is the meaning of 
> duplicating inventory items?
> -                          What about the InventoryItemDetail entries?
> -                -->
> -                <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                <set from-field="parameters.productId" 
> field="newTempValue.productId"/>
> -                <!-- this one is slightly different because it needs a new 
> sequenced inventoryItemId -->
> -                <sequenced-id sequence-name="InventoryItem" 
> field="newTempValue.inventoryItemId"/>
> -                <create-value value-field="newTempValue"/>
> -            </iterate>
> -        </if-not-empty>
> -
> -        <!-- if requested, remove related data as well -->
> -        <if-not-empty field="parameters.removePrices">
> -            <remove-by-and entity-name="ProductPrice" 
> map="productFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeIDs">
> -            <remove-by-and entity-name="GoodIdentification" 
> map="productFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeContent">
> -            <remove-by-and entity-name="ProductContent" 
> map="productFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeCategoryMembers">
> -            <remove-by-and entity-name="ProductCategoryMember" 
> map="productFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeAssocs">
> -            <remove-by-and entity-name="ProductAssoc" 
> map="productFindContext"/>
> -            <!-- small difference here, also do the reverse assocs... -->
> -            <remove-by-and entity-name="ProductAssoc" 
> map="reverseProductFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeAttributes">
> -            <remove-by-and entity-name="ProductAttribute" 
> map="productFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeFeatureAppls">
> -            <remove-by-and entity-name="ProductFeatureAppl" 
> map="productFindContext"/>
> -        </if-not-empty>
> -        <if-not-empty field="parameters.removeInventoryItems">
> -            <remove-by-and entity-name="InventoryItem" 
> map="productFindContext"/>
> -        </if-not-empty>
> -    </simple-method>
> -
> -    <!-- Product Keyword Services -->
> -    <simple-method method-name="forceIndexProductKeywords" 
> short-description="induce all the keywords of a product">
> -        <entity-one entity-name="Product" value-field="product"/>
> -        <call-class-method 
> class-name="org.apache.ofbiz.product.product.KeywordIndex" 
> method-name="forceIndexKeywords">
> -            <field field="product" 
> type="org.apache.ofbiz.entity.GenericValue"/>
> -        </call-class-method>
> -    </simple-method>
> -    <simple-method method-name="deleteProductKeywords" 
> short-description="delete all the keywords of a product">
> -        <entity-one entity-name="Product" value-field="product"/>
> -        <remove-related value-field="product" 
> relation-name="ProductKeyword"/>
> -    </simple-method>
> -
> -    <simple-method method-name="indexProductKeywords" 
> short-description="Index the Keywords for a Product" login-required="false">
> -        <!-- this service is meant to be called from an entity ECA for 
> entities that include a productId -->
> -        <!-- if it is the Product entity itself triggering this action, then 
> a [productInstance] parameter
> -            will be passed and we can save a few cycles looking that up -->
> -        <set from-field="parameters.productInstance" 
> field="productInstance"/>
> -        <if-empty field="productInstance">
> -            <set from-field="parameters.productId" 
> field="findProductMap.productId"/>
> -            <find-by-primary-key entity-name="Product" map="findProductMap" 
> value-field="productInstance"/>
> -        </if-empty>
> -
> -        <!-- induce keywords if autoCreateKeywords is emtpy or Y-->
> -        <if>
> -            <condition>
> -                <or>
> -                    <if-empty field="productInstance.autoCreateKeywords"/>
> -                    <if-compare field="productInstance.autoCreateKeywords" 
> operator="equals" value="Y"/>
> -                </or>
> -            </condition>
> -            <then>
> -                <call-class-method 
> class-name="org.apache.ofbiz.product.product.KeywordIndex" 
> method-name="indexKeywords">
> -                    <field field="productInstance" 
> type="org.apache.ofbiz.entity.GenericValue"/>
> -                </call-class-method>
> -            </then>
> -        </if>
> -    </simple-method>
> -
> -    <simple-method method-name="discontinueProductSales" 
> short-description="Discontinue Product Sales" login-required="false">
> -        <!-- set sales discontinuation date to now -->
> -        <now-timestamp field="nowTimestamp"/>
> -        <entity-one entity-name="Product" value-field="product"/>
> -        <set from-field="nowTimestamp" 
> field="product.salesDiscontinuationDate"/>
> -        <store-value value-field="product"/>
> -        <!-- expire product from all categories -->
> -        <get-related value-field="product" 
> relation-name="ProductCategoryMember" list="productCategoryMembers"/>
> -        <iterate list="productCategoryMembers" entry="productCategoryMember">
> -            <if-empty field="productCategoryMember.thruDate">
> -                <set from-field="nowTimestamp" 
> field="productCategoryMember.thruDate"/>
> -                <store-value value-field="productCategoryMember"/>
> -            </if-empty>
> -        </iterate>
> -        <!-- expire product from all associations going to it -->
> -        <get-related value-field="product" relation-name="AssocProductAssoc" 
> list="assocProductAssocs"/>
> -        <iterate list="assocProductAssocs" entry="assocProductAssoc">
> -            <if-empty field="assocProductAssoc.thruDate">
> -                <set from-field="nowTimestamp" 
> field="assocProductAssoc.thruDate"/>
> -                <store-value value-field="assocProductAssoc"/>
> -            </if-empty>
> -        </iterate>
> -    </simple-method>
> -
> -    <simple-method method-name="countProductView" short-description="Count 
> Product View" login-required="false">
> -        <if-empty field="parameters.weight">
> -            <calculate field="parameters.weight" type="Long"><number 
> value="1"/></calculate>
> -        </if-empty>
> -        <entity-one entity-name="ProductCalculatedInfo" 
> value-field="productCalculatedInfo"/>
> -        <if-empty field="productCalculatedInfo">
> -            <!-- go ahead and create it -->
> -            <make-value entity-name="ProductCalculatedInfo" 
> value-field="productCalculatedInfo"/>
> -            <set from-field="parameters.productId" 
> field="productCalculatedInfo.productId"/>
> -            <set from-field="parameters.weight" 
> field="productCalculatedInfo.totalTimesViewed"/>
> -            <create-value value-field="productCalculatedInfo"/>
> -        <else>
> -            <calculate field="productCalculatedInfo.totalTimesViewed" 
> type="Long">
> -                <calcop operator="add" 
> field="productCalculatedInfo.totalTimesViewed">
> -                    <calcop operator="get" 
> field="parameters.weight"></calcop>
> -                </calcop>
> -            </calculate>
> -            <store-value value-field="productCalculatedInfo"/>
> -        </else>
> -        </if-empty>
> -
> -        <!-- do the same for the virtual product... -->
> -        <entity-one entity-name="Product" value-field="product" 
> use-cache="true"/>
> -        <call-class-method 
> class-name="org.apache.ofbiz.product.product.ProductWorker" 
> method-name="getVariantVirtualId" ret-field="virtualProductId">
> -            <field field="product" type="GenericValue"/>
> -        </call-class-method>
> -        <if-not-empty field="virtualProductId">
> -            <set from-field="virtualProductId" field="callSubMap.productId"/>
> -            <set from-field="parameters.weight" field="callSubMap.weight"/>
> -            <call-service service-name="countProductView" 
> in-map-name="callSubMap"></call-service>
> -        </if-not-empty>
> -    </simple-method>
> -    
> -    <simple-method method-name="createProductReview" 
> short-description="Create a ProductReview" login-required="false">
> -        <make-value entity-name="ProductReview" value-field="newEntity"/>
> -        <set-nonpk-fields map="parameters" value-field="newEntity"/>
> -        <set from-field="userLogin.userLoginId" 
> field="newEntity.userLoginId"/>
> -        <set value="PRR_PENDING" field="newEntity.statusId"/>
> -
> -        <!-- code to check for auto-approved reviews (store setting) -->
> -        <entity-one entity-name="ProductStore" value-field="productStore"/>
> -
> -        <if-not-empty field="productStore">
> -            <if-compare field="productStore.autoApproveReviews" 
> operator="equals" value="Y">
> -                <set value="PRR_APPROVED" field="newEntity.statusId"/>
> -            </if-compare>
> -        </if-not-empty>
> -
> -        <!-- auto approve the review if it is just a rating and has no 
> review text -->
> -        <if-empty field="parameters.productReview">
> -            <set value="PRR_APPROVED" field="newEntity.statusId"/>
> -        </if-empty>
> -
> -        <!-- create the new ProductReview -->
> -        <sequenced-id sequence-name="ProductReview" 
> field="newEntity.productReviewId"/>
> -        <field-to-result field="newEntity.productReviewId" 
> result-name="productReviewId"/>
> -
> -        <if-empty field="newEntity.postedDateTime">
> -            <now-timestamp field="newEntity.postedDateTime"/>
> -        </if-empty>
> -
> -        <create-value value-field="newEntity"/>
> -
> -        <set from-field="newEntity.productId" field="productId"/>
> -        <property-to-field resource="ProductUiLabels" 
> property="ProductCreateProductReviewSuccess" field="successMessage"/>
> -        <call-simple-method method-name="updateProductWithReviewRatingAvg"/>
> -    </simple-method>
> -    <simple-method method-name="updateProductReview" 
> short-description="Update ProductReview">
> -        <set value="updateProductReview" field="callingMethodName"/>
> -        <set value="UPDATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <make-value entity-name="ProductReview" value-field="lookupPKMap"/>
> -        <set-pk-fields map="parameters" value-field="lookupPKMap"/>
> -        <find-by-primary-key map="lookupPKMap" value-field="lookedUpValue"/>
> -        <set-nonpk-fields map="parameters" value-field="lookedUpValue"/>
> -        <store-value value-field="lookedUpValue"/>
> -
> -        <set from-field="lookedUpValue.productId" field="productId"/>
> -        <call-simple-method method-name="updateProductWithReviewRatingAvg"/>
> -    </simple-method>
> -    <simple-method method-name="updateProductWithReviewRatingAvg" 
> short-description="Update Product with new Review Rating Avg" 
> login-required="false">
> -        <!-- this method is meant to be called in-line and depends in a 
> productId parameter -->
> -        <call-class-method 
> class-name="org.apache.ofbiz.product.product.ProductWorker" 
> method-name="getAverageProductRating" ret-field="averageCustomerRating">
> -            <field field="delegator" 
> type="org.apache.ofbiz.entity.Delegator"/>
> -            <field field="productId" type="java.lang.String"/>
> -        </call-class-method>
> -        <log level="info" message="Got new average customer rating 
> ${averageCustomerRating}"/>
> -        <if-compare field="averageCustomerRating" operator="equals" 
> value="0" type="BigDecimal">
> -            <return/>
> -        </if-compare>
> -        <!-- update the review average on the ProductCalculatedInfo entity 
> -->
> -        <entity-one entity-name="ProductCalculatedInfo" 
> value-field="productCalculatedInfo"/>
> -        <if-empty field="productCalculatedInfo">
> -            <!-- go ahead and create it -->
> -            <make-value entity-name="ProductCalculatedInfo" 
> value-field="productCalculatedInfo"/>
> -            <set from-field="productId" 
> field="productCalculatedInfo.productId"/>
> -            <set from-field="averageCustomerRating" 
> field="productCalculatedInfo.averageCustomerRating"/>
> -            <create-value value-field="productCalculatedInfo"/>
> -        <else>
> -            <set from-field="averageCustomerRating" 
> field="productCalculatedInfo.averageCustomerRating"/>
> -            <store-value value-field="productCalculatedInfo"/>
> -        </else>
> -        </if-empty>
> -    </simple-method>
> -    <simple-method method-name="copyToProductVariants" 
> short-description="Updates the Product's Variants">
> -        <set value="copyToProductVariants" field="callingMethodName"/>
> -        <set value="CREATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <set value="DELETE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <set from-field="parameters.virtualProductId" 
> field="productFindContext.productId"/>
> -        <find-by-primary-key entity-name="Product" map="productFindContext" 
> value-field="oldProduct"/>
> -
> -        <set from-field="parameters.virtualProductId" 
> field="variantsFindContext.productId"/>
> -        <set value="PRODUCT_VARIANT" 
> field="variantsFindContext.productAssocTypeId"/>
> -        <find-by-and entity-name="ProductAssoc" map="variantsFindContext" 
> list="variants"/>
> -        <filter-list-by-date list="variants"/>
> -        <iterate list="variants" entry="newProduct">
> -            <set from-field="newProduct.productIdTo" 
> field="productVariantContext.productId"/>
> -            <!-- if requested, duplicate related data -->
> -            <if-not-empty field="parameters.duplicatePrices">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="ProductPrice" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="ProductPrice" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -            <if-not-empty field="parameters.duplicateIDs">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="GoodIdentification" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="GoodIdentification" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -            <if-not-empty field="parameters.duplicateContent">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="ProductContent" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="ProductContent" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -            <if-not-empty field="parameters.duplicateCategoryMembers">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="ProductCategoryMember" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="ProductCategoryMember" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -            <if-not-empty field="parameters.duplicateAttributes">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="ProductAttribute" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="ProductAttribute" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -            <if-not-empty field="parameters.duplicateFacilities">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="ProductFacility" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="ProductFacility" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -            <if-not-empty field="parameters.duplicateLocations">
> -                <if-not-empty field="parameters.removeBefore">
> -                    <find-by-and entity-name="ProductFacilityLocation" 
> map="productVariantContext" list="foundVariantValues"/>
> -                    <iterate list="foundVariantValues" 
> entry="foundVariantValue">
> -                        <remove-value value-field="foundVariantValue"/>
> -                    </iterate>
> -                </if-not-empty>
> -                <find-by-and entity-name="ProductFacilityLocation" 
> map="productFindContext" list="foundValues"/>
> -                <iterate list="foundValues" entry="foundValue">
> -                    <clone-value value-field="foundValue" 
> new-value-field="newTempValue"/>
> -                    <set from-field="newProduct.productIdTo" 
> field="newTempValue.productId"/>
> -                    <create-value value-field="newTempValue"/>
> -                </iterate>
> -            </if-not-empty>
> -        </iterate>
> -    </simple-method>
> -
> -    <!-- a method to centralize product security code, meant to be called 
> in-line with
> -        call-simple-method, and the checkAction and callingMethodName 
> attributes should be in the method context -->
> -    <simple-method method-name="checkProductRelatedPermission" 
> short-description="Check Product Related Permission">
> -        <if-empty field="callingMethodName">
> -            <property-to-field resource="CommonUiLabels" 
> property="CommonPermissionThisOperation" field="callingMethodName"/>
> -        </if-empty>
> -        <if-empty field="checkAction">
> -            <set value="UPDATE" field="checkAction"/>
> -        </if-empty>
> -
> -        <!-- find all role-categories that this product is a member of -->
> -        <if>
> -            <condition>
> -                <not><if-has-permission permission="CATALOG" 
> action="_${checkAction}"/></not>
> -            </condition>
> -            <then>
> -                <set from-field="parameters.productId" 
> field="lookupRoleCategoriesMap.productId"/>
> -                <set from-field="userLogin.partyId" 
> field="lookupRoleCategoriesMap.partyId"/>
> -                <set value="LTD_ADMIN" 
> field="lookupRoleCategoriesMap.roleTypeId"/>
> -                <find-by-and entity-name="ProductCategoryMemberAndRole" 
> map="lookupRoleCategoriesMap" list="roleCategories"/>
> -                <filter-list-by-date list="roleCategories"/>
> -                <filter-list-by-date list="roleCategories" 
> from-field-name="roleFromDate" thru-field-name="roleThruDate"/>
> -            </then>
> -        </if>
> -        <if>
> -            <condition>
> -                <not>
> -                    <or>
> -                        <if-has-permission permission="CATALOG" 
> action="_${checkAction}"/>
> -                        <and>
> -                            <if-has-permission permission="CATALOG_ROLE" 
> action="_${checkAction}"/>
> -                            <not><if-empty field="roleCategories"/></not>
> -                        </and>
> -                        <and>
> -                            <not><if-empty 
> field="alternatePermissionRoot"/></not>
> -                            <if-has-permission 
> permission="${alternatePermissionRoot}" action="_${checkAction}"/>
> -                        </and>
> -                    </or>
> -                </not>
> -            </condition>
> -            <then>
> -                <set field="checkActionLabel" value="${groovy: 
> 'ProductCatalog' + checkAction.charAt(0) + 
> checkAction.substring(1).toLowerCase() + 'PermissionError'}"/>
> -                <set field="resourceDescription" 
> from-field="callingMethodName"/>
> -                <add-error>
> -                    <fail-property resource="ProductUiLabels" 
> property="${checkActionLabel}"/>
> -                </add-error>
> -            </then>
> -        </if>
> -    </simple-method>
> -    <simple-method method-name="productGenericPermission" 
> short-description="Main permission logic">
> -        <set field="mainAction" from-field="parameters.mainAction"/>
> -        <if-empty field="mainAction">
> -            <add-error>
> -                <fail-property resource="ProductUiLabels" 
> property="ProductMissingMainActionInPermissionService"/>
> -            </add-error>
> -            <check-errors/>
> -        </if-empty>
> -
> -        <set field="callingMethodName" 
> from-field="parameters.resourceDescription"/>
> -        <set field="checkAction" from-field="parameters.mainAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -
> -        <if-empty field="error_list">
> -            <set field="hasPermission" type="Boolean" value="true"/>
> -            <field-to-result field="hasPermission"/>
> -
> -            <else>
> -                <property-to-field resource="ProductUiLabels" 
> property="ProductPermissionError" field="failMessage"/>
> -                <set field="hasPermission" type="Boolean" value="false"/>
> -                <field-to-result field="hasPermission"/>
> -                <field-to-result field="failMessage"/>
> -            </else>
> -        </if-empty>
> -    </simple-method>
> -    <simple-method method-name="productPriceGenericPermission" 
> short-description="product price permission logic">
> -        <set field="mainAction" from-field="parameters.mainAction"/>
> -        <if-empty field="mainAction">
> -            <add-error>
> -                <fail-property resource="ProductUiLabels" 
> property="ProductMissingMainActionInPermissionService"/>
> -            </add-error>
> -            <check-errors/>
> -        </if-empty>
> -        <check-permission permission="CATALOG_PRICE_MAINT">
> -            <fail-property resource="ProductUiLabels" 
> property="ProductPriceMaintPermissionError"/>
> -        </check-permission>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <if-empty field="error_list">
> -            <set field="hasPermission" type="Boolean" value="true"/>
> -            <field-to-result field="hasPermission"/>
> -            <else>
> -                <property-to-field resource="ProductUiLabels" 
> property="ProductPermissionError" field="failMessage"/>
> -                <set field="hasPermission" type="Boolean" value="false"/>
> -                <field-to-result field="hasPermission"/>
> -                <field-to-result field="failMessage"/>
> -            </else>
> -        </if-empty>
> -    </simple-method>
> -
> -    <!-- ================================================================ -->
> -    <!-- ProductRole Services -->
> -    <!-- ================================================================ -->
> -
> -    <simple-method method-name="addPartyToProduct" short-description="Add 
> Party to Product">
> -        <set value="addPartyToProduct" field="callingMethodName"/>
> -        <set value="CREATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <make-value entity-name="ProductRole" value-field="newEntity"/>
> -        <set-pk-fields map="parameters" value-field="newEntity"/>
> -        <set-nonpk-fields map="parameters" value-field="newEntity"/>
> -
> -        <if-empty field="newEntity.fromDate">
> -            <now-timestamp field="newEntity.fromDate"/>
> -        </if-empty>
> -
> -        <create-value value-field="newEntity"/>
> -    </simple-method>
> -    <simple-method method-name="updatePartyToProduct" 
> short-description="Update Party to Product">
> -        <set value="updatePartyToProduct" field="callingMethodName"/>
> -        <set value="UPDATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <make-value entity-name="ProductRole" value-field="lookupPKMap"/>
> -        <set-pk-fields map="parameters" value-field="lookupPKMap"/>
> -        <find-by-primary-key entity-name="ProductRole" map="lookupPKMap" 
> value-field="lookedUpValue"/>
> -        <set-nonpk-fields map="parameters" value-field="lookedUpValue"/>
> -        <store-value value-field="lookedUpValue"/>
> -    </simple-method>
> -    <simple-method method-name="removePartyFromProduct" 
> short-description="Remove Party From Product">
> -        <set value="removePartyFromProduct" field="callingMethodName"/>
> -        <set value="DELETE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <make-value entity-name="ProductRole" value-field="lookupPKMap"/>
> -        <set-pk-fields map="parameters" value-field="lookupPKMap"/>
> -        <find-by-primary-key entity-name="ProductRole" map="lookupPKMap" 
> value-field="lookedUpValue"/>
> -        <remove-value value-field="lookedUpValue"/>
> -    </simple-method>
> -
> -    <!-- ProductCategoryGlAccount methods -->
> -    <simple-method method-name="createProductCategoryGlAccount" 
> short-description="Create a ProductCategoryGlAccount">
> -        <set value="createProductCategoryGlAccount" 
> field="callingMethodName"/>
> -        <set value="CREATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <make-value entity-name="ProductCategoryGlAccount" 
> value-field="newEntity"/>
> -        <set-nonpk-fields map="parameters" value-field="newEntity"/>
> -        <set-pk-fields map="parameters" value-field="newEntity"/>
> -        <create-value value-field="newEntity"/>
> -    </simple-method>
> -
> -    <simple-method method-name="updateProductCategoryGlAccount" 
> short-description="Update a ProductCategoryGlAccount">
> -        <set value="updateProductCategoryGlAccount" 
> field="callingMethodName"/>
> -        <set value="UPDATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <entity-one entity-name="ProductCategoryGlAccount" 
> value-field="lookedUpValue"/>
> -        <set-nonpk-fields map="parameters" value-field="lookedUpValue"/>
> -        <store-value value-field="lookedUpValue"/>
> -    </simple-method>
> -
> -    <simple-method method-name="deleteProductCategoryGlAccount" 
> short-description="Delete a ProductCategoryGlAccount">
> -        <set value="deleteProductCategoryGlAccount" 
> field="callingMethodName"/>
> -        <set value="DELETE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -
> -        <entity-one entity-name="ProductCategoryGlAccount" 
> value-field="lookedUpValue"/>
> -        <remove-value value-field="lookedUpValue"/>
> -    </simple-method>
> -
> -    <!-- Product GroupOrder Services -->
> -    <simple-method method-name="createProductGroupOrder" 
> short-description="Create ProductGroupOrder">
> -        <make-value entity-name="ProductGroupOrder" value-field="newEntity"/>
> -        <make-next-seq-id value-field="newEntity" 
> seq-field-name="groupOrderId"/>
> -        <field-to-result field="newEntity.groupOrderId" 
> result-name="groupOrderId"/>
> -        <set-nonpk-fields map="parameters" value-field="newEntity"/>
> -        <create-value value-field="newEntity"/>
> -    </simple-method>
> -
> -    <simple-method method-name="updateProductGroupOrder" 
> short-description="Update ProductGroupOrder">
> -        <entity-one entity-name="ProductGroupOrder" 
> value-field="productGroupOrder"/>
> -        <set-nonpk-fields map="parameters" value-field="productGroupOrder"/>
> -        <store-value value-field="productGroupOrder"/>
> -        
> -        <if-compare field="productGroupOrder.statusId" operator="equals" 
> value="GO_CREATED">
> -            <entity-one entity-name="JobSandbox" value-field="jobSandbox">
> -                <field-map field-name="jobId" 
> from-field="productGroupOrder.jobId"/>
> -            </entity-one>
> -            <if-not-empty field="jobSandbox">
> -                <set field="jobSandbox.runTime" 
> from-field="parameters.thruDate"/>
> -                <store-value value-field="jobSandbox"/>
> -            </if-not-empty>
> -        </if-compare>
> -    </simple-method>
> -
> -    <simple-method method-name="deleteProductGroupOrder" 
> short-description="Delete ProductGroupOrder">
> -        <entity-and entity-name="OrderItemGroupOrder" 
> list="orderItemGroupOrders">
> -            <field-map field-name="groupOrderId" 
> from-field="parameters.groupOrderId"/>
> -        </entity-and>
> -        <iterate list="orderItemGroupOrders" entry="orderItemGroupOrder">
> -            <remove-value value-field="orderItemGroupOrder"/>
> -        </iterate>
> -        
> -        <entity-one entity-name="ProductGroupOrder" 
> value-field="productGroupOrder"/>
> -        <remove-value value-field="productGroupOrder"/>
> -        
> -        <entity-one entity-name="JobSandbox" value-field="jobSandbox">
> -            <field-map field-name="jobId" 
> from-field="productGroupOrder.jobId"/>
> -        </entity-one>
> -        <remove-value value-field="jobSandbox"/>
> -        
> -        <entity-and entity-name="JobSandbox" list="jobSandboxList">
> -            <field-map field-name="runtimeDataId" 
> from-field="jobSandbox.runtimeDataId"/>
> -        </entity-and>
> -        <iterate list="jobSandboxList" entry="jobSandboxRelatedRuntimeData">
> -            <remove-value value-field="jobSandboxRelatedRuntimeData"/>
> -        </iterate>
> -        
> -        <entity-one entity-name="RuntimeData" value-field="runtimeData">
> -            <field-map field-name="runtimeDataId" 
> from-field="jobSandbox.runtimeDataId"/>
> -        </entity-one>
> -        <remove-value value-field="runtimeData"/>
> -    </simple-method>
> -
> -    <simple-method method-name="createJobForProductGroupOrder" 
> short-description="Create ProductGroupOrder">
> -        <entity-one entity-name="ProductGroupOrder" 
> value-field="productGroupOrder"/>
> -        <if-empty field="productGroupOrder.jobId">
> -            <!-- Create RuntimeData For ProductGroupOrder -->
> -            <set field="runtimeDataMap.groupOrderId" 
> from-field="parameters.groupOrderId"/>
> -            <call-class-method 
> class-name="org.apache.ofbiz.entity.serialize.XmlSerializer" 
> method-name="serialize"  ret-field="runtimeInfo">
> -                <field field="runtimeDataMap" type="Object"/>
> -            </call-class-method>
> -            <make-value entity-name="RuntimeData" value-field="runtimeData"/>
> -            <sequenced-id sequence-name="RuntimeData" 
> field="runtimeData.runtimeDataId"/>
> -            <set field="runtimeDataId" 
> from-field="runtimeData.runtimeDataId"/>
> -            <set field="runtimeData.runtimeInfo" from-field="runtimeInfo"/>
> -            <create-value value-field="runtimeData"/>
> -
> -             <!-- Create Job For ProductGroupOrder -->
> -             <!-- FIXME: Jobs should not be manually created -->
> -            <make-value entity-name="JobSandbox" value-field="jobSandbox"/>
> -            <sequenced-id sequence-name="JobSandbox" 
> field="jobSandbox.jobId"/>
> -            <set field="jobId" from-field="jobSandbox.jobId"/>
> -            <set field="jobSandbox.jobName" value="Check ProductGroupOrder 
> Expired"/>
> -            <set field="jobSandbox.runTime" 
> from-field="parameters.thruDate"/>
> -            <set field="jobSandbox.poolId" value="pool"/>
> -            <set field="jobSandbox.statusId" value="SERVICE_PENDING"/>
> -            <set field="jobSandbox.serviceName" 
> value="checkProductGroupOrderExpired"/>
> -            <set field="jobSandbox.runAsUser" value="system"/>
> -            <set field="jobSandbox.runtimeDataId" 
> from-field="runtimeDataId"/>
> -            <set field="jobSandbox.maxRecurrenceCount" value="1" 
> type="Long"/>
> -            <set field="jobSandbox.priority" value="50" type="Long"/>
> -            <create-value value-field="jobSandbox"/>
> -
> -            <set field="productGroupOrder.jobId" from-field="jobId"/>
> -            <store-value value-field="productGroupOrder"/>
> -        </if-empty>
> -    </simple-method>
> -
> -    <simple-method method-name="checkOrderItemForProductGroupOrder" 
> short-description="Check OrderItem For ProductGroupOrder">
> -        <entity-and entity-name="OrderItem" list="orderItems">
> -            <field-map field-name="orderId" from-field="parameters.orderId"/>
> -        </entity-and>
> -        <iterate list="orderItems" entry="orderItem">
> -            <set field="productId" from-field="orderItem.productId"/>
> -            <entity-one entity-name="Product" value-field="product">
> -                <field-map field-name="productId" 
> from-field="orderItem.productId"/>
> -            </entity-one>
> -            <if-compare field="product.isVariant" operator="equals" 
> value="Y">
> -                <entity-and entity-name="ProductAssoc" 
> list="variantProductAssocs" filter-by-date="true">
> -                    <field-map field-name="productIdTo" 
> from-field="orderItem.productId"/>
> -                    <field-map field-name="productAssocTypeId" 
> value="PRODUCT_VARIANT"/>
> -                </entity-and>
> -                <first-from-list list="variantProductAssocs" 
> entry="variantProductAssoc"/>
> -                <set field="productId" 
> from-field="variantProductAssoc.productId"/>
> -            </if-compare>
> -
> -            <entity-and entity-name="ProductGroupOrder" 
> list="productGroupOrders" filter-by-date="true">
> -                <field-map field-name="productId" from-field="productId"/>
> -            </entity-and>
> -            <if-not-empty field="productGroupOrders">
> -                <first-from-list list="productGroupOrders" 
> entry="productGroupOrder"/>
> -                <calculate field="productGroupOrder.soldOrderQty">
> -                    <calcop operator="add" 
> field="productGroupOrder.soldOrderQty">
> -                        <calcop operator="get" field="orderItem.quantity"/>
> -                    </calcop>
> -                </calculate>
> -                <store-value value-field="productGroupOrder"/>
> -                
> -                <set field="createOrderItemGroupOrderMap.orderId" 
> from-field="orderItem.orderId"/>
> -                <set field="createOrderItemGroupOrderMap.orderItemSeqId" 
> from-field="orderItem.orderItemSeqId"/>
> -                <set field="createOrderItemGroupOrderMap.groupOrderId" 
> from-field="productGroupOrder.groupOrderId"/>
> -                <call-service service-name="createOrderItemGroupOrder" 
> in-map-name="createOrderItemGroupOrderMap"/>
> -            </if-not-empty>
> -        </iterate>
> -    </simple-method>
> -    
> -    <simple-method method-name="cancleOrderItemGroupOrder" 
> short-description="Cancle OrderItemGroupOrder">
> -        <if-not-empty field="parameters.orderItemSeqId">
> -            <entity-and entity-name="OrderItem" list="orderItems">
> -                <field-map field-name="orderId" 
> from-field="parameters.orderId"/>
> -                <field-map field-name="orderItemSeqId" 
> from-field="parameters.orderItemSeqId" />
> -            </entity-and>
> -        <else>
> -            <entity-and entity-name="OrderItem" list="orderItems">
> -                <field-map field-name="orderId" 
> from-field="parameters.orderId"/>
> -            </entity-and>
> -        </else>
> -        </if-not-empty>
> -        <iterate list="orderItems" entry="orderItem">
> -            <entity-and entity-name="OrderItemGroupOrder" 
> list="orderItemGroupOrders">
> -                <field-map field-name="orderId" 
> from-field="orderItem.orderId"/>
> -                <field-map field-name="orderItemSeqId" 
> from-field="orderItem.orderItemSeqId"/>
> -            </entity-and>
> -            <if-not-empty field="orderItemGroupOrders">
> -                <first-from-list list="orderItemGroupOrders" 
> entry="orderItemGroupOrder"/>
> -                <entity-one entity-name="ProductGroupOrder" 
> value-field="productGroupOrder">
> -                    <field-map field-name="groupOrderId" 
> from-field="orderItemGroupOrder.groupOrderId"/>
> -                </entity-one>
> -                <if-not-empty field="productGroupOrder">
> -                    <if-compare field="productGroupOrder.statusId" 
> operator="equals" value="GO_CREATED">
> -                        <if-compare field="orderItem.statusId" 
> operator="equals" value="ITEM_CANCELLED">
> -                            <if-not-empty field="orderItem.cancelQuantity">
> -                                <set field="cancelQuantity" 
> from-field="orderItem.cancelQuantity"/>
> -                            <else>
> -                                <set field="cancelQuantity" 
> from-field="orderItem.quantity"/>
> -                            </else>
> -                            </if-not-empty>
> -                            <calculate 
> field="productGroupOrder.soldOrderQty">
> -                                <calcop operator="subtract" 
> field="productGroupOrder.soldOrderQty">
> -                                    <calcop operator="get" 
> field="cancelQuantity"/>
> -                                </calcop>
> -                            </calculate>
> -                        </if-compare>
> -                        <store-value value-field="productGroupOrder"/>
> -                        <remove-value value-field="orderItemGroupOrder"/>
> -                    </if-compare>
> -                </if-not-empty>
> -            </if-not-empty>
> -        </iterate>
> -    </simple-method>
> -    
> -    <simple-method method-name="checkProductGroupOrderExpired" 
> short-description="Check ProductGroupOrder Expired">
> -        <entity-one entity-name="ProductGroupOrder" 
> value-field="productGroupOrder"/>
> -        <if-not-empty field="productGroupOrder">
> -            <if-compare field="productGroupOrder.soldOrderQty" 
> operator="greater-equals" value="${productGroupOrder.reqOrderQty}">
> -                <set field="newItemStatusId" value="ITEM_APPROVED"/>
> -                <set field="groupOrderStatusId" value="GO_SUCCESS"/>
> -            <else>
> -                <set field="newItemStatusId" value="ITEM_CANCELLED"/>
> -                <set field="groupOrderStatusId" value="GO_CANCELLED"/>
> -            </else>
> -            </if-compare>
> -            
> -            <set field="updateProductGroupOrderMap.groupOrderId" 
> from-field="productGroupOrder.groupOrderId"/>
> -            <set field="updateProductGroupOrderMap.statusId" 
> from-field="groupOrderStatusId"/>
> -            <call-service service-name="updateProductGroupOrder" 
> in-map-name="updateProductGroupOrderMap"/>
> -            
> -            <entity-and entity-name="OrderItemGroupOrder" 
> list="orderItemGroupOrders">
> -                <field-map field-name="groupOrderId" 
> from-field="productGroupOrder.groupOrderId"/>
> -            </entity-and>
> -            <iterate list="orderItemGroupOrders" entry="orderItemGroupOrder">
> -                <set field="changeOrderItemStatusMap.orderId" 
> from-field="orderItemGroupOrder.orderId"/>
> -                <set field="changeOrderItemStatusMap.orderItemSeqId" 
> from-field="orderItemGroupOrder.orderItemSeqId"/>
> -                <set field="changeOrderItemStatusMap.statusId" 
> from-field="newItemStatusId"/>
> -                <call-service service-name="changeOrderItemStatus" 
> in-map-name="changeOrderItemStatusMap"/>
> -            </iterate>
> -        </if-not-empty>
> -    </simple-method>
> -    
> -    <simple-method method-name="setProductReviewStatus" 
> short-description="change the product review Status">
> -        <set value="setProductReviewStatus" field="callingMethodName"/>
> -        <set value="UPDATE" field="checkAction"/>
> -        <call-simple-method method-name="checkProductRelatedPermission"/>
> -        <check-errors/>
> -        
> -        <entity-one entity-name="ProductReview" value-field="productReview"/>
> -        <if-not-empty field="productReview">
> -            <if-compare-field field="productReview.statusId" 
> to-field="parameters.statusId" operator="not-equals">
> -                <entity-one entity-name="StatusValidChange" 
> value-field="statusChange">
> -                    <field-map field-name="statusId" 
> from-field="productReview.statusId"/>
> -                    <field-map field-name="statusIdTo" 
> from-field="parameters.statusId"/>
> -                </entity-one>
> -                <if-empty field="statusChange">
> -                    <set field="msg" value="Status is not a valid change: 
> from ${productReview.statusId} to ${parameters.statusId}"/>
> -                    <log level="error" message="${msg}"/>
> -                    <add-error>
> -                        <fail-property resource="ProductErrorUiLabels" 
> property="ProductReviewErrorCouldNotChangeOrderStatusFromTo"/>
> -                    </add-error>
> -                </if-empty>
> -            </if-compare-field>
> -        </if-not-empty>
> -        <check-errors/>
> -        
> -        <set field="productReview.statusId" 
> from-field="parameters.statusId"/>
> -        <store-value value-field="productReview"/>
> -        <field-to-result field="productReview.productReviewId" 
> result-name="productReviewId"/>
> -    </simple-method>
> -</simple-methods>
> diff --git a/applications/product/servicedef/services.xml 
> b/applications/product/servicedef/services.xml
> index 4e821b4..b44cba7 100644
> --- a/applications/product/servicedef/services.xml
> +++ b/applications/product/servicedef/services.xml
> @@ -37,22 +37,22 @@ under the License.
>          <override name="description" allow-html="safe"/>
>          <override name="longDescription" allow-html="safe"/>
>      </service>
> -    <service name="createProduct" default-entity-name="Product" 
> engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="createProduct" auth="true">
> +    <service name="createProduct" default-entity-name="Product" 
> engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="createProduct" auth="true">
>          <description>Create a Product</description>
>          <implements service="interfaceProduct"/>
>          <auto-attributes include="pk" mode="INOUT" optional="true"/>
>          <override name="productTypeId" optional="false"/>
>          <override name="internalName" optional="false"/>
>      </service>
> -    <service name="updateProduct" default-entity-name="Product" 
> engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="updateProduct" auth="true">
> +    <service name="updateProduct" default-entity-name="Product" 
> engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="updateProduct" auth="true">
>          <description>Update a Product</description>
>          <implements service="interfaceProduct"/>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>      </service>
> -    <service name="updateProductQuickAdminName" 
> default-entity-name="Product" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="updateProductQuickAdminName" auth="true">
> +    <service name="updateProductQuickAdminName" 
> default-entity-name="Product" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="updateProductQuickAdminName" auth="true">
>          <description>Update a Product from Quick Admin</description>
>          <implements service="interfaceProduct"/>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
> @@ -63,8 +63,8 @@ under the License.
>          <implements service="interfaceProduct"/>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>      </service>
> -    <service name="duplicateProduct" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="duplicateProduct" auth="true">
> +    <service name="duplicateProduct" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="duplicateProduct" auth="true">
>          <description>Duplicate a Product using a new productId</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="oldProductId" type="String" mode="IN" 
> optional="false"/>
> @@ -89,8 +89,8 @@ under the License.
>          <attribute name="removeFeatureAppls" type="String" mode="IN" 
> optional="true"/>
>          <attribute name="removeInventoryItems" type="String" mode="IN" 
> optional="true"/>
>      </service>
> -    <service name="copyToProductVariants" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="copyToProductVariants" auth="true">
> +    <service name="copyToProductVariants" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="copyToProductVariants" auth="true">
>          <description>Copy Virtual Product's data to the Variant 
> Products</description>
>          <attribute name="virtualProductId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="removeBefore" type="String" mode="IN" 
> optional="true"/>
> @@ -151,40 +151,40 @@ under the License.
>          <permission-service service-name="productGenericPermission" 
> main-action="DELETE"/>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>      </service>
> -    <service name="deleteProductKeywords" engine="simple"
> -            
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="deleteProductKeywords" auth="true">
> +    <service name="deleteProductKeywords" engine="groovy"
> +            
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="deleteProductKeywords" auth="true">
>          <description>Delete all the keywords of a product</description>
>          <permission-service service-name="productGenericPermission" 
> main-action="DELETE"/>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>      </service>
> -    <service name="indexProductKeywords" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="indexProductKeywords" auth="false">
> +    <service name="indexProductKeywords" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="indexProductKeywords" auth="false">
>          <description>Index the Keywords for a Product</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="productInstance" 
> type="org.apache.ofbiz.entity.GenericValue" mode="IN" optional="true"/>
>      </service>
> -    <service name="forceIndexProductKeywords" engine="simple"
> -            
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="forceIndexProductKeywords" auth="true">
> +    <service name="forceIndexProductKeywords" engine="groovy"
> +            
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="forceIndexProductKeywords" auth="true">
>          <description>Induce all the keywords of a product, ignoring the flag 
> in the Product.autoCreateKeywords flag</description>
>          <permission-service service-name="productGenericPermission" 
> main-action="CREATE"/>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>      </service>
>  
> -    <service name="discontinueProductSales" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="discontinueProductSales" auth="false">
> +    <service name="discontinueProductSales" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="discontinueProductSales" auth="false">
>          <description>Discontinue Product Sales</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>      </service>
>  
> -    <service name="countProductView" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="countProductView" auth="false">
> +    <service name="countProductView" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="countProductView" auth="false">
>          <description>count Product View</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="weight" type="Long" mode="IN" optional="true"/>
>      </service>
>  
> -    <service name="createProductReview" engine="simple"
> -            
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="createProductReview" auth="true">
> +    <service name="createProductReview" engine="groovy"
> +            
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="createProductReview" auth="true">
>          <description>Create a product review entity</description>
>          <auto-attributes entity-name="ProductReview" mode="IN" 
> include="nonpk" optional="true"/>
>          <attribute name="productReviewId" type="String" mode="OUT" 
> optional="false"/>
> @@ -192,8 +192,8 @@ under the License.
>          <override name="productId" optional="false"/>
>          <override name="productRating" optional="false"/>
>      </service>
> -    <service name="updateProductReview" engine="simple" 
> default-entity-name="ProductReview"
> -            
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="updateProductReview" auth="true">
> +    <service name="updateProductReview" engine="groovy" 
> default-entity-name="ProductReview"
> +            
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="updateProductReview" auth="true">
>          <description>Updates a product review record</description>
>          <required-permissions join-type="OR">
>              <check-permission permission="CATALOG_UPDATE"/>
> @@ -202,8 +202,8 @@ under the License.
>          <auto-attributes mode="IN" include="pk" optional="false"/>
>          <auto-attributes mode="IN" include="nonpk" optional="true"/>
>      </service>
> -    <service name="setProductReviewStatus" engine="simple"
> -            
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="setProductReviewStatus" auth="true">
> +    <service name="setProductReviewStatus" engine="groovy"
> +            
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="setProductReviewStatus" auth="true">
>          <description>Updates a product review record</description>
>          <required-permissions join-type="OR">
>              <check-permission permission="CATALOG_UPDATE"/>
> @@ -784,8 +784,8 @@ under the License.
>          <attribute name="fromDate" type="Timestamp" mode="IN" 
> optional="false"/>
>      </service>
>  
> -    <service name="addPartyToProduct" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="addPartyToProduct" auth="true">
> +    <service name="addPartyToProduct" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="addPartyToProduct" auth="true">
>          <description>Add Party To Product</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="partyId" type="String" mode="IN" optional="false"/>
> @@ -795,8 +795,8 @@ under the License.
>          <attribute name="sequenceNum" type="Long" mode="IN" optional="true"/>
>          <attribute name="comments" type="String" mode="IN" optional="true"/>
>      </service>
> -    <service name="updatePartyToProduct" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="updatePartyToProduct" auth="true">
> +    <service name="updatePartyToProduct" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="updatePartyToProduct" auth="true">
>          <description>Update Party To Product</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="partyId" type="String" mode="IN" optional="false"/>
> @@ -806,8 +806,8 @@ under the License.
>          <attribute name="sequenceNum" type="Long" mode="IN" optional="true"/>
>          <attribute name="comments" type="String" mode="IN" optional="true"/>
>      </service>
> -    <service name="removePartyFromProduct" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="removePartyFromProduct" auth="true">
> +    <service name="removePartyFromProduct" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="removePartyFromProduct" auth="true">
>          <description>Remove Party From Product</description>
>          <attribute name="productId" type="String" mode="IN" 
> optional="false"/>
>          <attribute name="partyId" type="String" mode="IN" optional="false"/>
> @@ -1240,16 +1240,16 @@ under the License.
>      </service>
>  
>      <!-- Permission Services -->
> -    <service name="productGenericPermission" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="productGenericPermission">
> +    <service name="productGenericPermission" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="productGenericPermission">
>          <implements service="permissionInterface"/>
>      </service>
>      <service name="productCategoryGenericPermission" engine="groovy"
>          
> location="component://product/groovyScripts/product/category/CategoryServices.groovy"
>  invoke="productCategoryGenericPermission">
>          <implements service="permissionInterface"/>
>      </service>
> -    <service name="productPriceGenericPermission" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="productPriceGenericPermission">
> +    <service name="productPriceGenericPermission" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="productPriceGenericPermission">
>          <implements service="permissionInterface"/>
>      </service>
>      <service name="checkCategoryPermissionWithViewPurchaseAllow" 
> engine="groovy"
> @@ -1285,20 +1285,20 @@ under the License.
>      </service>
>  
>      <!-- ProductCategoryGlAccount Services -->
> -    <service name="createProductCategoryGlAccount" 
> default-entity-name="ProductCategoryGlAccount" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="createProductCategoryGlAccount" auth="true">
> +    <service name="createProductCategoryGlAccount" 
> default-entity-name="ProductCategoryGlAccount" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="createProductCategoryGlAccount" auth="true">
>          <description>Create a ProductCategoryGlAccount</description>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>          <auto-attributes include="nonpk" mode="IN" optional="false"/>
>      </service>
> -    <service name="updateProductCategoryGlAccount" 
> default-entity-name="ProductCategoryGlAccount" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="updateProductCategoryGlAccount" auth="true">
> +    <service name="updateProductCategoryGlAccount" 
> default-entity-name="ProductCategoryGlAccount" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="updateProductCategoryGlAccount" auth="true">
>          <description>Update a ProductCategoryGlAccount</description>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>          <auto-attributes include="nonpk" mode="IN" optional="false"/>
>      </service>
> -    <service name="deleteProductCategoryGlAccount" 
> default-entity-name="ProductCategoryGlAccount" engine="simple"
> -                
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="deleteProductCategoryGlAccount" auth="true">
> +    <service name="deleteProductCategoryGlAccount" 
> default-entity-name="ProductCategoryGlAccount" engine="groovy"
> +                
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="deleteProductCategoryGlAccount" auth="true">
>          <description>Delete a ProductCategoryGlAccount</description>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>      </service>
> @@ -1608,48 +1608,48 @@ under the License.
>      </service>
>  
>      <!-- Product GroupOrder Services -->
> -    <service name="createProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="createProductGroupOrder" auth="true">
> +    <service name="createProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="createProductGroupOrder" auth="true">
>          <description>Create ProductGroupOrder</description>
>          <auto-attributes include="pk" mode="OUT" optional="false"/>
>          <auto-attributes include="nonpk" mode="IN" optional="true"/>
>      </service>
>  
> -    <service name="updateProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="updateProductGroupOrder" auth="true">
> +    <service name="updateProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="updateProductGroupOrder" auth="true">
>          <description>Update ProductGroupOrder</description>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>          <auto-attributes include="nonpk" mode="IN" optional="true"/>
>      </service>
>  
> -    <service name="deleteProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="deleteProductGroupOrder" auth="true">
> +    <service name="deleteProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="deleteProductGroupOrder" auth="true">
>          <description>Delete ProductGroupOrder</description>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>      </service>
>  
> -    <service name="createJobForProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="createJobForProductGroupOrder" auth="true">
> +    <service name="createJobForProductGroupOrder" 
> default-entity-name="ProductGroupOrder" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="createJobForProductGroupOrder" auth="true">
>          <description>Create Job For ProductGroupOrder</description>
>          <auto-attributes include="pk" mode="IN" optional="false"/>
>          <auto-attributes include="nonpk" mode="IN" optional="true"/>
>      </service>
>  
> -    <service name="checkOrderItemForProductGroupOrder" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="checkOrderItemForProductGroupOrder" auth="true">
> +    <service name="checkOrderItemForProductGroupOrder" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="checkOrderItemForProductGroupOrder" auth="true">
>          <description>Check OrderItem For ProductGroupOrder</description>
>          <attribute name="orderId" mode="IN" type="String" optional="false"/>
>      </service>
>  
> -    <service name="cancleOrderItemGroupOrder" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="cancleOrderItemGroupOrder" auth="true">
> +    <service name="cancleOrderItemGroupOrder" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="cancleOrderItemGroupOrder" auth="true">
>          <description>Cancle OrderItemGroupOrder</description>
>          <attribute name="orderId" mode="IN" type="String" optional="false"/>
>          <attribute name="orderItemSeqId" type="String" mode="IN" 
> optional="true"/>
>      </service>
>  
> -    <service name="checkProductGroupOrderExpired" engine="simple"
> -        
> location="component://product/minilang/product/product/ProductServices.xml" 
> invoke="checkProductGroupOrderExpired" auth="true">
> +    <service name="checkProductGroupOrderExpired" engine="groovy"
> +        
> location="component://product/groovyScripts/product/product/ProductServices.groovy"
>  invoke="checkProductGroupOrderExpired" auth="true">
>          <description>Check ProductGroupOrder Expired</description>
>          <attribute name="groupOrderId" mode="IN" type="String" 
> optional="false"/>
>      </service>
>

Attachment: pEpkey.asc
Description: application/pgp-keys

Reply via email to