svn commit: r1849800 - in /ofbiz/ofbiz-framework/trunk/applications/order: ./ config/ groovyScripts/quote/ groovyScripts/test/ minilang/quote/ minilang/test/ servicedef/ testdef/ testdef/data/ widget/ordermgr/

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

svn commit: r1849800 - in /ofbiz/ofbiz-framework/trunk/applications/order: ./ config/ groovyScripts/quote/ groovyScripts/test/ minilang/quote/ minilang/test/ servicedef/ testdef/ testdef/data/ widget/ordermgr/

nmalin
Author: nmalin
Date: Thu Dec 27 14:40:03 2018
New Revision: 1849800

URL: http://svn.apache.org/viewvc?rev=1849800&view=rev
Log:
Improved: Convert QuoteServices.xml mini lang to groovy -  Deprecate Mini Lang
(OFBIZ-10553) (OFBIZ-9350)
Convert all QuoteServices.xml and their xml test
Thanks to Antoine Ouvrard, Leila Mekika, Gil Portenseigne and Mathieu Lirzin for this team work to convert all function and tests case

Added:
    ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/
    ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy   (with props)
    ofbiz/ofbiz-framework/trunk/applications/order/testdef/QuoteTests.xml
      - copied, changed from r1849799, ofbiz/ofbiz-framework/trunk/applications/order/testdef/quotetests.xml
Removed:
    ofbiz/ofbiz-framework/trunk/applications/order/minilang/quote/QuoteServices.xml
    ofbiz/ofbiz-framework/trunk/applications/order/minilang/test/QuoteTests.xml
    ofbiz/ofbiz-framework/trunk/applications/order/testdef/quotetests.xml
Modified:
    ofbiz/ofbiz-framework/trunk/applications/order/config/OrderErrorUiLabels.xml
    ofbiz/ofbiz-framework/trunk/applications/order/config/OrderUiLabels.xml
    ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/quote/QuoteServices.groovy
    ofbiz/ofbiz-framework/trunk/applications/order/ofbiz-component.xml
    ofbiz/ofbiz-framework/trunk/applications/order/servicedef/services_quote.xml
    ofbiz/ofbiz-framework/trunk/applications/order/testdef/data/QuoteTestData.xml
    ofbiz/ofbiz-framework/trunk/applications/order/widget/ordermgr/QuoteWorkEffortForms.xml

Modified: ofbiz/ofbiz-framework/trunk/applications/order/config/OrderErrorUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/config/OrderErrorUiLabels.xml?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/config/OrderErrorUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/config/OrderErrorUiLabels.xml Thu Dec 27 14:40:03 2018
@@ -3181,6 +3181,14 @@
         <value xml:lang="zh">快速添加订单明细</value>
         <value xml:lang="zh-TW">快速添加訂單明細</value>
     </property>
+    <property key="OrderQuoteGetNextIdError">
+        <value xml:lang="en">Error during id creation</value>
+        <value xml:lang="fr">Erreur durant la création de l'ID.</value>
+    </property>
+    <property key="OrderQuoteIdAlreadyExists">
+        <value xml:lang="en">ERROR: Quote with ID ${quoteId} already exists</value>
+        <value xml:lang="fr">ERREUR : Un devis avec la référence ${quoteId} existe déjà.</value>
+    </property>
     <property key="OrderQuotePercent">
         <value xml:lang="ar">نسبة عرض السعر</value>
         <value xml:lang="de">Angebotsprozente</value>

Modified: ofbiz/ofbiz-framework/trunk/applications/order/config/OrderUiLabels.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/config/OrderUiLabels.xml?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/config/OrderUiLabels.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/config/OrderUiLabels.xml Thu Dec 27 14:40:03 2018
@@ -9014,6 +9014,7 @@
     </property>
     <property key="OrderOrderQuoteUpdatedSuccessfully">
         <value xml:lang="en">Quote updated successfully.</value>
+        <value xml:lang="fr">Le devis a été mis à jour avec succès.</value>
     </property>
     <property key="OrderOrderQuoteViewProfit">
         <value xml:lang="ar">مشاهدة ربحية عرض السعر</value>

Modified: ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/quote/QuoteServices.groovy
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/quote/QuoteServices.groovy?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/quote/QuoteServices.groovy (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/quote/QuoteServices.groovy Thu Dec 27 14:40:03 2018
@@ -17,13 +17,286 @@
  * under the License.
  */
 
-
+import org.apache.ofbiz.base.util.Debug
+import org.apache.ofbiz.base.util.UtilValidate
+import org.apache.ofbiz.base.util.UtilProperties
+import org.apache.ofbiz.base.util.UtilDateTime
+import org.apache.ofbiz.entity.GenericValue
+import org.apache.ofbiz.entity.condition.EntityCondition
+import org.apache.ofbiz.entity.condition.EntityOperator
+import org.apache.ofbiz.order.shoppingcart.ShoppingCart
+import org.apache.ofbiz.order.shoppingcart.ShoppingCartItem
+import org.apache.ofbiz.product.config.ProductConfigWorker
+import org.apache.ofbiz.product.config.ProductConfigWrapper
 import org.apache.ofbiz.service.ExecutionServiceException
+import org.apache.ofbiz.service.ModelService
 import org.apache.ofbiz.service.ServiceUtil
 
+String module = 'QuoteServices.groovy'
+
+/**
+ * Set the Quote status to ordered.
+ */
+def checkUpdateQuoteStatus() {
+    GenericValue quote = from('Quote').where(parameters).queryOne()
+    if (!quote) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteDoesNotExists', locale))
+    }
+    quote.statusId = 'QUO_ORDERED'
+    quote.store()
+    return success()
+}
+
+/**
+ * Get new Quote sequence Id.
+ */
+def getNextQuoteId() {
+    // Try to find PartyAcctgPreference for parameters.partyId, see if we need any special quote number sequencing
+    GenericValue partyAcctgPreference = from('PartyAcctgPreference').where('partyId', parameters.partyId).queryOne()
+    Debug.logInfo("In getNextQuoteId partyId is [${parameters.partyId}], partyAcctgPreference: ${partyAcctgPreference}",
+                  module)
+
+    Map customMethod = null
+    if (partyAcctgPreference) {
+        customMethod = partyAcctgPreference.getRelatedOne('QuoteCustomMethod', false)
+    } else {
+        Debug.logWarning("Acctg preference not defined for partyId [${parameters.partyId}]", module)
+    }
+
+    String customMethodName
+    if (customMethod?.customMethodName) {
+        customMethodName = customMethod.customMethodName
+    } else if (partyAcctgPreference?.oldQuoteSequenceEnumId == 'QTESQ_ENF_SEQ') {
+        // Retrieve service from deprecated enumeration
+        customMethodName = 'quoteSequenceEnforced'
+    }
+
+    String quoteId
+    if (customMethodName) {
+        Map serviceResult = run service: customMethodName, with: [
+            partyId: parameters.partyId,
+            partyAcctgPreference: partyAcctgPreference
+        ]
+        quoteId = serviceResult.quoteId
+    } else {
+        // Default to the default sequencing: QTESQ_STANDARD
+        quoteId = parameters.quoteId
+        if (quoteId) {
+            GenericValue quote = from('Quote').where('quoteId', quoteId).queryOne()
+            if (quote) {
+                // Return alert if ID already exists
+                return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteIdAlreadyExists', [quoteId: quoteId], locale))
+            } else {
+                // Check the provided ID
+                String errorMessage = UtilValidate.checkValidDatabaseId(quoteId)
+                if (errorMessage) {
+                    return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteGetNextIdError', locale) + errorMessage)
+                }
+            }
+        } else {
+            quoteId = delegator.getNextSeqId("Quote")
+        }
+    }
+
+    if (partyAcctgPreference) {
+        quoteId = "${partyAcctgPreference.quoteIdPrefix}${quoteId}"
+    }
+    return [successMessage: null, quoteId: quoteId]
+}
+
+/**
+ * Enforced Sequence (no gaps, per organization).
+ */
+def quoteSequenceEnforced() {
+    Debug.logInfo('In getNextQuoteId sequence enum Enforced', module)
+    GenericValue partyAcctgPreference = parameters.partyAcctgPreference
+    // This is sequential sequencing, we can't skip a number, also it must be a unique sequence per partyIdFrom
+
+    partyAcctgPreference.lastQuoteNumber = partyAcctgPreference.lastQuoteNumber ? partyAcctgPreference.lastQuoteNumber + 1: new Long('1')
+
+    partyAcctgPreference.store()
+    return [successMessage: null, quoteId: partyAcctgPreference.lastQuoteNumber]
+}
+
 /**
- * Ensures that a workEffort exist and create a QuoteWorkEffort.
+ * Create a new Quote.
  */
