diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e3edbd4c8..a3819fbc12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,7 @@ on: branches: - develop - v3 + - '4.3' pull_request: permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index 60f0c25b6a..dd6b2bd460 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,39 @@ # Release Notes for Craft Commerce -## Unreleased - -- Variant min and max quantity fields now validate together logically. ([#3234](https://github.com/craftcms/commerce/issues/3234)) -- Added the “Product Type” product condition rule. ([#3209](https://github.com/craftcms/commerce/issues/3209)) -- Fixed a bug where `hasMatchingAddresses()` was incorrectly returning `false`. ([#3183](https://github.com/craftcms/commerce/issues/3183)) -- Fixed a bug where changing a user’s email would cause extra user elements to be created. ([#3138](https://github.com/craftcms/commerce/issues/3138)) -- Fixed a bug where related sales were showing when creating a new product. +## 4.3.0 - Unreleased + +- Sales and discounts now support using related entries in their matching item conditions. ([#3134](https://github.com/craftcms/commerce/issues/3134), [#2717](https://github.com/craftcms/commerce/issues/2717)) +- It’s now possible to query products by shipping category and tax category. ([#3219](https://github.com/craftcms/commerce/issues/3219)) +- Guest customers registering during checkout now have their addresses saved to their account. ([#3203](https://github.com/craftcms/commerce/pull/3203)) +- Product conditions can now have “Product Type”, “Variant SKU”, “Variant Has Unlimited Stock”, “Variant Price”, and “Variant Stock” rules. ([#3209](https://github.com/craftcms/commerce/issues/3209)) +- Improved the performance of discount recalculation. +- Improved the performance of the `commerce/upgrade` command. ([#3208](https://github.com/craftcms/commerce/pull/3208)) +- Added the `commerce/cart/forget-cart` action. ([#3206](https://github.com/craftcms/commerce/issues/3206)) +- The `commerce/cart/update-cart` action now accepts `firstName` and `lastName` address parameters. ([#3015](https://github.com/craftcms/commerce/issues/3015)) +- Added `craft\commerce\controllers\OrdersController::EVENT_MODIFY_PURCHASABLES_TABLE_QUERY`. ([#3198](https://github.com/craftcms/commerce/pull/3198)) +- Added `craft\commerce\elements\Order::$orderCompletedEmail`. ([#3138](https://github.com/craftcms/commerce/issues/3138)) +- Added `craft\commerce\elements\db\ProductQuery::$shippingCategoryId`. +- Added `craft\commerce\elements\db\ProductQuery::$taxCategoryId`. +- Added `craft\commerce\elements\db\ProductQuery::shippingCategory()`. +- Added `craft\commerce\elements\db\ProductQuery::shippingCategoryId()`. +- Added `craft\commerce\elements\db\ProductQuery::taxCategory()`. +- Added `craft\commerce\elements\db\ProductQuery::taxCategoryId()`. +- Added `craft\commerce\models\Discount::hasBillingAddressCondition()`. +- Added `craft\commerce\models\Discount::hasCustomerCondition()`. +- Added `craft\commerce\models\Discount::hasOrderCondition()`. +- Added `craft\commerce\models\Discount::hasShippingAddressCondition()`. +- Deprecated payment source creation via the `commerce/subscriptions/subscribe` action. +- Deprecated `craft\commerce\elements\Order::setEmail()`. `Order::setCustomer()` should be used instead. +- Removed the `htmx` option from the`commerce/example-templates` command. +- Removed the `color` option from the`commerce/example-templates` command. +- Added `craft\commerce\events\ModifyPurchasablesTableQueryEvent`. ([#3198](https://github.com/craftcms/commerce/pull/3198)) +- Fixed a bug where products/variants could be saved with a minimum quantity that was set higher than the maximum quantity. ([#3234](https://github.com/craftcms/commerce/issues/3234)) +- Fixed a bug where `craft\commerce\elements\Order::hasMatchingAddresses()` could incorrectly return `false`. ([#3183](https://github.com/craftcms/commerce/issues/3183)) +- Fixed a bug where changing a user’s email could cause additional user elements to be created. ([#3138](https://github.com/craftcms/commerce/issues/3138)) +- Fixed a bug where related sales were displaying when creating a new product. - Fixed a bug where Commerce wasn’t invoking `craft\services\Elements::EVENT_AUTHORIZE_*` event handlers. - Fixed a bug where discounts’ per user usage counters weren’t getting migrated properly when upgrading to Commerce 4. -- Fixed a bug where address changes weren’t being synced to carts using them as a source. ([#3178](https://github.com/craftcms/commerce/issues/3178)) -- Added `craft\commerce\services\Orders::afterSaveAddressHandler()`. -- Added `craft\commerce\elements\Order::$orderCompletedEmail`. ([#3138](https://github.com/craftcms/commerce/issues/3138)) -- Added the `commerce/cart/forget-cart` action. ([#3206](https://github.com/craftcms/commerce/issues/3206)) -- The `commerce/cart/update` action now accepts `firstName` and `lastName` in address params. ([#3015](https://github.com/craftcms/commerce/issues/3015)) -- Removed the htmx option from the`commerce/example-templates` command. -- Removed the color option from the`commerce/example-templates` command. +- Fixed a bug where address changes weren’t being synced to carts that were using them. ([#3178](https://github.com/craftcms/commerce/issues/3178)) - Fixed an XSS vulnerability. ## 4.2.11 - 2023-06-05 diff --git a/example-templates/dist/shop/_private/address/list.twig b/example-templates/dist/shop/_private/address/list.twig index e2c2e439e3..f73974af96 100644 --- a/example-templates/dist/shop/_private/address/list.twig +++ b/example-templates/dist/shop/_private/address/list.twig @@ -2,84 +2,74 @@ {% set selectable = selectable ?? false %} {% set primaryBillingAddressId = primaryBillingAddressId ?? null %} {% set primaryShippingAddressId = primaryShippingAddressId ?? null %} -{% set cardWidth = cardWidth ?? 'md:w-1/2' %} {% set showDelete = showDelete ?? false %} {% set showAdd = showAdd ?? false %} {% set addUrl = '/shop/customer/addresses/edit?redirect=' ~ craft.app.request.fullPath %} {% if currentUser %} - {% if addresses|length %} -
- {% for address in addresses %} - {% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %} -
- {% tag selectable ? 'label' : 'div' with { - class: 'block relative address-select js-address-select border-blue-300 border-b-2 px-6 py-4 rounded-md shadow-md hover:shadow-lg', - data: { - 'address-id': address.id, - }, - } %} - + {% if addresses|length %} +
+ {% for address in addresses %} + {% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %} +
+ {% tag selectable ? 'label' : 'div' with { + class: 'block relative address-select js-address-select', + data: { + 'address-id': address.id, + }, + } %} + {% if selectable %} - {{ input('radio', name ~ 'Id', address.id, { - data: { - 'model-name': name, - }, - checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)), - }) }} + {{ input('radio', name ~ 'Id', address.id, { + data: { + 'model-name': name, + }, + checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)), + }) }} {% endif %} - + {{ address|address }} - + {{- 'Edit'|t -}} {% if showDelete and not selectable %} -
+ {{ csrfInput() }} - {{ actionInput('users/delete-address') }} - {{ redirectInput('shop/customer/addresses') }} - {{ hiddenInput('addressId', address.id) }} - {{ tag('button', { - type: 'submit', - class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white', - text: 'Delete'|t - }) }} + {{ actionInput('users/delete-address') }} + {{ redirectInput('shop/customer/addresses') }} + {{ hiddenInput('addressId', address.id) }} + {{ tag('button', { + type: 'submit', + class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white', + text: 'Delete'|t + }) }}
{% endif %}
- - {% if primaryBillingAddressId == address.id or primaryShippingAddressId == address.id %} - - {% if primaryBillingAddressId == address.id %} - 💳 - {% endif %} - {% if primaryShippingAddressId == address.id %} - 📦 - {% endif %} - + {% endtag %} +
+ {% endfor %} + {% if showAdd %} + +
+ Add Address +
+
{% endif %} - {% endtag %}
- {% endfor %} -
- {% endif %} - {% if showAdd %} -
- {{ 'Add address'|t }} -
- {% endif %} + {% endif %} {% endif %} {% js %} -const addressDeletes = document.querySelectorAll('.js-address-delete'); -for (let i = 0; i < addressDeletes.length; i++) { - addressDeletes[i].addEventListener('submit', ev => { - if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) { - ev.preventDefault(); + const addressDeletes = document.querySelectorAll('.js-address-delete'); + for (let i = 0; i < addressDeletes.length; i++) { + addressDeletes[i].addEventListener('submit', ev => { + if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) { + ev.preventDefault(); + } + }); } - }); -} -{% endjs %} \ No newline at end of file +{% endjs %} diff --git a/example-templates/dist/shop/_private/layouts/includes/header.twig b/example-templates/dist/shop/_private/layouts/includes/header.twig new file mode 100644 index 0000000000..4c479235f4 --- /dev/null +++ b/example-templates/dist/shop/_private/layouts/includes/header.twig @@ -0,0 +1,18 @@ +
+
+

+ + {{- siteName ~ ' Shop' -}} + +

