[ofbiz-framework] branch trunk updated: Improved: Convert ProductStoreServices.xml file from mini-lang to groovy DSL (OFBIZ-11449)

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

[ofbiz-framework] branch trunk updated: Improved: Convert ProductStoreServices.xml file from mini-lang to groovy DSL (OFBIZ-11449)

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

nmalin 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 c0054e9  Improved: Convert ProductStoreServices.xml file from mini-lang to groovy DSL (OFBIZ-11449)
c0054e9 is described below

commit c0054e97ea8d5baf09fc10613c96a7bb3381640a
Author: Nicolas Malin <[hidden email]>
AuthorDate: Wed Sep 16 09:34:22 2020 +0200

    Improved: Convert ProductStoreServices.xml file from mini-lang to groovy DSL
    (OFBIZ-11449)
   
    Thanks to Sebastian Berg for this patch
---
 .../product/store/ProductStoreServices.groovy      | 464 ++++++++++++++++++
 .../product/store/ProductStoreServices.xml         | 542 ---------------------
 applications/product/servicedef/services_store.xml |  32 +-
 3 files changed, 480 insertions(+), 558 deletions(-)

diff --git a/applications/product/groovyScripts/product/store/ProductStoreServices.groovy b/applications/product/groovyScripts/product/store/ProductStoreServices.groovy
new file mode 100644
index 0000000..9f05ebb
--- /dev/null
+++ b/applications/product/groovyScripts/product/store/ProductStoreServices.groovy
@@ -0,0 +1,464 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.ofbiz.base.util.Debug
+import org.apache.ofbiz.entity.condition.EntityCondition
+import org.apache.ofbiz.entity.condition.EntityConditionBuilder
+
+import java.sql.Timestamp
+
+import org.apache.ofbiz.base.util.UtilDateTime
+import org.apache.ofbiz.base.util.UtilProperties
+import org.apache.ofbiz.entity.GenericValue
+import org.apache.ofbiz.entity.util.EntityTypeUtil
+import org.apache.ofbiz.entity.util.EntityUtil
+import org.apache.ofbiz.service.ServiceUtil
+
+
+/**
+ * Create a Product Store
+ * @return
+ */
+def createProductStore() {
+    Map result = success()
+    if (!security.hasEntityPermission("CATALOG", "_CREATE", parameters.userLogin)) {
+        return error(UtilProperties.getMessage("ProductUiLabels", "ProductCatalogCreatePermissionError", parameters.locale))
+    }
+    if ("Y" == parameters.oneInventoryFacility
+            && !parameters.inventoryFacilityId) {
+        return error(UtilProperties.getMessage("ProductUiLabels", "InventoryFacilityIdRequired", parameters.locale))
+    }
+    if ("Y" == parameters.showPriceWithVatTax) {
+        if (!parameters.vatTaxAuthGeoId) {
+            return error(UtilProperties.getMessage("ProductUiLabels", "ProductVatTaxAuthGeoNotSet", parameters.locale))
+        }
+        if (!parameters.vatTaxAuthPartyId) {
+            return error(UtilProperties.getMessage("ProductUiLabels", "ProductVatTaxAuthPartyNotSet", parameters.locale))
+        }
+    }
+    GenericValue newEntity = makeValue("ProductStore")
+    newEntity.setNonPKFields(parameters)
+    String productStoreId = delegator.getNextSeqId("ProductStore")
+    newEntity.productStoreId = productStoreId
+    newEntity.create()
+
+    // create the ProductStoreFacility record
+    if (newEntity.inventoryFacilityId) {
+        makeValue("ProductStoreFacility", [
+                facilityId: newEntity.inventoryFacilityId,
+                productStoreId: newEntity.productStoreId,
+                fromDate: UtilDateTime.nowTimestamp()])
+                .create()
+    }
+    result.productStoreId = productStoreId
+    return result
+}
+
+/**
+ * Update a Product Store
+ * @return
+ */
+def updateProductStore() {
+    if (!security.hasEntityPermission("CATALOG", "_UPDATE", parameters.userLogin)) {
+        return error(UtilProperties.getMessage("ProductUiLabels", "ProductCatalogUpdatePermissionError", parameters.locale))
+    }
+    if ("Y" == parameters.oneInventoryFacility
+        && !parameters.inventoryFacilityId) {
+            return error(UtilProperties.getMessage("ProductUiLabels", "InventoryFacilityIdRequired", parameters.locale))
+    }
+    GenericValue store = from("ProductStore").where(productStoreId: parameters.productStoreId).queryOne()
+    String oldFacilityId = store.inventoryFacilityId
+    store.setNonPKFields(parameters)
+
+    // visualThemeId must be replaced by ecomThemeId because of Entity.field names conflict. See OFBIZ-10567
+    store.visualThemeId = parameters.ecomThemeId
+    if ("Y" == store.showPricesWithVatTax) {
+        if (!store.vatTaxAuthGeoId) {
+            return error(UtilProperties.getMessage("ProductUiLabels", "ProductVatTaxAuthGeoNotSet", parameters.locale))
+        }
+        if (!store.vatTaxAuthPartyId) {
+            return error(UtilProperties.getMessage("ProductUiLabels", "ProductVatTaxAuthPartyNotSet", parameters.locale))
+        }
+    }
+    store.store()
+
+    // update the ProductStoreFacility record
+    Timestamp nowTimestamp = UtilDateTime.nowTimestamp()
+    if (oldFacilityId != store.inventoryFacilityId) {
+        if ("Y" == store.oneInventoryFacility) {
+            // expire all the facilities
+            EntityConditionBuilder exprBldr = new EntityConditionBuilder()
+            EntityCondition thruDateCondition = exprBldr.OR() {
+                EQUALS(thruDate: null)
+                GREATER_THAN_EQUAL_TO(thruDate: nowTimestamp)
+            }
+            EntityCondition condition = exprBldr.AND(thruDateCondition) {
+                EQUALS(productStoreId: store.productStoreId)
+                LESS_THAN_EQUAL_TO(fromDate: nowTimestamp)
+            }
+            delegator.storeByCondition("ProductStoreFacility", condition, [thruDate: nowTimestamp])
+        }
+        // create the new entry
+        makeValue("ProductStoreFacility", [
+                facilityId: store.inventoryFacilityId,
+                productStoreId: store.productStoreId,
+                fromDate: nowTimestamp])
+                .create()
+    }
+    return success()
+}
+
+// Store Inventory Services
+
+/**
+ * Reserve Store Inventory
+ * @return
+ */
+def reserveStoreInventory() {
+    Map result = success()
+    BigDecimal quantityNotReserved
+
+    GenericValue productStore = from("ProductStore").where(parameters).cache().queryOne()
+    if (!productStore) {
+        return error(UtilProperties.getMessage("ProductUiLabels", "ProductProductStoreNotFound", parameters.locale))
+    }
+
+    GenericValue product = from("Product").where(parameters).cache().queryOne()
+    GenericValue orderHeader = from("OrderHeader").where(parameters).cache().queryOne()
+    parameters.priority = orderHeader.priority
+
+    // if prodCatalog is set to not reserve inventory, break here
+    if ("N" == productStore.reserveInventory) {
+        // note: if not set, defaults to yes, reserve inventory
+        logVerbose("ProductStore with id [" + productStore.productStoreId + "], is set to NOT reserve inventory, not reserving inventory")
+        result.quantityNotReserved = parameters.quantity
+        return result
+    }
+    String requireInventory = isStoreInventoryRequiredInline(product, productStore)
+    String facilityId = parameters.facilityId
+    if (!facilityId) {
+        if ("Y" == productStore.oneInventoryFacility) {
+            if (!productStore.inventoryFacilityId) {
+                return error(UtilProperties.getMessage("ProductUiLabels", "ProductProductStoreNoSpecifiedInventoryFacility", parameters.locale))
+            }
+            Map serviceResult = run service: "reserveProductInventoryByFacility", with: [*: parameters,
+                                                                                         facilityId: productStore.inventoryFacilityId,
+                                                                                         requireInventory: requireInventory,
+                                                                                         reserveOrderEnumId: productStore.reserveOrderEnumId]
+            quantityNotReserved = serviceResult.quantityNotReserved
+
+            if (Debug.infoOn()) {
+                if (quantityNotReserved == (BigDecimal) 0) {
+                    logInfo("Inventory IS reserved in facility with id [${productStore.inventoryFacilityId}] for product id [${parameters.productId}]; desired quantity was ${parameters.quantity}")
+                } else {
+                    logInfo("There is insufficient inventory available in facility with id [${productStore.inventoryFacilityId}] for product id [${parameters.productId}]; desired quantity is ${parameters.quantity}, amount could not reserve is ${quantityNotReserved}")
+                }
+            }
+        } else {
+            GenericValue storeFound
+            List productStoreFacilities = from("ProductStoreFacility")
+                    .where(productStoreId: productStore.productStoreId)
+                    .orderBy("sequenceNum")
+                    .cache()
+                    .queryList()
+            for (GenericValue productStoreFacility : productStoreFacilities) {
+                // in this case quantityNotReserved will always be empty until it finds a facility it can totally reserve from, then it will be 0.0 and we are done
+                if (!storeFound) {
+                    // TODO: must entire quantity be available in one location?
+                    // Right now the answer is yes, it only succeeds if one facility has sufficient inventory for the order.
+                    Map callServiceMapIABF = [productId: parameters.productId, facilityId: productStoreFacility.facilityId]
+                    logInfo("ProductStoreService:In productStoreFacilities loop: [" + parameters.facilityId + "]")
+                    Map serviceResultIABF = run service: "getInventoryAvailableByFacility", with: callServiceMapIABF
+                    BigDecimal availableToPromiseTotal = serviceResultIABF.availableToPromiseTotal
+
+                    if (availableToPromiseTotal >= parameters.quantity) {
+                        storeFound = productStoreFacility
+                    }
+                }
+            }
+            // didn't find anything? Take the first facility from list
+            if (!storeFound) {
+                storeFound = productStoreFacilities.get(0)
+            }
+            facilityId = storeFound.facilityId ?: ""
+            Map serviceResult = run service: "reserveProductInventoryByFacility", with: [*: parameters,
+                                                                                         facilityId: facilityId,
+                                                                                         requireInventory: requireInventory,
+                                                                                         reserveOrderEnumId: productStore.reserveOrderEnumId]
+            quantityNotReserved = serviceResult.quantityNotReserved
+            logInfo("Inventory IS reserved in facility with id [${storeFound.facilityId}] for product id [${parameters.productId}]; desired quantity was ${parameters.quantity}")
+        }
+    } else {
+        List productStoreFacilities = from("ProductStoreFacility").where(productStoreId: productStore.productStoreId, facilityId: facilityId).cache().orderBy("sequenceNum").queryList()
+        GenericValue facilityFound
+        for (GenericValue productStoreFacility : productStoreFacilities) {
+            // Search Product Store Facilities to insure the facility passed in is associated to the Product Store passed in
+            facilityFound = productStoreFacility
+            logInfo("ProductStoreService:Facility Found : [" + facilityFound + "]")
+        }
+        if (!facilityFound) {
+            return  error(UtilProperties.getMessage("ProductUiLabels", "FacilityNoAssociatedWithProcuctStore", parameters.locale))
+        }
+        Map serviceResult = run service: "reserveProductInventoryByFacility", with: [*: parameters,
+                                                                                     facilityId: facilityId,
+                                                                                     requireInventory: requireInventory,
+                                                                                     reserveOrderEnumId: productStore.reserveOrderEnumId]
+        quantityNotReserved = serviceResult.quantityNotReserved
+        if (Debug.infoOn()) {
+            if (quantityNotReserved == (BigDecimal) 0) {
+                logInfo("Inventory IS reserved in facility with id [${facilityId}] for product id [${parameters.productId}]; desired quantity was ${parameters.quantity}")
+            } else {
+                logInfo("There is insufficient inventory available in facility with id [${facilityId}] for product id [${parameters.productId}]; desired quantity is ${parameters.quantity}, amount could not reserve is ${quantityNotReserved}")
+            }
+        }
+    }
+    result.quantityNotReserved = quantityNotReserved
+    return result
+}
+
+/**
+ * Is Store Inventory Required
+ * @return
+ */
+def isStoreInventoryRequired() {
+    GenericValue productStore = parameters.productStore ?: from("ProductStore").where(parameters).cache().queryOne()
+    GenericValue product = parameters.product ?: from("Product").where(parameters).cache().queryOne()
+
+    Map result = success()
+    result.requireInventory = isStoreInventoryRequiredInline(product, productStore)
+    return result
+}
+
+/**
+ * Is Store Inventory Required
+ * @param product
+ * @param productStore
+ * @return
+ */
+def isStoreInventoryRequiredInline(GenericValue product, GenericValue productStore) {
+    String requireInventory = product.requireInventory
+    requireInventory = requireInventory ?: productStore.requireInventory
+    requireInventory = requireInventory ?: "Y"
+    return requireInventory
+}
+
+/**
+ * Is Store Inventory Available
+ * @return
+ */
+def isStoreInventoryAvailable() {
+    Map result = success()
+    GenericValue productStore = parameters.productStore ?: from("ProductStore").where(parameters).cache().queryOne()
+    GenericValue product = parameters.product ?: from("Product").where(parameters).cache().queryOne()
+
+    BigDecimal availableToPromiseTotal
+    String available
+
+    // If the given product is a SERVICE or DIGITAL_GOOD
+    if (product.productTypeId == "SERVICE" || product.productTypeId == "DIGITAL_GOOD") {
+        logVerbose("Product with id ${product.productId}, is of type ${product.productTypeId}, returning true for inventory available check")
+        result.available = "Y"
+        return result
+    }
+
+    // TODO: what to do with ASSET_USAGE? Only done elsewhere? Would need date/time range info to check availability
+
+    // if prodCatalog is set to not check inventory break here
+    if ("N" == productStore.checkInventory) {
+        logVerbose("ProductStore with id ${productStore.productStoreId}, is set to NOT check inventory," +
+                " returning true for inventory available check")
+        result.available = "Y"
+        return result
+    }
+    if ("Y" == productStore.oneInventoryFacility) {
+        if (!productStore.inventoryFacilityId) {
+            return error(UtilProperties.getMessage("ProductUiLabels", "ProductProductStoreNotCheckAvailability", parameters.locale))
+        }
+        boolean isMarketingPkg = EntityTypeUtil.hasParentType(delegator, 'ProductType', 'productTypeId',
+                product.productTypeId, 'parentTypeId', 'MARKETING_PKG')
+        String serviceName = isMarketingPkg ? "getMktgPackagesAvailable" : "getInventoryAvailableByFacility"
+        Map serviceResult = run service: serviceName, with: [productId: parameters.productId,
+                                                             facilityId: productStore.inventoryFacilityId]
+        availableToPromiseTotal = serviceResult.availableToPromiseTotal
+
+        // check to see if we got enough back...
+        if (availableToPromiseTotal >= parameters.quantity) {
+            available = "Y"
+            logInfo("Inventory IS available in facility with id ${productStore.inventoryFacilityId} for " +
+                    "product id ${parameters.productId}; desired quantity is ${parameters.quantity}," +
+                    "available quantity is ${availableToPromiseTotal}")
+        } else {
+            available = "N"
+            logInfo("Returning false because there is insufficient inventory available in facility with id " +
+                    "${productStore.inventoryFacilityId} for product id ${parameters.productId}; desired quantity" +
+                    " is ${parameters.quantity}, available quantity is ${availableToPromiseTotal}")
+        }
+    } else {
+        List productStoreFacilities = from("ProductStoreFacility")
+                .where(productStoreId: productStore.productStoreId)
+                .orderBy("sequenceNum")
+                .cache()
+                .queryList()
+        available = "N"
+        for (GenericValue productStoreFacility : productStoreFacilities) {
+            // TODO: must entire quantity be available in one location?
+            // Right now the answer is yes, it only succeeds if one facility has sufficient inventory for the order.
+            boolean isMarketingPkg = EntityTypeUtil.hasParentType(delegator, 'ProductType', 'productTypeId'
+                    , product.productTypeId, 'parentTypeId', 'MARKETING_PKG')
+            String serviceName = isMarketingPkg ? "getMktgPackagesAvailable" : "getInventoryAvailableByFacility"
+            Map serviceResult = run service: serviceName, with: [productId: parameters.productId,
+                                                                 facilityId: productStoreFacility.facilityId]
+            availableToPromiseTotal = serviceResult.availableToPromiseTotal
+
+            if (availableToPromiseTotal >= parameters.quantity) {
+                available = "Y"
+                logInfo("Inventory IS available in facility with id ${productStoreFacility.facilityId}" +
+                        " for product id ${parameters.productId}; desired quantity is ${parameters.quantity}," +
+                        " available quantity is ${availableToPromiseTotal}")
+            }
+        }
+    }
+    result.available = available
+
+    /* TODO: must entire quantity be available in one location?
+     *  Right now the answer is yes, it only succeeds if one facility has sufficient inventory for the order.
+     *  When we get into splitting options it is much more complicated. There are various options like:
+     *  - allow split between facilities
+     *  - in split order facilities by highest quantities
+     *  - in split order facilities by lowest quantities
+     *  - in split order facilities by order in database, ie sequence numbers on facility-store join table
+     *  - in split order facilities by nearest locations to customer (not an easy one there...)
+     */
+    // loop through all facilities attached to this catalog and check for individual or cumulative sufficient inventory
+    return result
+}
+
+/**
+ * Is Store Inventory Available or Not Required
+ * @return
+ */
+def isStoreInventoryAvailableOrNotRequired() {
+    Map result = success()
+    GenericValue productStore = parameters.productStore ?: from("ProductStore").where(parameters).cache().queryOne()
+    GenericValue product = parameters.product ?: from("Product").where(parameters).cache().queryOne()
+    if ("Y" != isStoreInventoryRequiredInline(product, productStore)) {
+        result.availableOrNotRequired = "Y"
+    } else {
+        Map serviceResult = run service: "isStoreInventoryAvailable", with: parameters
+        result.availableOrNotRequired = serviceResult.available
+    }
+    return result
+}
+
+/*
+ * =============================
+ * Permission Methods
+ * =============================
+ */
+
+// a methods 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
+
+/**
+ * Check ProductStore Related Permission
+ * @return
+ */
+def checkProductStoreRelatedPermission(Map inputParameter) {
+    List roleStores
+    String callingMethodName = inputParameter.resourceDescription
+    String checkAction = inputParameter.mainAction
+    String productStoreIdName = inputParameter.productStoreIdName
+    String productStoreIdToCheck = inputParameter.productStoreIdToCheck
+    if (!callingMethodName) {
+        callingMethodName = UtilProperties.getMessage("CommonUiLabels", "CommonPermissionThisOperation", locale)
+    }
+    if (!checkAction) {
+        checkAction = "UPDATE"
+    }
+    if (!productStoreIdName) {
+        productStoreIdName = inputParameter.productStoreId
+    }
+    if (!productStoreIdToCheck) {
+        productStoreIdToCheck = inputParameter.productstoreIdName
+    }
+
+    // find all role-store that this productStore is a member of
+    if (!security.hasEntityPermission("CATALOG", ("_" + checkAction), userLogin)) {
+        roleStores = from("ProductStoreRole").where(productStoreId: productStoreIdToCheck, partyId: userLogin.partyId, roleTypeId: "LTD_ADMIN").filterByDate().queryList()
+        roleStores = EntityUtil.filterByDate(roleStores, UtilDateTime.nowTimestamp(), "roleFromDate", "roleThruDate", true)
+    }
+    logInfo("Checking store permission, roleStores=${roleStores}")
+    if (!(security.hasEntityPermission("CATALOG", ("_" + checkAction), userLogin) ||
+    (security.hasEntityPermission("CATALOG_ROLE", ("_" + checkAction), userLogin) && roleStores))) {
+        logVerbose("Permission check failed, user does not have permission")
+        String checkActionLabel = 'ProductCatalog' + checkAction.charAt(0) + checkAction.substring(1).toLowerCase() + 'PermissionError'
+        return error(UtilProperties.getMessage("ProductUiLabels", checkActionLabel, locale))
+    }
+    return success()
+}
+
+/**
+ * Main permission logic
+ * @return
+ */
+def productStoreGenericPermission() {
+    Map result = success()
+    if (!parameters.mainAction) {
+        String errorMessage = UtilProperties.getMessage("ProductUiLabels", "ProductMissingMainActionInPermissionService", parameters.locale)
+        logError(errorMessage)
+        return error(errorMessage)
+    }
+    Map serviceInMap = parameters
+    Map serviceResult = checkProductStoreRelatedPermission(serviceInMap)
+    if (ServiceUtil.isSuccess(serviceResult)) {
+        result.hasPermission = true
+    } else {
+        String failMessage = UtilProperties.getMessage("ProductUiLabels", "ProductPermissionError", parameters.locale)
+        result.failMessage = failMessage
+        result.hasPermission = false
+    }
+    return result
+}
+
+/**
+ * When product store group hierarchy has been operate, synchronize primaryParentGroupId with ProductStoreGroupRollup
+ * @return
+ */
+def checkProductStoreGroupRollup() {
+    GenericValue productStoreGroup = from("ProductStoreGroup").where(parameters).queryOne()
+    if (!parameters.primaryParentGroupId) {
+        GenericValue productStoreGroupRollup = from("ProductStoreGroupRollup").where(parameters).queryOne()
+        if (productStoreGroupRollup) {
+            productStoreGroup.primaryParentGroupId = productStoreGroupRollup.parentGroupId
+            run service: "updateProductStoreGroup", with: productStoreGroup.getAllFields()
+        }
+    } else {
+        if (from("ProductStoreGroupRollup")
+                .where(productStoreGroupId: productStoreGroup.productStoreGroupId,
+                        parentGroupId: parameters.primaryParentGroupId)
+                .filterByDate()
+                .queryCount() == 0) {
+            run service: "createProductStoreGroupRollup", with: [productStoreGroupId: productStoreGroup.productStoreGroupId,
+                                                                parentGroupId: parameters.primaryParentGroupId,
+                                                                fromDate: UtilDateTime.nowTimestamp()]
+        }
+    }
+    return success()
+}
\ No newline at end of file
diff --git a/applications/product/minilang/product/store/ProductStoreServices.xml b/applications/product/minilang/product/store/ProductStoreServices.xml
deleted file mode 100644
index e5baab4..0000000
--- a/applications/product/minilang/product/store/ProductStoreServices.xml
+++ /dev/null
@@ -1,542 +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="createProductStore" short-description="Create a Product Store">
-        <check-permission permission="CATALOG" action="_CREATE">
-            <fail-property resource="ProductUiLabels" property="ProductCatalogCreatePermissionError"/>
-        </check-permission>
-        <check-errors/>
-
-        <if-compare field="parameters.oneInventoryFacility" operator="equals" value="Y">
-            <if-empty field="parameters.inventoryFacilityId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="InventoryFacilityIdRequired"/>
-                </add-error>
-            </if-empty>
-            <check-errors/>
-        </if-compare>
-
-        <if-compare field="parameters.showPricesWithVatTax" operator="equals" value="Y">
-            <if-empty field="parameters.vatTaxAuthGeoId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="ProductVatTaxAuthGeoNotSet"/>
-                </add-error>
-            </if-empty>
-            <if-empty field="parameters.vatTaxAuthPartyId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="ProductVatTaxAuthPartyNotSet"/>
-                </add-error>
-            </if-empty>
-            <check-errors/>
-        </if-compare>
-
-        <make-value entity-name="ProductStore" value-field="newEntity"/>
-        <set-nonpk-fields map="parameters" value-field="newEntity"/>
-
-        <sequenced-id sequence-name="ProductStore" field="productStoreId"/>
-        <set from-field="productStoreId" field="newEntity.productStoreId"/>
-
-        <field-to-result field="productStoreId" result-name="productStoreId"/>
-        <create-value value-field="newEntity"/>
-
-        <!-- create the ProductStoreFacility record -->
-        <now-timestamp field="nowTimestamp"/>
-
-        <if-not-empty field="newEntity.inventoryFacilityId">
-            <make-value entity-name="ProductStoreFacility" value-field="storeFacility"/>
-            <set from-field="newEntity.inventoryFacilityId" field="storeFacility.facilityId"/>
-            <set from-field="newEntity.productStoreId" field="storeFacility.productStoreId"/>
-            <set from-field="nowTimestamp" field="storeFacility.fromDate"/>
-            <create-value value-field="storeFacility"/>
-        </if-not-empty>
-    </simple-method>
-    <simple-method method-name="updateProductStore" short-description="Update a Product Store">
-        <check-permission permission="CATALOG" action="_UPDATE">
-            <fail-property resource="ProductUiLabels" property="ProductCatalogUpdatePermissionError"/>
-        </check-permission>
-        <check-errors/>
-        
-         <if-compare field="parameters.oneInventoryFacility" operator="equals" value="Y">
-            <if-empty field="parameters.inventoryFacilityId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="InventoryFacilityIdRequired"/>
-                </add-error>
-            </if-empty>
-            <check-errors/>
-        </if-compare>
-
-        <set from-field="parameters.productStoreId" field="lookupPKMap.productStoreId"/>
-        <find-by-primary-key entity-name="ProductStore" map="lookupPKMap" value-field="store"/>
-        <set from-field="store.inventoryFacilityId" field="oldFacilityId"/>
-        <set-nonpk-fields map="parameters" value-field="store"/>
-
-        <!-- visualThemeId must be replaced by ecomThemeId because of Entity.field names conflict. See OFBIZ-10567 -->
-        <set field="store.visualThemeId" from-field="parameters.ecomThemeId"/>
-
-        <if-compare field="store.showPricesWithVatTax" operator="equals" value="Y">
-            <if-empty field="store.vatTaxAuthGeoId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="ProductVatTaxAuthGeoNotSet"/>
-                </add-error>
-            </if-empty>
-            <if-empty field="store.vatTaxAuthPartyId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="ProductVatTaxAuthPartyNotSet"/>
-                </add-error>
-            </if-empty>
-            <check-errors/>
-        </if-compare>
-
-        <store-value value-field="store"/>
-
-        <!-- update the ProductStoreFacility record -->
-        <now-timestamp field="nowTimestamp"/>
-        <if-compare-field field="store.inventoryFacilityId" to-field="oldFacilityId" operator="not-equals">
-            <if-compare field="store.oneInventoryFacility" operator="equals" value="Y">
-                <!-- expire all the facilities -->
-                <set from-field="store.productStoreId" field="lookupPFMap.productStoreId"/>
-                <find-by-and entity-name="ProductStoreFacility" map="lookupPFMap" list="storeFacilities"/>
-                <filter-list-by-date list="storeFacilities"/>
-                <iterate list="storeFacilities" entry="facility">
-                    <set from-field="nowTimestamp" field="facility.thruDate"/>
-                    <store-value value-field="facility"/>
-                </iterate>
-            </if-compare>
-            <!-- create the new entry -->
-            <make-value entity-name="ProductStoreFacility" value-field="storeFacility"/>
-            <set from-field="store.inventoryFacilityId" field="storeFacility.facilityId"/>
-            <set from-field="store.productStoreId" field="storeFacility.productStoreId"/>
-            <set from-field="nowTimestamp" field="storeFacility.fromDate"/>
-            <create-value value-field="storeFacility"/>
-        </if-compare-field>
-    </simple-method>
-
-    <!-- Store Inventory Services -->
-    <simple-method method-name="reserveStoreInventory" short-description="Reserve Store Inventory">
-        <entity-one entity-name="Product" value-field="product" use-cache="true"/>
-        <entity-one entity-name="ProductStore" value-field="productStore" use-cache="true"/>
-        <entity-one entity-name="OrderHeader" value-field="orderHeader"/>
-        <set field="parameters.priority" from-field="orderHeader.priority"/>
-        <if-empty field="productStore">
-            <add-error>
-                <fail-property resource="ProductUiLabels" property="ProductProductStoreNotFound"/>
-            </add-error>
-            <check-errors/>
-        </if-empty>
-
-        <!-- if prodCatalog is set to not reserve inventory, break here -->
-        <if-compare value="N" operator="equals" field="productStore.reserveInventory">
-            <!-- note: if not set, defaults to yes, reserve inventory -->
-            <log level="verbose" message="ProductStore with id ${productStore.productStoreId}, is set to NOT reserve inventory, not reserving inventory"/>
-            <field-to-result field="parameters.quantity" result-name="quantityNotReserved"/>
-            <return response-code="success"/>
-        </if-compare>
-
-        <call-simple-method method-name="isStoreInventoryRequiredInline"/>
-
-        <set from-field="parameters.facilityId" field="facilityId"/>
-        <if-empty field="facilityId">
-            <if-compare field="productStore.oneInventoryFacility" operator="equals" value="Y">
-                <if-empty field="productStore.inventoryFacilityId">
-                    <add-error>
-                        <fail-property resource="ProductUiLabels" property="ProductProductStoreNoSpecifiedInventoryFacility"/>
-                    </add-error>
-                    <check-errors/>
-                </if-empty>
-                <set-service-fields service-name="reserveProductInventoryByFacility" map="parameters" to-map="callServiceMap"/>
-                <set from-field="productStore.inventoryFacilityId" field="callServiceMap.facilityId"/>
-                <set from-field="requireInventory" field="callServiceMap.requireInventory"/>
-                <set from-field="productStore.reserveOrderEnumId" field="callServiceMap.reserveOrderEnumId"/>
-                <call-service service-name="reserveProductInventoryByFacility" in-map-name="callServiceMap">
-                    <result-to-field result-name="quantityNotReserved"/>
-                </call-service>
-
-                <if-compare field="quantityNotReserved" operator="equals" value="0" type="BigDecimal">
-                    <log level="info" message="Inventory IS reserved in facility with id [${productStore.inventoryFacilityId}] for product id [${parameters.productId}]; desired quantity was ${parameters.quantity}"/>
-                <else>
-                    <log level="info" message="There is insufficient inventory available in facility with id [${productStore.inventoryFacilityId}] for product id [${parameters.productId}]; desired quantity is ${parameters.quantity}, amount could not reserve is ${quantityNotReserved}"/>
-                </else>
-                </if-compare>
-            <else>
-                <entity-and entity-name="ProductStoreFacility" list="productStoreFacilities" use-cache="true">
-                    <field-map field-name="productStoreId" from-field="productStore.productStoreId"/>
-                    <order-by field-name="sequenceNum"/>
-                </entity-and>
-                <iterate list="productStoreFacilities" entry="productStoreFacility">
-                    <!-- in this case quantityNotReserved will always be empty until it finds a facility it can totally reserve from, then it will be 0.0 and we are done -->
-                    <if-empty field="storeFound">
-                        <!-- TODO: must entire quantity be available in one location? -->
-                        <!-- Right now the answer is yes, it only succeeds if one facility has sufficient inventory for the order. -->
-                        <set from-field="parameters.productId" field="callServiceMap.productId"/>
-                        <set from-field="productStoreFacility.facilityId" field="callServiceMap.facilityId"/>
-                        <log level="info" message="ProductStoreService:In productStoreFacilities loop: [${parameters.facilityId}]"/>
-                        <call-service service-name="getInventoryAvailableByFacility" in-map-name="callServiceMap">
-                            <result-to-field result-name="availableToPromiseTotal"/>
-                        </call-service>
-                        <clear-field field="callServiceMap"/>
-
-                        <if-compare-field field="availableToPromiseTotal" to-field="parameters.quantity" operator="greater-equals" type="BigDecimal">
-                            <set field="storeFound" from-field="productStoreFacility"/>
-                        </if-compare-field>
-                        <clear-field field="availableToPromiseTotal"/>
-                    </if-empty>
-                </iterate>
-
-                <!-- didn't find anything? Take the first facility from list -->
-                <if-empty field="storeFound">
-                    <first-from-list list="productStoreFacilities" entry="storeFound"/>
-                </if-empty>
-                <set from-field="storeFound.facilityId" field="facilityId" default-value=""/>
-                <set-service-fields service-name="reserveProductInventoryByFacility" map="parameters" to-map="callServiceMap"/>
-                <set from-field="facilityId" field="callServiceMap.facilityId"/>
-                <set from-field="requireInventory" field="callServiceMap.requireInventory"/>
-                <set from-field="productStore.reserveOrderEnumId" field="callServiceMap.reserveOrderEnumId"/>
-                <call-service service-name="reserveProductInventoryByFacility" in-map-name="callServiceMap">
-                    <result-to-field result-name="quantityNotReserved"/>
-                </call-service>
-                <log level="info" message="Inventory IS reserved in facility with id [${storeFound.facilityId}] for product id [${parameters.productId}]; desired quantity was ${parameters.quantity}"/>
-            </else>
-            </if-compare>
-        <else>
-            <entity-and entity-name="ProductStoreFacility" list="productStoreFacilities" use-cache="true">
-                <field-map field-name="productStoreId" from-field="productStore.productStoreId"/>
-                <field-map field-name="facilityId" from-field="facilityId"/>
-                <order-by field-name="sequenceNum"/>
-            </entity-and>
-            <iterate list="productStoreFacilities" entry="productStoreFacility">
-                <!-- Search Product Store Facilities to insure the facility passed in is associated to the Product Store passed in -->
-                <set field="facilityFound" from-field="productStoreFacility"/>
-                <log level="info" message="ProductStoreService:Facility Found : [${facilityFound}]"/>
-            </iterate>
-            <if-empty field="facilityFound">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="FacilityNoAssociatedWithProcuctStore"/>
-                </add-error>
-                <check-errors/>
-            </if-empty>
-            <set-service-fields service-name="reserveProductInventoryByFacility" map="parameters" to-map="callServiceMap"/>
-            <set from-field="facilityId" field="callServiceMap.facilityId"/>
-            <set from-field="requireInventory" field="callServiceMap.requireInventory"/>
-            <set from-field="productStore.reserveOrderEnumId" field="callServiceMap.reserveOrderEnumId"/>
-            <call-service service-name="reserveProductInventoryByFacility" in-map-name="callServiceMap">
-                <result-to-field result-name="quantityNotReserved"/>
-            </call-service>
-            <if-compare field="quantityNotReserved" operator="equals" value="0" type="BigDecimal">
-                <log level="info" message="Inventory IS reserved in facility with id [${facilityId}] for product id [${parameters.productId}]; desired quantity was ${parameters.quantity}"/>
-            <else>
-                <log level="info" message="There is insufficient inventory available in facility with id [${facilityId}] for product id [${parameters.productId}]; desired quantity is ${parameters.quantity}, amount could not reserve is ${quantityNotReserved}"/>
-            </else>
-            </if-compare>
-        </else>
-        </if-empty>
-        <field-to-result field="quantityNotReserved"/>
-    </simple-method>
-
-    <simple-method method-name="isStoreInventoryRequired" short-description="Is Store Inventory Required" login-required="false">
-        <if-empty field="parameters.productStore">
-            <entity-one entity-name="ProductStore" value-field="productStore" use-cache="true"/>
-        <else>
-            <set from-field="parameters.productStore" field="productStore"/>
-        </else>
-        </if-empty>
-        <if-empty field="parameters.product">
-            <entity-one entity-name="Product" value-field="product" use-cache="true"/>
-        <else>
-            <set from-field="parameters.product" field="product"/>
-        </else>
-        </if-empty>
-
-        <call-simple-method method-name="isStoreInventoryRequiredInline"/>
-        <field-to-result field="requireInventory"/>
-    </simple-method>
-    <simple-method method-name="isStoreInventoryRequiredInline" short-description="Is Store Inventory Required" login-required="false">
-        <set from-field="product.requireInventory" field="requireInventory"/>
-        <if-empty field="requireInventory">
-            <set from-field="productStore.requireInventory" field="requireInventory"/>
-        </if-empty>
-        <if-empty field="requireInventory">
-            <set value="Y" field="requireInventory"/>
-        </if-empty>
-    </simple-method>
-
-    <simple-method method-name="isStoreInventoryAvailable" short-description="Is Store Inventory Available" login-required="false">
-        <if-empty field="parameters.productStore">
-            <entity-one entity-name="ProductStore" value-field="productStore" use-cache="true"/>
-        <else>
-            <set from-field="parameters.productStore" field="productStore"/>
-        </else>
-        </if-empty>
-        <if-empty field="parameters.product">
-            <entity-one entity-name="Product" value-field="product" use-cache="true"/>
-        <else>
-            <set from-field="parameters.product" field="product"/>
-        </else>
-        </if-empty>
-
-        <!-- If the given product is a SERVICE or DIGITAL_GOOD -->
-        <if>
-            <condition>
-                <or>
-                    <if-compare field="product.productTypeId" operator="equals" value="SERVICE"/>
-                    <if-compare field="product.productTypeId" operator="equals" value="DIGITAL_GOOD"/>
-                </or>
-            </condition>
-            <then>
-                <log level="verbose" message="Product with id ${product.productId}, is of type ${product.productTypeId}, returning true for inventory available check"/>
-                <set value="Y" field="available"/>
-                <field-to-result field="available"/>
-                <return/>
-            </then>
-        </if>
-
-        <!-- TODO: what to do with ASSET_USAGE? Only done elsewhere? Would need date/time range info to check availability -->
-
-        <!-- if prodCatalog is set to not check inventory break here -->
-        <if-compare field="productStore.checkInventory" operator="equals" value="N">
-            <!-- note: if not set, defaults to yes, check inventory -->
-            <log level="verbose" message="ProductStore with id ${productStore.productStoreId}, is set to NOT check inventory, returning true for inventory available check"/>
-            <set value="Y" field="available"/>
-            <field-to-result field="available"/>
-            <return/>
-        </if-compare>
-
-        <if-compare value="Y" operator="equals" field="productStore.oneInventoryFacility">
-            <if-empty field="productStore.inventoryFacilityId">
-                <add-error>
-                    <fail-property resource="ProductUiLabels" property="ProductProductStoreNotCheckAvailability"/>
-                </add-error>
-                <check-errors/>
-            </if-empty>
-
-            <set from-field="parameters.productId" field="callServiceMap.productId"/>
-            <set from-field="productStore.inventoryFacilityId" field="callServiceMap.facilityId"/>
-            <set field="isMarketingPkg" value="${groovy: org.apache.ofbiz.entity.util.EntityTypeUtil.hasParentType(delegator, 'ProductType', 'productTypeId', product.productTypeId, 'parentTypeId', 'MARKETING_PKG')}" type="Boolean"/>
-            <if-compare field="isMarketingPkg" operator="equals" value="true" type="Boolean">
-                <call-service service-name="getMktgPackagesAvailable" in-map-name="callServiceMap">
-                    <result-to-field result-name="availableToPromiseTotal"/>
-                </call-service>
-                <else>
-                    <call-service service-name="getInventoryAvailableByFacility" in-map-name="callServiceMap">
-                        <result-to-field result-name="availableToPromiseTotal"/>
-                    </call-service>
-                </else>
-            </if-compare>
-            <!-- check to see if we got enough back... -->
-            <if-compare-field field="availableToPromiseTotal" to-field="parameters.quantity" operator="greater-equals" type="BigDecimal">
-                <set value="Y" field="available"/>
-                <log level="info" message="Inventory IS available in facility with id ${productStore.inventoryFacilityId} for product id ${parameters.productId}; desired quantity is ${parameters.quantity}, available quantity is ${availableToPromiseTotal}"/>
-            <else>
-                <set value="N" field="available"/>
-                <log level="info" message="Returning false because there is insufficient inventory available in facility with id ${productStore.inventoryFacilityId} for product id ${parameters.productId}; desired quantity is ${parameters.quantity}, available quantity is ${availableToPromiseTotal}"/>
-            </else>
-            </if-compare-field>
-        <else>
-            <entity-and entity-name="ProductStoreFacility" list="productStoreFacilities" use-cache="true">
-                <field-map field-name="productStoreId" from-field="productStore.productStoreId"/>
-                <order-by field-name="sequenceNum"/>
-            </entity-and>
-
-            <set value="N" field="available"/>
-            <iterate list="productStoreFacilities" entry="productStoreFacility">
-                <if-compare field="available" operator="equals" value="N">
-                    <!-- TODO: must entire quantity be available in one location? -->
-                    <!-- Right now the answer is yes, it only succeeds if one facility has sufficient inventory for the order. -->
-                    <set from-field="parameters.productId" field="callServiceMap.productId"/>
-                    <set from-field="productStoreFacility.facilityId" field="callServiceMap.facilityId"/>
-                    <set field="isMarketingPkg" value="${groovy: org.apache.ofbiz.entity.util.EntityTypeUtil.hasParentType(delegator, 'ProductType', 'productTypeId', product.productTypeId, 'parentTypeId', 'MARKETING_PKG')}" type="Boolean"/>
-                    <if-compare field="isMarketingPkg" operator="equals" value="true" type="Boolean">
-                        <call-service service-name="getMktgPackagesAvailable" in-map-name="callServiceMap">
-                            <result-to-field result-name="availableToPromiseTotal"/>
-                        </call-service>
-                        <else>
-                            <call-service service-name="getInventoryAvailableByFacility" in-map-name="callServiceMap">
-                                <result-to-field result-name="availableToPromiseTotal"/>
-                            </call-service>
-                        </else>
-                    </if-compare>
-
-                    <clear-field field="callServiceMap"/>
-
-                    <if-compare-field field="availableToPromiseTotal" to-field="parameters.quantity" operator="greater-equals" type="BigDecimal">
-                        <set value="Y" field="available"/>
-                        <log level="info" message="Inventory IS available in facility with id ${productStoreFacility.facilityId} for product id ${parameters.productId}; desired quantity is ${parameters.quantity}, available quantity is ${availableToPromiseTotal}"/>
-                    </if-compare-field>
-                    <clear-field field="availableToPromiseTotal"/>
-                </if-compare>
-            </iterate>
-        </else>
-        </if-compare>
-
-        <field-to-result field="available"/>
-
-        <!--
-            /* TODO: must entire quantity be available in one location?
-             *  Right now the answer is yes, it only succeeds if one facility has sufficient inventory for the order.
-             *  When we get into splitting options it is much more complicated. There are various options like:
-             *  - allow split between facilities
-             *  - in split order facilities by highest quantities
-             *  - in split order facilities by lowest quantities
-             *  - in split order facilities by order in database, ie sequence numbers on facility-store join table
-             *  - in split order facilities by nearest locations to customer (not an easy one there...)
-             */
-
-            // loop through all facilities attached to this catalog and check for individual or cumulative sufficient inventory
-        -->
-    </simple-method>
-
-    <simple-method method-name="isStoreInventoryAvailableOrNotRequired" short-description="Is Store Inventory Available or Not Required" login-required="false">
-        <if-empty field="parameters.productStore">
-            <entity-one entity-name="ProductStore" value-field="productStore" use-cache="true"/>
-        <else>
-            <set from-field="parameters.productStore" field="productStore"/>
-        </else>
-        </if-empty>
-        <if-empty field="parameters.product">
-            <entity-one entity-name="Product" value-field="product" use-cache="true"/>
-        <else>
-            <set from-field="parameters.product" field="product"/>
-        </else>
-        </if-empty>
-
-        <call-simple-method method-name="isStoreInventoryRequiredInline"/>
-
-        <if-compare field="requireInventory" operator="not-equals" value="Y">
-            <set value="Y" field="availableOrNotRequired"/>
-            <field-to-result field="availableOrNotRequired"/>
-        <else>
-            <set-service-fields service-name="isStoreInventoryAvailable" map="parameters" to-map="callServiceMap"/>
-            <call-service service-name="isStoreInventoryAvailable" in-map-name="callServiceMap">
-                <result-to-result result-name="available" service-result-name="availableOrNotRequired"/>
-            </call-service>
-        </else>
-        </if-compare>
-    </simple-method>
-
-    <!-- ============================= -->
-    <!-- Permission Methods -->
-    <!-- ============================= -->
-
-    <!-- a methods 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="checkProductStoreRelatedPermission" short-description="Check ProductStore Related Permission">
-        <if-empty field="callingMethodName">
-            <property-to-field resource="CommonUiLabels" property="CommonPermissionThisOperation" field="callingMethodName"/>
-        </if-empty>
-        <if-empty field="checkAction">
-            <set field="checkAction" value="UPDATE"/>
-        </if-empty>
-        <if-empty field="productStoreIdName">
-            <set field="productStoreIdName" value="productStoreId"/>
-        </if-empty>
-        <if-empty field="productStoreIdToCheck">
-            <set field="productStoreIdToCheck" from-field="parameters.${productStoreIdName}"/>
-        </if-empty>
-
-        <!-- find all role-store that this productStore is a member of -->
-        <if>
-            <condition>
-                <not><if-has-permission permission="CATALOG" action="_${checkAction}"/></not>
-            </condition>
-            <then>
-                <entity-and entity-name="ProductStoreRole" list="roleStores" filter-by-date="true">
-                    <field-map field-name="productStoreId" from-field="productStoreIdToCheck"/>
-                    <field-map field-name="partyId" from-field="userLogin.partyId"/>
-                    <field-map field-name="roleTypeId" value="LTD_ADMIN"/>
-                </entity-and>
-                <filter-list-by-date list="roleStores" from-field-name="roleFromDate" thru-field-name="roleThruDate"/>
-            </then>
-        </if>
-        <log level="info" message="Checking store permission, roleStores=${roleStores}"/>
-        <if>
-            <condition>
-                <not>
-                    <or>
-                        <if-has-permission permission="CATALOG" action="_${checkAction}"/>
-                        <and>
-                            <if-has-permission permission="CATALOG_ROLE" action="_${checkAction}"/>
-                            <not><if-empty field="roleStores"/></not>
-                        </and>
-                    </or>
-                </not>
-            </condition>
-            <then>
-                <log level="verbose" message="Permission check failed, user does not have permission"/>
-                <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>
-                <set field="hasPermission" type="Boolean" value="false"/>
-            </then>
-        </if>
-    </simple-method>
-    <simple-method method-name="productStoreGenericPermission" 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="checkProductStoreRelatedPermission"/>
-
-        <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="checkProductStoreGroupRollup" short-description="When product store group hierarchy has been operate, synchronize primaryParentGroupId with ProductStoreGroupRollup">
-        <entity-one entity-name="ProductStoreGroup" value-field="productStoreGroup"/>
-        <if-empty field="parameters.primaryParentGroupId">
-            <entity-one entity-name="ProductStoreGroupRollup" value-field="productStoreGroupRollup"/>
-            <if-not-empty field="productStoreGroupRollup">
-                <set field="productStoreGroup.primaryParentGroupId"/>
-                <set-service-fields service-name="updateProductStoreGroup" map="productStoreGroup" to-map="productStoreGroupMap"/>
-                <call-service service-name="updateProductStoreGroup" in-map-name="productStoreGroupMap"/>
-            </if-not-empty>
-            <else>
-                <entity-and entity-name="ProductStoreGroupRollup" list="productStoreGroupRollups" filter-by-date="true">
-                    <field-map field-name="productStoreGroupId" from-field="productStoreGroup.productStoreGroupId"/>
-                    <field-map field-name="parentGroupId" from-field="parameters.primaryParentGroupId"/>
-                </entity-and>
-                <if-empty field="productStoreGroupRollups">
-                    <set field="productStoreGroupRollupMap.productStoreGroupId" from="productStoreGroup.productStoreGroupId"/>
-                    <set field="productStoreGroupRollupMap.parentGroupId" from="parameters.primaryParentGroupId"/>
-                    <set field="productStoreGroupRollupMap.fromDate" from="date:nowTimestamp()"/>
-                    <call-service service-name="createProductStoreGroupRollup" in-map-name="productStoreGroupRollupMap"/>
-                </if-empty>
-            </else>
-        </if-empty>
-    </simple-method>
-</simple-methods>
-
diff --git a/applications/product/servicedef/services_store.xml b/applications/product/servicedef/services_store.xml
index fd06ffb..8dfe986 100644
--- a/applications/product/servicedef/services_store.xml
+++ b/applications/product/servicedef/services_store.xml
@@ -25,15 +25,15 @@ under the License.
     <version>1.0</version>
 
     <!-- Product Store Services -->