+def createQuote() {
+    if (parameters.partyId
+            && parameters.partyId != userLogin.partyId
+            && !security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCreateQuote', locale))
+    }
+
+    // Create new entity and create all the fields.
+    GenericValue newEntity = makeValue('Quote', parameters)
+    newEntity.statusId = parameters.statusId ?: 'QUO_CREATED'
+
+    // Create a non existing ID; if we have a productStoreId do it for the payToPartyId of that ProductStore
+    // according to PartyAcctgPreferences, otherwise get from standard sequence.
+    GenericValue productStore
+    if (parameters.productStoreId) {
+        productStore = from('ProductStore').where('productStoreId', parameters.productStoreId).queryOne()
+    }
+    if (productStore?.payToPartyId) {
+        Map serviceResult = run service: 'getNextQuoteId', with: [partyId: productStore.payToPartyId]
+        newEntity.quoteId = serviceResult.quoteId
+    } else {
+        newEntity.quoteId = delegator.getNextSeqId("Quote")
+    }
+
+    // Finally create the record (should not exist already).
+    newEntity.create()
+
+    // If the logged in partyId that is creating the quote is not equal to the partyId
+    // then we associate it to the quote as the request taker.
+    // This is not done if they are the same e.g. a logged in customer that is
+    // creating a quote for its own sake.
+    if (parameters.partyId != userLogin.partyId) {
+        Map serviceResult = run service: 'createQuoteRole', with: [
+            quoteId: newEntity.quoteId,
+            partyId: userLogin.partyId,
+            roleTypeId: 'REQ_TAKER'
+        ]
+    }
+
+    // Set ProductStore's payToPartyId as internal organisation for quote.
+    if (productStore?.payToPartyId) {
+        Map serviceResult = run service: 'createQuoteRole', with: [
+            quoteId: newEntity.quoteId,
+            partyId: productStore.payToPartyId,
+            roleTypeId: 'INTERNAL_ORGANIZATIO'
+        ]
+    }
+    def msg = UtilProperties.getMessage('OrderUiLabels', 'OrderOrderQuoteCreatedSuccessfully', locale)
+    return [successMessage: msg, quoteId: newEntity.quoteId]
+}
+
+/**
+ * Update an existing quote.
+ * @return quoteId
+ */
+def updateQuote() {
+    if (!security.hasEntityPermission('ORDERMGR', '_UPDATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunUpdateQuote', locale))
+    }
+    quoteId = parameters.quoteId
+    GenericValue quote = from('Quote').where('quoteId', quoteId).queryOne()
+
+    if (!parameters.statusId) {
+        parameters.statusId = quote.statusId
+    }
+
+    if (parameters.statusId != quote.statusId) {
+        // Check if the status change is a valid change.
+        GenericValue validChange = from("StatusValidChange").where('statusId', quote.statusId, 'statusIdTo', parameters.statusId).queryOne()
+
+        if (!validChange) {
+            Debug.logError("The status change from ${quote.statusId} to ${parameters.statusId} is not a valid change", module)
+            // FIXME : LABEL :D
+            return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteStatusChangeIsNotValid', locale))
+        }
+    }
+
+    quote.setNonPKFields(parameters)
+    quote.store()
+
+    return success(UtilProperties.getMessage('OrderUiLabels', 'OrderOrderQuoteUpdatedSuccessfully', locale))
+}
+
+/**
+ * Copy an existing Quote.
+ */
+def copyQuote() {
+    if (!security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCopyQuote', locale))
+    }
+    GenericValue quote = from('Quote').where(parameters).queryOne()
+    if (!quote) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteDoesNotExists', locale))
+    }
+    Map serviceResult = run service: 'createQuote', with: [*:quote, statusId: null]
+    String quoteIdTo = serviceResult.quoteId
+
+    // Copy quoteItems.
+    if ('Y' == parameters.copyQuoteItems) {
+        List quoteItems = quote.getRelated('QuoteItem', null, null, false)
+        for (GenericValue quoteItem : quoteItems) {
+            Map serviceContext = dctx.makeValidContext('createQuoteItem', ModelService.IN_PARAM, [*: quoteItem, quoteId: quoteIdTo, userLogin: userLogin])
+            serviceResult = dispatcher.runSync('createQuoteItem', serviceContext)
+            if (ServiceUtil.isError(serviceResult)) {
+                return serviceResult
+            }
+        }
+    }
+
+    // Copy quoteAdjustments.
+    if ('Y' == parameters.copyQuoteAdjustments) {
+        List quoteAdjustments = quote.getRelated('QuoteAdjustment', null, null, false)
+        for (GenericValue quoteAdjustement : quoteAdjustments) {
+            if (!quoteAdjustment.quoteItemSeqId) {
+                Map serviceContext = dctx.makeValidContext('createQuoteAdjustment', ModelService.IN_PARAM, [*: quoteAdjustement, quoteId: quoteIdTo, userLogin: userLogin])
+                serviceResult = dispatcher.runSync('createQuoteAdjustment', serviceContext)
+                if (ServiceUtil.isError(serviceResult)) {
+                    return serviceResult
+                }
+            }
+        }
+    }
+
+    // Copy quoteRoles.
+    if ('Y' == parameters.copyQuoteRoles) {
+        List quoteRoles = quote.getRelated('QuoteRole', null, null, false)
+        for (GenericValue quoteRole : quoteRoles) {
+            if (quoteRole.roleTypeId != 'REQ_TAKER') {
+                Map serviceContext = dctx.makeValidContext('createQuoteRole', ModelService.IN_PARAM, [*: quoteRole, quoteId: quoteIdTo, userLogin: userLogin])
+                serviceResult = dispatcher.runSync('createQuoteRole', serviceContext)
+                if (ServiceUtil.isError(serviceResult)) {
+                    return serviceResult
+                }
+            }
+        }
+    }
+
+    // Copy quoteAttributes.
+    if ('Y' == parameters.copyQuoteAttributes) {
+        List quoteAttributes = quote.getRelated('QuoteAttribute', null, null, false)
+        for (GenericValue quoteAttribute : quoteAttributes) {
+            Map serviceContext = dctx.makeValidContext('createQuoteAttribute', ModelService.IN_PARAM, [*: quoteAttribute, quoteId: quoteIdTo, userLogin: userLogin])
+            serviceResult = dispatcher.runSync('createQuoteAttribute', serviceContext)
+            if (ServiceUtil.isError(serviceResult)) {
+                return serviceResult
+            }
+        }
+    }
+
+    // Copy quoteCoefficients.
+    if ('Y' == parameters.copyQuoteCoefficients) {
+        List quoteCoefficients = quote.getRelated('QuoteCoefficient', null, null, false)
+        for (GenericValue quoteCoefficient : quoteCoefficients) {
+            Map serviceContext = dctx.makeValidContext('createQuoteCoefficient', ModelService.IN_PARAM, [*: quoteCoefficient, quoteId: quoteIdTo, userLogin: userLogin])
+            serviceResult = dispatcher.runSync('createQuoteCoefficient', serviceContext)
+            if (ServiceUtil.isError(serviceResult)) {
+                return serviceResult
+            }
+        }
+    }
+
+    // Copy quoteTerms.
+    if ('Y' == parameters.copyQuoteTerms) {
+        List quoteTerms = quote.getRelated('QuoteTerm', null, null, false)
+        for (GenericValue quoteTerm : quoteTerms) {
+            Map serviceContext = dctx.makeValidContext('createQuoteTerm', ModelService.IN_PARAM, [*: quoteTerm, quoteId: quoteIdTo, userLogin: userLogin])
+            serviceResult = dispatcher.runSync('createQuoteTerm', serviceContext)
+            if (ServiceUtil.isError(serviceResult)) {
+                return serviceResult
+            }
+        }
+    }
+    def msg = UtilProperties.getMessage('OrderUiLabels', 'OrderOrderQuoteCreatedSuccessfully', locale);
+    return [successMessage: msg, quoteId: quoteIdTo]
+}
+
 def ensureWorkEffortAndCreateQuoteWorkEffort() {
     String workEffortId = parameters.workEffortId
     if (!workEffortId) {
@@ -43,3 +316,402 @@ def ensureWorkEffortAndCreateQuoteWorkEf
     serviceResult.workEffortId = workEffortId
     return serviceResult
 }
+/**
+ * Create a new QuoteItem, calculate the quoteUnitPrice from config or productPrice if not given.
+ */
+def createQuoteItem() {
+    GenericValue quote = from('Quote').where(parameters).queryOne()
+    if (!quote) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteDoesNotExists', locale))
+    }
+    if (!security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCreateQuoteItem', locale))
+    }
+    GenericValue quoteItem = delegator.makeValidValue('QuoteItem', parameters)
+    if (!quoteItem.quoteItemSeqId) {
+        delegator.setNextSubSeqId(quoteItem, 'quoteItemSeqId', 5, 1)
+    }
+
+    if (!parameters.quoteUnitPrice && parameters.productId) {
+        GenericValue product = from('Product').where(parameters).cache().queryOne()
+        if (product?.isVirtual == 'Y') {
+            return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderCannotAddVirtualProductToQuote', locale))
+        }
+        if (product?.productTypeId?.startsWith('AGGREGATED')
+                && parameters.configId) {
+            ProductConfigWrapper configWrapper = ProductConfigWorker.loadProductConfigWrapper(delegator, dispatcher, parameters.configId, product.productId, null, null, null, null, locale, userLogin)
+            quoteItem.quoteUnitPrice = configWrapper.getTotalPrice()
+        } else {
+            Map serviceResult = run service: 'calculateProductPrice', with: [
+                product: product,
+                quantity: quoteItem.quantity,
+                amount: parameters.selectedAmount
+            ]
+            quoteItem.quoteUnitPrice = serviceResult.price
+        }
+    }
+    quoteItem.create()
+    return [successMessage: null, quoteId: quoteItem.quoteId, quoteItemSeqId: quoteItem.quoteItemSeqId]
+}
+
+/**
+ * Update an existing QuoteItem.
+ */
+def updateQuoteItem() {
+    if (!security.hasEntityPermission('ORDERMGR', '_UPDATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunUpdateQuoteItem', locale))
+    }
+
+    Map pksQuoteItem = [quoteId: parameters.quoteId, quoteItemSeqId: parameters.quoteItemSeqId]
+    GenericValue quoteItem = from('QuoteItem').where(pksQuoteItem).queryOne()
+    if (!quoteItem) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteItemDoesNotExists', locale))
+    }
+    quoteItem.setNonPKFields(parameters)
+    quoteItem.store()
+    return success()
+}
+
+/**
+ * Remove a QuoteItem.
+ */
+def removeQuoteItem() {
+    Map pksQuoteItem = [quoteId: parameters.quoteId, quoteItemSeqId: parameters.quoteItemSeqId]
+    GenericValue quoteItem = from('QuoteItem').where(pksQuoteItem).queryOne()
+    if (!quoteItem) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteItemDoesNotExists', locale))
+    }
+    delegator.removeByAnd('QuoteTerm', pksQuoteItem)
+    delegator.removeByAnd('QuoteAdjustment', pksQuoteItem)
+    quoteItem.remove()
+    return success()
+}
+
+/**
+ * Copy an existing QuoteItem.
+ */
+def copyQuoteItem() {
+    if (!security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCopyQuoteItem', locale))
+    }
+    GenericValue quoteItem = from('QuoteItem').where(parameters).queryOne()
+    if (!quoteItem) {
+        return error(UtilProperties.getMessage('OrderUiLabels', 'OrderQuoteItemDoesNotExists', locale))
+    }
+    Map input = [
+        userLogin: userLogin,
+        *: quoteItem,
+        quoteId: parameters.quoteIdTo,
+        quoteItemSeqId: parameters.quoteItemSeqIdTo
+    ]
+    if (!parameters.quoteIdTo && !parameters.quoteItemSeqIdTo) {
+        input.quoteItemSeqId = null
+    }
+    Map serviceResult = run service: 'createQuoteItem', with: input
+    if ('Y' == parameters.copyQuoteAdjustments) {
+        List quoteAdjustments = quoteItem.getRelated('QuoteAdjustment', null, null, false)
+        for (GenericValue quoteAdjustment : quoteAdjustments) {
+            Map serviceContext = dctx.makeValidContext('createQuoteAdjustment', ModelService.IN_PARAM, [*: quoteAdjustment, quoteId: parameters.quoteIdTo, quoteItemSeqId: parameters.quoteItemSeqIdTo, userLogin: userLogin])
+            serviceResult = dispatcher.runSync('createQuoteAdjustment', serviceContext)
+            if (ServiceUtil.isError(serviceResult)) {
+                return serviceResult
+            }
+        }
+    }
+
+    return success()
+}
+
+/**
+ * Create a new Quote and QuoteItem for a given CustRequest.
+ */
+def createQuoteAndQuoteItemForRequest() {
+    if (!security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCreateQuoteAndQuoteItemForRequest', locale))
+    }
+    GenericValue custRequest = from('CustRequest').where(parameters).queryOne()
+    GenericValue custRequestItem = from('CustRequestItem').where(parameters).queryOne()
+    if (!custRequest) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderErrorCustRequestWithIdDoesntExist', locale))
+    }
+
+    Map input = [
+        userLogin: userLogin,
+        *: parameters,
+        *: custRequest,
+        quoteTypeId: 'PROPOSAL',
+        partyId: custRequest.fromPartyId,
+        quoteName: custRequest.custRequestName,
+        currencyUomId: custRequest.maximumAmountUomId
+    ]
+    if (!input.statusId) {
+        input.statusId = 'QUO_CREATED'
+    }
+    Map serviceResult = run service: 'createQuote', with: input
+    String quoteId = serviceResult.quoteId
+
+    serviceResult = run service: 'createQuoteItem', with: [
+        *: custRequestItem,
+        comments: custRequestItem.story,
+        quoteId: quoteId
+    ]
+    String quoteItemSeqId = serviceResult.quoteItemSeqId
+
+    // copy the roles from the request to the quote
+    List custRequestParties = from('CustRequestParty').where(custRequestId: custRequest.custRequestId).queryList()
+    custRequestParties?.each { GenericValue custPartyRole ->
+        serviceResult = run service: 'createQuoteRole', with: [*: custPartyRole, quoteId: quoteId]
+    }
+
+    return [successMessage: null, quoteId: quoteId, quoteItemSeqId: quoteItemSeqId]
+}
+
+/**
+ * Create a Quote from a ShoppingCart.
+ */
+def createQuoteFromCart() {
+    ShoppingCart cart = (ShoppingCart) parameters.cart
+
+    Map createQuoteInMap = parameters
+    createQuoteInMap.partyId = cart.getPartyId()
+
+    if (createQuoteInMap.partyId
+            && createQuoteInMap.partyId != userLogin.partyId
+            && !security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCreateQuoteFromCart', locale))
+    }
+
+    createQuoteInMap.currencyUomId = cart.getCurrency()
+    createQuoteInMap.salesChannelEnumId = cart.getChannelType()
+
+    String orderType = cart.getOrderType()
+    if (orderType && orderType == 'SALES_ORDER') {
+        createQuoteInMap.productStoreId = cart.getProductStoreId()
+        createQuoteInMap.quoteTypeId = 'PRODUCT_QUOTE'
+    }
+    if (orderType && orderType == 'PURCHASE_ORDER') {
+        createQuoteInMap.quoteTypeId = 'PURCHASE_QUOTE'
+    }
+
+    createQuoteInMap.statusId = 'QUO_CREATED'
+
+    Map serviceResult = run service: 'createQuote', with: createQuoteInMap
+    GenericValue quote = from('Quote').where('quoteId', serviceResult.quoteId).queryOne()
+
+    cart.items()?.each { ShoppingCartItem item ->
+        Map createQuoteItemInMap = [userLogin: userLogin, locale: locale]
+        if (item.getIsPromo()) {
+            createQuoteItemInMap.isPromo = 'Y'
+        }
+        if (item.getConfigWrapper()) {
+            createQuoteItemInMap.configId = item.getConfigWrapper().getConfigId()
+        }
+
+        if (parameters.applyStorePromotions != 'N' || createQuoteItemInMap.isPromo != 'Y') {
+            createQuoteItemInMap.quoteId = quote.quoteId
+            createQuoteItemInMap.productId = item.getProductId()
+            createQuoteItemInMap.quantity = item.getQuantity()
+            createQuoteItemInMap.selectedAmount = item.getSelectedAmount()
+            createQuoteItemInMap.quoteUnitPrice = item.getBasePrice()
+            createQuoteItemInMap.comments = item.getItemComment()
+            createQuoteItemInMap.reservStart = item.getReservStart()
+            createQuoteItemInMap.reservLength = item.getReservLength()
+            createQuoteItemInMap.reservPersons = item.getReservPersons()
+
+            Map serviceQuoteItemResult = run service: 'createQuoteItem', with: createQuoteItemInMap
+            //and the quoteItemSeqId is assigned to the shopping cart item (as orderItemSeqId)
+            item.setOrderItemSeqId(serviceQuoteItemResult.quoteItemSeqId)
+        }
+        if (parameters.applyStorePromotions != 'N') {
+            cart.makeAllQuoteAdjustments()?.each { GenericValue adjustment ->
+                adjustment.quoteId = quote.quoteId
+                adjustment.quoteAdjustmentId = delegator.getNextSeqId('QuoteAdjustment')
+                adjustment.create()
+            }
+        }
+    }
+    return [successMessage: null, quoteId: quote.quoteId]
+}
+
+/**
+ * Create a Quote from a Shopping List.
+ */
+def createQuoteFromShoppingList() {
+    Map serviceResult = run service: 'loadCartFromShoppingList', with: parameters
+    serviceResult = run service: 'createQuoteFromCart', with: [
+        cart: serviceResult.shoppingCart,
+        applyStorePromotions: parameters.applyStorePromotions
+    ]
+    return [successMessage: null, quoteId: serviceResult.quoteId]
+}
+
+/**
+ * Auto update a QuoteItem price.
+ */
+def autoUpdateQuotePrice() {
+    if (!security.hasEntityPermission('ORDERMGR', '_UPDATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunAutoUpdateQuotePrice', locale))
+    }
+    GenericValue quoteItem = from('QuoteItem').where(parameters).queryOne()
+    if (!quoteItem) {
+        return error(UtilProperties.getMessage('OrderUiLabels', 'OrderQuoteItemDoesNotExists', locale))
+    }
+    if (parameters.manualQuoteUnitPrice) {
+        quoteItem.quoteUnitPrice = parameters.manualQuoteUnitPrice
+    } else if (parameters.defaultQuoteUnitPrice) {
+        quoteItem.quoteUnitPrice = parameters.defaultQuoteUnitPrice
+    }
+    quoteItem.store()
+    return success()
+}
+
+/**
+ * Create a Quote from a CustRequest.
+ */
+def createQuoteFromCustRequest() {
+    if (!security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunCreateQuoteFromCustRequest', locale))
+    }
+
+    GenericValue custRequest = from('CustRequest').where('custRequestId', parameters.custRequestId).queryOne()
+
+    // Error if request type not equals to RF_QUOTE or RF_PUR_QUOTE
+    if (custRequest.custRequestTypeId != 'RF_QUOTE' && custRequest.custRequestTypeId != 'RF_PUR_QUOTE') {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteNotARequest', locale))
+    }
+
+    Map createQuoteInMap = [
+        partyId: custRequest.fromPartyId,
+        productStoreId: custRequest.productStoreId,
+        salesChannelEnumId: custRequest.salesChannelEnumId,
+        quoteName: custRequest.custRequestName,
+        description: custRequest.description,
+        currencyUomId: custRequest.maximumAmountUomId,
+        statusId: 'QUO_CREATED'
+    ]
+
+    // Set the quoteType (product or purchase)
+    if (parameters.quoteTypeId) {
+        createQuoteInMap.quoteTypeId = parameters.quoteTypeId
+    } else if (custRequest.custRequestTypeId == 'RF_QUOTE') {
+        createQuoteInMap.quoteTypeId = 'PRODUCT_QUOTE'
+    } else {
+        createQuoteInMap.quoteTypeId = 'PURCHASE_QUOTE'
+    }
+
+    Map serviceResult = run service: 'createQuote', with: createQuoteInMap
+    String quoteId = serviceResult.quoteId
+
+    exprdCond = [
+            EntityCondition.makeCondition('custRequestId', custRequest.custRequestId),
+            EntityCondition.makeCondition('statusId', EntityOperator.NOT_EQUAL, 'CRQ_CANCELLED'),
+            EntityCondition.makeCondition('statusId', EntityOperator.NOT_EQUAL, 'CRQ_REJECTED')
+    ]
+    List custRequestItems = from('CustRequestItem').where(exprdCond).queryList()
+
+    custRequestItems.each { GenericValue custRequestItem ->
+        Map serviceCQIResult = run service: 'createQuoteItem', with: [*:custRequestItem, quoteId: quoteId]
+    }
+
+    // Roles
+    custRequest.getRelated('CustRequestParty', null, null, false)?.each { GenericValue custRequestParty ->
+        run service: 'createQuoteRole', with: [
+                quoteId: quoteId,
+                partyId: custRequestParty.partyId,
+                roleTypeId: custRequestParty.roleTypeId
+        ]
+    }
+
+    return [successMessage: null, quoteId: quoteId]
+}
+
+/**
+ * Auto create QuoteAdjustments.
+ */
+def autoCreateQuoteAdjustments() {
+    if (!security.hasEntityPermission('ORDERMGR', '_CREATE', userLogin)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderSecurityErrorToRunAutoCreateQuoteAdjustments', locale))
+    }
+    String quoteId = parameters.quoteId
+    GenericValue quote = from('Quote').where('quoteId', quoteId).queryOne()
+    if (!quote) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderQuoteDoesNotExists', locale))
+    }
+
+    // All existing promo quote items are removed.
+    quote.getRelated('QuoteItem', [isPromo: 'Y'], null, false)?.each { GenericValue quoteItem ->
+        run service: 'removeQuoteItem', with: [*: quoteItem]
+    }
+
+    // All existing auto quote adjustments are removed.
+    quote.getRelated('QuoteAdjustment', null, null, false)?.each { GenericValue quoteAdjustment ->
+        // Make sure this is not a manual adjustments
+        if (quoteAdjustment.productPromoId) {
+            run service: 'removeQuoteAdjustment', with: [*: quoteAdjustment]
+        }
+    }
+
+    Map serviceResult = run service: 'loadCartFromQuote', with: [*: parameters, applyQuoteAdjustments: false]
+    ShoppingCart shoppingCart = (ShoppingCart) serviceResult.shoppingCart
+
+    shoppingCart.items()?.each { ShoppingCartItem item ->
+        String orderItemSeqId = item.getOrderItemSeqId()
+        if (!orderItemSeqId) {
+            // This is a new (promo) item, a new quote item is created
+            serviceResult = run service: 'createQuoteItem', with: [
+                quoteId: quoteId,
+                quantity: item.getQuantity(),
+                productId: item.getProductId(),
+                isPromo: 'Y'
+            ]
+            // and the quoteItemSeqId is assigned to the shopping cart item (as orderItemSeqId).
+            item.setOrderItemSeqId(serviceResult.quoteItemSeqId)
+        }
+    }
+
+    // Set the quoteUnitPrice from the item basePrice.
+    quote.getRelated('QuoteItem', null, null, false)?.each { GenericValue quoteItem ->
+        if (!quoteItem.quoteUnitPrice || quoteItem.quoteUnitPrice == 0) {
+            ShoppingCartItem item = shoppingCart.findCartItem(quoteItem.quoteItemSeqId)
+            if (item) {
+                quoteItem.quoteUnitPrice = item.getBasePrice()
+                run service: 'updateQuoteItem', with: [*: quoteItem]
+            }
+        }
+    }
+    shoppingCart.makeAllQuoteAdjustments()?.each { GenericValue adjustment ->
+        adjustment.quoteId = quoteId
+        run service: 'createQuoteAdjustment', with: [*: adjustment]
+    }
+    return success()
+}
+
+/**
+ * Create a new Note associated with a Quote
+ */
+def createQuoteNote() {
+    // Passed in field will be noteInfo, which matches entity, but service expects field called note.
+    Map serviceContext = dctx.makeValidContext('createNote', ModelService.IN_PARAM, [*: parameters, note: parameters.noteInfo])
+    Map serviceResult = dispatcher.runSync('createNote', serviceContext)
+    if (ServiceUtil.isError(serviceResult)) {
+        return error(UtilProperties.getMessage('OrderErrorUiLabels', 'OrderProblemCreatingTheNoteNoNoteIdReturned', locale))
+    }
+    GenericValue quoteNote = makeValue('QuoteNote')
+    quoteNote.quoteId = parameters.quoteId
+    quoteNote.noteId = serviceResult.noteId
+    quoteNote.create()
+    return success()
+}
+
+/**
+ * Create a Quote adjustment
+ */
+def createQuoteAdjustment() {
+    GenericValue quoteAdjustment = makeValue('QuoteAdjustment', parameters)
+    quoteAdjustment.quoteAdjustmentId = delegator.getNextSeqId("QuoteAdjustment")
+    quoteAdjustment.createdByUserLogin = userLogin.userLoginId
+    quoteAdjustment.createdDate = UtilDateTime.nowTimestamp()
+    quoteAdjustment.create()
+    return [successMessage: null, quoteAdjustmentId: quoteAdjustment.quoteAdjustmentId]
+}
+