+ {% if craft.app.sites.getAllSites()|length > 1 %} +
+ +
+ {% endif %} +
+
\ No newline at end of file diff --git a/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig b/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig index 032fe3a4ef..4c0b17c8d7 100644 --- a/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig +++ b/example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig @@ -23,6 +23,10 @@ Outputs the checkout progress navigation using the request path and included `ch label: 'Payment Method', url: 'shop/checkout/payment-method' }, + { + label: 'Options', + url: 'shop/checkout/options' + }, { label: 'Payment', url: 'shop/checkout/payment' diff --git a/example-templates/dist/shop/_private/layouts/includes/nav-customer.twig b/example-templates/dist/shop/_private/layouts/includes/nav-customer.twig new file mode 100644 index 0000000000..971790ccf1 --- /dev/null +++ b/example-templates/dist/shop/_private/layouts/includes/nav-customer.twig @@ -0,0 +1,40 @@ + +{# +Outputs the site’s global main navigation based on path and included `pages` array. + +@var cart \craft\commerce\elements\Order +@var currentUser \craft\elements\User +#} +{% set pages = [ + { + label: 'My Orders', + url: 'shop/customer/orders' + }, + { + label: 'My Addresses', + url: 'shop/customer/addresses' + }, + { + label: 'My Stored Cards', + url: 'shop/customer/cards' + } +] %} +{% set currentPath = craft.app.request.pathInfo %} +{# Show the nav if we in a customers page #} +{% if currentPath in pages|column('url') %} +{% set activeClasses = 'bg-gray-200' %} +
+
+ +
+
+{% endif %} diff --git a/example-templates/dist/shop/_private/layouts/includes/nav-main.twig b/example-templates/dist/shop/_private/layouts/includes/nav-main.twig index efb9299d82..21c6374319 100644 --- a/example-templates/dist/shop/_private/layouts/includes/nav-main.twig +++ b/example-templates/dist/shop/_private/layouts/includes/nav-main.twig @@ -21,6 +21,7 @@ Outputs the site’s global main navigation based on path and included `pages` a ] %} {% set currentPath = craft.app.request.pathInfo %} {% set activeClasses = 'bg-white' %} +{# Show the nav if we are not in a checkout page #} {% if 'checkout' not in currentPath %}
diff --git a/example-templates/dist/shop/_private/layouts/index.twig b/example-templates/dist/shop/_private/layouts/index.twig index ed9a12222a..f74daf7a33 100644 --- a/example-templates/dist/shop/_private/layouts/index.twig +++ b/example-templates/dist/shop/_private/layouts/index.twig @@ -28,28 +28,22 @@ Common, top-level layout template. {% set flashNotice = craft.app.session.getFlash('notice') %} {% set flashError = craft.app.session.getFlash('error') %} -
-
-
-

- - {{- siteName ~ ' Shop' -}} - -

- {% if craft.app.sites.getAllSites()|length > 1 %} -
- -
- {% endif %} -
-
+{% macro docs(text, link) %} + + {{ tag('a', { + text: 'ℹ︎ ' ~ text, + href: link, + class: 'text-gray-400 hover:text-gray-600 hover:underline', + target: '_blank', + }) }} + +{% endmacro %} +
+ {{ include('shop/_private/layouts/includes/header') }} {{ include('shop/_private/layouts/includes/nav-main') }} {{ include('shop/_private/layouts/includes/nav-checkout') }} + {{ include('shop/_private/layouts/includes/nav-customer') }}
diff --git a/example-templates/dist/shop/cart/index.twig b/example-templates/dist/shop/cart/index.twig index bd9ace0f52..f29aa0e14c 100644 --- a/example-templates/dist/shop/cart/index.twig +++ b/example-templates/dist/shop/cart/index.twig @@ -89,7 +89,7 @@ Outputs cart. {{ input('text', 'lineItems[' ~ item.id ~ '][note]', item.note, { id: 'lineitem-note-' ~ item.id, - class: 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded', + class: 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded w-full', size: 20, placeholder: 'My Note'|t }) }} diff --git a/example-templates/dist/shop/cart/load.twig b/example-templates/dist/shop/cart/load.twig index b8872a779a..ff95d1c12e 100644 --- a/example-templates/dist/shop/cart/load.twig +++ b/example-templates/dist/shop/cart/load.twig @@ -21,9 +21,9 @@ Outputs form for collecting a cart number to be loaded. {{- 'Cart Number'|t -}}
- {{ input('text', 'number', 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded', { + {{ input('text', 'number', null, { id: 'number', - class: ['w-full', ''], + class: ['w-full', 'border border-gray-300 hover:border-gray-500 px-4 py-2 leading-tight rounded'], placeholder: '7e89fh0ew8034…' }) }}
diff --git a/example-templates/dist/shop/checkout/_includes/base-payment-form-styles.twig b/example-templates/dist/shop/checkout/_includes/base-payment-form-styles.twig deleted file mode 100644 index 43a6ccb2e8..0000000000 --- a/example-templates/dist/shop/checkout/_includes/base-payment-form-styles.twig +++ /dev/null @@ -1,11 +0,0 @@ - \ No newline at end of file diff --git a/example-templates/dist/shop/checkout/options.twig b/example-templates/dist/shop/checkout/options.twig new file mode 100644 index 0000000000..2c845d937d --- /dev/null +++ b/example-templates/dist/shop/checkout/options.twig @@ -0,0 +1,108 @@ +{% extends 'shop/_private/layouts' %} + +{# @var cart \craft\commerce\elements\Order #} + +{% if cart is not defined %} + {% set cart = craft.commerce.carts.cart %} +{% endif %} + +{# + We can skip the "options" step if the customer is a logged in user and + the addresses have come from the address book +#} +{% if currentUser and cart.sourceBillingAddressId and cart.sourceShippingAddressId %} + {% redirect 'shop/checkout/payment' %} +{% endif %} + +{% if not cart.getCustomer() %} + {% redirect 'shop/checkout/email' %} +{% endif %} + +{% if not cart.gateway %} + {% redirect 'shop/checkout/payment-method' %} +{% endif %} + +{% block main %} +
+
+ +

+ {{- 'Options'|t -}} +

+ +
+ {{ csrfInput() }} + {{ actionInput('commerce/cart/update-cart') }} + {{ redirectInput(siteUrl('shop/checkout/payment')) }} + {{ successMessageInput('Options saved.') }} + + {% set user = cart.email ? craft.users.email(cart.email).one() : null %} + {% if not user or not user.getIsCredentialed() %} +
+ +
+ {{ _self.docs('Registering a user on order complete.', 'https://craftcms.com/docs/commerce/4.x/customers.html#registration-at-checkout') }} +
+
+ {% endif %} + + {% set saveAddressCheckboxesShown = false %} + {% if currentUser and cart.billingAddressId and not cart.sourceBillingAddressId %} + {% set saveAddressCheckboxesShown = true %} +
+ +
+ {% endif %} + + {% if currentUser and cart.shippingAddressId and not cart.sourceShippingAddressId %} +
+ {% set saveAddressCheckboxesShown = true %} + +
+ {% endif %} + + {% if saveAddressCheckboxesShown %} +
+ {{ _self.docs('Saving addresses on order complete.', '#') }} +
+ {% endif %} + +
+ {{ tag('button', { + type: 'submit', + name: 'submit', + class: 'cursor-pointer rounded px-4 py-2 inline-block bg-blue-500 hover:bg-blue-600 text-white hover:text-white', + text: 'Next'|t + }) }} +
+
+
+ +
+ {{ include('shop/checkout/_includes/order-summary', { + showShippingAddress: true, + showShippingMethod: true + }) }} +
+
+{% endblock %} diff --git a/example-templates/dist/shop/checkout/payment-method.twig b/example-templates/dist/shop/checkout/payment-method.twig index aea54601a8..5a55567cc6 100644 --- a/example-templates/dist/shop/checkout/payment-method.twig +++ b/example-templates/dist/shop/checkout/payment-method.twig @@ -20,7 +20,7 @@
{{ csrfInput() }} {{ actionInput('commerce/cart/update-cart') }} - {{ redirectInput(siteUrl('shop/checkout/payment')) }} + {{ redirectInput(siteUrl('shop/checkout/options')) }} {{ successMessageInput('Payment options selected.') }}
diff --git a/example-templates/dist/shop/checkout/payment.twig b/example-templates/dist/shop/checkout/payment.twig index 941389811b..9ff0511cc7 100644 --- a/example-templates/dist/shop/checkout/payment.twig +++ b/example-templates/dist/shop/checkout/payment.twig @@ -75,7 +75,6 @@ {% if className(cart.gateway) == 'craft\\commerce\\stripe\\gateways\\PaymentIntents' %} {% set params = { - paymentFormType: 'elements', appearance: { theme: 'stripe' }, @@ -98,9 +97,6 @@ {% endnamespace %}
- {# Force in some basic styling for the gateway-provided form markup (better to build your own form markup!) #} - {% include 'shop/checkout/_includes/base-payment-form-styles' %} - {% if cart.gateway.supportsPaymentSources() and currentUser %}
\n
\n\n\n\n\n\n","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderDetails.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderDetails.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OrderAdjustments.vue?vue&type=template&id=2baac682&\"\nimport script from \"./OrderAdjustments.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderAdjustments.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"order-flex justify-end\"},[_c('div',{staticClass:\"w-1/4\"},[(!_vm.editMode && _vm.draft.order.isCompleted)?_c('btn-link',{on:{\"click\":function($event){return _vm.enableEditMode()}}},[_vm._v(_vm._s(_vm._f(\"t\")('Edit adjustments','commerce')))]):_vm._e()],1),_vm._v(\" \"),_c('div',{staticClass:\"w-3/4\"},[_c('adjustments',{attrs:{\"editing\":_vm.editing && _vm.editMode,\"adjustments\":_vm.adjustments,\"recalculation-mode\":_vm.recalculationMode},on:{\"addAdjustment\":_vm.addOrderAdjustment,\"updateAdjustment\":_vm.updateOrderAdjustment,\"removeAdjustment\":_vm.removeOrderAdjustment}})],1)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./Total.vue?vue&type=template&id=5d0b23de&\"\nimport script from \"./Total.vue?vue&type=script&lang=js&\"\nexport * from \"./Total.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"order-flex justify-between align-center\"},[_c('h2',{staticClass:\"m-0\"},[_vm._v(_vm._s(_vm._f(\"t\")('Total','commerce')))]),_vm._v(\" \"),_c('h2',{staticClass:\"m-0\"},[_vm._v(_vm._s(_vm.order.totalPriceAsCurrency))])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./OrderDetails.vue?vue&type=template&id=02b8b036&\"\nimport script from \"./OrderDetails.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderDetails.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderDetails.vue?vue&type=style&index=0&id=02b8b036&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return (_vm.draft)?_c('div',[_c('div',{staticClass:\"order-details pt\",class:{'order-opacity-50': _vm.recalculateLoading || _vm.saveLoading}},[(!_vm.draft)?[_c('div',{staticClass:\"spinner\"})]:[(_vm.lineItemsErrors.length > 0)?_c('ul',_vm._l((_vm.lineItemsErrors),function(lineItemError,lineItemsErrorsKey){return _c('li',{key:'lineItemsErrors-' + lineItemsErrorsKey,staticClass:\"error\"},[_vm._v(\"\\n \"+_vm._s(lineItemError)+\"\\n \")])}),0):_vm._e(),_vm._v(\" \"),(_vm.lineItems.length > 0 || (_vm.editing && _vm.isProEdition))?[_c('line-items',{attrs:{\"line-items\":_vm.lineItems,\"editing\":_vm.editing,\"recalculation-mode\":_vm.recalculationMode},on:{\"updateLineItems\":_vm.updateLineItems}}),_vm._v(\" \"),(_vm.editing && _vm.isProEdition)?[_c('div',{staticClass:\"pb\",class:{\n 'orderedit-border-color orderedit-border-t pt':\n _vm.lineItems.length == 0,\n }},[_c('add-line-item',{on:{\"addLineItem\":_vm.addLineItem}})],1)]:_vm._e(),_vm._v(\" \"),(\n (_vm.editing && _vm.originalDraft.order.isCompleted) ||\n _vm.recalculateLoading\n )?_c('div',{staticClass:\"text-right pb\"},[(_vm.editing && _vm.originalDraft.order.isCompleted)?_c('div',{staticClass:\"recalculate-action\"},[_c('btn-link',{staticClass:\"recalculate-btn error\",on:{\"click\":function($event){return _vm.autoRecalculate()}}},[_vm._v(_vm._s(_vm._f(\"t\")('Recalculate order','commerce')))])],1):_vm._e(),_vm._v(\" \"),(_vm.recalculateLoading)?_c('div',{staticClass:\"spinner\"}):_vm._e()]):_vm._e(),_vm._v(\" \"),(_vm.lineItems.length > 0)?_c('div',{staticClass:\"order-total-summary pt\"},[(_vm.orderAdjustments.length > 0 || _vm.editing)?[_c('order-adjustments',{attrs:{\"adjustments\":_vm.orderAdjustments,\"editing\":_vm.editing,\"recalculation-mode\":_vm.recalculationMode},on:{\"updateOrderAdjustments\":_vm.updateOrderAdjustments}})]:_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"order-flex justify-end\"},[_c('div',{staticClass:\"w-3/4 orderedit-border-t orderedit-border-color pt\"},[_c('total',{attrs:{\"order\":_vm.draft.order}})],1)])],2):_vm._e()]:_vm._e()]],2)]):_vm._e()\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderStatus.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderStatus.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OrderStatus.vue?vue&type=template&id=6cee3f1e&\"\nimport script from \"./OrderStatus.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderStatus.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderStatus.vue?vue&type=style&index=0&id=6cee3f1e&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',[_c('div',[_c('a',{ref:\"orderStatus\",staticClass:\"btn menubtn order-status-btn\"},[(_vm.orderStatus.color)?[_c('span',{staticClass:\"status\",class:{[_vm.orderStatus.color]: true}})]:[_c('span',{staticClass:\"status\"})],_vm._v(\" \"),_c('span',{staticClass:\"order-status-btn-name\"},[_vm._v(_vm._s(_vm.orderStatus.name))])],2),_vm._v(\" \"),_c('div',{staticClass:\"menu\"},[_c('ul',{staticClass:\"padded\",attrs:{\"role\":\"listbox\"}},_vm._l((_vm.orderStatuses),function(status,key){return _c('li',{key:key},[_c('a',{class:{sel: _vm.orderStatus.id === status.value},attrs:{\"data-id\":status.id,\"data-color\":status.color,\"data-name\":status.name}},[_c('span',{staticClass:\"status\",class:{[status.color]: true}}),_vm._v(\" \"),_c('span',[_vm._v(_vm._s(status.name))])])])}),0)])]),_vm._v(\" \"),(_vm.originalOrderStatusId !== _vm.orderStatusId)?[_c('div',{staticClass:\"order-status-message\"},[_c('textarea',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.message),expression:\"message\"}],ref:\"textarea\",staticClass:\"text\",attrs:{\"placeholder\":_vm.$options.filters.t('Message', 'commerce'),\"maxlength\":\"10000\"},domProps:{\"value\":(_vm.message)},on:{\"input\":function($event){if($event.target.composing)return;_vm.message=$event.target.value}}}),_vm._v(\" \"),_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.suppressEmails),expression:\"suppressEmails\"}],staticClass:\"checkbox\",attrs:{\"id\":\"orderedit-suppress-emails\",\"type\":\"checkbox\"},domProps:{\"checked\":Array.isArray(_vm.suppressEmails)?_vm._i(_vm.suppressEmails,null)>-1:(_vm.suppressEmails)},on:{\"change\":function($event){var $$a=_vm.suppressEmails,$$el=$event.target,$$c=$$el.checked?(true):(false);if(Array.isArray($$a)){var $$v=null,$$i=_vm._i($$a,$$v);if($$el.checked){$$i<0&&(_vm.suppressEmails=$$a.concat([$$v]))}else{$$i>-1&&(_vm.suppressEmails=$$a.slice(0,$$i).concat($$a.slice($$i+1)))}}else{_vm.suppressEmails=$$c}}}}),_c('label',{attrs:{\"for\":\"orderedit-suppress-emails\"}},[_vm._v(_vm._s(_vm.$options.filters.t('Suppress emails', 'commerce')))])])]:_vm._e()],2)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderSite.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderSite.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OrderSite.vue?vue&type=template&id=68f7abb6&\"\nimport script from \"./OrderSite.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderSite.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderSite.vue?vue&type=style&index=0&id=68f7abb6&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',[_c('div',[_c('a',{ref:\"orderSite\",staticClass:\"btn menubtn order-site-btn\"},[_c('span',[_vm._v(_vm._s(_vm.orderSite.name)+\" (\"+_vm._s(_vm.orderSite.language)+\")\")])]),_vm._v(\" \"),_c('div',{staticClass:\"menu\"},[_c('ul',{staticClass:\"padded\",attrs:{\"role\":\"listbox\"}},_vm._l((_vm.orderSites),function(site,key){return _c('li',{key:key},[_c('a',{class:{sel: _vm.orderSite.id === site.value},attrs:{\"data-id\":site.id,\"data-name\":site.name}},[_vm._v(\"\\n \"+_vm._s(site.name)+\" (\"+_vm._s(site.language)+\")\\n \")])])}),0)])])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"v-select-btn btn\"},[_c('v-select',{ref:\"vSelect\",class:_vm.selectClass,attrs:{\"clearable\":_vm.clearable,\"clear-search-on-blur\":_vm.clearOnBlur,\"create-option\":_vm.createOption,\"components\":{OpenIndicator: _vm.OpenIndicator},\"disabled\":_vm.disabled,\"filterable\":_vm.filterable,\"filter-by\":_vm.filterBy,\"label\":_vm.label,\"options\":_vm.options,\"taggable\":_vm.taggable,\"value\":_vm.value,\"placeholder\":_vm.placeholder,\"searchInputQuerySelector\":_vm.searchInputQuerySelector,\"clearSearchOnSelect\":_vm.clearSearchOnSelect},on:{\"input\":function($event){return _vm.$emit('input', $event)},\"search\":_vm.onSearch,\"option:created\":_vm.onOptionCreated},scopedSlots:_vm._u([{key:\"option\",fn:function(option){return [_vm._t(\"option\",function(){return [_vm._v(_vm._s(option.name))]},{\"option\":option})]}},{key:\"spinner\",fn:function(spinner){return [_vm._t(\"spinner\",function(){return [(spinner.loading)?_c('div',{staticClass:\"spinner-wrapper\"},[_c('div',{staticClass:\"spinner\"})]):_vm._e()]},{\"spinner\":spinner})]}},{key:\"selected-option\",fn:function(option){return [_vm._t(\"selected-option\",function(){return [(option)?_c('div',{on:{\"click\":_vm.onOptionClick}},[_vm._v(\"\\n \"+_vm._s(option[_vm.label])+\"\\n \")]):_vm._e()]},{\"selectedOption\":option})]}},{key:\"search\",fn:function(search){return [_vm._t(\"search\",function(){return [_c('input',_vm._g(_vm._b({staticClass:\"vs__search\",attrs:{\"name\":_vm.searchInputName,\"type\":\"text\"}},'input',{\n ...search.attributes,\n ...{autocomplete: _vm.searchInputName},\n },false),_vm.getSearchEvents(search.events)))]},{\"search\":search})]}},{key:\"no-options\",fn:function(){return [_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t(\n 'Sorry, no matching options.',\n 'commerce'\n ))+\"\\n \")]},proxy:true}],null,true)})],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./OpenIndicator.vue?vue&type=template&id=9bcdae26&\"\nvar script = {}\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div')\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./SelectInput.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./SelectInput.vue?vue&type=script&lang=js&\"","\n\n\n\n\n","import { render, staticRenderFns } from \"./SelectInput.vue?vue&type=template&id=1905574c&\"\nimport script from \"./SelectInput.vue?vue&type=script&lang=js&\"\nexport * from \"./SelectInput.vue?vue&type=script&lang=js&\"\nimport style0 from \"./SelectInput.vue?vue&type=style&index=0&id=1905574c&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","import { render, staticRenderFns } from \"./ShippingMethod.vue?vue&type=template&id=32a2ff9c&\"\nimport script from \"./ShippingMethod.vue?vue&type=script&lang=js&\"\nexport * from \"./ShippingMethod.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',[_c('select-input',{attrs:{\"label\":\"name\",\"options\":_vm.shippingMethods,\"filterable\":true,\"placeholder\":_vm.shippingMethodHandle},on:{\"input\":_vm.onChange},scopedSlots:_vm._u([{key:\"option\",fn:function(slotProps){return [_c('div',{staticClass:\"shipping-method-select-option\"},[_c('span',{staticClass:\"status\",class:{\n enabled: slotProps.option.matchesOrder,\n disabled: !slotProps.option.matchesOrder,\n }}),_vm._v(_vm._s(slotProps.option.name)+\"\\n \")])]}},{key:\"selected-option\",fn:function(slotProps){return [_c('div',[_c('span',{staticClass:\"status\",class:{\n enabled: slotProps.selectedOption.matchesOrder,\n disabled: !slotProps.selectedOption.matchesOrder,\n }}),_vm._v(_vm._s(slotProps.selectedOption.name)+\"\\n \")])]}}]),model:{value:(_vm.selectedShippingMethod),callback:function ($$v) {_vm.selectedShippingMethod=$$v},expression:\"selectedShippingMethod\"}})],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./DateOrderedInput.vue?vue&type=template&id=71bc488e&\"\nimport script from \"./DateOrderedInput.vue?vue&type=script&lang=js&\"\nexport * from \"./DateOrderedInput.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"datetimewrapper\"},[_c('div',{staticClass:\"datewrapper\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.dateValue),expression:\"dateValue\"}],ref:\"dateInput\",staticClass:\"text\",attrs:{\"type\":\"text\",\"autocomplete\":\"false\",\"size\":\"10\",\"placeholder\":\" \"},domProps:{\"value\":(_vm.dateValue)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.dateValue=$event.target.value},_vm.onDateInput]}}),_vm._v(\" \"),_c('div',{attrs:{\"data-icon\":\"date\"}})]),_vm._v(\" \"),_c('div',{staticClass:\"timewrapper\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.timeValue),expression:\"timeValue\"}],ref:\"timeInput\",staticClass:\"text\",attrs:{\"type\":\"text\",\"autocomplete\":\"false\",\"size\":\"10\",\"placeholder\":\" \"},domProps:{\"value\":(_vm.timeValue)},on:{\"input\":[function($event){if($event.target.composing)return;_vm.timeValue=$event.target.value},_vm.onDateInput]}}),_vm._v(\" \"),_c('div',{attrs:{\"data-icon\":\"time\"}})])])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","/* jshint esversion: 6 */\n/* globals Craft */\nexport default {\n /**\n * Builds draft data and makes sure values have the right type.\n **/\n buildDraftData(draft) {\n const draftData = {\n order: {\n customerId: draft.order.customerId,\n orderStatusId: draft.order.orderStatusId,\n isCompleted: draft.order.isCompleted,\n reference: draft.order.reference,\n couponCode: draft.order.couponCode,\n recalculationMode: draft.order.recalculationMode,\n shippingMethodHandle: draft.order.shippingMethodHandle,\n shippingAddressId: draft.order.shippingAddressId,\n shippingAddress: null,\n billingAddressId: draft.order.billingAddressId,\n billingAddress: null,\n message: draft.order.message,\n dateOrdered: draft.order.dateOrdered,\n lineItems: [],\n orderAdjustments: [],\n orderSiteId: draft.order.orderSiteId,\n notices: draft.order.notices,\n },\n };\n\n if (draft.order.billingAddress) {\n draftData.order.billingAddress = draft.order.billingAddress;\n }\n\n if (draft.order.shippingAddress) {\n draftData.order.shippingAddress = draft.order.shippingAddress;\n }\n\n if (draft.order.sourceBillingAddressId != undefined) {\n draftData.order.sourceBillingAddressId =\n draft.order.sourceBillingAddressId;\n }\n\n if (draft.order.sourceShippingAddressId != undefined) {\n draftData.order.sourceShippingAddressId =\n draft.order.sourceShippingAddressId;\n }\n\n if (draft.order.suppressEmails != undefined) {\n draftData.order.suppressEmails = draft.order.suppressEmails;\n }\n\n if (\n draftData.order.dateOrdered &&\n !draftData.order.dateOrdered.hasOwnProperty('timezone')\n ) {\n draftData.order.dateOrdered.timezone = Craft.timezone;\n }\n\n draftData.order.id = this.parseInputValue('int', draft.order.id);\n\n draft.order.lineItems.forEach((lineItem, lineItemKey) => {\n let _lineItem = {};\n _lineItem.lineItemStatusId = this.parseInputValue(\n 'int',\n lineItem.lineItemStatusId\n );\n _lineItem.id = this.parseInputValue('int', lineItem.id);\n _lineItem.purchasableId = this.parseInputValue(\n 'int',\n lineItem.purchasableId\n );\n _lineItem.shippingCategoryId = this.parseInputValue(\n 'int',\n lineItem.shippingCategoryId\n );\n _lineItem.salePrice = this.parseInputValue('float', lineItem.salePrice);\n _lineItem.qty = this.parseInputValue('int', lineItem.qty);\n _lineItem.note = lineItem.note;\n _lineItem.privateNote = lineItem.privateNote;\n _lineItem.orderId = lineItem.orderId;\n _lineItem.options = lineItem.options;\n _lineItem.adjustments = [];\n _lineItem.uid = lineItem.uid;\n\n lineItem.adjustments.forEach((adjustment, adjustmentKey) => {\n let _adjustment = {};\n _adjustment.id = this.parseInputValue('int', adjustment.id);\n _adjustment.amount = this.parseInputValue('float', adjustment.amount);\n _adjustment.included = this.parseInputValue(\n 'bool',\n adjustment.included\n );\n _adjustment.orderId = this.parseInputValue('int', adjustment.orderId);\n _adjustment.lineItemId = this.parseInputValue(\n 'int',\n adjustment.lineItemId\n );\n _adjustment.name = adjustment.name;\n _adjustment.description = adjustment.description;\n _adjustment.type = adjustment.type;\n _adjustment.sourceSnapshot = adjustment.sourceSnapshot;\n\n _lineItem.adjustments[adjustmentKey] = _adjustment;\n });\n\n draftData.order.lineItems[lineItemKey] = _lineItem;\n });\n\n draft.order.orderAdjustments.forEach((adjustment, adjustmentKey) => {\n let _orderAdjustment = {};\n _orderAdjustment.id = this.parseInputValue('int', adjustment.id);\n _orderAdjustment.amount = this.parseInputValue(\n 'float',\n adjustment.amount\n );\n _orderAdjustment.included = this.parseInputValue(\n 'bool',\n adjustment.included\n );\n _orderAdjustment.orderId = this.parseInputValue(\n 'int',\n adjustment.orderId\n );\n _orderAdjustment.name = adjustment.name;\n _orderAdjustment.description = adjustment.description;\n _orderAdjustment.type = adjustment.type;\n _orderAdjustment.sourceSnapshot = adjustment.sourceSnapshot;\n\n draftData.order.orderAdjustments[adjustmentKey] = _orderAdjustment;\n });\n\n return draftData;\n },\n\n /**\n * Parse input value.\n **/\n parseInputValue(type, value) {\n let parsedValue = null;\n\n switch (type) {\n case 'int':\n parsedValue = parseInt(value);\n break;\n case 'float':\n parsedValue = parseFloat(value);\n break;\n case 'bool':\n parsedValue = !!value;\n break;\n }\n\n if (isNaN(parsedValue)) {\n return value;\n }\n\n return parsedValue;\n },\n};\n","/* global $ */\n\nimport utils from '../helpers/utils';\n\nexport default {\n methods: {\n saveOrder: function (draft) {\n if (this.$store.state.saveLoading) {\n return false;\n }\n\n this.$store.commit('updateSaveLoading', true);\n\n const data = utils.buildDraftData(draft);\n const dataString = JSON.stringify(data);\n\n this.$store.commit('updateOrderData', dataString);\n\n this.$nextTick(() => {\n $('#main-form').submit();\n });\n },\n },\n};\n","\n\n \n\n
\n
{{ 'Last Updated' | t('commerce') }}
\n {{ draft.order.dateUpdated.date }}\n {{ draft.order.dateUpdated.time }}\n
\n\n
\n
{{ 'IP Address' | t('commerce') }}
\n {{ draft.order.lastIp }}\n
\n\n
\n
{{ 'Origin' | t('commerce') }}
\n {{ originLabel(draft.order.origin) }}\n
\n \n
{{ 'Completed Email' | t('commerce') }}
\n {{ draft.order.orderCompletedEmail }}\n
\n
\n
\n\n\n\n\n\n","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderMeta.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderMeta.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OrderMeta.vue?vue&type=template&id=f158018c&\"\nimport script from \"./OrderMeta.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderMeta.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderMeta.vue?vue&type=style&index=0&id=f158018c&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return (_vm.draft)?_c('div',{staticClass:\"order-meta-container\",class:{loading: _vm.recalculateLoading || _vm.saveLoading}},[(_vm.editing)?_c('div',{staticClass:\"meta\",attrs:{\"id\":\"settings\"}},[_c('div',{staticClass:\"field\",attrs:{\"id\":\"reference-field\"}},[_c('div',{staticClass:\"heading\"},[_c('label',{attrs:{\"id\":\"reference-label\",\"for\":\"reference\"}},[_vm._v(_vm._s(_vm._f(\"t\")('Reference','commerce')))])]),_vm._v(\" \"),_c('div',{staticClass:\"input ltr\"},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.reference),expression:\"reference\"}],staticClass:\"text fullwidth\",attrs:{\"type\":\"text\",\"autocomplete\":\"off\",\"autocorrect\":\"off\",\"autocapitalize\":\"off\",\"placeholder\":_vm.$options.filters.t('Enter reference', 'commerce')},domProps:{\"value\":(_vm.reference)},on:{\"input\":function($event){if($event.target.composing)return;_vm.reference=$event.target.value}}})])]),_vm._v(\" \"),_c('field',{attrs:{\"label\":_vm.$options.filters.t('Coupon Code', 'commerce'),\"errors\":_vm.getErrors('couponCode')[0]}},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.couponCode),expression:\"couponCode\"}],staticClass:\"text fullwidth\",attrs:{\"type\":\"text\",\"autocomplete\":\"off\",\"autocorrect\":\"off\",\"autocapitalize\":\"off\",\"placeholder\":_vm.$options.filters.t('Enter coupon code', 'commerce')},domProps:{\"value\":(_vm.couponCode)},on:{\"input\":function($event){if($event.target.composing)return;_vm.couponCode=$event.target.value}}})]),_vm._v(\" \"),(_vm.order.isCompleted)?_c('field',{attrs:{\"label\":_vm.$options.filters.t('Date Ordered', 'commerce')}},[_c('date-ordered-input',{attrs:{\"date\":_vm.draft.order.dateOrdered},on:{\"update\":_vm.updateDateOrderedInput}})],1):_vm._e(),_vm._v(\" \"),_c('field',{attrs:{\"label\":_vm.$options.filters.t('Order Site', 'commerce')}},[_c('order-site',{attrs:{\"originalOrderSiteId\":_vm.originalDraft.order.orderSiteId,\"order\":_vm.order},on:{\"updateOrder\":_vm.updateOrder}})],1),_vm._v(\" \"),(!_vm.draft.order.isCompleted)?_c('div',{staticClass:\"field\",attrs:{\"id\":\"isCompleted-field\"}},[_c('div',{staticClass:\"heading\"},[_c('label',{attrs:{\"id\":\"isCompleted-label\",\"for\":\"isCompleted\"}},[_vm._v(_vm._s(_vm._f(\"t\")('Completed','commerce')))])]),_vm._v(\" \"),_c('div',{staticClass:\"input ltr\"},[_c('div',{staticClass:\"buttons\"},[_c('input',{staticClass:\"btn small\",class:{\n disabled: !_vm.hasCustomer || _vm.recalculateLoading,\n },attrs:{\"type\":\"button\",\"value\":_vm.$options.filters.t(\n 'Mark as completed',\n 'commerce'\n ),\"disabled\":!_vm.hasCustomer || _vm.recalculateLoading},on:{\"click\":_vm.markAsCompleted}})])])]):_vm._e(),_vm._v(\" \"),(_vm.draft.order.isCompleted)?[_c('div',{staticClass:\"field\",attrs:{\"id\":\"orderStatus-field\"}},[_c('div',{staticClass:\"heading\"},[_c('label',{attrs:{\"id\":\"orderStatus-label\",\"for\":\"status\"}},[_vm._v(_vm._s(_vm._f(\"t\")('Status','commerce')))])]),_vm._v(\" \"),_c('div',{staticClass:\"input ltr\"},[_c('order-status',{attrs:{\"originalOrderStatusId\":_vm.originalDraft.order.orderStatusId,\"original-message\":_vm.originalDraft.order.message,\"suppress-emails\":_vm.draft.order.suppressEmails,\"order\":_vm.order},on:{\"updateOrder\":_vm.updateOrder}})],1)])]:_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"field\",attrs:{\"id\":\"shippingMethod-field\"}},[_c('div',{staticClass:\"heading\"},[_c('label',{attrs:{\"id\":\"shippingMethod-label\",\"for\":\"slug\"}},[_vm._v(_vm._s(_vm._f(\"t\")('Shipping Method','commerce')))])]),_vm._v(\" \"),_c('div',{staticClass:\"input ltr\"},[_c('shipping-method',{attrs:{\"order\":_vm.order},on:{\"updateOrder\":_vm.updateOrder}})],1)])],2):_vm._e(),_vm._v(\" \"),(!_vm.editing)?_c('div',{staticClass:\"order-meta meta read-only\"},[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Reference','commerce')))]),_vm._v(\" \"),_c('p',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.reference))])]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Coupon Code','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value code\"},[_vm._v(_vm._s(_vm.draft.order.couponCode))])]),_vm._v(\" \"),(_vm.draft.order.isCompleted)?[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Date Ordered','commerce'))+\"\\n \")]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.dateOrdered.date)+\"\\n \"+_vm._s(_vm.draft.order.dateOrdered.time))])])]:_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Order Site','commerce')))]),_vm._v(\" \"),(_vm.draft.order.orderSite)?_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.orderSite.name)+\" (\"+_vm._s(_vm.draft.order.orderSite.language)+\")\")]):_vm._e()]),_vm._v(\" \"),(_vm.draft.order.isCompleted)?[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Status','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\",domProps:{\"innerHTML\":_vm._s(_vm.draft.order.orderStatusHtml)}})])]:_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"data order-meta-shipping-method\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Shipping Method','commerce')))]),_vm._v(\" \"),(_vm.draft.order.shippingMethodHandle)?_c('div',{staticClass:\"value\"},[(_vm.draft.order.shippingMethodName)?_c('span',[_vm._v(_vm._s(_vm.draft.order.shippingMethodName))]):_vm._e(),_vm._v(\" \"),_c('span',{staticClass:\"small code shipping-method-handle\"},[_vm._v(_vm._s(_vm.draft.order.shippingMethodHandle))])]):_vm._e()])],2):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"meta read-only\",attrs:{\"id\":\"meta\"}},[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('ID','commerce')))]),_vm._v(\" \"),_c('p',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.id))])]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Short Number','commerce')))]),_vm._v(\" \"),_c('div',{staticClass:\"value order-number-value\"},[_c('div',[_vm._v(\"\\n \"+_vm._s(_vm.draft.order.shortNumber)+\"\\n \")]),_vm._v(\" \"),_c('div',{staticClass:\"hidden-input\"},[_c('input',{ref:\"orderShortNumber\",attrs:{\"type\":\"text\"},domProps:{\"value\":_vm.draft.order.shortNumber}})]),_vm._v(\" \"),_c('btn-link',{on:{\"click\":function($event){return _vm.copy(_vm.$refs.orderShortNumber)}}},[_vm._v(_vm._s(_vm._f(\"t\")('Copy','commerce')))])],1)]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Number','commerce')))]),_vm._v(\" \"),_c('div',{staticClass:\"value order-number-value\"},[_c('div',[_vm._v(\"\\n \"+_vm._s(_vm.draft.order.number)+\"\\n \")]),_vm._v(\" \"),_c('div',{staticClass:\"hidden-input\"},[_c('input',{ref:\"orderNumber\",attrs:{\"type\":\"text\"},domProps:{\"value\":_vm.draft.order.number}})]),_vm._v(\" \"),_c('btn-link',{on:{\"click\":function($event){return _vm.copy(_vm.$refs.orderNumber)}}},[_vm._v(_vm._s(_vm._f(\"t\")('Copy','commerce')))])],1)]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Paid Status','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\",domProps:{\"innerHTML\":_vm._s(_vm.draft.order.paidStatusHtml)}})]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Total Price','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.totalPriceAsCurrency))])]),_vm._v(\" \"),(_vm.draft.order.totalPaid != 0)?[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Paid Amount','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.totalPaidAsCurrency))])])]:_vm._e(),_vm._v(\" \"),(!_vm.draft.order.datePaid && _vm.draft.order.dateAuthorized)?[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Date Authorized','commerce'))+\"\\n \")]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.dateAuthorized.date)+\"\\n \"+_vm._s(_vm.draft.order.dateAuthorized.time))])])]:_vm._e(),_vm._v(\" \"),(_vm.draft.order.datePaid)?[_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Date Paid','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.datePaid.date)+\"\\n \"+_vm._s(_vm.draft.order.datePaid.time))])])]:_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Last Updated','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.dateUpdated.date)+\"\\n \"+_vm._s(_vm.draft.order.dateUpdated.time))])]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('IP Address','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.lastIp))])]),_vm._v(\" \"),_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Origin','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.originLabel(_vm.draft.order.origin)))])]),_vm._v(\" \"),(\n _vm.draft.order.orderCompletedEmail &&\n _vm.draft.order.orderCompletedEmail != _vm.draft.order.email\n )?_c('div',{staticClass:\"data\"},[_c('h5',{staticClass:\"heading\"},[_vm._v(_vm._s(_vm._f(\"t\")('Completed Email','commerce')))]),_vm._v(\" \"),_c('span',{staticClass:\"value\"},[_vm._v(_vm._s(_vm.draft.order.orderCompletedEmail))])]):_vm._e()],2)]):_vm._e()\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderNotices.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderNotices.vue?vue&type=script&lang=js&\"","\n\n\n\n\n","import { render, staticRenderFns } from \"./OrderNotices.vue?vue&type=template&id=eedc3fb8&\"\nimport script from \"./OrderNotices.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderNotices.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderNotices.vue?vue&type=style&index=0&id=eedc3fb8&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return (_vm.showNotices)?_c('div',[_c('div',{staticClass:\"meta read-only warning\"},[_c('div',{staticClass:\"order-flex order-notices-header\"},[_c('div',[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Customer Notices','commerce'))+\"\\n \")]),_vm._v(\" \"),_c('div',[_c('button',{staticClass:\"btn small\",on:{\"click\":function($event){$event.preventDefault();return _vm.clearNotices.apply(null, arguments)}}},[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Clear notices','commerce'))+\"\\n \")])])]),_vm._v(\" \"),_c('div',{staticClass:\"order-notices-items\"},_vm._l((_vm.draft.order.notices),function(notice){return _c('div',{key:notice.id},[_c('hr'),_vm._v(\" \"),_c('div',{staticClass:\"order-notices-item\"},[_vm._v(\"\\n \"+_vm._s(notice.message)+\"\\n \")])])}),0)])]):_vm._e()\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OptionShortcutLabel.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OptionShortcutLabel.vue?vue&type=script&lang=js&\"","\n\n\n\n\n","import { render, staticRenderFns } from \"./OptionShortcutLabel.vue?vue&type=template&id=e4cdb9c2&\"\nimport script from \"./OptionShortcutLabel.vue?vue&type=script&lang=js&\"\nexport * from \"./OptionShortcutLabel.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OptionShortcutLabel.vue?vue&type=style&index=0&id=e4cdb9c2&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"option-shortcut-label\"},[(_vm.os === 'mac')?[_c('span',{staticClass:\"shortcut\"},[_vm._v(_vm._s((_vm.alt ? '⌥' : '') + (_vm.shift ? '⇧' : '') + '⌘' + _vm.shortcutKey))])]:[_c('span',{staticClass:\"shortcut\"},[_vm._v(_vm._s('Ctrl+' +\n (_vm.alt ? 'Alt+' : '') +\n (_vm.shift ? 'Shift+' : '') +\n _vm.shortcutKey))])]],2)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderActions.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderActions.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./UpdateOrderBtn.vue?vue&type=template&id=ccf7514e&\"\nimport script from \"./UpdateOrderBtn.vue?vue&type=script&lang=js&\"\nexport * from \"./UpdateOrderBtn.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"order-flex\",attrs:{\"id\":\"order-save\"}},[(_vm.editing)?[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.orderData),expression:\"orderData\"}],attrs:{\"type\":\"hidden\",\"name\":\"orderData\",\"id\":\"test\"},domProps:{\"value\":(_vm.orderData)},on:{\"input\":function($event){if($event.target.composing)return;_vm.orderData=$event.target.value}}}),_vm._v(\" \"),_c('input',{staticClass:\"btn submit\",class:{disabled: !_vm.hasCustomer || _vm.recalculateLoading},attrs:{\"id\":\"order-save-btn\",\"type\":\"button\",\"value\":_vm.$options.filters.t('Update order', 'commerce'),\"disabled\":!_vm.hasCustomer || _vm.recalculateLoading},on:{\"click\":function($event){return _vm.save()}}}),_vm._v(\" \"),_c('div',{staticClass:\"spacer\"})]:_vm._e(),_vm._v(\" \"),_c('div',[_c('div',{ref:\"updateMenuBtn\",staticClass:\"btn menubtn\",attrs:{\"data-icon\":\"settings\",\"title\":_vm.$options.filters.t('Actions', 'commerce')}}),_vm._v(\" \"),_c('div',{staticClass:\"menu\"},[(_vm.editing && _vm.hasCustomer && _vm.hasAddresses)?[_c('ul',[_c('li',[_c('a',{on:{\"click\":function($event){return _vm.save({redirect: _vm.ordersIndexUrl})}}},[_c('option-shortcut-label',{attrs:{\"os\":\"mac\",\"shortcut-key\":\"S\"}}),_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Save and return to all orders','commerce'))+\"\\n \")],1)])])]:_vm._e(),_vm._v(\" \"),(_vm.draft && !_vm.draft.order.isCompleted)?[_c('ul',[_c('li',[_c('a',{attrs:{\"href\":\"#\"},on:{\"click\":function($event){$event.preventDefault();return _vm.copy()}}},[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Share cart…','commerce'))+\"\\n \")])])])]:_vm._e(),_vm._v(\" \"),(_vm.canDelete)?[(_vm.editing && _vm.hasCustomer && _vm.hasAddresses)?[_c('hr')]:_vm._e(),_vm._v(\" \"),_c('ul',[_c('li',[_c('a',{staticClass:\"error\",on:{\"click\":function($event){return _vm.deleteOrder()}}},[_vm._v(_vm._s(_vm._f(\"t\")('Delete','commerce')))])])])]:_vm._e()],2)])],2)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./OrderActions.vue?vue&type=template&id=59a89a46&\"\nimport script from \"./OrderActions.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderActions.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderActions.vue?vue&type=style&index=0&id=59a89a46&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return (_vm.canEdit)?_c('div',{staticClass:\"order-flex\"},[_c('div',{staticClass:\"order-edit-action-buttons\"},[(_vm.saveLoading)?_c('div',{staticClass:\"spinner\",attrs:{\"id\":\"order-save-spinner\"}}):_vm._e(),_vm._v(\" \"),(!_vm.editing)?[_c('input',{staticClass:\"btn\",attrs:{\"id\":\"order-edit-btn\",\"type\":\"button\",\"value\":_vm.$options.filters.t('Edit', 'commerce')},on:{\"click\":function($event){return _vm.edit()}}})]:[_c('input',{staticClass:\"btn\",attrs:{\"id\":\"order-cancel-btn\",\"type\":\"button\",\"value\":_vm.$options.filters.t('Cancel', 'commerce')},on:{\"click\":function($event){return _vm.cancel()}}})]],2),_vm._v(\" \"),(_vm.editing || _vm.canDelete)?[_c('update-order-btn',{ref:\"updateOrderBtn\"})]:_vm._e()],2):_vm._e()\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Customer.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Customer.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./Customer.vue?vue&type=template&id=53195c65&\"\nimport script from \"./Customer.vue?vue&type=script&lang=js&\"\nexport * from \"./Customer.vue?vue&type=script&lang=js&\"\nimport style0 from \"./Customer.vue?vue&type=style&index=0&id=53195c65&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"customer-wrapper\"},[(_vm.customer)?_c('div',{staticClass:\"order-flex align-center\",class:{'customer-display': _vm.display}},[_c('div',{staticClass:\"customer-photo-wrapper\"},[_c('div',{staticClass:\"customer-photo order-flex justify-center align-center\",class:_vm.avatarClass},[(_vm.customer.photo && _vm.customer.photoThumbUrl)?_c('img',{staticClass:\"w-full\",attrs:{\"src\":_vm.customer.photoThumbUrl,\"alt\":_vm.customer.email}}):_c('div',{class:_vm.getBgColor(_vm.initialChar)},[_vm._v(\"\\n \"+_vm._s(_vm.initialChar)+\"\\n \")])]),_vm._v(\" \"),_c('span',{staticClass:\"status\",class:_vm.customer.status})]),_vm._v(\" \"),_c('div',{staticClass:\"customer-info-container ml-1\"},[(_vm.getDisplayName(false))?_c('div',[_vm._v(\"\\n \"+_vm._s(_vm.getDisplayName(false))+\"\\n \")]):_vm._e(),_vm._v(\" \"),_c('div',{staticClass:\"w-full light\"},[_vm._v(_vm._s(_vm.customer.email))]),_vm._v(\" \"),(_vm.display && _vm.customer.cpEditUrl)?_c('div',{staticClass:\"w-full\"},[(_vm.customer.cpEditUrl)?_c('a',{attrs:{\"href\":_vm.customer.cpEditUrl}},[_vm._v(_vm._s(_vm.$options.filters.t('View customer', 'commerce')))]):_vm._e()]):_vm._e()])]):_vm._e(),_vm._v(\" \"),(_vm.showRemove)?_c('button',{staticClass:\"customer-remove delete icon\",attrs:{\"type\":\"button\",\"title\":_vm.$options.filters.t('Remove', 'commerce'),\"aria-label\":_vm.removeButtonLabel},on:{\"click\":function($event){$event.preventDefault();return _vm.$emit('remove')}}}):_vm._e()])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n\n\n","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./AddressSelect.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./AddressSelect.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./AddressSelect.vue?vue&type=template&id=17faa358&scoped=true&\"\nimport script from \"./AddressSelect.vue?vue&type=script&lang=js&\"\nexport * from \"./AddressSelect.vue?vue&type=script&lang=js&\"\nimport style1 from \"./AddressSelect.vue?vue&type=style&index=1&id=17faa358&prod&land=scss&lang=css&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n \"17faa358\",\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return (_vm.customerId)?_c('div',[_c('a',{class:{disabled: !_vm.canSelectAddress},attrs:{\"disabled\":!_vm.canSelectAddress},on:{\"click\":function($event){$event.preventDefault();return _vm.open.apply(null, arguments)}}},[_vm._v(_vm._s(_vm.$options.filters.t('Select address', 'commerce')))]),_vm._v(\" \"),_c('div',{staticClass:\"hidden\"},[_c('div',{ref:\"addressselectmodal\",staticClass:\"order-edit-modal order-edit-modal--address-select modal fitted\"},[_c('div',{staticClass:\"body\"},[_vm._l((_vm.addresses),function(address,index){return _c('div',{key:index},[_c('label',{class:{\n selected:\n _vm.selectedAddress &&\n address.id == _vm.selectedAddress.id,\n }},[_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.selectedAddress),expression:\"selectedAddress\"}],staticClass:\"visually-hidden\",attrs:{\"type\":\"radio\",\"name\":'address-select-' + _vm.addressModel},domProps:{\"value\":address,\"checked\":_vm._q(_vm.selectedAddress,address)},on:{\"change\":function($event){_vm.selectedAddress=address}}}),_vm._v(\" \"),_c('ul',{domProps:{\"innerHTML\":_vm._s(address.html)}})])])}),_vm._v(\" \"),(_vm.isLoadMoreVisible)?_c('div',{staticClass:\"order-edit-modal-load-more\"},[_c('div',[_c('btn-link',{attrs:{\"button-class\":\"btn\"},on:{\"click\":_vm.loadMore}},[_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t('Load more', 'commerce'))+\"\\n \")]),_vm._v(\" \"),_c('div',{staticClass:\"spinner\",class:{hidden: !_vm.isLoadingMore}})],1)]):_vm._e()],2),_vm._v(\" \"),_c('div',{staticClass:\"footer\"},[_c('div',{staticClass:\"buttons right\"},[_c('btn-link',{attrs:{\"button-class\":\"btn\"},on:{\"click\":_vm.close}},[_vm._v(_vm._s(_vm.$options.filters.t('Cancel', 'commerce')))]),_vm._v(\" \"),_c('btn-link',{class:{disabled: _vm.isDoneDisabled},attrs:{\"button-class\":\"btn submit\",\"disabled\":_vm.isDoneDisabled},on:{\"click\":_vm.done}},[_vm._v(_vm._s(_vm.$options.filters.t('Done', 'commerce')))])],1)])])])]):_vm._e()\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./AddressEdit.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./AddressEdit.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./AddressEdit.vue?vue&type=template&id=1fe4ec45&\"\nimport script from \"./AddressEdit.vue?vue&type=script&lang=js&\"\nexport * from \"./AddressEdit.vue?vue&type=script&lang=js&\"\nimport style0 from \"./AddressEdit.vue?vue&type=style&index=0&id=1fe4ec45&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{ref:\"container\"},[_c('div',{staticClass:\"order-address-display\"},[(_vm.address)?[_c('ul',{ref:\"address\",domProps:{\"innerHTML\":_vm._s(_vm.address)}})]:[_c('div',{staticClass:\"zilch\"},[_vm._v(_vm._s(_vm.emptyMsg))])],_vm._v(\" \"),_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(_vm.hasCustomer),expression:\"hasCustomer\"}],staticClass:\"order-address-display-buttons order-flex\"},[_c('div',{ref:\"addressmenubtn\",staticClass:\"btn menubtn\",attrs:{\"data-icon\":\"settings\",\"title\":_vm.$options.filters.t('Actions', 'commerce')}}),_vm._v(\" \"),_c('div',{staticClass:\"menu\"},[_c('ul',[_c('li',[_c('a',{class:{disabled: !_vm.address},attrs:{\"disabled\":!_vm.address},on:{\"click\":function($event){$event.preventDefault();return _vm.handleEditAddress.apply(null, arguments)}}},[_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t(\n 'Edit address',\n 'commerce'\n ))+\"\\n \")])]),_vm._v(\" \"),_c('li',[_c('address-select',{attrs:{\"customer-id\":_vm.customerId},on:{\"update\":_vm.handleSelect}})],1),_vm._v(\" \"),_c('li',[_c('a',{on:{\"click\":function($event){$event.preventDefault();return _vm.handleNewAddress.apply(null, arguments)}}},[_vm._v(_vm._s(_vm.$options.filters.t('New address', 'commerce')))])]),_vm._v(\" \"),(_vm.copyToAddress)?_c('li',[_c('a',{class:{disabled: !_vm.address},attrs:{\"disabled\":!_vm.address},on:{\"click\":function($event){$event.preventDefault();return _vm.$emit('copy')}}},[_vm._v(_vm._s(_vm.$options.filters.t(\n 'Copy to {location}',\n 'commerce',\n {location: _vm.copyToAddress}\n )))])]):_vm._e()]),_vm._v(\" \"),_c('hr'),_vm._v(\" \"),_c('ul',[_c('li',[_c('a',{staticClass:\"error\",class:{disabled: !_vm.address},attrs:{\"disabled\":!_vm.address},on:{\"click\":function($event){$event.preventDefault();return _vm.$emit('remove')}}},[_vm._v(_vm._s(_vm.$options.filters.t(\n 'Remove address',\n 'commerce'\n )))])])])])])],2)])\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n \n \n \n \n\n\n\n\n\n","import mod from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./CustomerSelect.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./CustomerSelect.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./CustomerSelect.vue?vue&type=template&id=c338ae24&\"\nimport script from \"./CustomerSelect.vue?vue&type=script&lang=js&\"\nexport * from \"./CustomerSelect.vue?vue&type=script&lang=js&\"\nimport style0 from \"./CustomerSelect.vue?vue&type=style&index=0&id=c338ae24&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('select-input',{ref:\"vSelect\",staticClass:\"customer-select\",attrs:{\"label\":\"email\",\"options\":_vm.customers,\"filterable\":false,\"clearable\":false,\"pre-filtered\":true,\"create-option\":_vm.createOption,\"placeholder\":_vm.$options.filters.t('Search or enter customer email…', 'commerce'),\"clear-search-on-blur\":false,\"taggable\":\"\"},on:{\"input\":_vm.onChange,\"search\":_vm.onSearch,\"created\":_vm.onCreated},scopedSlots:_vm._u([{key:\"option\",fn:function(slotProps){return [(\n slotProps.option.id ||\n (!slotProps.option.id && !_vm.customers.length)\n )?_c('div',{staticClass:\"customer-select-option\"},[(\n !slotProps.option.id &&\n !_vm.customers.length &&\n _vm.$v.newCustomerEmail.$invalid\n )?[_c('div',{staticClass:\"order-flex justify-center\"},[_c('div',[_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t(\n 'A valid email is required to create a customer.',\n 'commerce'\n ))+\"\\n \")])])]:(\n !slotProps.option.id &&\n !_vm.$v.newCustomerEmail.$invalid &&\n !_vm.customers.length\n )?[_c('div',{staticClass:\"order-flex align-center\"},[_c('div',{staticClass:\"customer-photo-wrapper\"},[_c('div',{staticClass:\"customer-photo order-flex customer-photo--initial customer-photo--email justify-center align-center\"},[_c('span',{staticClass:\"icon\",attrs:{\"data-icon\":\"plus\"}})])]),_vm._v(\" \"),_c('div',{staticClass:\"ml-1\"},[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Create customer: “{email}”','commerce', {\n email: slotProps.option.email,\n }))+\"\\n \")])])]:(slotProps.option.id)?[_c('div',[_c('customer',{attrs:{\"customer\":slotProps.option}})],1)]:_vm._e()],2):_vm._e()]}}]),model:{value:(_vm.selectedCustomer),callback:function ($$v) {_vm.selectedCustomer=$$v},expression:\"selectedCustomer\"}})\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderCustomer.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderCustomer.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OrderCustomer.vue?vue&type=template&id=5472eb5c&\"\nimport script from \"./OrderCustomer.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderCustomer.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderCustomer.vue?vue&type=style&index=0&id=5472eb5c&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return (_vm.draft)?_c('div',{class:{'order-opacity-50': _vm.recalculateLoading || _vm.saveLoading}},[_c('div',[_c('div',{staticClass:\"order-flex justify-between align-center pb\"},[_c('h3',{staticClass:\"m-0\"},[_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t('Customer', 'commerce'))+\"\\n \")]),_vm._v(\" \"),(_vm.hasCustomer && (!_vm.editing || !_vm.editMode))?[_c('btn-link',{on:{\"click\":function($event){return _vm.enableEditMode()}}},[_vm._v(_vm._s(_vm.$options.filters.t('Edit', 'commerce')))])]:_vm._e()],2),_vm._v(\" \"),_c('div',{staticClass:\"customer-select-wrapper\"},[(_vm.hasCustomer)?_c('customer',{attrs:{\"customer\":_vm.customer,\"display\":true,\"show-remove\":_vm.editing && _vm.editMode},on:{\"remove\":_vm.removeCustomer}}):_vm._e(),_vm._v(\" \"),(!_vm.hasCustomer)?_c('customer-select',{attrs:{\"order\":_vm.draft.order},on:{\"update\":_vm.updateCustomer}}):_vm._e()],1)]),_vm._v(\" \"),_c('div',{staticClass:\"order-flex order-box-sizing px-4 -mx-4\",class:{pt: _vm.hasCustomer || _vm.hasAnAddress}},[_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(!_vm.editing || !_vm.editMode),expression:\"!editing || !editMode\"}],staticClass:\"w-1/2 pr\"},[(_vm.draft && _vm.draft.order.billingAddressHtml)?[_c('ul',{staticClass:\"order-address-display order-address-display--static\",domProps:{\"innerHTML\":_vm._s(_vm.draft.order.billingAddressHtml)}})]:[_c('div',{staticClass:\"zilch\"},[_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t('No billing address', 'commerce'))+\"\\n \")])]],2),_vm._v(\" \"),_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(!_vm.editing || !_vm.editMode),expression:\"!editing || !editMode\"}],staticClass:\"w-1/2 pl\"},[(_vm.draft && _vm.draft.order.shippingAddressHtml)?[_c('ul',{staticClass:\"order-address-display order-address-display--static\",domProps:{\"innerHTML\":_vm._s(_vm.draft.order.shippingAddressHtml)}})]:[_c('div',{staticClass:\"zilch\"},[_vm._v(\"\\n \"+_vm._s(_vm.$options.filters.t(\n 'No shipping address',\n 'commerce'\n ))+\"\\n \")])]],2),_vm._v(\" \"),_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(\n ((!_vm.hasCustomer && _vm.draft.order.isCompleted) ||\n _vm.hasCustomer) &&\n _vm.editing &&\n _vm.editMode\n ),expression:\"\\n ((!hasCustomer && draft.order.isCompleted) ||\\n hasCustomer) &&\\n editing &&\\n editMode\\n \"}],staticClass:\"w-1/2 pr\"},[_c('address-edit',{attrs:{\"title\":_vm.titles.billingAddress,\"model-name\":\"billing\",\"address\":_vm.draft.order.billingAddressHtml,\"copy-to-address\":_vm.$options.filters.t('shipping address', 'commerce'),\"customer-id\":_vm.draft.order.customerId,\"empty-message\":_vm.$options.filters.t('No billing address', 'commerce'),\"customer-updated\":_vm.customerUpdatedTime},on:{\"update\":_vm.updateBillingAddress,\"copy\":function($event){return _vm.copyAddress('shipping')},\"remove\":_vm.removeBillingAddress}})],1),_vm._v(\" \"),_c('div',{directives:[{name:\"show\",rawName:\"v-show\",value:(\n ((!_vm.hasCustomer && _vm.draft.order.isCompleted) ||\n _vm.hasCustomer) &&\n _vm.editing &&\n _vm.editMode\n ),expression:\"\\n ((!hasCustomer && draft.order.isCompleted) ||\\n hasCustomer) &&\\n editing &&\\n editMode\\n \"}],staticClass:\"w-1/2 pl\"},[_c('address-edit',{attrs:{\"title\":_vm.titles.shippingAddress,\"model-name\":\"shipping\",\"address\":_vm.draft.order.shippingAddressHtml,\"copy-to-address\":_vm.$options.filters.t('billing address', 'commerce'),\"customer-id\":_vm.draft.order.customerId,\"empty-message\":_vm.$options.filters.t('No shipping address', 'commerce'),\"customer-updated\":_vm.customerUpdatedTime},on:{\"update\":_vm.updateShippingAddress,\"copy\":function($event){return _vm.copyAddress('billing')},\"remove\":_vm.removeShippingAddress}})],1)])]):_vm._e()\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./OrderErrors.vue?vue&type=template&id=f3765d52&\"\nimport script from \"./OrderErrors.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderErrors.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n\n\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{class:{'pb-lg': _vm.draftErrors.length}},[(_vm.draftErrors.length)?[_c('h4',{staticClass:\"error\"},[_vm._v(\"\\n \"+_vm._s(this.$options.filters.t(\n 'There are errors on the order',\n 'commerce'\n ))+\"\\n \")]),_vm._v(\" \"),_c('ul',{staticClass:\"errors\"},_vm._l((_vm.draftErrors),function(error,index){return _c('li',{key:index},[_vm._v(\"\\n \"+_vm._s(error)+\"\\n \")])}),0)]:_vm._e()],2)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","\n\n\n\n\n","import mod from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderSecondaryActions.vue?vue&type=script&lang=js&\"; export default mod; export * from \"-!../../../../../../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-2.use!../../../../../../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./OrderSecondaryActions.vue?vue&type=script&lang=js&\"","import { render, staticRenderFns } from \"./OrderSecondaryActions.vue?vue&type=template&id=569c8a93&\"\nimport script from \"./OrderSecondaryActions.vue?vue&type=script&lang=js&\"\nexport * from \"./OrderSecondaryActions.vue?vue&type=script&lang=js&\"\nimport style0 from \"./OrderSecondaryActions.vue?vue&type=style&index=0&id=569c8a93&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',[(!_vm.editing && !_vm.hasOrderChanged)?_c('div',{staticClass:\"order-flex\"},[(_vm.defaultPdfUrl)?_c('div',[_c('div',{staticClass:\"btngroup\",attrs:{\"id\":\"order-save\"}},[_c('a',{staticClass:\"btn\",attrs:{\"href\":_vm.defaultPdfUrl.url,\"target\":\"_blank\"}},[_vm._v(_vm._s(_vm._f(\"t\")('Download PDF','commerce')))]),_vm._v(\" \"),(_vm.pdfUrls.length > 1)?[_c('div',{ref:\"downloadPdfMenuBtn\",staticClass:\"btn menubtn\"}),_vm._v(\" \"),_c('div',{staticClass:\"menu\"},[_c('ul',_vm._l((_vm.pdfUrls),function(pdfUrl,key){return _c('li',{key:'pdfUrl' + key},[_c('a',{attrs:{\"href\":pdfUrl.url,\"target\":\"_blank\"}},[_vm._v(_vm._s(pdfUrl.name))])])}),0)])]:_vm._e()],2)]):_vm._e(),_vm._v(\" \"),(_vm.emailTemplates.length > 0)?[_c('div',{staticClass:\"btngroup send-email\"},[_c('div',{ref:\"sendEmailMenuBtn\",staticClass:\"btn menubtn\"},[_vm._v(\"\\n \"+_vm._s(_vm._f(\"t\")('Send Email','commerce'))+\"\\n \")]),_vm._v(\" \"),_c('div',{staticClass:\"menu\"},[_c('ul',_vm._l((_vm.emailTemplates),function(emailTemplate,key){return _c('li',{key:'emailTemplate' + key},[_c('a',{attrs:{\"href\":emailTemplate.id},on:{\"click\":function($event){$event.preventDefault();return _vm.sendEmail(emailTemplate.id)}}},[_vm._v(_vm._s(emailTemplate.name))])])}),0)])]),_vm._v(\" \"),(_vm.emailLoading)?_c('div',[_vm._m(0)]):_vm._e()]:_vm._e()],2):(_vm.hasOrderChanged)?_c('div',[_c('span',[_vm._v(_vm._s(_vm._f(\"t\")('This order has unsaved changes.','commerce')))])]):_vm._e()])\n}\nvar staticRenderFns = [function (){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"order-email-spinner\"},[_c('div',{staticClass:\"spinner\"})])\n}]\n\nexport { render, staticRenderFns }","/* jshint esversion: 6, strict: false */\n/* globals Craft */\nimport Vue from 'vue';\nimport Vuex from 'vuex';\nimport ordersApi from '../api/orders';\nimport addressesApi from '../api/addresses';\nimport utils from '../helpers/utils';\nimport _isEqual from 'lodash.isequal';\n\nVue.use(Vuex);\n\nexport default new Vuex.Store({\n strict: true,\n state: {\n recalculateLoading: false,\n saveLoading: false,\n editing: false,\n draft: null,\n originalDraft: null,\n customers: [],\n orderData: null,\n recentlyAddedLineItems: [],\n unloadEventInit: false,\n },\n\n getters: {\n autoSetNewCartAddresses() {\n return window.orderEdit.autoSetNewCartAddresses;\n },\n\n currentUserId() {\n return window.orderEdit.currentUserId;\n },\n\n currentUserPermissions() {\n return window.orderEdit.currentUserPermissions;\n },\n\n canDelete(state, getters) {\n return getters.currentUserPermissions['commerce-deleteOrders'];\n },\n\n canEdit(state, getters) {\n return getters.currentUserPermissions['commerce-editOrders'];\n },\n\n countries() {\n return window.orderEdit.countries;\n },\n\n forceEdit() {\n return window.orderEdit.forceEdit;\n },\n\n emailTemplates() {\n return window.orderEdit.emailTemplates;\n },\n\n ordersIndexUrl() {\n return window.orderEdit.ordersIndexUrl;\n },\n\n edition() {\n return window.orderEdit.edition;\n },\n\n isProEdition() {\n return window.orderEdit.edition == 'pro';\n },\n\n isLiteEdition() {\n return window.orderEdit.edition == 'lite';\n },\n\n hasOrderChanged(state) {\n return !_isEqual(state.draft, state.originalDraft);\n },\n\n orderId() {\n return window.orderEdit.orderId;\n },\n\n taxCategories() {\n return window.orderEdit.taxCategories;\n },\n\n shippingCategories() {\n return window.orderEdit.shippingCategories;\n },\n\n statesByCountryId() {\n return window.orderEdit.statesByCountryId;\n },\n\n pdfUrls() {\n return window.orderEdit.pdfUrls;\n },\n\n originalCustomer() {\n return window.orderEdit.originalCustomer;\n },\n\n maxLineItems(state, getters) {\n if (getters.edition === 'lite') {\n return 1;\n }\n\n return null;\n },\n\n canAddLineItem(state, getters) {\n if (!getters.maxLineItems) {\n return true;\n }\n\n if (state.draft.order.lineItems.length < getters.maxLineItems) {\n return true;\n }\n\n return false;\n },\n\n hasAddresses(state) {\n if (!state.draft) {\n return false;\n }\n\n return (\n (state.draft.order.billingAddressId &&\n state.draft.order.shippingAddressId) ||\n (state.draft.order.billingAddress && state.draft.order.shippingAddress)\n );\n },\n\n hasAnAddress(state) {\n if (!state.draft) {\n return false;\n }\n\n return (\n state.draft.order.billingAddressId ||\n state.draft.order.shippingAddressId ||\n state.draft.order.billingAddress ||\n state.draft.order.shippingAddress\n );\n },\n\n hasCustomer(state) {\n if (!state.draft) {\n return false;\n }\n\n return state.draft.order.customerId;\n },\n\n hasLineItems(state) {\n if (!state.draft || !state.draft.order || !state.draft.order.lineItems) {\n return false;\n }\n\n return state.draft.order.lineItems.length > 0;\n },\n\n lineItemStatuses() {\n return window.orderEdit.lineItemStatuses;\n },\n\n shippingMethods(state) {\n const shippingMethodsObject = JSON.parse(\n JSON.stringify(state.draft.order.availableShippingMethodOptions)\n );\n const shippingMethods = [];\n\n for (let key in shippingMethodsObject) {\n const shippingMethod = shippingMethodsObject[key];\n shippingMethods.push(shippingMethod);\n }\n\n return shippingMethods;\n },\n\n orderStatuses() {\n return window.orderEdit.orderStatuses;\n },\n\n orderSites() {\n return window.orderEdit.orderSites;\n },\n\n getErrors(state) {\n return (errorKey) => {\n if (\n state &&\n state.draft &&\n state.draft.order &&\n state.draft.errors &&\n state.draft.errors[errorKey]\n ) {\n return [state.draft.errors[errorKey]];\n }\n\n return [];\n };\n },\n\n hasLineItemErrors(state) {\n return (key) => {\n if (state && state.draft && state.draft.order && state.draft.errors) {\n let errorKeys = Object.keys(state.draft.errors);\n let pattern = '^lineItems\\\\.' + key + '\\\\.';\n let regex = new RegExp(pattern, 'gm');\n for (let i = 0; i < errorKeys.length; i++) {\n let errorKey = errorKeys[i];\n if (errorKey.match(regex)) {\n return true;\n }\n }\n }\n\n return false;\n };\n },\n\n userPhotoFallback() {\n return window.orderEdit.userPhotoFallback;\n },\n },\n\n actions: {\n displayError(context, msg) {\n Craft.cp.displayError(msg);\n },\n\n displayNotice(context, msg) {\n Craft.cp.displayNotice(msg);\n },\n\n disableTransactionsTab() {\n const $transactionsTab = window.document.querySelector(\n '#tabs > div > a[href=\"#transactionsTab\"]'\n );\n\n if (!$transactionsTab) {\n return;\n }\n\n $transactionsTab.classList.add('disabled');\n $transactionsTab.href = '';\n $transactionsTab.classList.remove('sel');\n\n const $transactionsTabClone = $transactionsTab.cloneNode(true);\n\n $transactionsTabClone.addEventListener('click', function (ev) {\n ev.preventDefault();\n });\n\n $transactionsTab.parentNode.replaceChild(\n $transactionsTabClone,\n $transactionsTab\n );\n\n let $transactionsTabContent =\n window.document.querySelector('#transactionsTab');\n $transactionsTabContent.classList.add('hidden');\n },\n\n edit({commit, state, dispatch}) {\n const $tabLinks = window.document.querySelectorAll('#tabs > div > a');\n let $selectedLink = null;\n let $detailsLink = null;\n let switchToDetailsTab = false;\n\n $tabLinks.forEach(function ($tabLink) {\n if (\n $tabLink.getAttribute('href') === '#orderDetailsTab' &&\n state.draft.order.isCompleted\n ) {\n $detailsLink = $tabLink;\n }\n\n // Disable Transactions tab\n if (\n $tabLink.getAttribute('href') === '#transactionsTab' &&\n state.draft.order.isCompleted\n ) {\n switchToDetailsTab = $tabLink.classList.contains('sel');\n dispatch('disableTransactionsTab');\n }\n\n // Custom tabs\n if ($tabLink.classList.contains('custom-tab')) {\n // Selected link\n if ($tabLink.classList.contains('sel')) {\n $selectedLink = $tabLink;\n }\n\n // Disable static custom field tabs\n if ($tabLink.classList.contains('static')) {\n $tabLink.classList.add('hidden');\n } else {\n $tabLink.classList.remove('hidden');\n }\n }\n });\n\n if (switchToDetailsTab) {\n $detailsLink.classList.add('sel');\n let $detailsTab = window.document.querySelector('#orderDetailsTab');\n $detailsTab.classList.remove('hidden');\n }\n\n // Retrieve dynamic link corresponding to selected static one and click it\n if ($selectedLink && $selectedLink.classList.contains('static')) {\n const staticLink = $selectedLink.getAttribute('href');\n let prefixLength = '#static-'.length;\n const dynamicLink =\n '#' +\n staticLink.substr(prefixLength, staticLink.length - prefixLength);\n\n $tabLinks.forEach(function ($tabLink) {\n if (\n $tabLink.classList.contains('custom-tab') &&\n $tabLink.getAttribute('href') === dynamicLink\n ) {\n const $newSelectedLink = $tabLink;\n $newSelectedLink.click();\n }\n });\n }\n\n // Update `editing` state\n commit('updateEditing', true);\n },\n\n getOrder({state, commit}) {\n commit('updateRecalculateLoading', true);\n\n return ordersApi\n .get()\n .then((response) => {\n commit('updateRecalculateLoading', false);\n\n const draft = response.data;\n\n if (!state.originalDraft) {\n const originalDraft = draft;\n commit('updateOriginalDraft', originalDraft);\n }\n\n commit('updateDraft', draft);\n })\n .catch((error) => {\n commit('updateRecalculateLoading', false);\n\n let errorMsg = 'Couldn’t get order.';\n\n if (error.response.data.error) {\n errorMsg = error.response.data.error;\n }\n\n throw errorMsg;\n });\n },\n\n deleteOrder({getters, commit}) {\n commit('updateRecalculateLoading', true);\n\n const orderId = getters.orderId;\n\n return ordersApi.deleteOrder(orderId).then(() => {\n commit('updateRecalculateLoading', false);\n });\n },\n\n customerSearch({commit}, query) {\n return ordersApi.customerSearch(query).then((response) => {\n commit('updateCustomers', response.data.customers);\n });\n },\n\n autoRecalculate({state, dispatch}) {\n const draft = state.draft;\n draft.order.recalculationMode = 'all';\n return dispatch('recalculateOrder', draft);\n },\n\n recalculateOrder({commit}, draft) {\n commit('updateRecalculateLoading', true);\n\n const data = utils.buildDraftData(draft);\n\n // Recalculate\n return ordersApi\n .recalculate(data)\n .then((response) => {\n commit('updateRecalculateLoading', false);\n\n const draft = response.data;\n commit('updateDraft', draft);\n\n if (response.data.error) {\n throw {response};\n }\n })\n .catch((error) => {\n commit('updateRecalculateLoading', false);\n\n let errorMsg = 'Couldn’t recalculate order.';\n\n const draft = error.response.data;\n commit('updateDraft', draft);\n\n if (error.response.data.error) {\n errorMsg = error.response.data.error;\n throw {response};\n }\n\n throw errorMsg;\n });\n },\n\n sendEmail(context, emailTemplateId) {\n return ordersApi.sendEmail(emailTemplateId);\n },\n\n getAddressById(context, id) {\n return addressesApi\n .getById(id)\n .then((response) => {\n if (response.data && response.data.success && response.data.address) {\n return response.data.address;\n }\n\n return null;\n })\n .catch(() => {\n let errorMsg = 'Couldn’t retrieve address.';\n\n throw errorMsg;\n });\n },\n\n validateAddress(context, address) {\n return addressesApi\n .validate(address)\n .then((response) => {\n if (response.data) {\n return response.data;\n }\n\n return response;\n })\n .catch((error) => {\n let errorMsg = 'Couldn’t validate address.';\n\n if (error.response.data.error) {\n errorMsg = error.response.data.error;\n }\n\n throw errorMsg;\n });\n },\n\n clearRecentlyAddedLineItems({state}) {\n state.recentlyAddedLineItems = [];\n },\n },\n\n mutations: {\n updateEditing(state, editing) {\n if (!state.unloadEventInit && editing) {\n state.unloadEventInit = true;\n // Add event listener for leaving the page\n window.addEventListener('beforeunload', function (ev) {\n // Only check if we are not saving\n if (\n !state.saveLoading &&\n !_isEqual(state.draft, state.originalDraft)\n ) {\n ev.preventDefault();\n ev.returnValue = '';\n }\n });\n }\n\n state.editing = editing;\n },\n\n updateDraft(state, draft) {\n state.draft = draft;\n },\n\n updateDraftSuppressEmails(state, suppressEmails) {\n state.draft.order.suppressEmails = suppressEmails;\n },\n\n updateDraftOrderMessage(state, message) {\n state.draft.order.message = message;\n },\n\n updateOriginalDraft(state, originalDraft) {\n state.originalDraft = originalDraft;\n },\n\n updateCustomers(state, customers) {\n state.customers = customers;\n },\n\n updateRecalculateLoading(state, recalculateLoading) {\n state.recalculateLoading = recalculateLoading;\n },\n\n updateSaveLoading(state, saveLoading) {\n state.saveLoading = saveLoading;\n },\n\n updateOrderData(state, orderData) {\n state.orderData = orderData;\n },\n\n updateRecentlyAddedLineItems(state, lineItemIdentifier) {\n state.recentlyAddedLineItems.push(lineItemIdentifier);\n },\n },\n});\n","/* jshint esversion: 6, strict: false */\n/* globals Craft, Garnish, $ */\n\nimport axios from 'axios/index';\n\nexport default {\n get() {\n //If we have the order loaded into the page already return that data and save us a ajax trip\n if (window.orderEdit.data) {\n return new Promise((resolve) => {\n var response = {};\n response.data = window.orderEdit.data;\n resolve(response);\n });\n }\n },\n\n recalculate(data) {\n return Craft.sendActionRequest('POST', 'commerce/orders/refresh', {data});\n },\n\n customerSearch(options) {\n const data = {};\n const opts = Object.assign({query: null, cancelToken: null}, options);\n let config = {\n headers: {\n 'X-CSRF-Token': Craft.csrfTokenValue,\n },\n };\n\n if (typeof opts.cancelToken !== 'undefined' && opts.cancelToken) {\n config['cancelToken'] = opts.cancelToken;\n }\n\n if (typeof opts.query !== 'undefined' && opts.query) {\n data.query = encodeURIComponent(opts.query);\n }\n\n return axios.get(\n Craft.getActionUrl('commerce/orders/customer-search', data),\n config\n );\n },\n\n sendEmail(emailTemplateId) {\n return axios.post(\n Craft.getActionUrl('commerce/orders/send-email', {\n id: emailTemplateId,\n orderId: window.orderEdit.orderId,\n }),\n {},\n {\n headers: {\n 'X-CSRF-Token': Craft.csrfTokenValue,\n },\n }\n );\n },\n\n deleteOrder(orderId) {\n let formData = new FormData();\n formData.append('orderId', orderId);\n\n return axios.post(\n Craft.getActionUrl('commerce/orders/delete-order'),\n formData,\n {\n headers: {\n 'X-CSRF-Token': Craft.csrfTokenValue,\n },\n }\n );\n },\n};\n","/* jshint esversion: 6, strict: false */\n/* globals Craft */\n\nimport axios from 'axios/index';\n\nexport default {\n getById(id) {\n return axios.post(\n Craft.getActionUrl('commerce/addresses/get-address-by-id'),\n {id: id},\n {\n headers: {\n 'X-CSRF-Token': Craft.csrfTokenValue,\n },\n }\n );\n },\n\n validate(address) {\n const data = {\n address: address,\n };\n return axios.post(\n Craft.getActionUrl('commerce/orders/validate-address'),\n data,\n {\n headers: {\n 'X-CSRF-Token': Craft.csrfTokenValue,\n },\n }\n );\n },\n};\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',{staticClass:\"order-block py\"},[_vm._t(\"default\")],2)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./OrderBlock.vue?vue&type=template&id=05a503be&\"\nvar script = {}\nimport style0 from \"./OrderBlock.vue?vue&type=style&index=0&id=05a503be&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","import { render, staticRenderFns } from \"./OrderTitle.vue?vue&type=template&id=772b8bf0&\"\nvar script = {}\nimport style0 from \"./OrderTitle.vue?vue&type=style&index=0&id=772b8bf0&prod&lang=scss&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('h3',{staticClass:\"order-title\"},[_vm._t(\"default\")],2)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","import { render, staticRenderFns } from \"./QtyInput.vue?vue&type=template&id=46cd39af&\"\nimport script from \"./QtyInput.vue?vue&type=script&lang=js&\"\nexport * from \"./QtyInput.vue?vue&type=script&lang=js&\"\n\n\n/* normalize component */\nimport normalizer from \"!../../../../../../../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js\"\nvar component = normalizer(\n script,\n render,\n staticRenderFns,\n false,\n null,\n null,\n null\n \n)\n\nexport default component.exports","\n\n\n","var render = function render(){var _vm=this,_c=_vm._self._c;return _c('div',[(_vm.rowData.isAvailable)?_c('field',{scopedSlots:_vm._u([{key:\"default\",fn:function(slotProps){return [_c('input',{directives:[{name:\"model\",rawName:\"v-model\",value:(_vm.rowData.qty),expression:\"rowData.qty\"}],staticClass:\"text\",attrs:{\"id\":slotProps.id,\"type\":\"text\",\"size\":\"3\",\"placeholder\":\"0\"},domProps:{\"value\":(_vm.rowData.qty)},on:{\"input\":function($event){if($event.target.composing)return;_vm.$set(_vm.rowData, \"qty\", $event.target.value)}}})]}}],null,false,3158077025)}):_vm._e()],1)\n}\nvar staticRenderFns = []\n\nexport { render, staticRenderFns }","/* jshint esversion: 6, strict: false */\n/* globals Craft, process */\nimport Vue from 'vue';\nimport App from './apps/OrderDetails';\nimport 'prismjs/themes/prism.css';\nimport OrderMeta from './apps/OrderMeta';\nimport OrderNotices from './apps/OrderNotices';\nimport OrderActions from './apps/OrderActions';\nimport OrderCustomer from './apps/OrderCustomer';\nimport OrderErrors from './apps/OrderErrors';\nimport OrderSecondaryActions from './apps/OrderSecondaryActions';\nimport store from './store';\nimport {t} from '../base/filters/craft';\nimport BtnLink from '../base/components/BtnLink';\nimport OrderBlock from './components/OrderBlock';\nimport OrderTitle from './components/OrderTitle';\nimport QtyInput from './components/details/QtyInput';\n\nVue.config.productionTip = false;\nif (process.env.NODE_ENV === 'development') {\n Vue.config.devtools = true;\n}\nVue.filter('t', t);\nVue.component('btn-link', BtnLink);\nVue.component('order-block', OrderBlock);\nVue.component('order-title', OrderTitle);\nVue.component('qty-input', QtyInput);\n\n// Order actions\n// =========================================================================\n\nwindow.OrderActionsApp = new Vue({\n render: (h) => h(OrderActions),\n store,\n}).$mount('#order-actions-app');\n\n// Order errors\n// =========================================================================\nwindow.OrderErrorsApp = new Vue({\n render: (h) => h(OrderErrors),\n store,\n}).$mount('#order-errors-app');\n\n// Order customer\n// =========================================================================\nwindow.OrderCustomerApp = new Vue({\n render: (h) => h(OrderCustomer),\n store,\n}).$mount('#order-customer-app');\n\n// Order details\n// =========================================================================\n\nwindow.OrderDetailsApp = new Vue({\n render: (h) => h(App),\n store,\n\n methods: {\n externalRefresh() {\n const draft = this.$store.state.draft;\n this.$store\n .dispatch('recalculateOrder', draft)\n .then(() => {\n this.$store.dispatch('displayNotice', 'Order recalculated.');\n })\n .catch((error) => {\n this.$store.dispatch('displayError', error);\n });\n },\n },\n\n mounted() {\n this.$store.dispatch('getOrder').finally(() => {\n if (!this.$store.getters.hasLineItems) {\n this.$store.dispatch('disableTransactionsTab');\n }\n });\n\n this.$store.watch(\n (state, getters) => getters.hasOrderChanged,\n (newVal, oldVal) => {\n if (newVal) {\n this.$store.dispatch('disableTransactionsTab');\n }\n }\n );\n },\n}).$mount('#order-details-app');\n\n// Order meta\n// =========================================================================\n\nwindow.OrderMetaApp = new Vue({\n render: (h) => h(OrderMeta),\n store,\n}).$mount('#order-meta-app');\n\n// Order meta\n// =========================================================================\n\nwindow.OrderNotices = new Vue({\n render: (h) => h(OrderNotices),\n store,\n}).$mount('#order-notices-app');\n\n// Order secondary actions\n// =========================================================================\n\nwindow.OrderSecondaryActionsApp = new Vue({\n render: (h) => h(OrderSecondaryActions),\n store,\n}).$mount('#order-secondary-actions-app');\n","/* global Craft */\n\nexport function t(message, category, params) {\n return Craft.t(category, message, params);\n}\n"],"names":["module","exports","utils","settle","cookies","buildURL","buildFullPath","parseHeaders","isURLSameOrigin","createError","transitionalDefaults","Cancel","config","Promise","resolve","reject","onCanceled","requestData","data","requestHeaders","headers","responseType","done","cancelToken","unsubscribe","signal","removeEventListener","isFormData","request","XMLHttpRequest","auth","username","password","unescape","encodeURIComponent","Authorization","btoa","fullPath","baseURL","url","onloadend","responseHeaders","getAllResponseHeaders","response","responseText","status","statusText","value","err","open","method","toUpperCase","params","paramsSerializer","timeout","onreadystatechange","readyState","responseURL","indexOf","setTimeout","onabort","onerror","ontimeout","timeoutErrorMessage","transitional","clarifyTimeoutError","isStandardBrowserEnv","xsrfValue","withCredentials","xsrfCookieName","read","undefined","xsrfHeaderName","forEach","val","key","toLowerCase","setRequestHeader","isUndefined","onDownloadProgress","addEventListener","onUploadProgress","upload","cancel","type","abort","subscribe","aborted","send","bind","Axios","mergeConfig","axios","createInstance","defaultConfig","context","instance","prototype","extend","create","instanceConfig","CancelToken","isCancel","VERSION","all","promises","spread","isAxiosError","message","this","toString","__CANCEL__","executor","TypeError","resolvePromise","promise","token","then","_listeners","i","l","length","onfulfilled","_resolve","reason","throwIfRequested","listener","push","index","splice","source","c","InterceptorManager","dispatchRequest","validator","validators","defaults","interceptors","configOrUrl","assertOptions","silentJSONParsing","boolean","forcedJSONParsing","requestInterceptorChain","synchronousRequestInterceptors","interceptor","runWhen","synchronous","unshift","fulfilled","rejected","responseInterceptorChain","chain","Array","apply","concat","shift","newConfig","onFulfilled","onRejected","error","getUri","replace","handlers","use","options","eject","id","fn","h","isAbsoluteURL","combineURLs","requestedURL","enhanceError","code","Error","transformData","throwIfCancellationRequested","call","transformRequest","merge","common","adapter","transformResponse","toJSON","name","description","number","fileName","lineNumber","columnNumber","stack","config1","config2","getMergedValue","target","isPlainObject","isArray","slice","mergeDeepProperties","prop","valueFromConfig2","defaultToConfig2","mergeDirectKeys","mergeMap","Object","keys","configValue","validateStatus","fns","normalizeHeaderName","DEFAULT_CONTENT_TYPE","setContentTypeIfUnset","process","isArrayBuffer","isBuffer","isStream","isFile","isBlob","isArrayBufferView","buffer","isURLSearchParams","isObject","rawValue","parser","encoder","isString","JSON","parse","trim","e","stringify","stringifySafely","strictJSONParsing","maxContentLength","maxBodyLength","thisArg","args","arguments","encode","serializedParams","parts","v","isDate","toISOString","join","hashmarkIndex","relativeURL","write","expires","path","domain","secure","cookie","isNumber","Date","toGMTString","document","match","RegExp","decodeURIComponent","remove","now","test","payload","originURL","msie","navigator","userAgent","urlParsingNode","createElement","resolveURL","href","setAttribute","protocol","host","search","hash","hostname","port","pathname","charAt","window","location","requestURL","parsed","normalizedName","ignoreDuplicateOf","split","line","substr","callback","arr","thing","deprecatedWarnings","version","formatMessage","opt","desc","opts","console","warn","schema","allowUnknown","result","getPrototypeOf","isFunction","obj","hasOwnProperty","constructor","ArrayBuffer","isView","pipe","product","assignValue","a","b","str","stripBOM","content","charCodeAt","reTrim","reIsBadHex","reIsBinary","reIsOctal","freeParseInt","parseInt","freeGlobal","g","freeSelf","self","root","Function","objectToString","nativeMax","Math","max","nativeMin","min","toNumber","isObjectLike","isSymbol","other","valueOf","isBinary","func","wait","lastArgs","lastThis","maxWait","timerId","lastCallTime","lastInvokeTime","leading","maxing","trailing","invokeFunc","time","leadingEdge","timerExpired","shouldInvoke","timeSinceLastCall","trailingEdge","remainingWait","debounced","isInvoking","clearTimeout","flush","HASH_UNDEFINED","MAX_SAFE_INTEGER","argsTag","arrayTag","boolTag","dateTag","errorTag","funcTag","mapTag","numberTag","objectTag","promiseTag","regexpTag","setTag","stringTag","symbolTag","weakMapTag","arrayBufferTag","dataViewTag","reIsDeepProp","reIsPlainProp","reLeadingDot","rePropName","reEscapeChar","reIsHostCtor","reIsUint","typedArrayTags","freeExports","nodeType","freeModule","freeProcess","nodeUtil","binding","nodeIsTypedArray","isTypedArray","arraySome","array","predicate","isHostObject","mapToArray","map","size","setToArray","set","uid","transform","arrayProto","funcProto","objectProto","coreJsData","maskSrcKey","exec","IE_PROTO","funcToString","reIsNative","Symbol","Uint8Array","propertyIsEnumerable","nativeKeys","arg","DataView","getNative","Map","Set","WeakMap","nativeCreate","dataViewCtorString","toSource","mapCtorString","promiseCtorString","setCtorString","weakMapCtorString","symbolProto","symbolValueOf","symbolToString","Hash","entries","clear","entry","ListCache","MapCache","SetCache","values","__data__","add","Stack","assocIndexOf","eq","baseGet","object","isKey","castPath","toKey","baseHasIn","baseIsEqual","customizer","bitmask","equalFunc","objIsArr","othIsArr","objTag","othTag","getTag","objIsObj","othIsObj","isSameTag","equalArrays","tag","byteLength","byteOffset","convert","isPartial","stacked","get","equalByTag","objIsWrapped","othIsWrapped","objUnwrapped","othUnwrapped","objProps","objLength","skipCtor","objValue","othValue","compared","objCtor","othCtor","equalObjects","baseIsEqualDeep","baseIteratee","identity","srcValue","isStrictComparable","matchesStrictComparable","defaultValue","hasFunc","isLength","isIndex","isArguments","hasPath","hasIn","UNORDERED_COMPARE_FLAG","baseMatchesProperty","matchData","getMatchData","baseIsMatch","basePropertyDeep","stringToPath","arrLength","othLength","seen","arrValue","othIndex","has","getMapData","getValue","isMasked","baseIsNative","pop","cache","pairs","LARGE_ARRAY_SIZE","Ctor","ctorString","memoize","string","baseToString","quote","findIndexFunc","find","fromIndex","remainder","fromRight","baseFindIndex","collection","iterable","isArrayLike","iteratee","resolver","memoized","Cache","isArrayLikeObject","baseUnary","inherited","n","baseTimes","String","skipIndexes","arrayLikeKeys","baseKeys","moduleExports","nativeObjectToString","Buffer","symToStringTag","toStringTag","nativeGetSymbols","getOwnPropertySymbols","nativeIsBuffer","baseGetTag","isOwn","unmasked","getRawTag","baseIsArguments","getAllKeys","keysFunc","symbolsFunc","offset","arrayPush","baseGetAllKeys","getSymbols","resIndex","symbol","arrayFilter","isArr","isArg","isBuff","isType","FUNC_ERROR_TEXT","PLACEHOLDER","WRAP_PARTIAL_FLAG","WRAP_ARY_FLAG","INFINITY","NAN","MAX_ARRAY_LENGTH","wrapFlags","genTag","float32Tag","float64Tag","int8Tag","int16Tag","int32Tag","uint8Tag","uint8ClampedTag","uint16Tag","uint32Tag","reEmptyStringLeading","reEmptyStringMiddle","reEmptyStringTrailing","reEscapedHtml","reUnescapedHtml","reHasEscapedHtml","reHasUnescapedHtml","reEscape","reEvaluate","reInterpolate","reRegExpChar","reHasRegExpChar","reTrimStart","reWhitespace","reWrapComment","reWrapDetails","reSplitDetails","reAsciiWord","reForbiddenIdentifierChars","reEsTemplate","reFlags","reLatin","reNoMatch","reUnescapedString","rsComboRange","rsComboMarksRange","rsLowerRange","rsUpperRange","rsBreakRange","rsMathOpRange","rsBreak","rsCombo","rsDigits","rsLower","rsMisc","rsFitz","rsNonAstral","rsRegional","rsSurrPair","rsUpper","rsMiscLower","rsMiscUpper","rsOptContrLower","rsOptContrUpper","reOptMod","rsModifier","rsOptVar","rsSeq","rsEmoji","rsSymbol","reApos","reComboMark","reUnicode","reUnicodeWord","reHasUnicode","reHasUnicodeWord","contextProps","templateCounter","cloneableTags","stringEscapes","freeParseFloat","parseFloat","require","types","nodeIsArrayBuffer","nodeIsDate","nodeIsMap","isMap","nodeIsRegExp","isRegExp","nodeIsSet","isSet","arrayAggregator","setter","accumulator","arrayEach","arrayEachRight","arrayEvery","arrayIncludes","baseIndexOf","arrayIncludesWith","comparator","arrayMap","arrayReduce","initAccum","arrayReduceRight","asciiSize","baseProperty","baseFindKey","eachFunc","strictIndexOf","baseIsNaN","baseIndexOfWith","baseMean","baseSum","basePropertyOf","baseReduce","current","baseTrim","trimmedEndIndex","baseValues","props","cacheHas","charsStartIndex","strSymbols","chrSymbols","charsEndIndex","countHolders","placeholder","deburrLetter","escapeHtmlChar","escapeStringChar","chr","hasUnicode","overArg","replaceHolders","setToPairs","stringSize","lastIndex","unicodeSize","stringToArray","unicodeToArray","asciiToArray","unescapeHtmlChar","_","runInContext","pick","idCounter","objectCtorString","oldDash","allocUnsafe","getPrototype","objectCreate","spreadableSymbol","isConcatSpreadable","symIterator","iterator","defineProperty","ctxClearTimeout","ctxNow","ctxSetTimeout","nativeCeil","ceil","nativeFloor","floor","nativeIsFinite","isFinite","nativeJoin","nativeNow","nativeParseInt","nativeRandom","random","nativeReverse","reverse","metaMap","realNames","lodash","LazyWrapper","LodashWrapper","wrapperClone","baseCreate","proto","baseLodash","chainAll","__wrapped__","__actions__","__chain__","__index__","__values__","__dir__","__filtered__","__iteratees__","__takeCount__","__views__","arraySample","baseRandom","arraySampleSize","shuffleSelf","copyArray","baseClamp","arrayShuffle","assignMergeValue","baseAssignValue","baseAggregator","baseEach","baseAssign","copyObject","baseAt","paths","skip","lower","upper","baseClone","isDeep","isFlat","isFull","input","initCloneArray","isFunc","cloneBuffer","initCloneObject","getSymbolsIn","copySymbolsIn","keysIn","baseAssignIn","copySymbols","cloneArrayBuffer","dataView","cloneDataView","cloneTypedArray","regexp","cloneRegExp","initCloneByTag","subValue","getAllKeysIn","baseConformsTo","baseDelay","baseDifference","includes","isCommon","valuesLength","outer","computed","valuesIndex","templateSettings","createBaseEach","baseForOwn","baseEachRight","baseForOwnRight","baseEvery","baseExtremum","baseFilter","baseFlatten","depth","isStrict","isFlattenable","baseFor","createBaseFor","baseForRight","baseFunctions","baseGt","baseHas","baseIntersection","arrays","caches","maxLength","Infinity","baseInvoke","parent","last","objStacked","othStacked","noCustomizer","COMPARE_PARTIAL_FLAG","baseMatches","property","isPrototype","baseLt","baseMap","baseMerge","srcIndex","mergeFunc","safeGet","newValue","isTyped","toPlainObject","baseMergeDeep","baseNth","baseOrderBy","iteratees","orders","getIteratee","criteria","comparer","sort","objCriteria","othCriteria","ordersLength","compareAscending","compareMultiple","baseSortBy","basePickBy","baseSet","basePullAll","basePullAt","indexes","previous","baseUnset","baseRepeat","baseRest","start","setToString","overRest","baseSample","baseSampleSize","nested","baseSetData","baseSetToString","constant","baseShuffle","baseSlice","end","baseSome","baseSortedIndex","retHighest","low","high","mid","baseSortedIndexBy","valIsNaN","valIsNull","valIsSymbol","valIsUndefined","othIsDefined","othIsNull","othIsReflexive","othIsSymbol","setLow","baseSortedUniq","baseToNumber","baseUniq","createSet","seenIndex","baseUpdate","updater","baseWhile","isDrop","baseWrapperValue","actions","action","baseXor","baseZipObject","assignFunc","valsLength","castArrayLikeObject","castFunction","castRest","castSlice","copy","arrayBuffer","typedArray","valIsDefined","valIsReflexive","composeArgs","partials","holders","isCurried","argsIndex","argsLength","holdersLength","leftIndex","leftLength","rangeLength","isUncurried","composeArgsRight","holdersIndex","rightIndex","rightLength","isNew","createAggregator","initializer","createAssigner","assigner","sources","guard","isIterateeCall","createCaseFirst","methodName","createCompounder","words","deburr","createCtor","thisBinding","createFind","createFlow","flatRest","funcs","prereq","thru","wrapper","getFuncName","funcName","getData","isLaziable","plant","createHybrid","partialsRight","holdersRight","argPos","ary","arity","isAry","isBind","isBindKey","isFlip","getHolder","holdersCount","newHolders","createRecurry","reorder","createInverter","toIteratee","baseInverter","createMathOperation","operator","createOver","arrayFunc","createPadding","chars","charsLength","createRange","step","toFinite","baseRange","createRelationalOperation","wrapFunc","isCurry","newData","setData","setWrapToString","createRound","precision","toInteger","pair","noop","createToPairs","baseToPairs","createWrap","srcBitmask","newBitmask","isCombo","mergeData","createCurry","createPartial","createBind","customDefaultsAssignIn","customDefaultsMerge","customOmitClone","arrStacked","flatten","otherFunc","stubArray","isMaskable","stubFalse","otherArgs","oldArray","shortOut","reference","details","insertWrapDetails","updateWrapDetails","getWrapDetails","count","lastCalled","stamp","remaining","rand","subString","clone","difference","differenceBy","differenceWith","findIndex","findLastIndex","head","intersection","mapped","intersectionBy","intersectionWith","pull","pullAll","pullAt","union","unionBy","unionWith","unzip","group","unzipWith","without","xor","xorBy","xorWith","zip","zipWith","wrapperAt","countBy","findLast","forEachRight","groupBy","invokeMap","keyBy","partition","sortBy","before","bindKey","WRAP_BIND_FLAG","debounce","timeWaiting","defer","delay","negate","overArgs","transforms","funcsLength","partial","partialRight","rearg","gt","gte","isError","isInteger","lt","lte","toArray","next","iteratorToArray","toLength","assign","assignIn","assignInWith","assignWith","at","propsIndex","propsLength","defaultsDeep","mergeWith","invert","invertBy","invoke","nativeKeysIn","isProto","baseKeysIn","omit","CLONE_DEEP_FLAG","basePick","pickBy","toPairs","toPairsIn","camelCase","word","capitalize","upperFirst","kebabCase","lowerCase","lowerFirst","snakeCase","startCase","upperCase","pattern","hasUnicodeWord","unicodeWords","asciiWords","attempt","bindAll","methodNames","flow","flowRight","methodOf","mixin","over","overEvery","overSome","range","rangeRight","augend","addend","divide","dividend","divisor","multiply","multiplier","multiplicand","round","subtract","minuend","subtrahend","after","castArray","chunk","compact","cond","conforms","baseConforms","properties","curry","curryRight","drop","dropRight","dropRightWhile","dropWhile","fill","baseFill","filter","flatMap","flatMapDeep","flatMapDepth","flattenDeep","flattenDepth","flip","fromPairs","functions","functionsIn","initial","mapKeys","mapValues","matches","matchesProperty","nthArg","omitBy","once","orderBy","propertyOf","pullAllBy","pullAllWith","rest","sampleSize","setWith","shuffle","sortedUniq","sortedUniqBy","separator","limit","tail","take","takeRight","takeRightWhile","takeWhile","tap","throttle","toPath","isArrLike","unary","uniq","uniqBy","uniqWith","unset","update","updateWith","valuesIn","wrap","zipObject","zipObjectDeep","entriesIn","extendWith","clamp","cloneDeep","cloneDeepWith","cloneWith","conformsTo","defaultTo","endsWith","position","escape","escapeRegExp","every","findKey","findLastKey","forIn","forInRight","forOwn","forOwnRight","inRange","baseInRange","isBoolean","isElement","isEmpty","isEqual","isEqualWith","isMatch","isMatchWith","isNaN","isNative","isNil","isNull","isSafeInteger","isWeakMap","isWeakSet","lastIndexOf","strictLastIndexOf","maxBy","mean","meanBy","minBy","stubObject","stubString","stubTrue","nth","noConflict","pad","strLength","padEnd","padStart","radix","floating","temp","reduce","reduceRight","repeat","sample","some","sortedIndex","sortedIndexBy","sortedIndexOf","sortedLastIndex","sortedLastIndexBy","sortedLastIndexOf","startsWith","sum","sumBy","template","settings","isEscaping","isEvaluating","imports","importsKeys","importsValues","interpolate","reDelimiters","evaluate","sourceURL","escapeValue","interpolateValue","esTemplateValue","evaluateValue","variable","times","toLower","toSafeInteger","toUpper","trimEnd","trimStart","truncate","omission","substring","global","newEnd","uniqueId","prefix","each","eachRight","first","isFilter","takeName","dropName","checkIteratee","isTaker","lodashFunc","retUnwrapped","isLazy","useLazy","isHybrid","isUnwrapped","onlyLazy","chainName","dir","isRight","view","getView","iterLength","takeCount","iterIndex","commit","wrapped","modules","installedModules","moduleId","m","d","getter","o","enumerable","r","t","mode","__esModule","ns","p","s","anObject","that","ignoreCase","multiline","unicode","sticky","it","is","hide","redefine","fails","defined","wks","KEY","SYMBOL","strfn","rxfn","O","toIObject","toAbsoluteIndex","IS_INCLUDES","$this","el","arrayIndexOf","names","SPLIT","$split","_split","$push","NPCG","separator2","lastLength","output","flags","lastLastIndex","splitLimit","separatorCopy","SRC","TO_STRING","$toString","TPL","inspectSource","safe","store","USE_SYMBOL","S","$export","INCLUDES","P","F","searchString","dP","createDesc","f","configurable","IE8_DOM_DEFINE","toPrimitive","Attributes","getTime","__webpack_require__","bitmap","writable","MATCH","$match","re","core","SHARED","copyright","ctx","own","out","exp","IS_FORCED","IS_GLOBAL","G","IS_STATIC","IS_PROTO","IS_BIND","B","expProto","U","W","R","shared","cof","$includes","IObject","$flags","DESCRIPTORS","define","Iterator","node","peeked","closingTag","_revisit","_selects","_rejects","higher","traverse","child","expr","peek","compile","closing","revisit","parentNode","selects","rejects","reset","opening","atOpening","atClosing","prev","select","exprs","len","peak","abs","matchHtmlRegExp","html","$keys","enumBugKeys","__g","px","__e","regexCache","charSets","default","extras","regex","toRegex","aFunction","UNSCOPABLES","ArrayProto","REPLACE","$replace","searchValue","replaceValue","globals","ret","unique","prefixed","ENDS_WITH","$endsWith","endPosition","__webpack_exports__","_node_modules_mini_css_extract_plugin_dist_loader_js_ref_6_oneOf_1_0_node_modules_css_loader_index_js_ref_6_oneOf_1_1_node_modules_vue_loader_lib_loaders_stylePostLoader_js_node_modules_postcss_loader_src_index_js_ref_6_oneOf_1_2_node_modules_cache_loader_dist_cjs_js_ref_0_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Editor_vue_vue_type_style_index_0_lang_css___WEBPACK_IMPORTED_MODULE_0__","hasOwn","getKeys","isEnum","isEntries","IS_WRAP","C","virtual","NAME","$values","currentScript","scripts","getElementsByTagName","res","src","setPublicPath_i","values_default","now_default","escape_html","escape_html_default","lang","langPrism","node_modules_unescape","unescape_default","dom_iterator","dom_iterator_default","makeSelection","selection","getSelection","focus","removeAllRanges","addRange","selection_range","pos","activeElement","rangeCount","getRangeAt","cloneRange","selectNodeContents","setEnd","endContainer","endOffset","setStart","startContainer","startOffset","atStart","commonAncestorContainer","startindex","setSelection","Node","TEXT_NODE","textContent","olen","atLength","collapse","getLine","plain","cursorPos","startSlice","lastNewline","indentRe","deindentSpacesRe","FORBIDDEN_KEYS","ctrl","alt","pauseBreak","capsLock","esc","pageUp","pageDown","home","arrowLeft","arrowUp","arrowRight","arrowDown","printScreen","meta","f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12","numLock","scrollLock","components_Editorvue_type_script_lang_js_","model","event","emitEvents","Boolean","language","lineNumbers","autoStyleLineNumbers","readonly","undoStack","lineNumbersHeight","undoOffset","undoTimestamp","lastPos","codeData","composing","watch","immediate","handler","newVal","_this","$nextTick","setLineNumbersHeight","_this2","styleLineNumbers","rawLang","Prism","languages","highlight","prism","lineNumbersCount","totalLines","updated","$refs","pre","mounted","_this3","recordChange","getPlain","onPaste","preventDefault","currentCursorPos","text","originalEvent","clipboardData","execCommand","newCursorPos","updateContent","$pre","$once","methods","getComputedStyle","height","$editor","$lineNumbers","$el","querySelector","editorStyles","btlr","bblr","style","handleClick","evt","$emit","_innerHTML","innerHTML","_plain","utils_htmlToPlain","timestamp","record","restoreStackState","_this$undoStack","undo","redo","handleKeyDown","keyCode","ignoreTabKey","_selectionRange","deindent","getDeindentLevel","innerText","_cursorPos","indentation","getIndent","metaKey","ctrlKey","altKey","shiftKey","handleKeyUp","keyupCode","which","Editor","scriptExports","render","staticRenderFns","functionalTemplate","injectStyles","scopeId","moduleIdentifier","shadowMode","hook","_compiled","functional","_injectStyles","originalRender","existing","beforeCreate","normalizeComponent","_obj","_vm","_h","$createElement","_c","_self","staticClass","attrs","staticStyle","_v","_l","_s","_e","_g","ref","class","domProps","on","$listeners","from","VueSelect","mixins","autoscroll","typeAheadPointer","maybeAdjustScroll","dropdownMenu","children","getDropdownViewport","getBoundingClientRect","top","bottom","scrollTop","offsetTop","filteredOptions","selectable","typeAheadToLastSelected","selectedValue","typeAheadUp","typeAheadDown","typeAheadSelect","u","loading","mutableLoading","toggleLoading","_scopeId","$vnode","ssrContext","__VUE_SSR_CONTEXT__","_registeredComponents","_ssrRegister","$root","$options","shadowRoot","Deselect","xmlns","width","OpenIndicator","inserted","appendToBody","toggle","left","scrollX","pageXOffset","scrollY","pageYOffset","unbindPosition","calculatePosition","body","appendChild","unbind","removeChild","y","getOwnPropertyDescriptor","getOwnPropertyDescriptors","defineProperties","components","directives","disabled","clearable","deselectFromDropdown","searchable","multiple","transition","clearSearchOnSelect","closeOnSelect","label","autocomplete","getOptionLabel","getOptionKey","onTab","selectOnTab","isComposing","taggable","tabindex","Number","pushTags","filterable","filterBy","toLocaleLowerCase","createOption","optionList","resetOnOptionsChange","clearSearchOnBlur","noDrop","inputId","selectOnKeyCodes","searchInputQuerySelector","mapKeydown","dropdownShouldOpen","pushedTags","_value","isTrackingValues","propsData","$data","searchEl","$scopedSlots","selectedOptions","scope","searching","attributes","searchPlaceholder","dropdownOpen","events","compositionstart","compositionend","keydown","onSearchKeyDown","blur","onSearchBlur","onSearchFocus","spinner","noOptions","openIndicator","role","listHeader","listFooter","header","deselect","footer","childComponents","stateClasses","isValueEmpty","optionExists","showClearButton","clearSelection","setInternalValueFromOptions","created","$on","pushTag","findOptionFromReducedValue","isOptionSelected","updateValue","onAfterSelect","optionComparator","toggleDropdown","deselectButtons","clearButton","contains","isOptionDeselectable","closeSearchOptions","maybeDeleteValue","normalizeOptionForSlot","onEscape","mousedown","onMousedown","onMouseUp","_t","refInFor","title","click","option","_b","rawName","expression","mouseup","mouseover","stopPropagation","display","visibility","ajax","pointer","pointerScroll","locals","listToStyles","parentId","list","styles","newStyles","item","part","css","media","sourceMap","hasDocument","DEBUG","stylesInDom","singletonElement","singletonCounter","isProduction","ssrIdKey","isOldIE","addStylesClient","_isProduction","_options","addStylesToDom","newList","mayRemove","domStyle","refs","j","addStyle","createStyleElement","styleElement","styleIndex","applyToSingletonTag","applyToTag","newObj","textStore","replaceText","replacement","styleSheet","cssText","cssNode","createTextNode","childNodes","insertBefore","ssrId","firstChild","_vval","_params","_toConsumableArray","_arrayLikeToArray","_arrayWithoutHoles","iter","_iterableToArray","minLen","_unsupportedIterableToArray","_nonIterableSpread","arr2","ownKeys","enumerableOnly","symbols","sym","_objectSpread","_defineProperty","_typeof","NIL","buildFromKeys","keyFn","build","getPath","fallback","validationGetters","$invalid","proxy","nestedKeys","refProxy","ruleKeys","rule","$dirty","dirty","$anyDirty","$error","$pending","$anyError","_this4","_this5","getRef","$params","_this6","vals","validations","setDirtyRecursive","newState","validationMethods","$touch","$reset","$flattenParams","isNested","childParams","getterNames","_cachedComponent","_cachedVue","validateModel","_getComponent","Vue","VBase","oldVval","patchChildren","vm","beforeDestroy","getModel","lazyModel","getModelKey","hasIter","ValidationRule","lazyParentModel","rootModel","runRule","pushParams","rawOutput","asyncVm","makePendingAsyncVm","rawParams","popParams","$sub","run","_this7","__ob__","arrayDep","dep","depend","_indirectWatcher","Watcher","lazy","_lastModel","teardown","destroyed","Validation","_this8","k","_this9","keyDefs","getterDefs","methodDefs","iterDefs","$iter","$model","_this10","renderNested","renderRule","GroupValidation","EachValidation","tracker","_this11","trackBy","$trackBy","x","getModelLazy","_this12","_this13","def","usedTracks","track","refVals","$v","getComponent","rootVm","super","getVue","validationMixin","_vuelidate","$destroy","_setTarget","withParams","paramsOrClosure","maybeValidator","withParamsClosure","_len","_key","lastTarget","newTarget","addParams","closure","_len2","_key2","_default","_common","valid","req","_withParams","parentVm","_email","_required","_interopRequireDefault","_getRequireWildcardCache","nodeInterop","cacheBabelInterop","cacheNodeInterop","hasPropertyDescriptor","_interopRequireWildcard","nibbles","nibbleValid","nibble","numeric","hexValid","hex","equalTo","isUndef","isDef","sameVval","vval","createVm","Vm","createKeyToOldIdx","beginIdx","endIdx","addVvals","vvals","startIdx","removeVvals","ch","patchVval","updateVval","oldCh","newCh","oldKeyToIdx","idxInOld","elmToMove","oldStartIdx","newStartIdx","oldEndIdx","oldStartVval","oldEndVval","newEndIdx","newStartVval","newEndVval","updateChildren","vuelidate","__webpack_module_cache__","cachedModule","loaded","__webpack_modules__","definition","globalThis","nmd","Vuex","errors","inputClass","instructions","required","lineItem","editing","mapGetters","lineItemStatusId","lineItemStatus","color","onSelectStatus","onOptionSelect","filters","_f","lineItemStatuses","sel","LineItemStatusInput","$event","autoShow","modalClass","resizable","show","showFooter","modal","isVisible","_initModal","onHide","onShow","hideModal","showModal","Field","Modal","PrismEditor","errorKeys","errorValues","isWaiting","renderPrism","showPrismEditor","hasErrors","hasDuplicateKeys","inputType","normalizeOptions","onAddOption","onEditOptions","closeModal","onModalHide","onModalShow","onChange","onOptionsChange","onRemoveOption","prepOptions","updateLineItemOptions","jsonValid","updateLineItem","showForm","row","pt","scopedSlots","_u","slotProps","$set","$$v","hidden","note","privateNote","adjustment","adjustmentKey","errorPrefix","recalculationMode","showLabels","adjustmentOptions","allowedAdjustmentTypes","amount","included","isAllowedAdjustmentType","Adjustment","$$selectedVal","selected","adjustmentOption","_i","$$a","$$el","$$c","checked","$$i","getErrors","amountAsCurrency","adjustments","orderId","Adjustments","addAdjustment","lineItemId","sourceSnapshot","authorId","updateAdjustment","removeAdjustment","showSnapshot","LineItemStatus","LineItemOptions","LineItemNotes","LineItemAdjustments","Snapshot","lineItemKey","editMode","originalLineItem","mapState","recentlyAddedLineItems","salePrice","qty","shippingCategory","taxCategory","highlightLineItem","mapActions","enableEditMode","applyEdit","cancelEdit","_initSnapshotModal","openSnapshotModal","closeSnapshotModal","onHideSnapshotModal","removeLineItem","updateLineItemStatusId","LineItem","hasLineItemErrors","purchasableCpEditUrl","sku","isProEdition","salePriceAsCurrency","onSale","priceAsCurrency","saleAmountAsCurrency","subtotalAsCurrency","totalAsCurrency","snapshot","lineItems","addLineItem","fields","loadOnStart","apiUrl","httpMethod","reactiveApiUrl","apiMode","dataTotal","dataManager","dataPath","paginationPath","queryParams","page","perPage","appendParams","httpOptions","httpFetch","initialPage","sortOrder","multiSort","tableHeight","multiSortKey","rowClassCallback","rowClass","detailRowComponent","detailRowTransition","tableClass","loadingClass","ascendingIcon","descendingIcon","ascendingClass","descendingClass","sortableIcon","detailRowClass","handleIcon","tableBodyClass","tableHeaderClass","minRows","silent","noDataTemplate","showSortIcons","eventPrefix","tableFields","tableData","tablePagination","currentPage","selectedTo","visibleDetailRows","lastScrollPosition","scrollBarWidth","scrollVisible","normalizeFields","normalizeSortOrder","isFixedHeader","getScrollBarWidth","fireEvent","loadData","elem","getElementsByClassName","handleScroll","useDetailRow","countVisibleFields","field","visible","countTableData","displayEmptyDataRow","lessThanMinRows","blankRows","isApiMode","isDataMode","inner","widthWithoutScrollbar","offsetWidth","overflow","widthWithScrollbar","horizontal","currentTarget","scrollLeft","setTitle","titleClass","dataClass","sortField","getObjectValue","fixHeader","isSpecialField","titleCase","getTitle","renderTitle","isInCurrentSortGroup","hasSortableIcon","sortIconOpacity","renderIconTag","sortIcon","renderSequence","renderNormalField","hasCallback","callCallback","fieldName","txt","delimiter","notIn","success","loadSuccess","failed","loadFailed","getAppendParams","getAllQueryParams","fetch","catch","callDataManager","scrollHeight","clientHeight","parentFunctionExists","$parent","callParentFunction","eventName","msg","getSortParam","getDefaultSortParam","direction","extractName","extractArgs","isSortable","currentSortOrderPosition","fieldIsInSortOrderPosition","multiColumnSort","singleColumnSort","clearSortOrder","sortClass","cls","toggleCheckbox","dataItem","isChecked","idColumn","selectId","unselectId","isSelectedRow","rowSelected","checkCheckboxesState","selector","els","querySelectorAll","cb","indeterminate","toggleAllCheckboxes","gotoPreviousPage","gotoNextPage","last_page","gotoPage","isVisibleDetailRow","rowId","showDetailRow","hideDetailRow","toggleDetailRow","showField","hideField","toggleField","classes","renderIcon","makePagination","total","onRowClass","onRowChanged","onRowClicked","onRowDoubleClicked","onDetailRowClick","onCellClicked","onCellDoubleClicked","onCellRightClicked","changePage","reload","refresh","resetData","oldVal","fieldIndex","itemIndex","wrapperClass","activeClass","disabledClass","pageClass","linkClass","paginationClass","paginationInfoClass","dropdownClass","icons","onEachSide","totalPage","isOnFirstPage","current_page","isOnLastPage","notEnoughPages","windowSize","windowStart","loadPage","isCurrentPage","setPaginationData","itemLabels","singular","Craft","plural","paginationLabel","to","items","actionUrl","confirmationMessage","failMessage","successMessage","escapeHtml","confirm","confirmDelete","continueDelete","sendActionRequest","cp","displayNotice","displayError","selectAll","checks","allowMultiple","enabled","ids","icon","button","buttonDisabled","tokenName","csrfTokenName","tokenValue","csrfTokenValue","param","finally","form","submit","enableButton","isMenuButtonInitialised","enable","disableButton","disable","actionsList","_tmpActionsList","hasMultipleSelected","isMenuButton","initUiElements","$","menubtn","actList","ind","act","rowData","rowIndex","addDelimiter","listKeys","detail","showAsList","btnClass","isEnabled","buttonClass","linkHref","ui","createCopyTextBtn","_objectSpread2","_extends","_objectWithoutProperties","excluded","sourceKeys","_objectWithoutPropertiesLoose","sourceSymbolKeys","IE11OrLess","Edge","FireFox","Safari","IOS","ChromeForAndroid","captureMode","capture","passive","off","msMatchesSelector","webkitMatchesSelector","getParentOrHost","closest","includeCTX","_throttleTimeout","R_SPACE","toggleClass","state","classList","className","defaultView","currentStyle","matrix","selfOnly","appliedTransforms","matrixFn","DOMMatrix","WebKitCSSMatrix","CSSMatrix","MSCSSMatrix","tagName","getWindowScrollingElement","scrollingElement","documentElement","getRect","relativeToContainingBlock","relativeToNonStaticParent","undoScale","container","elRect","right","innerHeight","innerWidth","containerRect","elMatrix","scaleX","scaleY","isScrolledPast","elSide","parentSide","getParentAutoScrollElement","elSideVal","parentSideVal","getChild","childNum","includeDragEl","currentChild","Sortable","ghost","dragged","draggable","lastChild","lastElementChild","previousElementSibling","nodeName","getRelativeScrollOffset","offsetLeft","winScroller","includeSelf","gotSelf","clientWidth","scrollWidth","elemCSS","overflowX","overflowY","isRectEqual","rect1","rect2","ms","scrollBy","Polymer","jQuery","Zepto","dom","cloneNode","expando","plugins","initializeByDefault","PluginManager","mount","plugin","pluginName","pluginEvent","sortable","eventCanceled","eventNameGlobal","initializePlugins","initialized","modified","modifyOption","getEventProperties","eventProperties","modifiedValue","optionListeners","_excluded","_ref","dragEl","parentEl","ghostEl","rootEl","nextEl","lastDownEl","cloneEl","cloneHidden","dragStarted","moved","putSortable","activeSortable","active","oldIndex","oldDraggableIndex","newIndex","newDraggableIndex","hideGhostForTarget","_hideGhostForTarget","unhideGhostForTarget","_unhideGhostForTarget","cloneNowHidden","cloneNowShown","dispatchSortableEvent","_dispatchEvent","info","targetEl","toEl","fromEl","extraEventProperties","onName","CustomEvent","createEvent","initEvent","bubbles","cancelable","pullMode","lastPutMode","allEventProperties","dispatchEvent","activeGroup","tapEvt","touchEvt","lastDx","lastDy","tapDistanceLeft","tapDistanceTop","lastDirection","targetMoveDistance","ghostRelativeParent","awaitingDragStarted","ignoreNextClick","sortables","pastFirstInvertThresh","isCircumstantialInvert","ghostRelativeParentInitialScroll","_silent","savedInputChecked","documentExists","PositionGhostAbsolutely","CSSFloatProperty","supportDraggable","supportCssPointerEvents","pointerEvents","_detectDirection","elCSS","elWidth","paddingLeft","paddingRight","borderLeftWidth","borderRightWidth","child1","child2","firstChildCSS","secondChildCSS","firstChildWidth","marginLeft","marginRight","secondChildWidth","flexDirection","gridTemplateColumns","touchingSideChild2","_prepareGroup","toFn","sameGroup","otherGroup","originalGroup","checkPull","checkPut","put","revertClone","stopImmediatePropagation","nearestEmptyInsertDetectEvent","touches","nearest","clientX","clientY","threshold","emptyInsertThreshold","rect","insideHorizontally","insideVertically","_onDragOver","_checkOutsideTargetEl","_isOutsideThisEl","animationCallbackId","animationStates","handle","swapThreshold","invertSwap","invertedSwapThreshold","removeCloneOnHide","ghostClass","chosenClass","dragClass","ignore","preventOnFilter","animation","easing","dataTransfer","dropBubble","dragoverBubble","dataIdAttr","delayOnTouchOnly","touchStartThreshold","devicePixelRatio","forceFallback","fallbackClass","fallbackOnBody","fallbackTolerance","fallbackOffset","supportPointer","nativeDraggable","_onTapStart","captureAnimationState","fromRect","thisAnimationDuration","childMatrix","addAnimationState","removeAnimationState","indexOfObject","animateAll","animating","animationTime","toRect","prevFromRect","prevToRect","animatingRect","targetMatrix","sqrt","pow","calculateRealTime","animate","animationResetTimer","currentRect","duration","translateX","translateY","animatingX","animatingY","forRepaintDummy","repaint","animated","_onMove","dragRect","targetRect","willInsertAfter","retVal","onMoveFn","onMove","draggedRect","related","relatedRect","_disableDraggable","_unsilent","_generateId","_nextTick","_cancelNextTick","_getDirection","touch","pointerType","originalTarget","composedPath","inputs","idx","_saveInputCheckedState","isContentEditable","_prepareDragStart","dragStartFn","ownerDocument","nextSibling","_lastX","_lastY","_onDrop","_disableDelayedDragEvents","_triggerDragStart","_disableDelayedDrag","_delayedDragTouchMoveHandler","_dragStartTimer","_onTouchMove","_onDragStart","empty","_dragStarted","_appendGhost","_nulling","_emulateDragOver","elementFromPoint","ghostMatrix","relativeScrollOffset","dx","dy","cssMatrix","removeAttribute","_hideClone","cloneId","_loopId","setInterval","effectAllowed","_dragStartId","revert","vertical","isOwner","canSort","fromSortable","completedFired","dragOverEvent","_ignoreWhileAnimating","completed","elLastChild","_ghostIsLast","changed","_ghostIsFirst","targetBeforeFirstSwap","sibling","differentLevel","differentRowCol","dragElS1Opp","dragElS2Opp","dragElOppLength","targetS1Opp","targetS2Opp","targetOppLength","_dragElInRowColumn","side1","scrolledPastTop","scrollBefore","isLastTarget","mouseOnAxis","targetLength","targetS1","targetS2","_getInsertDirection","_getSwapDirection","dragIndex","nextElementSibling","moveVector","extra","axis","insertion","_showClone","_offMoveEvents","_offUpEvents","clearInterval","save","handleEvent","dropEffect","_globalDragOver","order","getAttribute","useAnimation","destroy","dst","nextTick","cancelNextTick","detectDirection","element","scrollEl","scrollRootEl","lastAutoScrollX","lastAutoScrollY","touchEvt$1","pointerElemChangedInterval","autoScrolls","scrolling","clearAutoScrolls","autoScroll","pid","clearPointerElemChangedInterval","isFallback","scroll","scrollCustomFn","sens","scrollSensitivity","speed","scrollSpeed","scrollThisInstance","scrollFn","layersOut","currentParent","canScrollX","canScrollY","scrollPosX","scrollPosY","vx","vy","layer","scrollOffsetY","scrollOffsetX","bubbleScroll","toSortable","changedTouches","onSpill","Revert","Remove","startIndex","dragStart","_ref2","_ref3","_ref4","parentSortable","AutoScroll","forceAutoScrollFallback","_handleAutoScroll","_handleFallbackAutoScroll","dragOverCompleted","dragOverBubble","nulling","ogElemScroller","newElem","AdminTableCopyTextButton","AdminTableActionButton","AdminTableCheckbox","AdminTableDeleteButton","AdminTablePagination","AdminTableButton","Vuetable","allowMultipleSelections","beforeDelete","buttons","checkboxes","checkboxStatus","columns","deleteAction","deleteCallback","deleteConfirmationMessage","deleteFailMessage","deleteSuccessMessage","emptyMessage","fullPage","fullPane","minItems","padded","reorderAction","reorderSuccessMessage","reorderFailMessage","tableDataEndpoint","onLoaded","onLoading","onData","onPagination","onSelect","detailRow","AdminTableDetailRow","dragging","isLoading","searchClearTitle","searchTerm","tableBodySelector","init","tableBody","canReorder","onSort","handleReorder","onStart","startReorder","onEnd","endReorder","vuetable","handleSelectAll","ev","elements","dataset","startPosition","addCheck","handleOnSelectCallback","removeCheck","handleSearch","tableLength","disabledCheckboxesCount","handleDetailRow","deselectAll","delete","onLoadSuccess","emitData","onPaginationData","paginationData","pagination","onChangePage","tableId","getActionUrl","canDelete","checkboxCount","customColumns","searchPlaceholderText","showToolbar","tableCss","checkbox","tablepane","menu","_showDelete","AdminTable","selectedPurchasables","currentTableData","purchasableTableColumns","purchasables","formDisabled","submitDisabled","lineItemAdd","purchasableId","purchasable","isCheckboxEnabled","handleCheckboxSelect","handleTableData","deep","AddLineItem","LineItems","OrderAdjustments","BtnLink","draft","addOrderAdjustment","updateOrderAdjustment","removeOrderAdjustment","isCompleted","Total","totalPriceAsCurrency","recalculateLoading","saveLoading","originalDraft","lineItemsErrors","orderAdjustments","purchasableIds","updateLineItems","updateOrderAdjustments","recalculate","autoRecalculate","dispatch","lineItemError","lineItemsErrorsKey","originalOrderStatusId","suppressEmails","isRecalculating","textareaHasFocus","orderMessage","originalMessage","orderStatus","orderStatusId","orderStatuses","originalOrderSiteId","orderSite","orderSiteId","onSelectSite","orderSites","site","VSelect","selectClass","searchInputName","preFiltered","clearOnBlur","onSearch","searchText","getSearchEvents","onOptionClick","onOptionCreated","SelectInput","selectedShippingMethod","shippingMethods","shippingMethod","shippingMethodHandle","noneShippingMethod","matchesOrder","orderShippingMethodHandle","selectedOption","date","dateValue","timeValue","onDateChange","timezone","onDateInput","defaultDate","buildDraftData","draftData","customerId","couponCode","shippingAddressId","shippingAddress","billingAddressId","billingAddress","dateOrdered","notices","sourceBillingAddressId","sourceShippingAddressId","parseInputValue","_lineItem","shippingCategoryId","_adjustment","_orderAdjustment","parsedValue","saveOrder","$store","dataString","OrderStatus","OrderSite","ShippingMethod","DateOrderedInput","markAsCompleted","updateOrder","updateDateOrderedInput","originLabel","origin","hasCustomer","orderStatusHtml","shippingMethodName","shortNumber","orderShortNumber","orderNumber","paidStatusHtml","totalPaid","totalPaidAsCurrency","datePaid","dateAuthorized","dateUpdated","lastIp","orderCompletedEmail","email","showNotices","clearNotices","notice","os","shortcutKey","UpdateOrderBtn","OptionShortcutLabel","ordersIndexUrl","orderData","deleteOrder","returnToOrders","hasAddresses","redirect","$tabLinks","$tabLink","$selectedTabLink","$newSelectedTabHash","canEdit","edit","customer","showRemove","colors","getBgColor","getDisplayName","customerName","initialChar","avatarClass","cl","removeButtonLabel","photo","photoThumbUrl","cpEditUrl","addressModel","addresses","isLoadingMore","selectedAddress","isDoneDisabled","canSelectAddress","isLoadMoreVisible","getAddresses","per_page","loadMore","close","address","_q","AddressSelect","copyToAddress","addressCard","emptyMsg","_initAddressCard","ownerId","maxAddresses","handleEditAddress","handleNewAddress","elementType","elementId","draftId","slideout","addressId","handleSelect","Customer","customerSearchRequest","selectedCustomer","newCustomerEmail","customers","onCreated","query","AddressEdit","CustomerSelect","customerUpdatedTime","customerUrl","titles","hasBillingAddress","hasShippingAddress","copyAddress","destinationAddress","_copy","updateBillingAddress","updateShippingAddress","removeBillingAddress","removeShippingAddress","updateAddress","updateCustomer","billingPromise","shippingPromise","removeCustomer","hasAnAddress","billingAddressHtml","shippingAddressHtml","draftErrors","emailLoading","defaultPdfUrl","sendEmail","hasOrderChanged","pdfUrls","pdfUrl","emailTemplates","emailTemplate","_m","strict","unloadEventInit","getters","autoSetNewCartAddresses","orderEdit","currentUserId","currentUserPermissions","countries","forceEdit","edition","isLiteEdition","_isEqual","taxCategories","shippingCategories","statesByCountryId","originalCustomer","maxLineItems","canAddLineItem","hasLineItems","shippingMethodsObject","availableShippingMethodOptions","errorKey","userPhotoFallback","disableTransactionsTab","$transactionsTab","$transactionsTabClone","replaceChild","$selectedLink","$detailsLink","switchToDetailsTab","staticLink","prefixLength","dynamicLink","getOrder","ordersApi","errorMsg","formData","FormData","append","customerSearch","recalculateOrder","emailTemplateId","getAddressById","addressesApi","validateAddress","clearRecentlyAddedLineItems","mutations","updateEditing","returnValue","updateDraft","updateDraftSuppressEmails","updateDraftOrderMessage","updateOriginalDraft","updateCustomers","updateRecalculateLoading","updateSaveLoading","updateOrderData","updateRecentlyAddedLineItems","lineItemIdentifier","isAvailable","category","OrderBlock","OrderTitle","QtyInput","OrderActionsApp","OrderActions","$mount","OrderErrorsApp","OrderErrors","OrderCustomerApp","OrderCustomer","OrderDetailsApp","App","externalRefresh","OrderMetaApp","OrderMeta","OrderNotices","OrderSecondaryActionsApp","OrderSecondaryActions"],"sourceRoot":""} \ No newline at end of file diff --git a/src/web/assets/commerceui/src/js/order/apps/OrderCustomer.vue b/src/web/assets/commerceui/src/js/order/apps/OrderCustomer.vue index e57a8e6116..4ee72db885 100644 --- a/src/web/assets/commerceui/src/js/order/apps/OrderCustomer.vue +++ b/src/web/assets/commerceui/src/js/order/apps/OrderCustomer.vue @@ -420,9 +420,11 @@ .customer-select-wrapper { width: 50%; + padding-right: 14px; @media only screen and (max-width: 767px) { width: 100%; + padding-right: 0; } } diff --git a/src/web/assets/commerceui/src/js/order/apps/OrderMeta.vue b/src/web/assets/commerceui/src/js/order/apps/OrderMeta.vue index e3d3d1c072..ddd41aadf9 100644 --- a/src/web/assets/commerceui/src/js/order/apps/OrderMeta.vue +++ b/src/web/assets/commerceui/src/js/order/apps/OrderMeta.vue @@ -290,6 +290,16 @@
{{ 'Origin' | t('commerce') }}
{{ originLabel(draft.order.origin) }} +
+
{{ 'Completed Email' | t('commerce') }}
+ {{ draft.order.orderCompletedEmail }} +
diff --git a/tests/fixtures/ProductTypeFixture.php b/tests/fixtures/ProductTypeFixture.php index 86a37856b5..9e14ed7852 100644 --- a/tests/fixtures/ProductTypeFixture.php +++ b/tests/fixtures/ProductTypeFixture.php @@ -31,5 +31,11 @@ class ProductTypeFixture extends ActiveFixture /** * @inheritdoc */ - public $depends = [ProductTypeSitesFixture::class]; + public $depends = [ + ShippingCategoryFixture::class, + ProductTypesShippingCategoriesFixture::class, + TaxCategoryFixture::class, + ProductTypesTaxCategoriesFixture::class, + ProductTypeSitesFixture::class, + ]; } diff --git a/tests/fixtures/data/product-types.php b/tests/fixtures/data/product-types.php index 10fc402fc2..e54586f9ea 100644 --- a/tests/fixtures/data/product-types.php +++ b/tests/fixtures/data/product-types.php @@ -1,7 +1,7 @@ [ 'id' => '2000', 'name' => 'Hoodies', 'handle' => 'hoodies', @@ -13,7 +13,7 @@ 'hasProductTitleField' => true, 'hasVariantTitleField' => false, ], - [ + 'tees' => [ 'id' => '2001', 'name' => 'T-Shirts', 'handle' => 'tShirts', diff --git a/tests/fixtures/data/shipping-category.php b/tests/fixtures/data/shipping-category.php index bec54858db..3fc4e6b142 100644 --- a/tests/fixtures/data/shipping-category.php +++ b/tests/fixtures/data/shipping-category.php @@ -8,10 +8,10 @@ return [ [ 'id' => 101, - 'name' => 'General 1', - 'handle' => 'general_1', - 'description' => 'this is the default shipping category', - 'default' => '1', - 'uid' => 'xx-xx-xx', + 'name' => 'Another Shipping Category', + 'handle' => 'anotherShippingCategory', + 'description' => 'this is another shipping category', + 'default' => 0, + 'uid' => 'ship-category-1001---------------uid', ], ]; diff --git a/tests/fixtures/data/tax-category.php b/tests/fixtures/data/tax-category.php index 3ac7330120..d6b683b2f7 100644 --- a/tests/fixtures/data/tax-category.php +++ b/tests/fixtures/data/tax-category.php @@ -8,10 +8,10 @@ return [ [ 'id' => 101, - 'name' => 'General 1', - 'handle' => 'general_1', - 'description' => 'this is the default tax category', + 'name' => 'Another Tax Category', + 'handle' => 'anotherTaxCategory', + 'description' => 'this is another tax category', 'default' => '1', - 'uid' => 'xx-xx-xx', + 'uid' => 'tax--category-1001---------------uid', ], ]; diff --git a/tests/unit/controllers/CartTest.php b/tests/unit/controllers/CartTest.php index a64214df51..e49a61c543 100644 --- a/tests/unit/controllers/CartTest.php +++ b/tests/unit/controllers/CartTest.php @@ -406,4 +406,79 @@ public function autoSetNewCartAddressesDataProvider(): array ], ]; } + + /** + * @param bool|null $saveBillingAddress + * @param bool|null $saveShippingAddress + * @param bool|null $saveBoth + * @return void + * @throws ElementNotFoundException + * @throws Exception + * @throws InvalidConfigException + * @throws InvalidPluginException + * @throws InvalidRouteException + * @throws Throwable + * @since 4.3.0 + * @dataProvider setSaveAddressesDataProvider + */ + public function testSetSaveAddresses(?bool $saveBillingAddress, ?bool $saveShippingAddress, ?bool $saveBoth): void + { + Craft::$app->getPlugins()->switchEdition('commerce', Plugin::EDITION_PRO); + $this->request->headers->set('X-Http-Method-Override', 'POST'); + + $bodyParams = []; + if ($saveBoth) { + $bodyParams['saveAddressesOnOrderComplete'] = true; + } else { + $bodyParams['saveBillingAddressOnOrderComplete'] = $saveBillingAddress; + $bodyParams['saveShippingAddressOnOrderComplete'] = $saveShippingAddress; + } + + $this->request->setBodyParams($bodyParams); + $this->cartController->runAction('update-cart'); + + $cart = Plugin::getInstance()->getCarts()->getCart(); + + if ($saveBoth) { + self::assertTrue($cart->saveBillingAddressOnOrderComplete); + self::assertTrue($cart->saveShippingAddressOnOrderComplete); + } else { + self::assertEquals($saveBillingAddress, $cart->saveBillingAddressOnOrderComplete); + self::assertEquals($saveShippingAddress, $cart->saveShippingAddressOnOrderComplete); + } + + Plugin::getInstance()->getCarts()->forgetCart(); + + Craft::$app->getElements()->deleteElement($cart, true); + } + + /** + * @return array[] + * @since 4.3.0 + */ + public function setSaveAddressesDataProvider(): array + { + return [ + 'save-billing' => [ + true, // save billing + false, // save shipping + false, // save both + ], + 'save-shipping' => [ + false, // save billing + true, // save shipping + false, // save both + ], + 'save-both' => [ + false, // save billing + false, // save shipping + true, // save both + ], + 'save-both-individually' => [ + true, // save billing + true, // save shipping + false, // save both + ], + ]; + } } diff --git a/tests/unit/elements/order/OrderCustomerTest.php b/tests/unit/elements/order/OrderCustomerTest.php new file mode 100644 index 0000000000..8404d5030f --- /dev/null +++ b/tests/unit/elements/order/OrderCustomerTest.php @@ -0,0 +1,112 @@ + + * @since 4.3.0 + */ +class OrderCustomerTest extends Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'orders' => [ + 'class' => OrdersFixture::class, + ], + ]; + } + + /** + * @param string $email + * @return void + * @throws Exception + * @throws InvalidConfigException + * @dataProvider emailDataProvider + */ + public function testSetEmail(string $email): void + { + \Craft::$app->set('deprecator', $this->make(Deprecator::class, [ + 'log' => function(string $key, string $message, ?string $file = null, ?int $line = null) { + self::once(); + self::assertEquals(Order::class . '::setEmail', $key); + }, + ])); + + $order = new Order(); + $order->setEmail($email); + + self::assertEquals($email, $order->getEmail()); + self::assertNotNull($order->getCustomer()); + self::assertEquals($email, $order->getCustomer()->email); + } + + /** + * @return array[] + */ + public function emailDataProvider(): array + { + return [ + 'existing-credentialed-user' => ['email' => 'customer1@crafttest.com'], + 'existing-inactive-user' => ['email' => 'inactive.user@crafttest.com'], + ]; + } + + /** + * @param string $email + * @return void + * @dataProvider customerDataProvider + */ + public function testSetCustomer(string $email): void + { + $user = \Craft::$app->getUsers()->getUserByUsernameOrEmail($email); + $order = new Order(); + $order->setCustomer($user); + + self::assertEquals($email, $order->getEmail()); + self::assertNotNull($order->getCustomer()); + self::assertEquals($email, $order->getCustomer()->email); + self::assertEquals($user->id, $order->getCustomer()->id); + self::assertEquals($user->id, $order->getCustomerId()); + + // Test remove customer + $order->setCustomer(); + self::assertNull($order->getCustomer()); + self::assertNull($order->getCustomerId()); + self::assertNull($order->getEmail()); + } + + /** + * @return array[] + */ + public function customerDataProvider(): array + { + return [ + 'existing-credentialed-user' => ['email' => 'customer1@crafttest.com'], + 'existing-inactive-user' => ['email' => 'inactive.user@crafttest.com'], + ]; + } +} diff --git a/tests/unit/elements/order/OrderMarkAsCompleteTest.php b/tests/unit/elements/order/OrderMarkAsCompleteTest.php index 76c3b0f805..60a3e9a6da 100644 --- a/tests/unit/elements/order/OrderMarkAsCompleteTest.php +++ b/tests/unit/elements/order/OrderMarkAsCompleteTest.php @@ -60,7 +60,8 @@ public function testUpdatedProperties(): void { $order = new Order(); $email = 'test@newemailaddress.xyz'; - $order->setEmail($email); + $user = \Craft::$app->getUsers()->ensureUserByEmail($email); + $order->setCustomer($user); /** @var Order $order */ $completedOrder = $this->tester->grabFixture('orders')->getElement('completed-new'); $lineItem = $completedOrder->getLineItems()[0]; @@ -80,7 +81,7 @@ public function testUpdatedProperties(): void self::assertEquals($email, $order->orderCompletedEmail); $this->_deleteElementIds[] = $order->id; - $this->_deleteElementIds[] = $order->getCustomerId(); + $this->_deleteElementIds[] = $user->id; } /** diff --git a/tests/unit/elements/product/ProductQueryTest.php b/tests/unit/elements/product/ProductQueryTest.php new file mode 100644 index 0000000000..a4db81ec0e --- /dev/null +++ b/tests/unit/elements/product/ProductQueryTest.php @@ -0,0 +1,276 @@ + + * @since 4.3.0 + */ +class ProductQueryTest extends Unit +{ + /** + * @var UnitTester + */ + protected $tester; + + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @return void + */ + public function testQuery(): void + { + self::assertInstanceOf(ProductQuery::class, Product::find()); + } + + /** + * @param mixed $price + * @param int $count + * @return void + * @dataProvider defaultPriceDataProvider + */ + public function testDefaultPrice(mixed $price, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'defaultPrice')); + $query->defaultPrice($price); + + self::assertCount($count, $query->all()); + } + + /** + * @return array[] + */ + public function defaultPriceDataProvider(): array + { + return [ + 'exact-results' => [123.99, 1], + 'exact-no-results' => [999, 0], + 'greater-than-results' => ['> 1', 2], + 'greater-than-no-results' => ['> 999', 0], + 'less-than-results' => ['< 150', 2], + 'less-than-no-results' => ['< 1', 0], + 'range-results' => [['and', '> 5', '< 200'], 2], + 'range-no-results' => [['and', '> 500', '< 2000'], 0], + 'in-results' => [[123.99, 19.99], 2], + 'in-no-results' => [[1, 2], 0], + ]; + } + + /** + * @param VariantQuery $variantQuery + * @param int $count + * @return void + * @dataProvider hasVariantDataProvider + */ + public function testHasVariant(VariantQuery $variantQuery, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'hasVariant')); + $query->hasVariant($variantQuery); + + self::assertCount($count, $query->all()); + } + + /** + * @return array[] + */ + public function hasVariantDataProvider(): array + { + return [ + 'no-params' => [Variant::find(), 2], + 'specific-variant' => [Variant::find()->sku('rad-hood'), 1], + ]; + } + + /** + * @param mixed $shippingCategoryId + * @param int $count + * @return void + * @dataProvider shippingCategoryIdDataProvider + */ + public function testShippingCategoryId(mixed $shippingCategoryId, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'shippingCategoryId')); + $query->shippingCategoryId($shippingCategoryId); + + self::assertCount($count, $query->all()); + } + + /** + * @param mixed $shippingCategoryId + * @param int $count + * @return void + * @dataProvider shippingCategoryIdDataProvider + */ + public function testShippingCategoryIdProperty(mixed $shippingCategoryId, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'shippingCategoryId')); + $query->shippingCategoryId = $shippingCategoryId; + + self::assertCount($count, $query->all()); + } + + /** + * @return array + */ + public function shippingCategoryIdDataProvider(): array + { + return [ + 'no-params' => [null, 2], + 'specific-id' => [101, 1], + 'in' => [[101, 102], 1], + 'not-in' => [['not', 102, 103], 2], + 'greater-than' => ['> 100', 1], + 'less-than' => ['< 100', 1], + ]; + } + + /** + * @param mixed $shippingCategory + * @param int $count + * @return void + * @dataProvider shippingCategoryDataProvider + */ + public function testShippingCategory(mixed $shippingCategory, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'shippingCategoryId')); + $query->shippingCategory($shippingCategory); + + self::assertCount($count, $query->all()); + } + + /** + * @return array + */ + public function shippingCategoryDataProvider(): array + { + $matchingShippingCategory = new ShippingCategory(['id' => 101]); + $nonMatchingShippingCategory = new ShippingCategory(['id' => 999]); + + return [ + 'no-params' => [null, 2], + 'specific-handle' => ['anotherShippingCategory', 1], + 'in' => [['anotherShippingCategory', 'general'], 2], + 'not-in' => [['not', 'foo', 'bar'], 2], + 'matching-shipping-category' => [$matchingShippingCategory, 1], + 'non-matching-shipping-category' => [$nonMatchingShippingCategory, 0], + ]; + } + + /** + * @param mixed $taxCategoryId + * @param int $count + * @return void + * @dataProvider taxCategoryIdDataProvider + */ + public function testTaxCategoryId(mixed $taxCategoryId, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'taxCategoryId')); + $query->taxCategoryId($taxCategoryId); + + self::assertCount($count, $query->all()); + } + + /** + * @param mixed $taxCategoryId + * @param int $count + * @return void + * @dataProvider taxCategoryIdDataProvider + */ + public function testTaxCategoryIdProperty(mixed $taxCategoryId, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'taxCategoryId')); + $query->taxCategoryId = $taxCategoryId; + + self::assertCount($count, $query->all()); + } + + /** + * @return array + */ + public function taxCategoryIdDataProvider(): array + { + return [ + 'no-params' => [null, 2], + 'specific-id' => [101, 1], + 'in' => [[101, 102], 1], + 'not-in' => [['not', 102, 103], 2], + 'greater-than' => ['> 100', 1], + 'less-than' => ['< 100', 1], + ]; + } + + /** + * @param mixed $taxCategory + * @param int $count + * @return void + * @dataProvider taxCategoryDataProvider + */ + public function testTaxCategory(mixed $taxCategory, int $count): void + { + $query = Product::find(); + + self::assertTrue(method_exists($query, 'taxCategoryId')); + $query->taxCategory($taxCategory); + + self::assertCount($count, $query->all()); + } + + /** + * @return array + */ + public function taxCategoryDataProvider(): array + { + $matchingTaxCategory = new TaxCategory(['id' => 101]); + $nonMatchingTaxCategory = new TaxCategory(['id' => 999]); + + return [ + 'no-params' => [null, 2], + 'specific-handle' => ['anotherTaxCategory', 1], + 'in' => [['anotherTaxCategory', 'general'], 2], + 'not-in' => [['not', 'foo', 'bar'], 2], + 'matching-tax-category' => [$matchingTaxCategory, 1], + 'non-matching-tax-category' => [$nonMatchingTaxCategory, 0], + ]; + } +} diff --git a/tests/unit/elements/product/ProductTest.php b/tests/unit/elements/product/ProductTest.php index 1a00a4d9da..e2d6df129e 100644 --- a/tests/unit/elements/product/ProductTest.php +++ b/tests/unit/elements/product/ProductTest.php @@ -5,7 +5,7 @@ * @license https://craftcms.github.io/license/ */ -namespace craftcommercetests\unit\elements\order; +namespace craftcommercetests\unit\elements\product; use Codeception\Test\Unit; use craft\commerce\elements\Product; diff --git a/tests/unit/elements/product/conditions/ProductConditionTest.php b/tests/unit/elements/product/conditions/ProductConditionTest.php new file mode 100644 index 0000000000..57b2ab0d06 --- /dev/null +++ b/tests/unit/elements/product/conditions/ProductConditionTest.php @@ -0,0 +1,57 @@ + + * @since 4.3.0 + */ +class ProductTest extends Unit +{ + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @group Product Condition + */ + public function testCreateCondition(): void + { + self::assertInstanceOf(ProductCondition::class, Product::createCondition()); + } + + /** + * @group Product Condition + */ + public function testConditionRuleTypes(): void + { + $rules = Product::createCondition()->getConditionRuleTypes(); + + self::assertContains(ProductTypeConditionRule::class, $rules); + self::assertContains(ProductVariantSkuConditionRule::class, $rules); + self::assertContains(ProductVariantStockConditionRule::class, $rules); + } +} diff --git a/tests/unit/elements/product/conditions/ProductTypeConditionRuleTest.php b/tests/unit/elements/product/conditions/ProductTypeConditionRuleTest.php new file mode 100644 index 0000000000..258198c690 --- /dev/null +++ b/tests/unit/elements/product/conditions/ProductTypeConditionRuleTest.php @@ -0,0 +1,119 @@ + + * @since 4.3.0 + */ +class ProductTypeConditionRuleTest extends Unit +{ + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'product-types' => [ + 'class' => ProductTypeFixture::class, + ], + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @group Product + */ + public function testMatchElement(): void + { + $productTypeModel = Plugin::getInstance()->getProductTypes()->getProductTypeByHandle('hoodies'); + $condition = Product::createCondition(); + /** @var ProductTypeConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductTypeConditionRule::class); + $rule->setValues([$productTypeModel->uid]); + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertTrue($condition->matchElement($product)); + } + + /** + * @group Product + */ + public function testNotMatchElement(): void + { + $productTypeModel = Plugin::getInstance()->getProductTypes()->getProductTypeByHandle('tShirts'); + $condition = Product::createCondition(); + /** @var ProductTypeConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductTypeConditionRule::class); + $rule->setValues([$productTypeModel->uid]); + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertFalse($condition->matchElement($product)); + } + + /** + * @group Product + */ + public function testMatchElementNotIn(): void + { + $productTypeModel = Plugin::getInstance()->getProductTypes()->getProductTypeByHandle('tShirts'); + $condition = Product::createCondition(); + /** @var ProductTypeConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductTypeConditionRule::class); + $rule->setValues([$productTypeModel->uid]); + $rule->operator = 'ni'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertTrue($condition->matchElement($product)); + } + + /** + * @group Product + */ + public function testModifyQueryMatch(): void + { + $productTypeModel = Plugin::getInstance()->getProductTypes()->getProductTypeByHandle('hoodies'); + $condition = Product::createCondition(); + /** @var ProductTypeConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductTypeConditionRule::class); + $rule->setValues([$productTypeModel->uid]); + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + $query = Product::find(); + $condition->modifyQuery($query); + + self::assertContainsEquals($product->id, $query->ids()); + } +} diff --git a/tests/unit/elements/product/conditions/ProductVariantHasUnlimitedStockConditionRuleTest.php b/tests/unit/elements/product/conditions/ProductVariantHasUnlimitedStockConditionRuleTest.php new file mode 100644 index 0000000000..f9401f4937 --- /dev/null +++ b/tests/unit/elements/product/conditions/ProductVariantHasUnlimitedStockConditionRuleTest.php @@ -0,0 +1,138 @@ + + * @since 4.3.0 + */ +class ProductVariantHasUnlimitedStockConditionRuleTest extends Unit +{ + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @group Product + * @dataProvider matchElementDataProvider + * @param bool $hasUnlimitedStock + * @throws InvalidConfigException + */ + public function testMatchElement(bool $hasUnlimitedStock): void + { + $condition = Product::createCondition(); + /** @var ProductVariantHasUnlimitedStockConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantHasUnlimitedStockConditionRule::class); + $rule->value = $hasUnlimitedStock; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + if (!$hasUnlimitedStock) { + $variants = $product->getVariants(); + $variants[0]->hasUnlimitedStock = false; + $product->setVariants($variants); + } + + self::assertTrue($condition->matchElement($product)); + } + + /** + * @group Product + * @dataProvider matchElementDataProvider + * @param bool $hasUnlimitedStock + * @throws InvalidConfigException + */ + public function testNotMatchElement(bool $hasUnlimitedStock): void + { + $condition = Product::createCondition(); + /** @var ProductVariantHasUnlimitedStockConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantHasUnlimitedStockConditionRule::class); + $rule->value = $hasUnlimitedStock; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + if ($hasUnlimitedStock) { + $variants = $product->getVariants(); + $variants[0]->hasUnlimitedStock = false; + $product->setVariants($variants); + } + + self::assertFalse($condition->matchElement($product)); + } + + /** + * @param bool $hasUnlimitedStock + * @return void + * @throws ElementNotFoundException + * @throws Exception + * @throws InvalidConfigException + * @throws \Throwable + * @dataProvider matchElementDataProvider + */ + public function testModifyQueryMatch(bool $hasUnlimitedStock): void + { + $condition = Product::createCondition(); + /** @var ProductVariantHasUnlimitedStockConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantHasUnlimitedStockConditionRule::class); + $rule->value = $hasUnlimitedStock; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + if (!$hasUnlimitedStock) { + $variants = $product->getVariants(); + $variants[0]->stock = 9; + $variants[0]->hasUnlimitedStock = false; + $product->setVariants($variants); + } + + \Craft::$app->getElements()->saveElement($product, false, false, false, false); + + $query = Product::find(); + $condition->modifyQuery($query); + + self::assertContainsEquals($product->id, $query->ids()); + } + + /** + * @return array + */ + public function matchElementDataProvider(): array + { + return [ + [true], + [false], + ]; + } +} diff --git a/tests/unit/elements/product/conditions/ProductVariantPriceConditionRuleTest.php b/tests/unit/elements/product/conditions/ProductVariantPriceConditionRuleTest.php new file mode 100644 index 0000000000..82364e6d9c --- /dev/null +++ b/tests/unit/elements/product/conditions/ProductVariantPriceConditionRuleTest.php @@ -0,0 +1,116 @@ + + * @since 4.3.0 + */ +class ProductVariantPriceConditionRuleTest extends Unit +{ + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @group Product + * @dataProvider matchElementDataProvider + */ + public function testMatchElement(float|int $price, ?string $operator, bool $expected): void + { + $condition = Product::createCondition(); + /** @var ProductVariantPriceConditionRule $rule */ + $rule = Craft::$app->getConditions()->createConditionRule(ProductVariantPriceConditionRule::class); + $rule->value = $price; + + if ($operator) { + $rule->operator = $operator; + } + + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertSame($expected, $condition->matchElement($product)); + } + + /** + * @return void + * @throws Throwable + * @throws ElementNotFoundException + * @throws Exception + * @throws InvalidConfigException + * @dataProvider modifyQueryDataProvider + */ + public function testModifyQueryMatch(float|int $price, ?string $operator, int $expected): void + { + $condition = Product::createCondition(); + /** @var ProductVariantPriceConditionRule $rule */ + $rule = Craft::$app->getConditions()->createConditionRule(ProductVariantPriceConditionRule::class); + $rule->value = $price; + + if ($operator) { + $rule->operator = $operator; + } + + $condition->addConditionRule($rule); + + $query = Product::find(); + $condition->modifyQuery($query); + + self::assertCount($expected, $query->ids()); + } + + /** + * @return array[] + */ + public function matchElementDataProvider(): array + { + return [ + [100, '>', true], + [1000, '>', false], + [1000, '<', true], + [123.99, null, true], + ]; + } + + /** + * @return array[] + */ + public function modifyQueryDataProvider(): array + { + return [ + [100, '>', 1], + [1000, '>', 0], + [1000, '<', 2], + [123.99, null, 1], + ]; + } +} diff --git a/tests/unit/elements/product/conditions/ProductVariantSkuConditionRuleTest.php b/tests/unit/elements/product/conditions/ProductVariantSkuConditionRuleTest.php new file mode 100644 index 0000000000..6de798d0e7 --- /dev/null +++ b/tests/unit/elements/product/conditions/ProductVariantSkuConditionRuleTest.php @@ -0,0 +1,89 @@ + + * @since 4.3.0 + */ +class ProductVariantSkuConditionRuleTest extends Unit +{ + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @group Product + */ + public function testMatchElement(): void + { + $condition = Product::createCondition(); + /** @var ProductVariantSkuConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantSkuConditionRule::class); + $rule->value = 'rad-hood'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertTrue($condition->matchElement($product)); + } + + /** + * @group Product + */ + public function testNotMatchElement(): void + { + $condition = Product::createCondition(); + /** @var ProductVariantSkuConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantSkuConditionRule::class); + $rule->value = 'does-not-exist'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertFalse($condition->matchElement($product)); + } + + public function testModifyQueryMatch(): void + { + $condition = Product::createCondition(); + /** @var ProductVariantSkuConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantSkuConditionRule::class); + $rule->value = 'rad'; + $rule->operator = 'bw'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + $query = Product::find(); + $condition->modifyQuery($query); + + self::assertContainsEquals($product->id, $query->ids()); + } +} diff --git a/tests/unit/elements/product/conditions/ProductVariantStockConditionRuleTest.php b/tests/unit/elements/product/conditions/ProductVariantStockConditionRuleTest.php new file mode 100644 index 0000000000..d3b1996b02 --- /dev/null +++ b/tests/unit/elements/product/conditions/ProductVariantStockConditionRuleTest.php @@ -0,0 +1,103 @@ + + * @since 4.3.0 + */ +class ProductVariantStockConditionRuleTest extends Unit +{ + /** + * @return array + */ + public function _fixtures(): array + { + return [ + 'products' => [ + 'class' => ProductFixture::class, + ], + ]; + } + + /** + * @group Product + */ + public function testMatchElement(): void + { + $condition = Product::createCondition(); + /** @var ProductVariantStockConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantStockConditionRule::class); + $rule->value = 10; + $rule->operator = '<'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + $variants = $product->getVariants(); + $variants[0]->stock = 9; + $variants[0]->hasUnlimitedStock = false; + $product->setVariants($variants); + + self::assertTrue($condition->matchElement($product)); + } + + /** + * @group Product + */ + public function testNotMatchElement(): void + { + $condition = Product::createCondition(); + /** @var ProductVariantStockConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantStockConditionRule::class); + $rule->value = 10; + $rule->operator = '<'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + self::assertFalse($condition->matchElement($product)); + } + + public function testModifyQueryMatch(): void + { + $condition = Product::createCondition(); + /** @var ProductVariantStockConditionRule $rule */ + $rule = \Craft::$app->getConditions()->createConditionRule(ProductVariantStockConditionRule::class); + $rule->value = 10; + $rule->operator = '<'; + $condition->addConditionRule($rule); + + $productsFixture = $this->tester->grabFixture('products'); + /** @var Product $product */ + $product = $productsFixture->getElement('rad-hoodie'); + + $variants = $product->getVariants(); + $variants[0]->stock = 9; + $variants[0]->hasUnlimitedStock = false; + $product->setVariants($variants); + + \Craft::$app->getElements()->saveElement($product, false, false, false, false); + + $query = Product::find(); + $condition->modifyQuery($query); + + self::assertContainsEquals($product->id, $query->ids()); + } +} diff --git a/tests/unit/models/DiscountTest.php b/tests/unit/models/DiscountTest.php index 3b078e1c8c..e9858194ae 100644 --- a/tests/unit/models/DiscountTest.php +++ b/tests/unit/models/DiscountTest.php @@ -8,7 +8,13 @@ namespace craftcommercetests\unit\models; use Codeception\Test\Unit; +use craft\commerce\elements\conditions\addresses\DiscountAddressCondition; +use craft\commerce\elements\conditions\customers\DiscountCustomerCondition; +use craft\commerce\elements\conditions\orders\DiscountOrderCondition; use craft\commerce\models\Discount; +use craft\elements\conditions\ElementConditionInterface; +use craft\elements\conditions\IdConditionRule; +use yii\base\InvalidConfigException; /** * DiscountTest @@ -44,4 +50,151 @@ public function getPercentDiscountAsPercentDataProvider(): array ['-0.1050400', '10.504%'], ]; } + + /** + * @param ElementConditionInterface|array|string $condition + * @param bool $expected + * @return void + * @throws InvalidConfigException + * @since 4.3.0 + * @dataProvider conditionBuilderDataProvider + */ + public function testHasOrderCondition(ElementConditionInterface|array|string $condition, bool $expected): void + { + if ($condition === 'class' || $condition === 'rules') { + /** @var DiscountOrderCondition $condition */ + $conditionBuilder = \Craft::$app->getConditions()->createCondition(DiscountOrderCondition::class); + + if ($condition === 'rules') { + $rule = \Craft::$app->getConditions()->createConditionRule([ + 'type' => IdConditionRule::class, + 'value' => 1, + ]); + $conditionBuilder->addConditionRule($rule); + } + + $condition = $conditionBuilder; + } + + /** @var Discount $discount */ + $discount = \Craft::createObject([ + 'class' => Discount::class, + 'orderCondition' => $condition, + ]); + + self::assertSame($expected, $discount->hasOrderCondition()); + } + + /** + * @param ElementConditionInterface|array|string $condition + * @param bool $expected + * @return void + * @throws InvalidConfigException + * @since 4.3.0 + * @dataProvider conditionBuilderDataProvider + */ + public function testHasCustomerCondition(ElementConditionInterface|array|string $condition, bool $expected): void + { + if ($condition === 'class' || $condition === 'rules') { + /** @var DiscountCustomerCondition $condition */ + $conditionBuilder = \Craft::$app->getConditions()->createCondition(DiscountCustomerCondition::class); + + if ($condition === 'rules') { + $rule = \Craft::$app->getConditions()->createConditionRule([ + 'type' => IdConditionRule::class, + 'value' => 1, + ]); + $conditionBuilder->addConditionRule($rule); + } + + $condition = $conditionBuilder; + } + + /** @var Discount $discount */ + $discount = \Craft::createObject([ + 'class' => Discount::class, + 'customerCondition' => $condition, + ]); + + self::assertSame($expected, $discount->hasCustomerCondition()); + } + + + /** + * @param ElementConditionInterface|array|string $condition + * @param bool $expected + * @return void + * @throws InvalidConfigException + * @since 4.3.0 + * @dataProvider conditionBuilderDataProvider + */ + public function testHasBillingAddressCondition(ElementConditionInterface|array|string $condition, bool $expected): void + { + if ($condition === 'class' || $condition === 'rules') { + /** @var DiscountAddressCondition $condition */ + $conditionBuilder = \Craft::$app->getConditions()->createCondition(DiscountAddressCondition::class); + + if ($condition === 'rules') { + $rule = \Craft::$app->getConditions()->createConditionRule([ + 'type' => IdConditionRule::class, + 'value' => 1, + ]); + $conditionBuilder->addConditionRule($rule); + } + + $condition = $conditionBuilder; + } + + /** @var Discount $discount */ + $discount = \Craft::createObject([ + 'class' => Discount::class, + 'billingAddressCondition' => $condition, + ]); + + self::assertSame($expected, $discount->hasBillingAddressCondition()); + } + + /** + * @param ElementConditionInterface|array|string $condition + * @param bool $expected + * @return void + * @throws InvalidConfigException + * @since 4.3.0 + * @dataProvider conditionBuilderDataProvider + */ + public function testHasShippingAddressCondition(ElementConditionInterface|array|string $condition, bool $expected): void + { + if ($condition === 'class' || $condition === 'rules') { + /** @var DiscountAddressCondition $condition */ + $conditionBuilder = \Craft::$app->getConditions()->createCondition(DiscountAddressCondition::class); + + if ($condition === 'rules') { + $rule = \Craft::$app->getConditions()->createConditionRule([ + 'type' => IdConditionRule::class, + 'value' => 1, + ]); + $conditionBuilder->addConditionRule($rule); + } + + $condition = $conditionBuilder; + } + + /** @var Discount $discount */ + $discount = \Craft::createObject([ + 'class' => Discount::class, + 'shippingAddressCondition' => $condition, + ]); + + self::assertSame($expected, $discount->hasShippingAddressCondition()); + } + + public function conditionBuilderDataProvider(): array + { + return [ + 'blank-string' => ['', false], + 'empty-array' => [[], false], + 'no-rules' => ['class', false], + 'rules' => ['rules', true], + ]; + } } diff --git a/tests/unit/services/CartsTest.php b/tests/unit/services/CartsTest.php index ceedb49fb9..aac96a20af 100644 --- a/tests/unit/services/CartsTest.php +++ b/tests/unit/services/CartsTest.php @@ -13,6 +13,7 @@ use craft\commerce\Plugin; use craft\commerce\services\Carts; use craftcommercetests\fixtures\CustomerAddressFixture; +use craftcommercetests\fixtures\CustomerFixture; use UnitTester; /** @@ -31,6 +32,9 @@ class CartsTest extends Unit public function _fixtures(): array { return [ + 'customer' => [ + 'class' => CustomerFixture::class, + ], 'customerAddresses' => [ 'class' => CustomerAddressFixture::class, ], @@ -60,17 +64,16 @@ public function testGetCartAutoSetAddresses(string $email, bool $autoSet, bool $ }, ])); + $user = Craft::$app->getUsers()->getUserByUsernameOrEmail($email); if ($loggedIn) { - $user = Craft::$app->getUsers()->getUserByUsernameOrEmail($email); Craft::$app->getUser()->setIdentity($user); Craft::$app->getUser()->getIdentity()->password = $user->password; } $newCart = new Order(); - $newCart->setEmail($email); + $newCart->setCustomer($user); $newCart->number = $cartNumber; Craft::$app->getElements()->saveElement($newCart, false); - // Plugin::getInstance()->getCarts()->__set('cart', $newCart); $cart = Plugin::getInstance()->getCarts()->getCart(); @@ -97,4 +100,39 @@ public function getCartDataProvider(): array 'logged-in-user-auto-set-addresses' => ['cred.user@crafttest.com', true, true, true, true], ]; } + + public function testGetCartSwitchCustomer(): void + { + $cartNumber = Plugin::getInstance()->getCarts()->generateCartNumber(); + Plugin::getInstance()->set('carts', $this->make(Carts::class, [ + 'getSessionCartNumber' => function() use ($cartNumber) { + return $cartNumber; + }, + ])); + + $inactiveUser = $this->tester->grabFixture('customer')->getElement('inactive-user'); + $credUser = $this->tester->grabFixture('customer')->getElement('credentialed-user'); + $originalIdentity = Craft::$app->getUser()->getIdentity(); + Craft::$app->getUser()->setIdentity($credUser); + Craft::$app->getUser()->getIdentity()->password = $credUser->password; + + + $order = new Order(); + $order->number = $cartNumber; + $order->setCustomer($inactiveUser); + + Craft::$app->getElements()->saveElement($order, false); + self::assertEquals($inactiveUser->id, $order->getCustomerId()); + + $cart = Plugin::getInstance()->getCarts()->getCart(); + + // assert customer has changed; + self::assertNotEquals($inactiveUser->id, $cart->getCustomerId()); + self::assertEquals($credUser->id, $cart->getCustomerId()); + self::assertEquals($credUser->email, $cart->getEmail()); + + // Reset data + Craft::$app->getUser()->setIdentity($originalIdentity); + Craft::$app->getElements()->deleteElement($cart, true); + } } diff --git a/tests/unit/services/CustomersTest.php b/tests/unit/services/CustomersTest.php index 50586f5cff..d938d69072 100644 --- a/tests/unit/services/CustomersTest.php +++ b/tests/unit/services/CustomersTest.php @@ -9,11 +9,17 @@ use Codeception\Test\Unit; use craft\commerce\elements\Order; +use craft\commerce\errors\OrderStatusException; use craft\commerce\Plugin; use craft\commerce\services\Customers; +use craft\elements\Address; +use craft\elements\User; +use craft\errors\ElementNotFoundException; use craftcommercetests\fixtures\CustomerFixture; use craftcommercetests\fixtures\OrdersFixture; use UnitTester; +use yii\base\Exception; +use yii\base\InvalidConfigException; /** * CustomersTest @@ -82,21 +88,390 @@ public function testOrderCompleteHandlerCalled(): void }, ])); - $order = new Order(); - $email = 'test@newemailaddress.xyz'; - $order->setEmail($email); + $order = $this->_createOrder('test@newemailaddress.xyz'); - /** @var Order $order */ - $completedOrder = $this->fixtureData->getElement('completed-new'); - $lineItem = $completedOrder->getLineItems()[0]; - $qty = 4; - $note = 'My note'; - $lineItem = Plugin::getInstance()->getLineItems()->createLineItem($order, $lineItem->purchasableId, [], $qty, $note); - $order->setLineItems([$lineItem]); + self::assertTrue($order->markAsComplete()); + + $this->_deleteElementIds[] = $order->id; + $this->_deleteElementIds[] = $order->getCustomer()->id; + } + + /** + * @param string $email + * @param bool $register + * @param bool $deleteUser an argument to help with cleanup + * @return void + * @throws \Throwable + * @throws OrderStatusException + * @throws ElementNotFoundException + * @throws Exception + * @throws InvalidConfigException + * @dataProvider registerOnCheckoutDataProvider + */ + public function testRegisterOnCheckout(string $email, bool $register, bool $deleteUser): void + { + $order = $this->_createOrder($email); + $originallyCredentialed = $order->getCustomer()->getIsCredentialed(); + + $order->registerUserOnOrderComplete = $register; self::assertTrue($order->markAsComplete()); + $foundUser = User::find()->email($email)->status(null)->one(); + self::assertNotNull($foundUser); + + if ($register || $originallyCredentialed) { + self::assertTrue($foundUser->getIsCredentialed()); + } else { + self::assertFalse($foundUser->getIsCredentialed()); + } + $this->_deleteElementIds[] = $order->id; + if ($deleteUser) { + $this->_deleteElementIds[] = $order->getCustomer()->id; + } + } + + /** + * @return array[] + */ + public function registerOnCheckoutDataProvider(): array + { + return [ + 'dont-register-guest' => ['guest@crafttest.com', false, true], + 'register-guest' => ['guest@crafttest.com', true, true], + 'register-credentialed-user' => ['cred.user@crafttest.com', true, false], + 'dont-register-credentialed-user' => ['cred.user@crafttest.com', false, false], + ]; + } + + /** + * @param string $email + * @param bool $deleteUser + * @param Address|null $billingAddres + * @param Address|null $shippingAddress + * @return void + * @throws ElementNotFoundException + * @throws Exception + * @throws OrderStatusException + * @throws \Throwable + * @dataProvider registerOnCheckoutCopyAddressesDataProvider + */ + public function testRegisterOnCheckoutCopyAddresses(string $email, ?array $billingAddress, ?array $shippingAddress, int $addressCount): void + { + $isOnlyOneAddress = empty($billingAddress) || empty($shippingAddress); + $order = $this->_createOrder($email); + $order->registerUserOnOrderComplete = true; + \Craft::$app->getElements()->saveElement($order, false); + + if (!empty($billingAddress)) { + $order->setBillingAddress($billingAddress); + } + + if (!empty($shippingAddress)) { + $order->setShippingAddress($shippingAddress); + } + + self::assertTrue($order->markAsComplete()); + + $userAddresses = Address::find()->ownerId($order->getCustomer()->id)->all(); + self::assertCount($addressCount, $userAddresses); + + $primaryCount = 0; + foreach ($userAddresses as $userAddress) { + if ($addressCount === 1) { + $addressTitle = \Craft::t('commerce', 'Address'); + if ($isOnlyOneAddress) { + $addressTitle = !empty($billingAddress) ? \Craft::t('commerce', 'Billing Address') : \Craft::t('commerce', 'Shipping Address'); + } + self::assertEquals($addressTitle, $userAddress->title); + + $address = $billingAddress ?? $shippingAddress; + self::assertEquals($address['fullName'], $userAddress->fullName); + self::assertEquals($address['addressLine1'], $userAddress->addressLine1); + self::assertEquals($address['locality'], $userAddress->locality); + self::assertEquals($address['administrativeArea'], $userAddress->administrativeArea); + self::assertEquals($address['postalCode'], $userAddress->postalCode); + self::assertEquals($address['countryCode'], $userAddress->countryCode); + } + + if ($userAddress->getIsPrimaryBilling()) { + if ($addressCount === 2) { + self::assertEquals(\Craft::t('commerce', 'Billing Address'), $userAddress->title); + self::assertEquals($billingAddress['fullName'], $userAddress->fullName); + self::assertEquals($billingAddress['addressLine1'], $userAddress->addressLine1); + self::assertEquals($billingAddress['locality'], $userAddress->locality); + self::assertEquals($billingAddress['administrativeArea'], $userAddress->administrativeArea); + self::assertEquals($billingAddress['postalCode'], $userAddress->postalCode); + self::assertEquals($billingAddress['countryCode'], $userAddress->countryCode); + } + + $primaryCount++; + } + if ($userAddress->getIsPrimaryShipping()) { + if ($addressCount === 2) { + self::assertEquals(\Craft::t('commerce', 'Shipping Address'), $userAddress->title); + self::assertEquals($shippingAddress['fullName'], $userAddress->fullName); + self::assertEquals($shippingAddress['addressLine1'], $userAddress->addressLine1); + self::assertEquals($shippingAddress['locality'], $userAddress->locality); + self::assertEquals($shippingAddress['administrativeArea'], $userAddress->administrativeArea); + self::assertEquals($shippingAddress['postalCode'], $userAddress->postalCode); + self::assertEquals($shippingAddress['countryCode'], $userAddress->countryCode); + } + + $primaryCount++; + } + } + + self::assertEquals($isOnlyOneAddress ? 1 : 2, $primaryCount); + + $this->_deleteElementIds[] = $order->id; + $this->_deleteElementIds[] = $order->getCustomer()->id; + } + + /** + * @return array[] + */ + public function registerOnCheckoutCopyAddressesDataProvider(): array + { + $billingAddress = [ + 'fullName' => 'Guest Billing', + 'addressLine1' => '1 Main Billing Street', + 'locality' => 'Billingsville', + 'administrativeArea' => 'OR', + 'postalCode' => '12345', + 'countryCode' => 'US', + ]; + $shippingAddress = [ + 'fullName' => 'Guest Shipping', + 'addressLine1' => '1 Main Shipping Street', + 'locality' => 'Shippingsville', + 'administrativeArea' => 'AL', + 'postalCode' => '98765', + 'countryCode' => 'US', + ]; + + return [ + 'guest-two-addresses' => [ + 'guest.person@crafttest.com', + $billingAddress, + $shippingAddress, + 2, + ], + 'guest-matching-addresses' => [ + 'guest.person@crafttest.com', + $billingAddress, + $billingAddress, + 1, + ], + 'guest-one-billing-address' => [ + 'guest.person@crafttest.com', + $billingAddress, + null, + 1, + ], + 'guest-one-shipping-address' => [ + 'guest.person@crafttest.com', + null, + $shippingAddress, + 1, + ], + ]; + } + + /** + * @param bool|null $saveBilling + * @param array|null $billingAddress + * @param bool|null $saveShipping + * @param array|null $shippingAddress + * @param bool $setSourceBilling + * @param bool $setSourceShipping + * @return void + * @throws ElementNotFoundException + * @throws Exception + * @throws OrderStatusException + * @throws \Throwable + * @dataProvider saveAddressesOnOrderCompleteDataProvider + * @since 4.3.0 + */ + public function testSaveAddressesOnOrderComplete(?bool $saveBilling, ?array $billingAddress, ?bool $saveShipping, ?array $shippingAddress, int $newAddressCount, bool $setSourceBilling, bool $setSourceShipping): void + { + $order = $this->_createOrder('cred.user@crafttest.com'); + $customer = $order->getCustomer(); + $sourceAddress = [ + 'fullName' => 'Source Address', + 'addressLine1' => '1 Source Road', + 'locality' => 'Sourcington', + 'administrativeArea' => 'OR', + 'postalCode' => '991199', + 'countryCode' => 'US', + 'ownerId' => $customer->id, + ]; + + if ($setSourceBilling || $setSourceShipping) { + $sourceAddressModel = \Craft::createObject([ + 'class' => Address::class, + 'attributes' => $sourceAddress, + ]); + \Craft::$app->getElements()->saveElement($sourceAddressModel, false, false ,false); + $this->_deleteElementIds[] = $sourceAddressModel->id; + + if ($setSourceBilling) { + $order->sourceBillingAddressId = $sourceAddressModel->id; + } + + if ($setSourceShipping) { + $order->sourceShippingAddressId = $sourceAddressModel->id; + } + } + $originalAddressIds = collect($customer->getAddresses())->pluck('id')->all(); + + $order->saveBillingAddressOnOrderComplete = $saveBilling; + $order->saveShippingAddressOnOrderComplete = $saveShipping; + + $order->setBillingAddress($billingAddress); + $order->setShippingAddress($shippingAddress); + + \Craft::$app->getElements()->saveElement($order, false, false, false); + + self::assertTrue($order->markAsComplete()); + + // @TODO change this to `$customer->getAddresses()` when `getAddresses()` memoization is fixed + $idWhere = array_merge(['not'], $originalAddressIds); + $addresses = Address::find()->ownerId($customer->id)->id($idWhere)->all(); + self::assertCount($newAddressCount, $addresses); + $addressNames = collect($addresses)->pluck('fullName')->all(); + $addressLine1s = collect($addresses)->pluck('addressLine1')->all(); + + if ($billingAddress && $saveBilling && !$setSourceBilling) { + self::assertContains($billingAddress['fullName'], $addressNames); + self::assertContains($billingAddress['addressLine1'], $addressLine1s); + } + + if ($shippingAddress && $saveShipping && !$setSourceShipping) { + self::assertContains($shippingAddress['fullName'], $addressNames); + self::assertContains($shippingAddress['addressLine1'], $addressLine1s); + } + + $this->_deleteElementIds[] = $order->id; +// \Craft::$app->getElements()->deleteElementById($order->id, null, null, true); + // No need to delete the customer as it comes from the fixtures + } + + /** + * @return array + */ + public function saveAddressesOnOrderCompleteDataProvider(): array + { + $billingAddress = [ + 'fullName' => 'Billing Name', + 'addressLine1' => '1 Main Billing Street', + 'locality' => 'Billingsville', + 'administrativeArea' => 'OR', + 'postalCode' => '12345', + 'countryCode' => 'US', + ]; + $shippingAddress = [ + 'fullName' => 'Shipping Name', + 'addressLine1' => '1 Main Shipping Street', + 'locality' => 'Shippingsville', + 'administrativeArea' => 'AL', + 'postalCode' => '98765', + 'countryCode' => 'US', + ]; + + return [ + 'save-both' => [ + true, // save billing + $billingAddress, // billing address + true, // save shipping + $shippingAddress, // shipping address + 2, // new address count + false, // set source billing + false, // set source shipping + ], + 'save-billing-only' => [ + true, + $billingAddress, + false, + null, + 1, + false, + false, + ], + 'save-shipping-only' => [ + false, + null, + true, + $shippingAddress, + 1, + false, + false, + ], + 'save-both-but-same-address' => [ + true, + $billingAddress, + true, + $billingAddress, + 1, + false, + false, + ], + 'try-to-save-both-but-no-addresses' => [ + true, + null, + true, + null, + 0, + false, + false, + ], + 'try-to-save-but-source-billing-present' => [ + true, + $billingAddress, + false, + null, + 0, + true, + false, + ], + 'try-to-save-but-source-shipping-present' => [ + false, + null, + true, + $shippingAddress, + 0, + false, + true, + ], + 'try-to-save-both-but-sources-present' => [ + true, + $billingAddress, + true, + $shippingAddress, + 0, + true, + true, + ], + 'try-save-both-but-billing-source-present' => [ + true, + $billingAddress, + true, + $shippingAddress, + 1, + true, + false, + ], + 'try-save-both-but-shipping-source-present' => [ + true, + $billingAddress, + true, + $shippingAddress, + 1, + false, + true, + ], + ]; } /** @@ -111,4 +486,20 @@ protected function _after(): void \Craft::$app->getElements()->deleteElementById($elementId, null, null, true); } } + + private function _createOrder(string $email): Order + { + $order = new Order(); + $user = \Craft::$app->getUsers()->ensureUserByEmail($email); + $order->setCustomer($user); + + $completedOrder = $this->fixtureData->getElement('completed-new'); + $lineItem = $completedOrder->getLineItems()[0]; + $qty = 4; + $note = 'My note'; + $lineItem = Plugin::getInstance()->getLineItems()->createLineItem($order, $lineItem->purchasableId, [], $qty, $note); + $order->setLineItems([$lineItem]); + + return $order; + } } diff --git a/tests/unit/services/DiscountsTest.php b/tests/unit/services/DiscountsTest.php index 308bfde8d4..d55ea74861 100644 --- a/tests/unit/services/DiscountsTest.php +++ b/tests/unit/services/DiscountsTest.php @@ -12,6 +12,7 @@ use Craft; use craft\commerce\db\Table; use craft\commerce\elements\Order; +use craft\commerce\elements\Variant; use craft\commerce\models\Discount; use craft\commerce\models\LineItem; use craft\commerce\models\OrderAdjustment; @@ -19,11 +20,15 @@ use craft\commerce\services\Discounts; use craft\commerce\test\mockclasses\Purchasable; use craft\db\Query; +use craft\elements\Category; use craft\elements\User; +use craftcommercetests\fixtures\CategoriesFixture; use craftcommercetests\fixtures\CustomerFixture; use craftcommercetests\fixtures\DiscountsFixture; +use craftcommercetests\fixtures\ProductFixture; use DateInterval; use DateTime; +use DateTimeZone; use UnitTester; use yii\base\InvalidConfigException; use yii\db\Exception; @@ -64,6 +69,12 @@ public function _fixtures(): array 'customers' => [ 'class' => CustomerFixture::class, ], + 'products' => [ + 'class' => ProductFixture::class, + ], + 'categories' => [ + 'class' => CategoriesFixture::class, + ], ]; } @@ -367,20 +378,379 @@ public function testVoidIfInvalidCouponCode(): void } /** + * @param array|false $attributes + * @param int $count * @return void * @throws \Exception + * @dataProvider gatAllActiveDiscountsDataProvider + */ + public function testGetAllActiveDiscounts(array|false $attributes, int $count, array $discounts): void + { + $originalEdition = Plugin::getInstance()->edition; + Plugin::getInstance()->edition = Plugin::EDITION_PRO; + + if (!empty($discounts)) { + foreach ($discounts as &$discount) { + $emailUses = $discount['_emailUses'] ?? []; + + if (isset($discount['purchasableIds'])) { + $discount['purchasableIds'] = Variant::find()->sku($discount['purchasableIds'])->ids(); + } + + if (isset($discount['categoryIds'])) { + $discount['categoryIds'] = Category::find()->slug($discount['categoryIds'])->ids(); + } + + $discountModel = Craft::createObject([ + 'class' => Discount::class, + 'attributes' => $discount, + ]); + Plugin::getInstance()->getDiscounts()->saveDiscount($discountModel); + $discount = $discountModel->id; + + if ($discountModel->totalDiscountUses > 0) { + Craft::$app->getDb()->createCommand() + ->update(Table::DISCOUNTS, [ + 'totalDiscountUses' => $discountModel->totalDiscountUses, + ], [ + 'id' => $discountModel->id, + ]) + ->execute(); + } + + if (!empty($emailUses)) { + $emailUses = collect($emailUses)->map(fn($uses, $email) => [$email, $discountModel->id, $uses])->all(); + Craft::$app->getDb()->createCommand() + ->batchInsert(Table::EMAIL_DISCOUNTUSES, ['email', 'discountId', 'uses'], $emailUses) + ->execute(); + } + } + } + + if ($attributes === false) { + $activeDiscounts = $this->discounts->getAllActiveDiscounts(); + } else { + $order = new Order(array_diff_key($attributes, array_flip(['_lineItems']))); + + if (isset($attributes['_lineItems'])) { + $lineItems = []; + foreach ($attributes['_lineItems'] as $sku => $qty) { + $variant = Variant::find()->sku($sku)->one(); + $lineItems[] = Plugin::getInstance()->getLineItems()->createLineItem($order, $variant->id, [], $qty); + } + $order->setLineItems($lineItems); + } + + $activeDiscounts = $this->discounts->getAllActiveDiscounts($order); + } + + if ($count > 0) { + self::assertCount($count, $activeDiscounts); + self::assertNotEmpty($activeDiscounts); + } else { + self::assertEmpty($activeDiscounts); + } + + // Tidy up the discounts + if (!empty($discounts)) { + foreach ($discounts as $discountId) { + Plugin::getInstance()->getDiscounts()->deleteDiscountById($discountId); + } + } + + Plugin::getInstance()->edition = $originalEdition; + } + + /** + * @return array[] */ - public function testGetAllActiveDiscounts(): void + public function gatAllActiveDiscountsDataProvider(): array { - $activeDiscounts = $this->discounts->getAllActiveDiscounts(); - $activeDiscountsCodeExists = $this->discounts->getAllActiveDiscounts(new Order(['couponCode' => 'discount_1'])); - $activeDiscountsCodeDoesntExists = $this->discounts->getAllActiveDiscounts(new Order(['couponCode' => 'coupon_code_doesnt_exist'])); - - self::assertNotEmpty($activeDiscounts); - self::assertCount(1, $activeDiscounts); - self::assertNotEmpty($activeDiscountsCodeExists); - self::assertCount(1, $activeDiscountsCodeExists); - self::assertEmpty($activeDiscountsCodeDoesntExists); + $yesterday = (new DateTime('now', new DateTimeZone('America/Los_Angeles')))->setTime(12, 0)->modify('-1 day'); + $tomorrow = (new DateTime('now', new DateTimeZone('America/Los_Angeles')))->setTime(12, 0)->modify('+1 day'); + + function _createDiscounts($discounts) + { + return collect($discounts)->mapWithKeys(function(array $d, string $key) { + return [$key => array_merge([ + 'name' => 'Discount - ' . $key, + 'perItemDiscount' => '1', + 'enabled' => true, + 'allCategories' => true, + 'allPurchasables' => true, + 'percentageOffSubject' => 'original', + ], $d)]; + })->all(); + } + + return [ + 'no-order' => [false, 1, []], + 'order-with-valid-coupon' => [['couponCode' => 'discount_1'], 1, []], + 'order-with-invalid-coupon' => [['couponCode' => 'coupon_code_doesnt_exist'], 0, []], + 'order-discounts-dates' => [ + [], + 3, + _createDiscounts([ + 'date-from-valid' => [ + 'dateFrom' => $yesterday, + ], + 'date-from-invalid' => [ + 'dateFrom' => $tomorrow, + ], + 'date-to-valid' => [ + 'dateTo' => $tomorrow, + ], + 'date-to-invalid' => [ + 'dateTo' => $yesterday, + ], + 'date-to-from-valid' => [ + 'dateFrom' => $yesterday, + 'dateTo' => $tomorrow, + ], + 'date-to-from-invalid' => [ + 'dateFrom' => $tomorrow, + 'dateTo' => $tomorrow->modify('+1 day'), + ], + ]), + ], + 'order-discounts-limits' => [ + [], + 4, + _createDiscounts([ + 'total-limit-zero' => [ + 'totalDiscountUseLimit' => 0, + ], + 'total-limit-zero-with-uses' => [ + 'totalDiscountUses' => 10, + 'totalDiscountUseLimit' => 0, + ], + 'total-limit-valid-with-no-uses' => [ + 'totalDiscountUses' => 0, + 'totalDiscountUseLimit' => 10, + ], + 'total-limit-valid-with-uses' => [ + 'totalDiscountUses' => 7, + 'totalDiscountUseLimit' => 10, + ], + 'total-limit-invalid-equals' => [ + 'totalDiscountUses' => 10, + 'totalDiscountUseLimit' => 10, + ], + 'total-limit-invalid-extra' => [ + 'totalDiscountUses' => 11, + 'totalDiscountUseLimit' => 10, + ], + ]), + ], + 'order-discounts-email-limits-no-email' => [ + [], + 1, + _createDiscounts([ + 'total-limit-zero' => [ + 'perEmailLimit' => 0, + ], + 'total-limit' => [ + 'perEmailLimit' => 1, + ], + ]), + ], + 'order-discounts-email-limits' => [ + ['email' => 'per.email.limit@crafttest.com'], + 4, + _createDiscounts([ + 'total-limit-zero' => [ + 'perEmailLimit' => 0, + ], + 'total-limit-zero-with-uses' => [ + '_emailUses' => ['per.email.limit@crafttest.com' => 10], + 'perEmailLimit' => 0, + ], + 'total-limit-valid-with-no-uses' => [ + 'perEmailLimit' => 10, + ], + 'total-limit-valid-with-uses' => [ + '_emailUses' => ['per.email.limit@crafttest.com' => 7], + 'perEmailLimit' => 10, + ], + 'total-limit-invalid-equals' => [ + '_emailUses' => ['per.email.limit@crafttest.com' => 10], + 'perEmailLimit' => 10, + ], + 'total-limit-invalid-extra' => [ + '_emailUses' => ['per.email.limit@crafttest.com' => 11], + 'perEmailLimit' => 10, + ], + ]), + ], + 'purchase-total-limit-no-items' => [ + [], + 4, + _createDiscounts([ + 'purchase-total-zero' => [ + 'purchaseTotal' => 0, + ], + 'purchase-total-all-purchasables-false' => [ + 'purchaseTotal' => 10, + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + ], + 'purchase-total-all-categories-false' => [ + 'purchaseTotal' => 10, + 'allCategories' => false, + 'categoryIds' => ['commerce-category'], + ], + 'purchase-total-both-all-false' => [ + 'purchaseTotal' => 10, + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + 'allCategories' => false, + 'categoryIds' => ['commerce-category'], + ], + ]), + ], + 'purchase-total-limit-with-items' => [ + [ + '_lineItems' => ['rad-hood' => 1], + ], + 5, + _createDiscounts([ + 'purchase-total-zero' => [ + 'purchaseTotal' => 0, + ], + 'purchase-total-all-purchasables-false' => [ + 'purchaseTotal' => 10, + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + ], + 'purchase-total-all-categories-false' => [ + 'purchaseTotal' => 10, + 'allCategories' => false, + 'categoryIds' => ['commerce-category'], + ], + 'purchase-total-both-all-false' => [ + 'purchaseTotal' => 10, + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + 'allCategories' => false, + 'categoryIds' => ['commerce-category'], + ], + 'purchase-total-valid' => [ + 'purchaseTotal' => 150, + ], + 'purchase-total-invalid' => [ + 'purchaseTotal' => 10.99, + ], + ]), + ], + 'qty-limits-no-items' => [ + [], + 6, + _createDiscounts([ + 'purchase-qty-zero' => [ + 'purchaseQty' => 0, + ], + 'max-qty-zero' => [ + 'maxPurchaseQty' => 0, + ], + 'both-zero' => [ + 'purchaseQty' => 0, + 'maxPurchaseQty' => 0, + ], + 'purchase-qty-all-purchasables-false' => [ + 'purchaseQty' => 4, + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + ], + 'purchase-total-all-categories-false' => [ + 'purchaseTotal' => 10, + 'allCategories' => false, + 'categoryIds' => ['commerce-category'], + ], + 'purchase-total-both-all-false' => [ + 'purchaseTotal' => 10, + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + 'allCategories' => false, + 'categoryIds' => ['commerce-category'], + ], + ]), + ], + 'qty-limits-with-items' => [ + ['_lineItems' => ['rad-hood' => 4]], + 6, + _createDiscounts([ + 'purchase-qty-zero' => [ + 'purchaseQty' => 0, + ], + 'max-qty-zero' => [ + 'maxPurchaseQty' => 0, + ], + 'both-zero' => [ + 'purchaseQty' => 0, + 'maxPurchaseQty' => 0, + ], + 'purchase-qty-valid' => [ + 'purchaseQty' => 3, + ], + 'purchase-qty-invalid' => [ + 'purchaseQty' => 5, + ], + 'max-qty-valid' => [ + 'maxPurchaseQty' => 10, + ], + 'max-qty-invalid' => [ + 'maxPurchaseQty' => 3, + ], + 'both-valid' => [ + 'purchaseQty' => 2, + 'maxPurchaseQty' => 10, + ], + 'both-invalid' => [ + 'purchaseQty' => 10, + 'maxPurchaseQty' => 14, + ], + ]), + ], + 'purchasables-one-lineitem' => [ + ['_lineItems' => ['rad-hood' => 1]], + 3, + _createDiscounts([ + 'all-purchasables' => [ + 'allPurchasables' => true, + ], + 'one-to-one' => [ + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + ], + 'one-to-many' => [ + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood', 'hct-white'], + ], + 'no-match' => [ + 'allPurchasables' => false, + 'purchasableIds' => ['hct-blue'], + ], + ]), + ], + 'purchasables-multi-lineitems' => [ + ['_lineItems' => ['rad-hood' => 1, 'hct-white' => 1]], + 2, + _createDiscounts([ + 'one' => [ + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood'], + ], + 'many' => [ + 'allPurchasables' => false, + 'purchasableIds' => ['rad-hood', 'hct-white'], + ], + 'no-match' => [ + 'allPurchasables' => false, + 'purchasableIds' => ['hct-blue'], + ], + ]), + ], + ]; } /** diff --git a/tests/unit/services/OrdersTest.php b/tests/unit/services/OrdersTest.php index d7ce3c5fb1..5b3e9a08f8 100644 --- a/tests/unit/services/OrdersTest.php +++ b/tests/unit/services/OrdersTest.php @@ -98,12 +98,15 @@ public function testGetOrdersByCustomer(): void public function testGetOrdersByEmail(): void { - $orders = $this->service->getOrdersByEmail($this->fixtureData->getElement('completed-new')->email); + /** @var Order $orderFixture */ + $orderFixture = $this->fixtureData->getElement('completed-new'); + $email = $orderFixture->getEmail(); + $orders = $this->service->getOrdersByEmail($email); self::assertIsArray($orders); self::assertCount(3, $orders); foreach ($orders as $order) { - self::assertEquals($this->fixtureData->getElement('completed-new')->email, $order->email); + self::assertEquals($email, $order->getEmail()); } } }