-    <service name="createProductStore" default-entity-name="ProductStore" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="createProductStore">
+    <service name="createProductStore" default-entity-name="ProductStore" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="createProductStore">
         <description>Create a Product Store</description>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
         <auto-attributes include="pk" mode="OUT" optional="false"/>
         <override name="storeName" optional="false"/>
     </service>
-    <service name="updateProductStore" default-entity-name="ProductStore" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="updateProductStore">
+    <service name="updateProductStore" default-entity-name="ProductStore" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="updateProductStore">
         <description>Update a Product Store</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <!-- visualThemeId must be replaced by ecomThemeId because of Entity.field names conflict. See OFBIZ-10567 -->
@@ -43,8 +43,8 @@ under the License.
         <attribute name="ecomThemeId" mode="IN" type="String" optional="true"/>
     </service>
 
-    <service name="reserveStoreInventory" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="reserveStoreInventory">
+    <service name="reserveStoreInventory" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="reserveStoreInventory">
         <description>Reserve Inventory in a Product Store</description>
         <attribute name="productStoreId" type="String" mode="IN" optional="false">
             <type-validate>
@@ -72,8 +72,8 @@ under the License.
         </attribute>
     </service>
 
-    <service name="isStoreInventoryRequired" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="isStoreInventoryRequired" auth="false">
+    <service name="isStoreInventoryRequired" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="isStoreInventoryRequired" auth="false">
         <description>Checks if Store Inventory is Required</description>
         <attribute name="productStoreId" type="String" mode="IN" optional="false">
             <type-validate>