Added: ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy?rev=1849800&view=auto
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy (added)
+++ ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy Thu Dec 27 14:40:03 2018
@@ -0,0 +1,515 @@
+/*
+ * 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.testtools.GroovyScriptTestCase
+import org.apache.ofbiz.order.shoppingcart.ShoppingCart
+import org.apache.ofbiz.entity.util.EntityQuery
+import org.apache.ofbiz.entity.GenericValue
+import org.apache.ofbiz.service.ServiceUtil
+import org.apache.ofbiz.base.util.Debug
+
+import java.sql.Timestamp
+
+import static org.apache.ofbiz.base.util.UtilDateTime.nowTimestamp
+import static org.apache.ofbiz.entity.condition.EntityCondition.makeCondition
+import static org.apache.ofbiz.entity.condition.EntityComparisonOperator.GREATER_THAN_EQUAL_TO
+
+class QuoteTests extends GroovyScriptTestCase {
+
+    // Retrieves a particular login record.
+    private GenericValue getUserLogin(String userLoginId) {
+        GenericValue userLogin = EntityQuery.use(delegator)
+                .from('UserLogin').where(userLoginId: userLoginId).queryOne()
+        assert userLogin
+        return userLogin
+    }
+
+    // Test case for successfully creating a QuoteWorkEffort record.
+    void testCreateQuoteWorkEffort() {
+        GenericValue userLogin = getUserLogin('DemoRepStore')
+
+        def quoteId = '9001'
+        def workEffortId = '9007'
+
+        def input = [userLogin: userLogin, quoteId: quoteId, workEffortId: workEffortId]
+        Map serviceResult = dispatcher.runSync('ensureWorkEffortAndCreateQuoteWorkEffort', input)
+
+        // Confirm the service output parameters.
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert serviceResult.workEffortId == input.workEffortId
+
+        // Confirm the database changes.
+        GenericValue quoteWorkEffort = EntityQuery.use(delegator)
+                .from('QuoteWorkEffort').where(quoteId: quoteId, workEffortId: workEffortId).queryOne()
+        assert quoteWorkEffort
+    }
+
+    // Test case for unsuccessfully creating a QuoteWorkEffort record by attempting
+    // to use a quoteId and workEffortId that has already been used in an existing
+    // QuoteWorkEffortRecord.
+    void testCreateQuoteWorkEffortFail() {
+        // Use to confirm nothing has changed at the end of the test
+        Timestamp startTime = nowTimestamp()
+        GenericValue userLogin = getUserLogin('DemoRepStore')
+
+        def quoteId = '9001'
+        def workEffortId = '9007'
+
+        // Execute the service, note break-on-error is false so that the test
+        // itself doesn't fail and we also need a separate transaction so our
+        // lookup below doesn't fail due to the rollback
+        def input = [userLogin: userLogin, quoteId: quoteId, workEffortId: workEffortId]
+        Map serviceResult
+        try {
+            serviceResult = dispatcher.runSync('ensureWorkEffortAndCreateQuoteWorkEffort', input)
+        } catch (Exception e) {
+            serviceResult = ServiceUtil.returnError(e.toString())
+        }
+        assert ServiceUtil.isError(serviceResult)
+
+        // Confirm the database changes, in this case nothing should have changed
+        GenericValue quoteWorkEffort = EntityQuery.use(delegator)
+                .from('QuoteWorkEffort').where(
+                    makeCondition(quoteId: quoteId, workEffortId: workEffortId),
+                    makeCondition('lastUpdatedStamp', GREATER_THAN_EQUAL_TO, startTime)
+                ).queryOne()
+
+        assert !quoteWorkEffort
+    }
+
+    // Test case for CheckUpdateQuotestatus
+    void testCheckUpdateQuotestatus() {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                quoteId: '9001',
+        ]
+
+        Map serviceResult = dispatcher.runSync('checkUpdateQuoteStatus', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quote = EntityQuery.use(delegator).from('Quote').where(quoteId: '9001').queryOne()
+        assert quote.statusId == 'QUO_ORDERED'
+    }
+
+    // Test case for calling createQuoteWorkEffort without a workEffortId which
+    // triggers an ECA to create the WorkEffort first.
+    void testCreateWorkEffortAndQuoteWorkEffort() {
+        GenericValue userLogin = getUserLogin('flexadmin')
+
+        // Use the bare minimum inputs necessary to create the work effort as we
+        // aren't testing that service, only that it plays well as an ECA.
+        def input = [
+            currentStatusId: 'ROU_ACTIVE',
+            workEffortName: 'Test WorkEffort',
+            workEffortTypeId: 'ROUTING',
+            quoteId: '9000',
+            userLogin: userLogin
+        ]
+        Map serviceResult = dispatcher.runSync('ensureWorkEffortAndCreateQuoteWorkEffort', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert serviceResult.workEffortId
+
+        // Confirm that a matching WorkEffort was created.
+        GenericValue workEfforts = EntityQuery.use(delegator)
+                .from('WorkEffort').where(
+                    workEffortId: serviceResult.workEffortId,
+                    currentStatusId: input.currentStatusId,
+                    workEffortName: input.workEffortName,
+                    workEffortTypeId: input.workEffortTypeId
+                ).queryOne()
+        assert workEfforts
+
+        GenericValue quoteWorkEffort = EntityQuery.use(delegator)
+                .from('WorkEffort').where(
+                    quoteId: input.quoteId,
+                    workEffortId: serviceResult.workEffortId
+                ).queryOne()
+        assert quoteWorkEffort
+    }
+
+    // Test createQuote service
+    void testCreateQuote () {
+        GenericValue userLogin = getUserLogin('system')
+        Map input = [
+                userLogin: userLogin,
+                partyId: 'Company'
+        ]
+        Map serviceResult = dispatcher.runSync('createQuote', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert serviceResult.quoteId
+        GenericValue quote = EntityQuery.use(delegator).from('Quote').where(quoteId: serviceResult.quoteId).queryOne()
+        assert quote
+    }
+
+    // Test updateQuote service
+    void testUpdateQuote() {
+        GenericValue userLogin = getUserLogin('system')
+        Map input = [
+                userLogin: userLogin,
+                quoteId: '9000',
+                statusId: 'QUO_APPROVED'
+        ]
+        Map serviceResult = dispatcher.runSync('updateQuote', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quote = EntityQuery.use(delegator).from('Quote').where(quoteId: '9000').queryOne()
+        assert quote.statusId == 'QUO_APPROVED'
+
+        input.statusId = 'QUO_CREATED'
+        serviceResult = dispatcher.runSync('updateQuote', input)
+        assert ServiceUtil.isError(serviceResult)
+    }
+
+    // Test copyQuote service
+    void testCopyQuote() {
+        GenericValue userLogin = getUserLogin('system')
+        Map input = [
+                userLogin: userLogin,
+                quoteId: '9000'
+        ]
+        Map serviceResult = dispatcher.runSync('copyQuote', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert serviceResult.quoteId
+    }
+
+    // Test createQuoteItem service
+    void testCreateQuoteItem() {
+        GenericValue userLogin = getUserLogin('system')
+        Map input = [
+                userLogin: userLogin,
+                quoteId: '9000',
+                quoteItemSeqId: '00004',
+                productId: 'GZ-1001'
+        ]
+        Map serviceResult = dispatcher.runSync('createQuoteItem', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where(quoteId: '9000', quoteItemSeqId: '00004').queryOne()
+        assert quoteItem.quoteUnitPrice
+    }
+
+    // Test updateQuoteItem service
+    void testUpdateQuoteItem() {
+        GenericValue userLogin = getUserLogin('system')
+
+        Map input = [
+                userLogin: userLogin,
+                quoteId: '9000',
+                quoteItemSeqId: '00002',
+                productId: 'GZ-1001'
+        ]
+        Map serviceResult = dispatcher.runSync('updateQuoteItem', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where(quoteId: '9000', quoteItemSeqId: '00002').queryOne()
+        assert quoteItem.productId == 'GZ-1001'
+    }
+
+    // Test removeQuoteItem service
+    void testRemoveQuoteItem() {
+        GenericValue userLogin = getUserLogin('system')
+
+        Map input = [
+                userLogin: userLogin,
+                quoteId: '9000',
+                quoteItemSeqId: '00002'
+        ]
+        Map serviceResult = dispatcher.runSync('removeQuoteItem', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where(quoteId: '9000', quoteItemSeqId: '00002').queryOne()
+        assert !quoteItem
+        GenericValue quoteTerm = EntityQuery.use(delegator).from('QuoteTerm').where(quoteId: '9000', quoteItemSeqId: '00002', termTypeId: 'FIN_PAYMENT_DISC').queryOne()
+        assert !quoteTerm
+    }
+
+    // test create a Term
+    void testCreateQuoteTerm () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                termTypeId: 'FIN_PAYMENT_DISC',
+                quoteId: '9000',
+                quoteItemSeqId: '00001',
+                termValue: 40L,
+                termDays: 4L,
+                uomId: 'CNY',
+                description: 'create quoteTerm'
+        ]
+
+        Map serviceResult = dispatcher.runSync('createQuoteTerm', input)
+        List<GenericValue> terms = EntityQuery.use(delegator).from('QuoteTerm')
+                .where(termTypeId: 'FIN_PAYMENT_DISC', quoteId: '9000', quoteItemSeqId: '00001').queryList()
+
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert terms
+        GenericValue term = terms[0]
+        assert input.termTypeId == term.termTypeId
+        assert input.termValue == term.termValue
+        assert input.termDays == term.termDays
+        assert input.uomId == term.uomId
+        assert input.description == term.description
+    }
+
+    // Update a term.
+    void testUpdateQuoteTerm() {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+            termTypeId: 'FIN_PAYMENT_DISC',
+            quoteId: '9000',
+            quoteItemSeqId: '00002',
+            termValue: 30L,
+            termDays: 3L,
+            uomId: 'CNY',
+            description: 'update quoteterm',
+            userLogin: userLogin
+        ]
+        Map serviceResult = dispatcher.runSync('updateQuoteTerm', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+
+        // Confirm that a matching Quoteterm was updated
+        GenericValue quoteTerm = EntityQuery.use(delegator)
+                .from('QuoteTerm').where(
+                    termTypeId: input.termTypeId,
+                    quoteId: input.quoteId,
+                    quoteItemSeqId: input.quoteItemSeqId
+                ).queryOne()
+        assert quoteTerm
+        assert quoteTerm.termTypeId == input.termTypeId
+        assert quoteTerm.quoteId == input.quoteId
+        assert quoteTerm.quoteItemSeqId == input.quoteItemSeqId
+        assert quoteTerm.termValue == input.termValue
+        assert quoteTerm.termDays == input.termDays
+        assert quoteTerm.uomId == input.uomId
+        assert quoteTerm.description == input.description
+    }
+
+    // delete a term
+    void testDeleteQuoteTerm () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                termTypeId: 'FIN_PAYMENT_DISC',
+                quoteId: '9000',
+                quoteItemSeqId: '00003'
+        ]
+
+        Map serviceResult = dispatcher.runSync('deleteQuoteTerm', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteTerm = EntityQuery.use(delegator).from('QuoteTerm').where(termTypeId: serviceResult.termTypeId, quoteId: serviceResult.quoteId, quoteItemSeqId: serviceResult.quoteItemSeqId).queryOne()
+        assert !quoteTerm
+    }
+
+    // Create Quote Attribute
+    void testCreateQuoteAttribute () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                quoteId: '9001',
+                attrName: 'Test'
+        ]
+
+        Map serviceResult = dispatcher.runSync('createQuoteAttribute', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+    }
+
+    // Create Quote Coefficient
+    void testCreateQuoteCoefficient () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                quoteId: '9001',
+                coeffName: 'Test'
+        ]
+
+        Map serviceResult = dispatcher.runSync('createQuoteCoefficient', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+    }
+
+    // Get Next Quote Id
+    void testGetNextQuoteId () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                partyId: 'DemoCustomer-1'
+        ]
+
+        Map serviceResult = dispatcher.runSync('getNextQuoteId', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert serviceResult.quoteId
+    }
+
+    // Test Quote Sequence Enforced
+    void testQuoteSequenceEnforced() {
+        GenericValue userLogin = getUserLogin('system')
+        GenericValue partyAcctgPreference = EntityQuery.use(delegator)
+                .from('PartyAcctgPreference').where('partyId', 'DemoCustomer').queryOne()
+        Long lastQuoteNumber = partyAcctgPreference.lastQuoteNumber
+        if (!lastQuoteNumber) {
+            lastQuoteNumber = 0
+        }
+
+        def input = [
+                userLogin: userLogin,
+                partyId: 'DemoCustomer',
+                partyAcctgPreference: partyAcctgPreference
+        ]
+
+        Map serviceResult = dispatcher.runSync('quoteSequenceEnforced', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        assert serviceResult.quoteId == lastQuoteNumber +1L
+    }
+
+    // Copy Quote Item
+    void testCopyQuoteItem () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                quoteId: '9001',
+                quoteItemSeqId: '00001',
+                quoteIdTo: '9001',
+                quoteItemSeqIdTo: '00002',
+                copyQuoteAdjustments: 'Y'
+        ]
+
+        Map serviceResult = dispatcher.runSync('copyQuoteItem', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteAdjustment = EntityQuery.use(delegator).from('QuoteAdjustment').where('quoteId', '9001', 'quoteItemSeqId', '00002', 'quoteAdjustmentTypeId', 'SALES_TAX').queryFirst()
+        assert quoteAdjustment
+    }
+
+    // Test createQuoteAndQuoteItemForRequest
+    void testCreateQuoteAndQuoteItemForRequest () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                custRequestId: '9000',
+                custRequestItemSeqId: '00001'
+        ]
+        Map serviceResult = dispatcher.runSync('createQuoteAndQuoteItemForRequest', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where('quoteId', serviceResult.quoteId, 'custRequestItemSeqId', '00001').queryFirst()
+        assert quoteItem
+    }
+
+    // Test createQuoteFromCart
+    void testCreateQuoteFromCart() {
+        GenericValue userLogin = getUserLogin('system')
+        String productId = 'SV-1001'
+        String partyId = 'DemoCustomer'
+
+        ShoppingCart cart = new ShoppingCart(delegator, '9000', Locale.getDefault(), 'USD')
+        cart.setOrderType('SALES_ORDER')
+        cart.setChannelType('WEB_SALES_CHANNEL')
+        cart.setBillToCustomerPartyId(partyId)
+        cart.setPlacingCustomerPartyId(partyId)
+        cart.setShipToCustomerPartyId(partyId)
+        cart.setEndUserCustomerPartyId(partyId)
+        cart.setUserLogin(userLogin, dispatcher)
+        cart.addOrIncreaseItem(productId, null, BigDecimal.ONE, null, null, null,
+                null, null, null, null, 'DemoCatalog', null, null,
+                null, null, dispatcher)
+        cart.setDefaultCheckoutOptions(dispatcher)
+
+        def input = [
+            userLogin: userLogin,
+            cart: cart,
+            applyStorePromotions: 'Y'
+        ]
+        Map serviceResult = dispatcher.runSync('createQuoteFromCart', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where('quoteId', serviceResult.quoteId, 'productId', productId).queryFirst()
+        assert quoteItem
+        GenericValue quoteAdjustment = EntityQuery.use(delegator).from('QuoteAdjustment').where('quoteId', serviceResult.quoteId).queryFirst()
+        assert quoteAdjustment
+    }
+
+    // Test createQuoteFromShoppingList
+    void testCreateQuoteFromShoppingList() {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+            userLogin: userLogin,
+            shoppingListId: '9000',
+            applyStorePromotions: 'Y'
+        ]
+        Map serviceResult = dispatcher.runSync('createQuoteFromShoppingList', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where('quoteId', serviceResult.quoteId, 'productId', 'SV-1001').queryFirst()
+        assert quoteItem
+        GenericValue quoteAdjustment = EntityQuery.use(delegator).from('QuoteAdjustment').where('quoteId', serviceResult.quoteId).queryFirst()
+        assert quoteAdjustment
+    }
+
+    // Test autoUpdateQuotePrice
+    void testAutoUpdateQuotePrice() {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+            userLogin: userLogin,
+            quoteId: '9000',
+            quoteItemSeqId: '00001',
+            defaultQuoteUnitPrice: BigDecimal.valueOf(12)
+        ]
+        Map serviceResult = dispatcher.runSync('autoUpdateQuotePrice', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where('quoteId', '9000', 'quoteItemSeqId', '00001').queryOne()
+        assert quoteItem.quoteUnitPrice == 12
+    }
+
+    // Test createQuoteFromCustRequest
+    void testCreateQuoteFromCustRequest () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                custRequestId: '9000'
+        ]
+        Map serviceResult = dispatcher.runSync('createQuoteFromCustRequest', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue quoteItem = EntityQuery.use(delegator).from('QuoteItem').where('quoteId', serviceResult.quoteId, 'custRequestId', '9000').queryFirst()
+        assert quoteItem
+    }
+
+    // Test autoCreateQuoteAdjustments
+    void testAutoCreateQuoteAdjustments () {
+        GenericValue userLogin = EntityQuery.use(delegator)
+        .from('UserLogin').where(userLoginId: 'system').queryOne()
+        assert userLogin
+        GenericValue quote = EntityQuery.use(delegator)
+        .from('Quote').where(quoteId: '9001').queryOne()
+
+        def input = [
+            userLogin: userLogin,
+            quoteId: '9001'
+        ]
+        Map serviceResult = dispatcher.runSync('autoCreateQuoteAdjustments', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+        GenericValue promoQuoteAdjustment = EntityQuery.use(delegator).from('QuoteAdjustment').where('quoteId', '9001', 'quoteAdjustmentTypeId', 'PROMOTION_ADJUSTMENT').queryFirst()
+        assert promoQuoteAdjustment
+    }
+
+    // Create Quote Note
+    void testCreateQuoteNote () {
+        GenericValue userLogin = getUserLogin('system')
+        def input = [
+                userLogin: userLogin,
+                quoteId: '9001',
+                noteName: 'Test Note',
+                noteInfo: 'This is a test'
+        ]
+
+        Map serviceResult = dispatcher.runSync('createQuoteNote', input)
+        assert ServiceUtil.isSuccess(serviceResult)
+    }
+
+}

Propchange: ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy
------------------------------------------------------------------------------
    svn:keywords = Date Rev Author URL Id

Propchange: ofbiz/ofbiz-framework/trunk/applications/order/groovyScripts/test/QuoteTests.groovy
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: ofbiz/ofbiz-framework/trunk/applications/order/ofbiz-component.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/ofbiz-component.xml?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/ofbiz-component.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/ofbiz-component.xml Thu Dec 27 14:40:03 2018
@@ -51,7 +51,7 @@ under the License.
     <test-suite loader="main" location="testdef/FinAccountTests.xml"/>
     <test-suite loader="main" location="testdef/OrderTest.xml"/>
     <test-suite loader="main" location="testdef/CustRequestTests.xml"/>
-    <test-suite loader="main" location="testdef/quotetests.xml"/>
+    <test-suite loader="main" location="testdef/QuoteTests.xml"/>
     <test-suite loader="main" location="testdef/ShoppingListTests.xml"/>
     <test-suite loader="main" location="testdef/ShoppingCartTests.xml"/>
 

Modified: ofbiz/ofbiz-framework/trunk/applications/order/servicedef/services_quote.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/servicedef/services_quote.xml?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/servicedef/services_quote.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/servicedef/services_quote.xml Thu Dec 27 14:40:03 2018
@@ -25,35 +25,35 @@ under the License.
     <version>1.0</version>
 
     <!-- Quote -->
-    <service name="getNextQuoteId" engine="simple"
-        location="component://order/minilang/quote/QuoteServices.xml" invoke="getNextQuoteId">
+    <service name="getNextQuoteId" engine="groovy"
+        location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="getNextQuoteId">
         <description>Get the Next Quote ID According to Settings on the PartyAcctgPreference Entity for the given Party</description>
         <implements service="createQuote"/>
-        <attribute name="partyId" type="String" mode="IN" optional="false"/>
-        <attribute name="quoteId" type="String" mode="OUT" optional="false"/>
+        <attribute name="partyId" type="String" mode="IN"/>
+        <attribute name="quoteId" type="String" mode="OUT"/>
     </service>
 
-    <service name="quoteSequenceEnforced" engine="simple"
-        location="component://order/minilang/quote/QuoteServices.xml" invoke="quoteSequenceEnforced">
+    <service name="quoteSequenceEnforced" engine="groovy"
+        location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="quoteSequenceEnforced">
         <implements service="getNextQuoteId" optional="true"/>
         <attribute name="partyAcctgPreference" type="org.apache.ofbiz.entity.GenericValue" mode="IN"/>
         <override name="quoteId" type="Long" mode="OUT"/>
     </service>
 
-    <service name="createQuote" default-entity-name="Quote" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuote" auth="true">
+    <service name="createQuote" default-entity-name="Quote" engine="groovy"
+             location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuote" auth="true">
         <description>Create an Quote</description>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
         <auto-attributes include="pk" mode="OUT" optional="true"/>
     </service>
-    <service name="updateQuote" default-entity-name="Quote" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="updateQuote" auth="true">
+    <service name="updateQuote" default-entity-name="Quote" engine="groovy"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="updateQuote" auth="true">
         <description>Update a Quote</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="copyQuote" default-entity-name="Quote" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="copyQuote" auth="true">
+    <service name="copyQuote" default-entity-name="Quote" engine="groovy"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="copyQuote" auth="true">
         <description>Copy a Quote</description>
         <auto-attributes include="pk" mode="INOUT" optional="false"/>
         <attribute name="copyQuoteRoles" type="String" mode="IN" optional="true"/>
@@ -63,11 +63,12 @@ under the License.
         <attribute name="copyQuoteAdjustments" type="String" mode="IN" optional="true"/>
         <attribute name="copyQuoteTerms" type="String" mode="IN" optional="true"/>
     </service>
-    <service name="checkUpdateQuoteStatus" default-entity-name="Quote" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="checkUpdateQuoteStatus" auth="true">
+    <service name="checkUpdateQuoteStatus" default-entity-name="Quote" engine="groovy"
+             location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="checkUpdateQuoteStatus" auth="true">
         <description>Set the Quote status to ordered.</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
+
     <!-- QuoteRole  -->
     <service name="createQuoteRole" default-entity-name="QuoteRole" engine="entity-auto" invoke="create" auth="true">
         <description>Create a QuoteRole</description>
@@ -102,26 +103,26 @@ under the License.
         <auto-attributes include="pk" mode="IN" optional="false"/>
     </service>
     <!-- QuoteItem  -->
-    <service name="createQuoteItem" default-entity-name="QuoteItem" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteItem" auth="true">
+    <service name="createQuoteItem" default-entity-name="QuoteItem" engine="groovy"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteItem" auth="true">
         <description>Create a QuoteItem</description>
         <auto-attributes include="pk" mode="INOUT" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="updateQuoteItem" default-entity-name="QuoteItem" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="updateQuoteItem" auth="true">
+    <service name="updateQuoteItem" default-entity-name="QuoteItem" engine="groovy"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="updateQuoteItem" auth="true">
         <description>Update a QuoteItem</description>
         <auto-attributes include="pk" mode="IN" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="removeQuoteItem" default-entity-name="QuoteItem" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="removeQuoteItem" auth="true">
+    <service name="removeQuoteItem" default-entity-name="QuoteItem" engine="groovy"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="removeQuoteItem" auth="true">
         <description>Remove a QuoteItem</description>
         <auto-attributes include="pk" mode="INOUT" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="copyQuoteItem" default-entity-name="QuoteItem" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="copyQuoteItem" auth="true">
+    <service name="copyQuoteItem" default-entity-name="QuoteItem" engine="groovy"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="copyQuoteItem" auth="true">
         <description>Copy a QuoteItem</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
@@ -130,84 +131,98 @@ under the License.
         <attribute name="copyQuoteAdjustments" type="String" mode="IN" optional="true"/>
     </service>
     <!-- QuoteAttribute  -->
-    <service name="createQuoteAttribute" default-entity-name="QuoteAttribute" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteAttribute" auth="true">
+    <service name="createQuoteAttribute" default-entity-name="QuoteAttribute" engine="entity-auto" invoke="create" auth="true">
         <description>Create a QuoteAttribute</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_CREATE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="updateQuoteAttribute" default-entity-name="QuoteAttribute" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="updateQuoteAttribute" auth="true">
+    <service name="updateQuoteAttribute" default-entity-name="QuoteAttribute" engine="entity-auto" invoke="update" auth="true">
         <description>Update a QuoteAttribute</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_UPDATE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="removeQuoteAttribute" engine="simple" default-entity-name="QuoteAttribute"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="removeQuoteAttribute" auth="true">
+    <service name="removeQuoteAttribute" default-entity-name="QuoteAttribute" engine="entity-auto" invoke="delete" auth="true">
         <description>Remove a QuoteAttribute</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_DELETE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
     <!-- QuoteCoefficient  -->
-    <service name="createQuoteCoefficient" default-entity-name="QuoteCoefficient" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteCoefficient" auth="true">
+    <service name="createQuoteCoefficient" default-entity-name="QuoteCoefficient" engine="entity-auto" invoke="create" auth="true">
         <description>Create a QuoteCoefficient</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_CREATE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="updateQuoteCoefficient" default-entity-name="QuoteCoefficient" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="updateQuoteCoefficient" auth="true">
+    <service name="updateQuoteCoefficient" default-entity-name="QuoteCoefficient" engine="entity-auto" invoke="update" auth="true">
         <description>Update a QuoteCoefficient</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_UPDATE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="removeQuoteCoefficient" engine="simple" default-entity-name="QuoteCoefficient"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="removeQuoteCoefficient" auth="true">
+    <service name="removeQuoteCoefficient" default-entity-name="QuoteCoefficient" engine="entity-auto" invoke="delete" auth="true">
         <description>Remove a QuoteCoefficient</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_DELETE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
     <!-- Specialized Quote services -->
-    <service name="createQuoteAndQuoteItemForRequest" engine="simple" default-entity-name="QuoteItem"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteAndQuoteItemForRequest" auth="true">
+    <service name="createQuoteAndQuoteItemForRequest" engine="groovy" default-entity-name="QuoteItem"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteAndQuoteItemForRequest" auth="true">
         <description>Create a new Quote and Quote Item for a CustRequest</description>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
         <auto-attributes include="pk" mode="OUT" optional="true"/>
         <override name="custRequestId" optional="false"/>
     </service>
-    <service name="autoUpdateQuotePrice" engine="simple" default-entity-name="QuoteItem"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="autoUpdateQuotePrice" auth="true">
+    <service name="autoUpdateQuotePrice" engine="groovy" default-entity-name="QuoteItem"
+                location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="autoUpdateQuotePrice" auth="true">
         <description>Update the QuoteItem price with the passed value (if present) or automatically from the averageCost</description>
         <auto-attributes include="pk" mode="IN" optional="false"/>
         <attribute name="manualQuoteUnitPrice" type="BigDecimal" mode="IN" optional="true"/>
         <attribute name="defaultQuoteUnitPrice" type="BigDecimal" mode="IN" optional="true"/>
-        <!--<attribute name="averageCost" type="BigDecimal" mode="IN" optional="true"/>
-        <attribute name="costToPriceMult" type="BigDecimal" mode="IN" optional="true"/>-->
     </service>
-    <service name="autoCreateQuoteAdjustments" engine="simple" auth="true"
-            location="component://order/minilang/quote/QuoteServices.xml" invoke="autoCreateQuoteAdjustments">
+    <service name="autoCreateQuoteAdjustments" engine="groovy" auth="true"
+            location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="autoCreateQuoteAdjustments">
         <description>Remove all existing quote adjustments, recalc them and persist in QuoteAdjustment.</description>
         <attribute name="quoteId" type="String" mode="IN" optional="false"/>
     </service>
-    <service name="createQuoteAdjustment" default-entity-name="QuoteAdjustment" engine="simple" auth="true"
-            location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteAdjustment">
+    <service name="createQuoteAdjustment" default-entity-name="QuoteAdjustment" engine="groovy" auth="true"
+            location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteAdjustment">
         <description>Creates a new quote adjustment record</description>
         <auto-attributes mode="IN" include="nonpk" optional="true"/>
         <auto-attributes mode="OUT" include="pk" optional="false"/>
         <override name="quoteAdjustmentTypeId" optional="false"/>
         <override name="quoteId" optional="false"/>
+        <override name="description" allow-html="any"/>
     </service>
-    <service name="updateQuoteAdjustment" default-entity-name="QuoteAdjustment" engine="simple"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="updateQuoteAdjustment" auth="true">
+    <service name="updateQuoteAdjustment" default-entity-name="QuoteAdjustment" engine="entity-auto" invoke="update" auth="true">
         <description>Update a QuoteAdjustment</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_UPDATE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="true"/>
         <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
-    <service name="removeQuoteAdjustment" engine="simple" default-entity-name="QuoteAdjustment"
-                location="component://order/minilang/quote/QuoteServices.xml" invoke="removeQuoteAdjustment" auth="true">
+    <service name="removeQuoteAdjustment" default-entity-name="QuoteAdjustment" engine="entity-auto" invoke="delete" auth="true">
         <description>Remove a QuoteAdjustment</description>
+        <required-permissions join-type="AND">
+            <check-permission permission="ORDERMGR" action="_DELETE"/>
+        </required-permissions>
         <auto-attributes include="pk" mode="IN" optional="true"/>
-        <auto-attributes include="nonpk" mode="IN" optional="true"/>
     </service>
 
     <!--Duplicate the service createQuoteWorkEffort, the first inform the deprecation, the second override and work normally-->
@@ -242,22 +257,22 @@ under the License.
         <auto-attributes mode="IN" include="nonpk" optional="true"/>
         <attribute name="quoteId" type="String" mode="IN"/>
     </service>
-    <service name="createQuoteFromCart" engine="simple" auth="true"
-            location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteFromCart">
+    <service name="createQuoteFromCart" engine="groovy" auth="true"
+            location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteFromCart">
         <description>Creates a new quote from a shopping cart</description>
         <attribute name="cart" type="org.apache.ofbiz.order.shoppingcart.ShoppingCart" mode="IN" optional="false"/>
         <attribute name="applyStorePromotions" type="String" mode="IN" optional="true"/>
         <attribute name="quoteId" type="String" mode="OUT" optional="false"/>
     </service>
-    <service name="createQuoteFromShoppingList" engine="simple" auth="true"
-            location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteFromShoppingList">
+    <service name="createQuoteFromShoppingList" engine="groovy" auth="true"
+            location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteFromShoppingList">
         <description>Creates a new quote from a shopping list</description>
         <attribute name="shoppingListId" type="String" mode="IN" optional="false"/>
         <attribute name="applyStorePromotions" type="String" mode="IN" optional="true"/>
         <attribute name="quoteId" type="String" mode="OUT" optional="false"/>
     </service>
-    <service name="createQuoteFromCustRequest" engine="simple" auth="true"
-            location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteFromCustRequest">
+    <service name="createQuoteFromCustRequest" engine="groovy" auth="true"
+            location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteFromCustRequest">
         <description>Creates a new quote from a customer request</description>
         <attribute name="custRequestId" type="String" mode="IN" optional="false"/>
         <attribute name="quoteTypeId" type="String" mode="IN" optional="true"/>
@@ -290,8 +305,8 @@ under the License.
         <attribute name="quoteAdjustments" type="List" mode="IN" optional="true"/>
         <attribute name="quoteId" type="String" mode="OUT" optional="false"/>
     </service>
-    <service name="createQuoteNote" engine="simple"
-            location="component://order/minilang/quote/QuoteServices.xml" invoke="createQuoteNote" auth="true">
+    <service name="createQuoteNote" engine="groovy"
+            location="component://order/groovyScripts/quote/QuoteServices.groovy" invoke="createQuoteNote" auth="true">
         <description>Create a note item and associate with a quote</description>
         <attribute name="quoteId" type="String" mode="IN"/>
         <attribute name="noteInfo" type="String" mode="IN" allow-html="any"/>

Copied: ofbiz/ofbiz-framework/trunk/applications/order/testdef/QuoteTests.xml (from r1849799, ofbiz/ofbiz-framework/trunk/applications/order/testdef/quotetests.xml)
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/testdef/QuoteTests.xml?p2=ofbiz/ofbiz-framework/trunk/applications/order/testdef/QuoteTests.xml&p1=ofbiz/ofbiz-framework/trunk/applications/order/testdef/quotetests.xml&r1=1849799&r2=1849800&rev=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/testdef/quotetests.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/testdef/QuoteTests.xml Thu Dec 27 14:40:03 2018
@@ -25,8 +25,9 @@ under the License.
     <test-case case-name="loadQuoteTestData">
         <entity-xml action="load" entity-xml-url="component://order/testdef/data/QuoteTestData.xml"/>
     </test-case>
-
-    <test-case case-name="quote-tests">
-        <simple-method-test location="component://order/minilang/test/QuoteTests.xml"/>
+    <!-- <test-case case-name="quote-tests"> <simple-method-test location="component://order/minilang/test/QuoteTests.xml"/>
+        </test-case> -->
+     <test-case case-name="quoteTests">
+        <groovy-test-suite name="quoteTests" location="component://order/groovyScripts/test/QuoteTests.groovy"/>
     </test-case>
 </test-suite>

Modified: ofbiz/ofbiz-framework/trunk/applications/order/testdef/data/QuoteTestData.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/testdef/data/QuoteTestData.xml?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/testdef/data/QuoteTestData.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/testdef/data/QuoteTestData.xml Thu Dec 27 14:40:03 2018
@@ -20,6 +20,25 @@ under the License.
 
 <entity-engine-xml>
     <Quote quoteId="9000" quoteTypeId="PRODUCT_QUOTE" partyId="DemoCustomer" issueDate="2009-12-11 12:00:00.000" statusId="QUO_CREATED" currencyUomId="USD" productStoreId="9000" salesChannelEnumId="EMAIL_SALES_CHANNEL" validFromDate="2009-12-11 12:00:00.000" quoteName="Most competitive quote ever"/>
-    <QuoteItem quoteId="9000" quoteItemSeqId="00001" productId="GZ-1001"/>
+    <QuoteItem quoteId="9000" quoteItemSeqId="00001" productId="GZ-1001" quantity="1"/>
+    <QuoteItem quoteId="9000" quoteItemSeqId="00002" productId="GZ-2644" quantity="1"/>
+    <QuoteItem quoteId="9000" quoteItemSeqId="00003" productId="GZ-1001" quantity="1"/>
     <WorkEffort workEffortId="9007" workEffortTypeId="TASK" workEffortName="Quote WorkEffort"/>
+    <QuoteTerm quoteId="9000" quoteItemSeqId="00002" termTypeId="FIN_PAYMENT_DISC" termValue="10" termDays="5" uomId="CNY" description="Update test term"/>
+    <QuoteTerm quoteId="9000" quoteItemSeqId="00003" termTypeId="FIN_PAYMENT_DISC" termValue="5" termDays="7" uomId="CNY" description="Delete test term"/>
+    <Quote quoteId="9001" quoteTypeId="PRODUCT_QUOTE" partyId="DemoCustomer" issueDate="2009-12-11 12:00:00.000" statusId="QUO_CREATED" currencyUomId="USD" productStoreId="9000" salesChannelEnumId="EMAIL_SALES_CHANNEL" validFromDate="2009-12-11 12:00:00.000" quoteName="Most competitive quote ever"/>
+    <QuoteItem quoteId="9001" quoteItemSeqId="00001" productId="GZ-1005" quantity="1"/>
+    <QuoteAdjustment quoteAdjustmentId="9001" quoteId="9001" quoteItemSeqId="00001" quoteAdjustmentTypeId="SALES_TAX" amount="2"/>
+    <PartyAcctgPreference partyId="DemoCustomer" quoteSeqCustMethId="QUOTE_HOOK_ENF_SEQ"/>
+    <CustRequest custRequestId="9000" custRequestDate="2008-07-28 09:45:31.928" custRequestTypeId="RF_QUOTE" statusId="CRQ_SUBMITTED" fromPartyId="DemoCustomer" priority="9" custRequestName="Customer Request Usage" description="Demo CustRequest" productStoreId="9000"/>
+    <CustRequestItem custRequestId="9000" statusId="CRQ_SUBMITTED" custRequestItemSeqId="00001" productId="GZ-1001" story="This can be the longer story of an item on the customer request."/>
+    <ShoppingList shoppingListId="9000" shoppingListTypeId="SLT_WISH_LIST" productStoreId="9000" currencyUom="USD" isActive="Y" isPublic="N" listName="Test Shopping List" partyId="DemoCustomer"/>
+    <ShoppingListItem shoppingListId="9000" shoppingListItemSeqId="00001" productId="SV-1001" quantity="1.000000"/>
+    <ProductPromo productPromoId="9010" promoName="Test Percent off product set " promoText="20% off any one item, either GZ-1005 (.NIT Gizmo) or GZ-1006 (Open Gizmo) with a limit of 1 per order" userEntered="Y" showToCustomer="Y" requireCode="N" useLimitPerOrder="1" createdDate="2001-05-13 12:00:00.0" createdByUserLogin="admin" lastModifiedDate="2001-05-13 12:00:00.0" lastModifiedByUserLogin="admin"/>
+
+    <ProductPromoRule productPromoId="9010" productPromoRuleId="01" ruleName="Test Percent off rule"/><ProductPromoAction productPromoId="9010" productPromoRuleId="01" productPromoActionSeqId="01" productPromoActionEnumId="PROMO_PROD_DISC" orderAdjustmentTypeId="PROMOTION_ADJUSTMENT" quantity="1.0" amount="20.0"/>
+    <ProductPromoProduct productPromoId="9010" productPromoRuleId="01" productPromoActionSeqId="01" productPromoCondSeqId="_NA_" productId="GZ-1005" productPromoApplEnumId="PPPA_INCLUDE"/>
+    <ProductPromoProduct productPromoId="9010" productPromoRuleId="_NA_" productPromoActionSeqId="_NA_" productPromoCondSeqId="_NA_" productId="GZ-1006" productPromoApplEnumId="PPPA_INCLUDE"/>
+    <ProductStorePromoAppl productStoreId="9000" productPromoId="9010" fromDate="2001-05-13 12:00:00.0" sequenceNum="5"/>
+
 </entity-engine-xml>

Modified: ofbiz/ofbiz-framework/trunk/applications/order/widget/ordermgr/QuoteWorkEffortForms.xml
URL: http://svn.apache.org/viewvc/ofbiz/ofbiz-framework/trunk/applications/order/widget/ordermgr/QuoteWorkEffortForms.xml?rev=1849800&r1=1849799&r2=1849800&view=diff
==============================================================================
--- ofbiz/ofbiz-framework/trunk/applications/order/widget/ordermgr/QuoteWorkEffortForms.xml (original)
+++ ofbiz/ofbiz-framework/trunk/applications/order/widget/ordermgr/QuoteWorkEffortForms.xml Thu Dec 27 14:40:03 2018
@@ -47,7 +47,7 @@ under the License.
         </field>
     </form>
 
-    <form name="AddQuoteWorkEffort" extends="EditWorkEffort" extends-resource="component://workeffort/widget/WorkEffortForms.xml" target="/ordermgr/control/ensureWorkEffortAndCreateQuoteWorkEffort" target-type="inter-app" title="" type="single"
+    <form name="AddQuoteWorkEffort" extends="EditWorkEffort" extends-resource="component://workeffort/widget/WorkEffortForms.xml" target="/ordermgr/control/createQuoteWorkEffort" target-type="inter-app" title="" type="single"
         header-row-style="header-row" default-table-style="basic-table">
 
         <field name="quoteId" map-name="parameters"><display/></field>