@@ -93,8 +93,8 @@ under the License.
          </type-validate>
         </attribute>
     </service>
-    <service name="isStoreInventoryAvailable" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="isStoreInventoryAvailable" auth="false">
+    <service name="isStoreInventoryAvailable" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="isStoreInventoryAvailable" auth="false">
         <description>Checks if Store Inventory is Required</description>
         <attribute name="productStoreId" type="String" mode="IN" optional="false">
          <type-validate>
@@ -119,8 +119,8 @@ under the License.
          </type-validate>
         </attribute>
     </service>
-    <service name="isStoreInventoryAvailableOrNotRequired" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="isStoreInventoryAvailableOrNotRequired" auth="false">
+    <service name="isStoreInventoryAvailableOrNotRequired" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="isStoreInventoryAvailableOrNotRequired" auth="false">
         <description>Checks if Store Inventory is Required</description>
         <attribute name="productStoreId" type="String" mode="IN" optional="false">
          <type-validate>
@@ -412,8 +412,8 @@ under the License.
         <description>Delete a ProductStoreGroupRollup</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
-    <service name="checkProductStoreGroupRollup" engine="simple"
-            location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="checkProductStoreGroupRollup" auth="true">
+    <service name="checkProductStoreGroupRollup" engine="groovy"
+            location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="checkProductStoreGroupRollup" auth="true">
         <description>Check if a productStoreGroupId with a primaryParentGroupId has related productStoreGroupRollup or for first ProductStoreGroupRollup on a ProductStoreGroup set relation on primaryParentGroupId</description>
         <attribute name="productStoreGroupId" mode="IN" type="String"/>
         <attribute name="primaryParentGroupId" mode="IN" type="String" optional="true"/>
@@ -422,8 +422,8 @@ under the License.
     </service>
 
     <!-- Permission Services -->
-    <service name="productStoreGenericPermission" engine="simple"
-        location="component://product/minilang/product/store/ProductStoreServices.xml" invoke="productStoreGenericPermission">
+    <service name="productStoreGenericPermission" engine="groovy"
+        location="component://product/groovyScripts/product/store/ProductStoreServices.groovy" invoke="productStoreGenericPermission">
         <implements service="permissionInterface"/>
     </service>
     <service name="createProductStoreGroupRole" default-entity-name="ProductStoreGroupRole" engine="entity-auto" invoke="create" auth="true">