diff --git a/.editorconfig b/.editorconfig index bed7731df01..10e9c8601bc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -15,3 +15,6 @@ insert_final_newline = true [*.md] trim_trailing_whitespace = false + +[*.json] +indent_size = 2 diff --git a/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/index.js b/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/index.js index 3f662a5ced8..13b8ff84b43 100644 --- a/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/index.js +++ b/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/index.js @@ -55,7 +55,7 @@ Component.register('sw-product-detail-context-prices', { const priceRuleGroups = {}; this.product.priceRules.forEach((rule) => { - if (this.priceRuleStore.getById(rule.id).isDeleted === true) { + if (this.priceRuleStore.getById(rule.id).isDeleted) { return; } diff --git a/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/sw-product-detail-context-prices.scss b/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/sw-product-detail-context-prices.scss index 213e911add9..a2fed20001e 100644 --- a/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/sw-product-detail-context-prices.scss +++ b/src/Administration/Resources/administration/src/module/sw-product/view/sw-product-detail-context-prices/sw-product-detail-context-prices.scss @@ -30,6 +30,7 @@ .sw-context-button { .sw-button { width: 100%; + height: 100%; } } } @@ -47,4 +48,4 @@ justify-items: center; align-items: center; } -} \ No newline at end of file +} diff --git a/src/Administration/Resources/administration/src/module/sw-settings-shipping/page/sw-settings-shipping-detail/sw-settings-shipping-detail.html.twig b/src/Administration/Resources/administration/src/module/sw-settings-shipping/page/sw-settings-shipping-detail/sw-settings-shipping-detail.html.twig index 586848ff6a1..028975f337b 100644 --- a/src/Administration/Resources/administration/src/module/sw-settings-shipping/page/sw-settings-shipping-detail/sw-settings-shipping-detail.html.twig +++ b/src/Administration/Resources/administration/src/module/sw-settings-shipping/page/sw-settings-shipping-detail/sw-settings-shipping-detail.html.twig @@ -52,7 +52,8 @@ {% block sw_settings_shipping_detail_content_tabs_advanced_prices %} + :title="$tc('sw-settings-shipping.detail.tabAdvancedPrices')" + class="sw-shipping-detail-page__price-settings"> {{ $tc('sw-settings-shipping.detail.tabAdvancedPrices') }} {% endblock %} diff --git a/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/de_DE.json b/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/de_DE.json index 10d9e2f9730..b8451ea57cb 100644 --- a/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/de_DE.json +++ b/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/de_DE.json @@ -6,7 +6,7 @@ }, "list": { "textHeadline": "Versandarten", - "textDeleteConfirm": "Bist Du sicher, dass du die Versandart \"{name}\" löschen möchtest?", + "textDeleteConfirm": "Bist Du sicher, dass Du die Versandart \"{name}\" löschen möchtest?", "columnName": "Name", "columnActive": "Aktiv", "columnShippingfree": "Versandkostenfrei", @@ -29,9 +29,9 @@ "labelBindShippingfree": "Versandkostenfrei", "labelMinDeliveryTime": "Minimum Lieferzeit", "labelMaxDeliveryTime": "Maximal Lieferzeit", - "placeholderName": "Namen eingeben...", - "placeholderDescription": "Beschreibung eingeben...", - "placeholderComment": "Kommentar eingeben...", + "placeholderName": "Namen eingeben ...", + "placeholderDescription": "Beschreibung eingeben ...", + "placeholderComment": "Kommentar eingeben ...", "labelCalculation": "Berechnung", "labelComment": "Kommentar", "buttonCancel": "Abbrechen", @@ -48,6 +48,26 @@ "weight": "Gewicht", "price": "Preis", "lineItemCount": "Anzahl Produkte" + }, + "priceRules": { + "cardTitlePriceRule": "Neue Preisregel", + "placeholderRule": "Bitte wählen ...", + "placeholderQuantityEnd": "Unendlich", + "buttonPriceRuleDelete": "Preisregel löschen", + "buttonPriceRuleDuplicate": "Preisregel duplizieren", + "buttonPriceRuleCurrencyAdd": "Währung hinzufügen", + "buttonAddAdditionalPriceRule": "Weitere Preisregel hinzufügen", + "textCurrencyDefault": "(Standard)", + "textPriceRuleForCurrency": "Preisregel für Währung", + "columnQuantityStart": "Menge von", + "columnQuantityEnd": "Menge bis", + "columnWeightStart": "Gewicht von", + "columnWeightEnd": "Gewicht bis", + "columnPriceStart": "Preis von", + "columnPriceEnd": "Preis bis", + "columnPrice": "Preis (Brutto)", + "contextMenuDuplicate": "Duplizieren", + "contextMenuDelete": "Löschen" } } -} \ No newline at end of file +} diff --git a/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/en_GB.json b/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/en_GB.json index b643d696712..578a3d50a62 100644 --- a/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/en_GB.json +++ b/src/Administration/Resources/administration/src/module/sw-settings-shipping/snippet/en_GB.json @@ -26,8 +26,8 @@ "labelName": "Name", "labelActive": "Active", "labelBindShippingfree": "Free shipping", - "labelMinDeliveryTime": "min deliverytime", - "labelMaxDeliveryTime": "max deliverytime", + "labelMinDeliveryTime": "Min. delivery time", + "labelMaxDeliveryTime": "Max. delivery time", "placeholderName": "Enter name", "placeholderDescription": "Enter description", "placeholderComment": "Enter comment", @@ -48,6 +48,26 @@ "weight": "Weight", "price": "Price", "lineItemCount": "Line item count" + }, + "priceRules": { + "cardTitlePriceRule": "New price rule", + "placeholderRule": "Please choose ...", + "placeholderQuantityEnd": "Infinite", + "buttonPriceRuleDelete": "Delete price rule", + "buttonPriceRuleDuplicate": "Duplicate price rule", + "buttonPriceRuleCurrencyAdd": "Add currency", + "buttonAddAdditionalPriceRule": "Add additionally price rule", + "textCurrencyDefault": "(Default)", + "textPriceRuleForCurrency": "Price rule for currency", + "columnQuantityStart": "Amount from", + "columnQuantityEnd": "Amount to", + "columnWeightStart": "Weight from", + "columnWeightEnd": "Weight to", + "columnPriceStart": "Price from", + "columnPriceEnd": "Price to", + "columnPrice": "Price (Gross)", + "contextMenuDuplicate": "Duplicate", + "contextMenuDelete": "Delete" } } } diff --git a/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/index.js b/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/index.js index 9d5d3c27e69..c4e154a0ace 100644 --- a/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/index.js +++ b/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/index.js @@ -1,66 +1,297 @@ import { Component, State } from 'src/core/shopware'; import LocalStore from 'src/core/data/LocalStore'; +import CriteriaFactory from 'src/core/factory/criteria.factory'; import utils from 'src/core/service/util.service'; import template from './sw-settings-shipping-detail-advanced-prices.html.twig'; +import './sw-settings-shipping-detail-advanced-prices.scss'; Component.register('sw-settings-shipping-detail-advanced-prices', { template, props: { shippingMethod: { type: Object, - required: true, - default: {} + required: true + }, + isLoading: { + type: Boolean } }, + data() { + return { + currencies: [], + rules: [], + totalRules: 0, + isLoadingRules: false + }; + }, + created() { + this.createdComponent(); + }, computed: { + ruleFilter() { + return CriteriaFactory.multi('OR', + CriteriaFactory.contains('rule.moduleTypes.types', 'price'), + CriteriaFactory.equals('rule.moduleTypes', null)); + }, + selectValues() { const values = [ { - label: this.$tc('sw-settings-shipping.constants.weight'), - value: '0' + label: this.$tc('sw-settings-shipping.constants.lineItemCount'), + value: 1 }, { label: this.$tc('sw-settings-shipping.constants.price'), - value: '1' + value: 2 }, { - label: this.$tc('sw-settings-shipping.constants.lineItemCount'), - value: '2' + label: this.$tc('sw-settings-shipping.constants.weight'), + value: 3 } ]; return new LocalStore(values, 'value'); }, - productStore() { - return State.getStore('product'); + + priceRuleStore() { + return this.shippingMethod.getAssociation('priceRules'); + }, + + ruleStore() { + return State.getStore('rule'); + }, + + currencyStore() { + return State.getStore('currency'); + }, + + priceRuleGroups() { + const priceRuleGroups = {}; + + this.shippingMethod.priceRules.forEach((rule) => { + if (this.priceRuleStore.getById(rule.id).isDeleted) { + return; + } + + if (!priceRuleGroups[rule.ruleId]) { + priceRuleGroups[rule.ruleId] = { + ruleId: rule.ruleId, + rule: this.findRuleById(rule.ruleId), + currencies: {}, + calculation: rule.calculation + }; + } + + if (!priceRuleGroups[rule.ruleId].currencies[rule.currencyId]) { + priceRuleGroups[rule.ruleId].currencies[rule.currencyId] = { + currencyId: rule.currencyId, + currency: this.findCurrencyById(rule.currencyId), + prices: [] + }; + } + + priceRuleGroups[rule.ruleId].currencies[rule.currencyId].prices.push(rule); + }); + + return priceRuleGroups; + }, + + canAddPriceRule() { + const usedRules = Object.keys(this.priceRuleGroups).length; + const availableRules = this.rules.length; + + return usedRules !== availableRules; + }, + + isLoaded() { + return !this.isLoading && + !this.isLoadingRules && + this.currencies.length && + this.shippingMethod; }, - priceStore() { - return this.shippingMethod.getAssociation('prices'); + defaultCurrency() { + return this.currencies.find((currency) => { + return currency.isDefault; + }); } }, + methods: { createId() { return utils.createId(); }, - defaultValue() { - return { - calculation: '0' - }; - } - }, - watch: { - calculation: { - handler(newValue) { - if (!newValue) { + createdComponent() { + this.shippingMethod.getAssociation('priceRules').getList({ + page: 1, + limit: 500 + }); + this.isLoadingRules = true; + + this.ruleStore.getList({ + page: 1, + limit: 500, + criteria: this.ruleFilter + }).then((response) => { + this.rules = response.items; + this.totalRules = response.total; + + this.isLoadingRules = false; + }); + + this.currencyStore.getList({ + page: 1, + limit: 500 + }).then((response) => { + this.currencies = response.items; + }); + }, + + onRuleChange(value, ruleId) { + this.shippingMethod.priceRules.forEach((priceRule) => { + if (priceRule.ruleId === ruleId) { + priceRule.ruleId = value; + } + }); + }, + + onAddNewPriceGroup() { + const newPriceRule = this.priceRuleStore.create(); + newPriceRule.shippingMethodId = this.shippingMethod.id; + newPriceRule.quantityStart = 1; + newPriceRule.calculation = 1; + newPriceRule.currencyId = this.defaultCurrency.id; + + this.shippingMethod.priceRules.push(newPriceRule); + }, + + onAddCurrency(ruleId, currency) { + const defaultCurrencyPrices = this.priceRuleGroups[ruleId].currencies[this.defaultCurrency.id].prices; + + defaultCurrencyPrices.forEach((price) => { + const newPriceRule = this.priceRuleStore.duplicate(price.id); + + newPriceRule.currencyId = currency.id; + this.shippingMethod.priceRules.push(newPriceRule); + }); + }, + + onPriceGroupDelete(ruleId) { + this.priceRuleStore.forEach((item) => { + if (item.ruleId === ruleId) { + item.delete(); + } + }); + + this.shippingMethod.priceRules = this.shippingMethod.priceRules.filter((priceRule) => { + return priceRule.ruleId !== ruleId; + }); + }, + + onPriceGroupDuplicate(priceGroup) { + Object.keys(priceGroup.currencies).forEach((currencyId) => { + priceGroup.currencies[currencyId].prices.forEach((price) => { + const newPriceRule = this.priceRuleStore.duplicate(price.id); + newPriceRule.ruleId = null; + + this.shippingMethod.priceRules.push(newPriceRule); + }); + }); + }, + + onPriceRuleDuplicate(priceRule) { + const newPriceRule = this.priceRuleStore.duplicate(priceRule.id); + this.shippingMethod.priceRules.push(newPriceRule); + }, + + onPriceRuleDelete(priceRule) { + // Do not delete the last price of the default currency + if (priceRule.currencyId === this.defaultCurrency.id) { + const priceRuleGroup = this.priceRuleGroups[priceRule.ruleId]; + const defaultCurrencyPrices = priceRuleGroup.currencies[this.defaultCurrency.id].prices; + if (defaultCurrencyPrices.length <= 1 && Object.keys(priceRuleGroup.currencies).length > 1) { return; } - this.shippingMethod.calculation = Number(newValue); } + + this.shippingMethod.priceRules = this.shippingMethod.priceRules.filter((price) => { + return price.id !== priceRule.id; + }); + + this.priceRuleStore.getById(priceRule.id).delete(); + }, + + onQuantityEndChange(value, price, priceGroup) { + const currencyPrices = priceGroup.currencies[price.currencyId].prices; + + if (!currencyPrices.length) { + return; + } + + if (currencyPrices[currencyPrices.length - 1].id === price.id && value !== null) { + const newPriceRule = this.priceRuleStore.create(); + + newPriceRule.shippingMethodId = this.shippingMethod.id; + newPriceRule.ruleId = priceGroup.ruleId; + newPriceRule.quantityStart = price.quantityEnd + 1; + newPriceRule.quantityEnd = null; + newPriceRule.currencyId = price.currencyId; + + this.shippingMethod.priceRules.push(newPriceRule); + } + }, + + calculationStartLabel(calculation) { + const calculationType = { + 1: this.$tc('sw-settings-shipping.priceRules.columnQuantityStart'), + 2: this.$tc('sw-settings-shipping.priceRules.columnPriceStart'), + 3: this.$tc('sw-settings-shipping.priceRules.columnWeightStart') + }; + + return calculationType[calculation] || this.$tc('sw-settings-shipping.priceRules.columnQuantityStart'); + }, + + calculationEndLabel(calculation) { + const calculationType = { + 1: this.$tc('sw-settings-shipping.priceRules.columnQuantityEnd'), + 2: this.$tc('sw-settings-shipping.priceRules.columnPriceEnd'), + 3: this.$tc('sw-settings-shipping.priceRules.columnWeightEnd') + }; + + return calculationType[calculation] || this.$tc('sw-settings-shipping.priceRules.columnQuantityEnd'); + }, + + onCalculationChange(calculation, ruleId) { + if (!ruleId) { + return; + } + const priceRules = this.shippingMethod.priceRules.filter(priceRule => priceRule.ruleId === ruleId); + priceRules.forEach(priceRule => { priceRule.calculation = Number(calculation); }); + + if (calculation === 1) { + priceRules.forEach(priceRule => { + if (priceRule.quantityStart === null || priceRule.quantityStart < 1) { + priceRule.quantityStart = 1; + } + }); + } else { + priceRules.forEach(priceRule => { + if (priceRule.quantityStart === null) { + priceRule.quantityStart = 0; + } + }); + } + }, + + findRuleById(ruleId) { + return this.rules.find((rule) => { + return rule.id === ruleId; + }); + }, + + findCurrencyById(currencyId) { + return this.currencies.find((currency) => { + return currency.id === currencyId; + }); } - }, - data() { - return { - calculation: String(this.shippingMethod.calculation) - }; } }); diff --git a/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.html.twig b/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.html.twig index 75be5a35fc3..46c3efc7883 100644 --- a/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.html.twig +++ b/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.html.twig @@ -1,59 +1,190 @@ -{# TODO: Only filled with content for the purpose of not being empty! #} -{# TODO: Will be done in NEXT-1821 #} - {% block sw_settings_shipping_detail_advanced_prices %} - - - {% block sw_settings_shipping_detail_advanced_prices_field_calculation %} - - +
+ {% block sw_settings_shipping_detail_advanced_prices_empty_state %} + + + + {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_card %} + + + {% block sw_settings_shipping_detail_advanced_prices_price_card_toolbar %} +
+ + {% block sw_settings_shipping_detail_advanced_prices_price_card_rule_selection %} + + + + {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_card_action_delete %} + + + {{ $tc('sw-settings-shipping.priceRules.buttonPriceRuleDelete') }} + + {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_card_action_duplicate %} + + + {{ $tc('sw-settings-shipping.priceRules.buttonPriceRuleDuplicate') }} + + {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_card_action_currency %} + + {% block sw_settings_shipping_detail_advanced_prices_price_card_action_currency_button %} + + + {{ $tc('sw-settings-shipping.priceRules.buttonPriceRuleCurrencyAdd') }} + + {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_card_action_currency_menu %} + + {{ currency.symbol }} + {{ currency.name }} + {{ (currency.isDefault) ? $tc('sw-settings-shipping.priceRules.textCurrencyDefault') : '' }} + + {% endblock %} + + {% endblock %} +
+ {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_field_calculation %} + + + {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_matrix %} +
+ + {% block sw_settings_shipping_detail_advanced_prices_price_matrix_title %} +

+ {{ $tc('sw-settings-shipping.priceRules.textPriceRuleForCurrency') }}: + {{ currencyRule.currency.name }} {{ (currencyRule.currency.isDefault) ? + $tc('sw-settings-shipping.priceRules.textCurrencyDefault') : '' }} +

+ {% endblock %} + + {% block sw_settings_shipping_detail_advanced_prices_price_matrix_grid %} + + + + {% endblock %} +
+ {% endblock %} +
{% endblock %} - {% block sw_settings_shipping_detail_advanced_prices_field_price_table %} - - - + {% block sw_settings_shipping_detail_advanced_prices_actions %} +
+ {% block sw_settings_shipping_detail_advanced_prices_actions_add_button %} + + {{ $tc('sw-settings-shipping.priceRules.buttonAddAdditionalPriceRule') }} + + {% endblock %} +
{% endblock %} - +
{% endblock %} diff --git a/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.scss b/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.scss new file mode 100644 index 00000000000..5f94b66538c --- /dev/null +++ b/src/Administration/Resources/administration/src/module/sw-settings-shipping/view/sw-settings-shipping-detail-advanced-prices/sw-settings-shipping-detail-advanced-prices.scss @@ -0,0 +1,33 @@ +.sw-settings-shipping-detail-advanced-prices { + .sw-settings-shipping-detail-advanced-prices__toolbar { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, auto)); + grid-gap: 10px 20px; + margin-bottom: 30px; + + .sw-select__inner { + padding: 6px 50px 0 6px; + } + + .sw-context-button { + .sw-button { + width: 100%; + height: 100%; + } + } + } + + .sw-settings-shipping-detail-advanced-prices__prices { + margin-top: 30px; + } + + .sw-settings-shipping-detail-advanced-prices__actions { + max-width: 800px; + margin: 40px auto; + display: grid; + grid-auto-flow: column; + justify-content: center; + justify-items: center; + align-items: center; + } +} diff --git a/src/Administration/Resources/e2e/repos/administration/page-objects/module/sw-shipping-method.page-object.js b/src/Administration/Resources/e2e/repos/administration/page-objects/module/sw-shipping-method.page-object.js index 104dd4b0b20..ef9f5dffe24 100644 --- a/src/Administration/Resources/e2e/repos/administration/page-objects/module/sw-shipping-method.page-object.js +++ b/src/Administration/Resources/e2e/repos/administration/page-objects/module/sw-shipping-method.page-object.js @@ -10,7 +10,6 @@ class ShippingMethodPageObject extends GeneralPageObject { shippingSaveAction: '.sw-settings-shipping-method-detail__save-action', shippingBackToListViewAction: '.sw-icon.icon--default-action-settings.sw-icon--small' } - }; } @@ -26,6 +25,25 @@ class ShippingMethodPageObject extends GeneralPageObject { .checkNotification(`Shipping rate "${name}" has been saved successfully.`); } + createShippingMethodPriceRule(name) { + this.browser + .click('.sw-shipping-detail-page__price-settings') + .waitForElementVisible('.context-prices__actions button') + .click('.context-prices__actions button') + .click('.context-prices__rule') + .waitForElementVisible('.sw-select__results-list') + .click('.sw-select-option--0') + .expect.element('.sw-card__title').to.have.text.that.contains('Ruler'); + + this.browser + .waitForElementVisible('.context-prices__prices') + .fillField(`${this.elements.gridRow}--0 input[name=sw-field--item-quantityEnd]`, '20') + .fillField(`${this.elements.gridRow}--0 input[name=sw-field--item-price]`, '10') + .fillField(`${this.elements.gridRow}--1 input[name=sw-field--item-price]`, '8') + .click(this.elements.shippingSaveAction) + .checkNotification(`Shipping rate "${name}" has been saved successfully.`); + } + moveToListViewFromDetail() { this.browser .click(this.elements.shippingBackToListViewAction) diff --git a/src/Administration/Resources/e2e/repos/administration/specs/settings/shipping-method/shipping-method-edit.spec.js b/src/Administration/Resources/e2e/repos/administration/specs/settings/shipping-method/shipping-method-edit-price-rules.spec.js similarity index 50% rename from src/Administration/Resources/e2e/repos/administration/specs/settings/shipping-method/shipping-method-edit.spec.js rename to src/Administration/Resources/e2e/repos/administration/specs/settings/shipping-method/shipping-method-edit-price-rules.spec.js index fc6dce96996..cb6e5b494c2 100644 --- a/src/Administration/Resources/e2e/repos/administration/specs/settings/shipping-method/shipping-method-edit.spec.js +++ b/src/Administration/Resources/e2e/repos/administration/specs/settings/shipping-method/shipping-method-edit-price-rules.spec.js @@ -3,8 +3,13 @@ const shippingMethodPage = require('administration/page-objects/module/sw-shippi const shippingMethodName = 'automated test shipping'; module.exports = { - '@tags': ['settings', 'shipping-method', 'shipping-method-edit', 'edit'], + '@tags': ['settings', 'shipping-method', 'shipping-method-edit', 'edit', 'price-rule'], '@disabled': !global.flags.isActive('next688'), + before: (browser, done) => { + global.AdminFixtureService.create('rule').then(() => { + done(); + }); + }, 'navigate to shipping page': browser => { browser .openMainMenuEntry({ @@ -32,5 +37,29 @@ module.exports = { .refresh() .waitForElementVisible('textarea[name=sw-field--shippingMethod-description]') .expect.element('textarea[name=sw-field--shippingMethod-description]').to.have.value.that.contains('Lorem ipsum'); + }, + 'create shippingMethod price rule': browser => { + const page = shippingMethodPage(browser); + page.createShippingMethodPriceRule(shippingMethodName); + }, + 'edit shippingMethod price rule': browser => { + const page = shippingMethodPage(browser); + browser + .click('div[name=calculation]') + .waitForElementVisible('.sw-select__results-list') + .click('.sw-select__results-list .sw-select-option--1') + .clearValue(`.context-prices__prices ${page.elements.gridRow}--1 input[name=sw-field--item-price]`) + .fillField(`.context-prices__prices ${page.elements.gridRow}--1 input[name=sw-field--item-price]`, '9') + .click(page.elements.shippingSaveAction) + .checkNotification(`Shipping rate "${shippingMethodName}" has been saved successfully.`); + }, + 'delete shippingMethod price rule': browser => { + const page = shippingMethodPage(browser); + + browser + .clickContextMenuItem('.sw-context-menu-item--danger', `${page.elements.gridRow}--1 ${page.elements.contextMenuButton}`) + .assert.elementNotPresent(`${page.elements.gridRow}--1`) + .click('.sw-settings-shipping-detail__delete-action') + .assert.elementNotPresent('.context-price'); } }; diff --git a/src/Core/Checkout/Cart/Delivery/DeliveryCalculator.php b/src/Core/Checkout/Cart/Delivery/DeliveryCalculator.php index 7eefe6857db..9dddfe56a7e 100644 --- a/src/Core/Checkout/Cart/Delivery/DeliveryCalculator.php +++ b/src/Core/Checkout/Cart/Delivery/DeliveryCalculator.php @@ -2,7 +2,6 @@ namespace Shopware\Core\Checkout\Cart\Delivery; -use Doctrine\DBAL\Connection; use Shopware\Core\Checkout\Cart\Delivery\Struct\Delivery; use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryCollection; use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection; @@ -11,24 +10,15 @@ use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition; use Shopware\Core\Checkout\Cart\Tax\PercentageTaxRuleBuilder; use Shopware\Core\Checkout\CheckoutContext; -use Shopware\Core\Checkout\Shipping\ShippingMethodEntity; -use Shopware\Core\Framework\Context; -use Shopware\Core\Framework\Struct\Uuid; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleEntity; class DeliveryCalculator { - public const CALCULATION_BY_WEIGHT = 0; + public const CALCULATION_BY_LINE_ITEM_COUNT = 1; - public const CALCULATION_BY_PRICE = 1; + public const CALCULATION_BY_PRICE = 2; - public const CALCULATION_BY_LINE_ITEM_COUNT = 2; - - public const CALCULATION_BY_CUSTOM = 3; - - /** - * @var Connection - */ - private $connection; + public const CALCULATION_BY_WEIGHT = 3; /** * @var QuantityPriceCalculator @@ -41,11 +31,9 @@ class DeliveryCalculator private $percentageTaxRuleBuilder; public function __construct( - Connection $connection, QuantityPriceCalculator $priceCalculator, PercentageTaxRuleBuilder $percentageTaxRuleBuilder ) { - $this->connection = $connection; $this->priceCalculator = $priceCalculator; $this->percentageTaxRuleBuilder = $percentageTaxRuleBuilder; } @@ -59,6 +47,7 @@ public function calculate(DeliveryCollection $deliveries, CheckoutContext $conte private function calculateDelivery(Delivery $delivery, CheckoutContext $context): void { + $costs = null; if ($delivery->getShippingCosts()->getUnitPrice() > 0) { $costs = $this->calculateShippingCosts( $delivery->getShippingCosts()->getTotalPrice(), @@ -71,56 +60,52 @@ private function calculateDelivery(Delivery $delivery, CheckoutContext $context) return; } - switch ($delivery->getShippingMethod()->getCalculation()) { - case self::CALCULATION_BY_WEIGHT: - $costs = $this->calculateShippingCosts( - $this->findShippingCosts( - $delivery->getShippingMethod(), - $delivery->getPositions()->getWeight(), - $context->getContext() - ), - $delivery->getPositions()->getLineItems(), - $context - ); + foreach ($delivery->getShippingMethod()->getPriceRules() as $priceRule) { + // TODO: Ticket number: NEXT-2360, Price rules shouldn't be loaded in general (access price rules different at this point) + if (!in_array($priceRule->getRuleId(), $context->getRuleIds(), true)) { + continue; + } - break; - case self::CALCULATION_BY_PRICE: - $costs = $this->calculateShippingCosts( - $this->findShippingCosts( - $delivery->getShippingMethod(), - $delivery->getPositions()->getPrices()->sum()->getTotalPrice(), - $context->getContext() - ), - $delivery->getPositions()->getLineItems(), - $context - ); + if (!$this->matchesQuantity($delivery, $priceRule)) { + continue; + } - break; + $costs = $this->calculateShippingCosts( + $priceRule->getPrice(), + $delivery->getPositions()->getLineItems(), + $context + ); + break; + } + + if (!$costs) { + return; + } + $delivery->setShippingCosts($costs); + } + + private function matchesQuantity(Delivery $delivery, ShippingMethodPriceRuleEntity $shippingMethodPriceRule): bool + { + $start = $shippingMethodPriceRule->getQuantityStart(); + $end = $shippingMethodPriceRule->getQuantityEnd(); + + switch ($shippingMethodPriceRule->getCalculation()) { + case self::CALCULATION_BY_PRICE: + $value = $delivery->getPositions()->getPrices()->sum()->getTotalPrice(); + break; case self::CALCULATION_BY_LINE_ITEM_COUNT: - $costs = $this->calculateShippingCosts( - $this->findShippingCosts( - $delivery->getShippingMethod(), - $delivery->getPositions()->getQuantity(), - $context->getContext() - ), - $delivery->getPositions()->getLineItems(), - $context - ); + $value = $delivery->getPositions()->getQuantity(); + break; + case self::CALCULATION_BY_WEIGHT: + $value = $delivery->getPositions()->getWeight(); break; - - case self::CALCULATION_BY_CUSTOM: - return; default: - $price = $delivery->getPositions()->getLineItems()->getPrices()->sum()->getTotalPrice() / 100; - $costs = $this->calculateShippingCosts( - $price, - $delivery->getPositions()->getLineItems(), - $context - ); + $value = $delivery->getPositions()->getLineItems()->getPrices()->sum()->getTotalPrice() / 100; + break; } - $delivery->setShippingCosts($costs); + return ($value >= $start) && (!$end || $value <= $end); } private function calculateShippingCosts(float $price, LineItemCollection $calculatedLineItems, CheckoutContext $context): CalculatedPrice @@ -133,19 +118,4 @@ private function calculateShippingCosts(float $price, LineItemCollection $calcul return $this->priceCalculator->calculate($definition, $context); } - - private function findShippingCosts(ShippingMethodEntity $shippingMethod, float $value, Context $context): float - { - $query = $this->connection->createQueryBuilder(); - $query->select('costs.price'); - $query->from('shipping_method_price', 'costs'); - $query->andWhere('costs.`quantity_from` <= :value'); - $query->andWhere('costs.shipping_method_id = :id'); - $query->setParameter('id', Uuid::fromHexToBytes($shippingMethod->getId())); - $query->setParameter('value', $value); - $query->addOrderBy('price', 'DESC'); - $query->setMaxResults(1); - - return (float) $query->execute()->fetchColumn(); - } } diff --git a/src/Core/Checkout/DependencyInjection/cart.xml b/src/Core/Checkout/DependencyInjection/cart.xml index f21527e4fb3..fca4ccfdbf3 100644 --- a/src/Core/Checkout/DependencyInjection/cart.xml +++ b/src/Core/Checkout/DependencyInjection/cart.xml @@ -84,7 +84,6 @@ - diff --git a/src/Core/Checkout/DependencyInjection/shipping.xml b/src/Core/Checkout/DependencyInjection/shipping.xml index 08834ee76b2..7156897107b 100644 --- a/src/Core/Checkout/DependencyInjection/shipping.xml +++ b/src/Core/Checkout/DependencyInjection/shipping.xml @@ -9,8 +9,8 @@ - - + + diff --git a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceCollection.php b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceCollection.php deleted file mode 100644 index a1e3c83da39..00000000000 --- a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceCollection.php +++ /dev/null @@ -1,36 +0,0 @@ -fmap(function (ShippingMethodPriceEntity $shippingMethodPrice) { - return $shippingMethodPrice->getShippingMethodId(); - }); - } - - public function filterByShippingMethodId(string $id): self - { - return $this->filter(function (ShippingMethodPriceEntity $shippingMethodPrice) use ($id) { - return $shippingMethodPrice->getShippingMethodId() === $id; - }); - } - - protected function getExpectedClass(): string - { - return ShippingMethodPriceEntity::class; - } -} diff --git a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleCollection.php b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleCollection.php new file mode 100644 index 00000000000..a40ba155246 --- /dev/null +++ b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleCollection.php @@ -0,0 +1,36 @@ +fmap(function (ShippingMethodPriceRuleEntity $shippingMethodPrice) { + return $shippingMethodPrice->getShippingMethodId(); + }); + } + + public function filterByShippingMethodId(string $id): self + { + return $this->filter(function (ShippingMethodPriceRuleEntity $shippingMethodPrice) use ($id) { + return $shippingMethodPrice->getShippingMethodId() === $id; + }); + } + + protected function getExpectedClass(): string + { + return ShippingMethodPriceRuleEntity::class; + } +} diff --git a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceDefinition.php b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleDefinition.php similarity index 59% rename from src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceDefinition.php rename to src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleDefinition.php index 84868079b40..dc138f0d6c2 100644 --- a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceDefinition.php +++ b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleDefinition.php @@ -1,35 +1,39 @@ addFlags(new PrimaryKey(), new Required()), (new FkField('shipping_method_id', 'shippingMethodId', ShippingMethodDefinition::class))->addFlags(new Required()), - - (new FloatField('quantity_from', 'quantityFrom'))->addFlags(new Required()), + (new FkField('currency_id', 'currencyId', CurrencyDefinition::class))->addFlags(new Required()), + (new FkField('rule_id', 'ruleId', RuleDefinition::class))->addFlags(new Required()), + new IntField('calculation', 'calculation'), + (new IntField('quantity_start', 'quantityStart'))->addFlags(new Required()), + new IntField('quantity_end', 'quantityEnd'), (new FloatField('price', 'price'))->addFlags(new Required()), - (new FloatField('factor', 'factor'))->addFlags(new Required()), new AttributesField(), new CreatedAtField(), new UpdatedAtField(), - new ManyToOneAssociationField('shippingMethod', 'shipping_method_id', ShippingMethodDefinition::class, false), + (new ManyToOneAssociationField('shippingMethod', 'shipping_method_id', ShippingMethodDefinition::class, false, 'id'))->addFlags(new ReverseInherited('priceRules')), + new ManyToOneAssociationField('currency', 'currency_id', CurrencyDefinition::class, false), + new ManyToOneAssociationField('rule', 'rule_id', RuleDefinition::class, false), ]); } } diff --git a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceEntity.php b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleEntity.php similarity index 52% rename from src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceEntity.php rename to src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleEntity.php index c13af919009..5b503936871 100644 --- a/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPrice/ShippingMethodPriceEntity.php +++ b/src/Core/Checkout/Shipping/Aggregate/ShippingMethodPriceRule/ShippingMethodPriceRuleEntity.php @@ -1,12 +1,14 @@ shippingMethodId; @@ -59,14 +86,24 @@ public function setShippingMethodId(string $shippingMethodId): void $this->shippingMethodId = $shippingMethodId; } - public function getQuantityFrom(): float + public function getQuantityStart(): float + { + return $this->quantityStart; + } + + public function setQuantityStart(int $quantityStart): void { - return $this->quantityFrom; + $this->quantityStart = $quantityStart; } - public function setQuantityFrom(float $quantityFrom): void + public function getQuantityEnd(): ?int { - $this->quantityFrom = $quantityFrom; + return $this->quantityEnd; + } + + public function setQuantityEnd(int $quantityEnd): void + { + $this->quantityEnd = $quantityEnd; } public function getPrice(): float @@ -79,14 +116,14 @@ public function setPrice(float $price): void $this->price = $price; } - public function getFactor(): float + public function getCalculation(): int { - return $this->factor; + return $this->calculation; } - public function setFactor(float $factor): void + public function setCalculation(int $calculation): void { - $this->factor = $factor; + $this->calculation = $calculation; } public function getCreatedAt(): ?\DateTimeInterface @@ -128,4 +165,44 @@ public function setAttributes(?array $attributes): void { $this->attributes = $attributes; } + + public function getCurrencyId(): string + { + return $this->currencyId; + } + + public function setCurrencyId(string $currencyId): void + { + $this->currencyId = $currencyId; + } + + public function getRuleId(): string + { + return $this->ruleId; + } + + public function setRuleId(string $ruleId): void + { + $this->ruleId = $ruleId; + } + + public function getRule(): ?RuleEntity + { + return $this->rule; + } + + public function setRule(?RuleEntity $rule): void + { + $this->rule = $rule; + } + + public function getCurrency(): ?CurrencyEntity + { + return $this->currency; + } + + public function setCurrency(?CurrencyEntity $currency): void + { + $this->currency = $currency; + } } diff --git a/src/Core/Checkout/Shipping/ShippingMethodCollection.php b/src/Core/Checkout/Shipping/ShippingMethodCollection.php index b38dfdba9d6..77d70c5440b 100644 --- a/src/Core/Checkout/Shipping/ShippingMethodCollection.php +++ b/src/Core/Checkout/Shipping/ShippingMethodCollection.php @@ -3,7 +3,7 @@ namespace Shopware\Core\Checkout\Shipping; use Shopware\Core\Checkout\CheckoutContext; -use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPrice\ShippingMethodPriceCollection; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleCollection; use Shopware\Core\Framework\DataAbstractionLayer\EntityCollection; /** @@ -33,23 +33,23 @@ public function getPriceIds(): array $ids = [[]]; foreach ($this->getIterator() as $element) { - $ids[] = $element->getPrices()->getIds(); + $ids[] = $element->getPriceRules()->getIds(); } return array_merge(...$ids); } - public function getPrices(): ShippingMethodPriceCollection + public function getPriceRules(): ShippingMethodPriceRuleCollection { $prices = [[]]; foreach ($this->getIterator() as $element) { - $prices[] = $element->getPrices(); + $prices[] = $element->getPriceRules(); } $prices = array_merge(...$prices); - return new ShippingMethodPriceCollection($prices); + return new ShippingMethodPriceRuleCollection($prices); } protected function getExpectedClass(): string diff --git a/src/Core/Checkout/Shipping/ShippingMethodDefinition.php b/src/Core/Checkout/Shipping/ShippingMethodDefinition.php index 317b17777c4..096e8a30d03 100644 --- a/src/Core/Checkout/Shipping/ShippingMethodDefinition.php +++ b/src/Core/Checkout/Shipping/ShippingMethodDefinition.php @@ -3,7 +3,7 @@ namespace Shopware\Core\Checkout\Shipping; use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryDefinition; -use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPrice\ShippingMethodPriceDefinition; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleDefinition; use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodRules\ShippingMethodRuleDefinition; use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodTranslation\ShippingMethodTranslationDefinition; use Shopware\Core\Content\Rule\RuleDefinition; @@ -64,7 +64,6 @@ protected static function defineFields(): FieldCollection (new BoolField('bind_shippingfree', 'bindShippingfree'))->addFlags(new Required()), (new TranslatedField('name'))->addFlags(new SearchRanking(SearchRanking::HIGH_SEARCH_RANKING)), new BoolField('active', 'active'), - new IntField('calculation', 'calculation'), new IntField('min_delivery_time', 'minDeliveryTime'), new IntField('max_delivery_time', 'maxDeliveryTime'), new FloatField('shipping_free', 'shippingFree'), @@ -76,10 +75,10 @@ protected static function defineFields(): FieldCollection (new TranslatedField('description'))->addFlags(new SearchRanking(SearchRanking::LOW_SEARCH_RAKING)), (new TranslatedField('comment'))->addFlags(new SearchRanking(SearchRanking::MIDDLE_SEARCH_RANKING)), (new OneToManyAssociationField('orderDeliveries', OrderDeliveryDefinition::class, 'shipping_method_id', false, 'id'))->addFlags(new RestrictDelete()), - (new OneToManyAssociationField('prices', ShippingMethodPriceDefinition::class, 'shipping_method_id', true, 'id'))->addFlags(new CascadeDelete()), (new TranslationsAssociationField(ShippingMethodTranslationDefinition::class, 'shipping_method_id'))->addFlags(new Required()), new ManyToManyAssociationField('salesChannels', SalesChannelDefinition::class, SalesChannelShippingMethodDefinition::class, false, 'shipping_method_id', 'sales_channel_id'), (new ManyToManyAssociationField('availabilityRules', RuleDefinition::class, ShippingMethodRuleDefinition::class, false, 'shipping_method_id', 'rule_id'))->addFlags(new CascadeDelete()), + (new OneToManyAssociationField('priceRules', ShippingMethodPriceRuleDefinition::class, 'shipping_method_id', true, 'id'))->addFlags(new CascadeDelete()), ]); } } diff --git a/src/Core/Checkout/Shipping/ShippingMethodEntity.php b/src/Core/Checkout/Shipping/ShippingMethodEntity.php index f378ce04a36..56b2fdf8e93 100644 --- a/src/Core/Checkout/Shipping/ShippingMethodEntity.php +++ b/src/Core/Checkout/Shipping/ShippingMethodEntity.php @@ -3,7 +3,7 @@ namespace Shopware\Core\Checkout\Shipping; use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryCollection; -use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPrice\ShippingMethodPriceCollection; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleCollection; use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodTranslation\ShippingMethodTranslationCollection; use Shopware\Core\Content\Rule\RuleCollection; use Shopware\Core\Framework\DataAbstractionLayer\Entity; @@ -28,11 +28,6 @@ class ShippingMethodEntity extends Entity */ protected $active; - /** - * @var int - */ - protected $calculation; - /** * @var float|null */ @@ -58,11 +53,6 @@ class ShippingMethodEntity extends Entity */ protected $comment; - /** - * @var ShippingMethodPriceCollection - */ - protected $prices; - /** * @var int */ @@ -108,9 +98,15 @@ class ShippingMethodEntity extends Entity */ protected $availabilityRuleIds; + /** + * @var ShippingMethodPriceRuleCollection|null + */ + protected $priceRules; + public function __construct() { $this->availabilityRuleIds = []; + $this->priceRules = new ShippingMethodPriceRuleCollection(); } public function getBindShippingfree(): bool @@ -143,16 +139,6 @@ public function setActive(bool $active): void $this->active = $active; } - public function getCalculation(): int - { - return $this->calculation; - } - - public function setCalculation(int $calculation): void - { - $this->calculation = $calculation; - } - public function getShippingFree(): ?float { return $this->shippingFree; @@ -203,16 +189,6 @@ public function setComment(?string $comment): void $this->comment = $comment; } - public function getPrices(): ShippingMethodPriceCollection - { - return $this->prices; - } - - public function setPrices(ShippingMethodPriceCollection $prices): void - { - $this->prices = $prices; - } - public function getMinDeliveryTime(): int { return $this->minDeliveryTime; @@ -302,4 +278,14 @@ public function setAvailabilityRuleIds(array $availabilityRuleIds): void { $this->availabilityRuleIds = $availabilityRuleIds; } + + public function getPriceRules(): ?ShippingMethodPriceRuleCollection + { + return $this->priceRules; + } + + public function setPriceRules(?ShippingMethodPriceRuleCollection $priceRules): void + { + $this->priceRules = $priceRules; + } } diff --git a/src/Core/Checkout/Test/Cart/Common/Generator.php b/src/Core/Checkout/Test/Cart/Common/Generator.php index 086fe5e6202..d8fdb07d325 100644 --- a/src/Core/Checkout/Test/Cart/Common/Generator.php +++ b/src/Core/Checkout/Test/Cart/Common/Generator.php @@ -5,7 +5,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Shopware\Core\Checkout\Cart\Cart; -use Shopware\Core\Checkout\Cart\Delivery\DeliveryCalculator; use Shopware\Core\Checkout\Cart\Delivery\Struct\ShippingLocation; use Shopware\Core\Checkout\Cart\LineItem\LineItem; use Shopware\Core\Checkout\Cart\LineItem\LineItemCollection; @@ -109,7 +108,6 @@ public static function createCheckoutContext( $shippingMethod = new ShippingMethodEntity(); $shippingMethod->setId('8beeb66e9dda46b18891a059257a590e'); - $shippingMethod->setCalculation(DeliveryCalculator::CALCULATION_BY_PRICE); $shippingMethod->setMinDeliveryTime(1); $shippingMethod->setMaxDeliveryTime(2); diff --git a/src/Core/Checkout/Test/Cart/Order/RecalculationServiceTest.php b/src/Core/Checkout/Test/Cart/Order/RecalculationServiceTest.php index a94a026c487..459a98daf86 100644 --- a/src/Core/Checkout/Test/Cart/Order/RecalculationServiceTest.php +++ b/src/Core/Checkout/Test/Cart/Order/RecalculationServiceTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Shopware\Core\Checkout\Cart\Cart; use Shopware\Core\Checkout\Cart\CartBehaviorContext; +use Shopware\Core\Checkout\Cart\Delivery\DeliveryCalculator; use Shopware\Core\Checkout\Cart\Delivery\Struct\Delivery; use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryDate; use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryInformation; @@ -30,6 +31,7 @@ use Shopware\Core\Checkout\Order\Aggregate\OrderAddress\OrderAddressEntity; use Shopware\Core\Checkout\Order\OrderDefinition; use Shopware\Core\Checkout\Order\OrderEntity; +use Shopware\Core\Checkout\Shipping\ShippingMethodEntity; use Shopware\Core\Checkout\Test\Cart\Common\TrueRule; use Shopware\Core\Defaults; use Shopware\Core\Framework\Context; @@ -69,10 +71,11 @@ protected function setUp(): void parent::setUp(); $this->context = Context::createDefaultContext(); - $ruleId = Uuid::uuid4()->getHex(); + $priceRuleId = Uuid::uuid4()->getHex(); $this->customerId = $this->createCustomer(); - $shippingMethodId = $this->createShippingMethod($ruleId); + $shippingMethodId = $this->createShippingMethod($priceRuleId); + $this->checkoutContext = $this->getContainer()->get(CheckoutContextFactory::class)->create( Uuid::uuid4()->getHex(), Defaults::SALES_CHANNEL, @@ -82,7 +85,7 @@ protected function setUp(): void ] ); - $this->checkoutContext->setRuleIds([$ruleId]); + $this->checkoutContext->setRuleIds([$priceRuleId]); } public function testPersistOrderAndConvertToCart(): void @@ -354,6 +357,7 @@ public function testChangeShippingCosts(): void $versionContext = $this->context->createWithVersionId($versionId); $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); $deliveries = $orderDeliveryRepository->search($critera, $versionContext); @@ -392,12 +396,241 @@ public function testChangeShippingCosts(): void static::assertEquals($shippingCosts->getTaxRules(), $newShippingCosts->getTaxRules()); static::assertEquals( 5, - $newShippingCosts->getCalculatedTaxes()->get(5)->getPrice() - + $newShippingCosts->getCalculatedTaxes()->get(7)->getPrice() - + $newShippingCosts->getCalculatedTaxes()->get(19)->getPrice() + $newShippingCosts->getCalculatedTaxes()->get('5')->getPrice() + + $newShippingCosts->getCalculatedTaxes()->get('7')->getPrice() + + $newShippingCosts->getCalculatedTaxes()->get('19')->getPrice() ); } + public function testForeachLoopInCalculateDeliveryFunction(): void + { + $priceRuleId = Uuid::uuid4()->getHex(); + $shippingMethodId = Uuid::uuid4()->getHex(); + $shippingMethod = $this->addSecondPriceRuleToShippingMethod($priceRuleId, $shippingMethodId); + $this->checkoutContext->setRuleIds(array_merge($this->checkoutContext->getRuleIds(), [$priceRuleId])); + + $prop = ReflectionHelper::getProperty(CheckoutContext::class, 'shippingMethod'); + $prop->setValue($this->checkoutContext, $shippingMethod); + + // create order + $cart = $this->generateDemoCart(); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(15.0, $shippingCosts->getUnitPrice()); + static::assertSame(15.0, $shippingCosts->getTotalPrice()); + } + + public function testStartAndEndConditionsInPriceRule(): void + { + $priceRuleId = Uuid::uuid4()->getHex(); + $shippingMethodId = Uuid::uuid4()->getHex(); + $shippingMethod = $this->addSecondShippingMethodPriceRule($priceRuleId, $shippingMethodId); + $this->checkoutContext->setRuleIds(array_merge($this->checkoutContext->getRuleIds(), [$priceRuleId])); + + $prop = ReflectionHelper::getProperty(CheckoutContext::class, 'shippingMethod'); + $prop->setValue($this->checkoutContext, $shippingMethod); + + // create order + $cart = $this->generateDemoCart(); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + $firstPriceRule = $deliveries->first()->getShippingMethod()->getPriceRules()->first(); + $secondPriceRule = $deliveries->first()->getShippingMethod()->getPriceRules()->last(); + + static::assertSame($firstPriceRule->getRuleId(), $secondPriceRule->getRuleId()); + static::assertGreaterThan($firstPriceRule->getQuantityStart(), $firstPriceRule->getQuantityEnd()); + static::assertGreaterThan($firstPriceRule->getQuantityEnd(), $secondPriceRule->getQuantityStart()); + static::assertGreaterThan($secondPriceRule->getQuantityStart(), $secondPriceRule->getQuantityEnd()); + } + + public function testIfCorrectConditionIsUsedCalculationByLineItemCount(): void + { + $priceRuleId = Uuid::uuid4()->getHex(); + $shippingMethodId = Uuid::uuid4()->getHex(); + $shippingMethod = $this->addSecondShippingMethodPriceRule($priceRuleId, $shippingMethodId); + $this->checkoutContext->setRuleIds(array_merge($this->checkoutContext->getRuleIds(), [$priceRuleId])); + + $prop = ReflectionHelper::getProperty(CheckoutContext::class, 'shippingMethod'); + $prop->setValue($this->checkoutContext, $shippingMethod); + + // create order + $cart = $this->generateDemoCart(); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(15.0, $shippingCosts->getUnitPrice()); + static::assertSame(15.0, $shippingCosts->getTotalPrice()); + + // increase quantity for first LineItem + $cart->getLineItems()->first()->setQuantity(8); + $cart = $this->getContainer()->get(Enrichment::class)->enrich($cart, $this->checkoutContext); + $cart = $this->getContainer()->get(Processor::class)->process($cart, $this->checkoutContext, new CartBehaviorContext()); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(10.0, $shippingCosts->getUnitPrice()); + static::assertSame(10.0, $shippingCosts->getTotalPrice()); + } + + public function testIfCorrectConditionIsUsedPriceCalculation(): void + { + $priceRuleId = Uuid::uuid4()->getHex(); + $shippingMethodId = Uuid::uuid4()->getHex(); + $shippingMethod = $this->createTwoConditionsWithDifferentQuantities($priceRuleId, $shippingMethodId, DeliveryCalculator::CALCULATION_BY_PRICE); + $this->checkoutContext->setRuleIds(array_merge($this->checkoutContext->getRuleIds(), [$priceRuleId])); + + $prop = ReflectionHelper::getProperty(CheckoutContext::class, 'shippingMethod'); + $prop->setValue($this->checkoutContext, $shippingMethod); + + // create order + $cart = $this->generateDemoCart(); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(9.99, $shippingCosts->getUnitPrice()); + static::assertSame(9.99, $shippingCosts->getTotalPrice()); + + // decrease price for first LineItem + $cart->getLineItems()->first()->setPriceDefinition(new QuantityPriceDefinition(1, new TaxRuleCollection([new TaxRule(19)]), 5)); + $cart = $this->getContainer()->get(Enrichment::class)->enrich($cart, $this->checkoutContext); + $cart = $this->getContainer()->get(Processor::class)->process($cart, $this->checkoutContext, new CartBehaviorContext()); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(15.0, $shippingCosts->getUnitPrice()); + static::assertSame(15.0, $shippingCosts->getTotalPrice()); + } + + public function testIfCorrectConditionIsUsedWeightCalculation(): void + { + $priceRuleId = Uuid::uuid4()->getHex(); + $shippingMethodId = Uuid::uuid4()->getHex(); + $shippingMethod = $this->createTwoConditionsWithDifferentQuantities($priceRuleId, $shippingMethodId, DeliveryCalculator::CALCULATION_BY_WEIGHT); + $this->checkoutContext->setRuleIds(array_merge($this->checkoutContext->getRuleIds(), [$priceRuleId])); + + $prop = ReflectionHelper::getProperty(CheckoutContext::class, 'shippingMethod'); + $prop->setValue($this->checkoutContext, $shippingMethod); + + // create order + $cart = $this->generateDemoCart(); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(15.0, $shippingCosts->getUnitPrice()); + static::assertSame(15.0, $shippingCosts->getTotalPrice()); + + // increase weight for first LineItem + $deliveryInformation = new DeliveryInformation( + 100, + 75, + new DeliveryDate(new \DateTime(), new \DateTime()), + new DeliveryDate(new \DateTime(), new \DateTime()), + false + ); + $cart->getLineItems()->first()->setDeliveryInformation($deliveryInformation); + $cart = $this->getContainer()->get(Enrichment::class)->enrich($cart, $this->checkoutContext); + $cart = $this->getContainer()->get(Processor::class)->process($cart, $this->checkoutContext, new CartBehaviorContext()); + $orderId = $this->persistCart($cart); + + // create version of order + $versionId = $this->createVersionedOrder($orderId); + $versionContext = $this->context->createWithVersionId($versionId); + + $critera = new Criteria(); + $critera->addAssociation('shippingMethod', (new Criteria())->addAssociation('priceRules')); + $critera->addFilter(new EqualsFilter('order_delivery.orderId', $orderId)); + $orderDeliveryRepository = $this->getContainer()->get('order_delivery.repository'); + $deliveries = $orderDeliveryRepository->search($critera, $versionContext); + + /** @var CalculatedPrice $shippingCosts */ + $shippingCosts = $deliveries->first()->getShippingCosts(); + static::assertSame(1, $shippingCosts->getQuantity()); + static::assertSame(9.99, $shippingCosts->getUnitPrice()); + static::assertSame(9.99, $shippingCosts->getTotalPrice()); + } + public function testReplaceBillingAddress(): void { // create order @@ -755,7 +988,7 @@ private function addCustomLineItemToVersionedOrder(string $orderId, string $vers static::assertSame($calculatedTaxes->getTax(), 53.18); } - private function createShippingMethod(string $ruleId): string + private function createShippingMethod(string $priceRuleId): string { $shippingMethodId = Uuid::uuid4()->getHex(); $repository = $this->getContainer()->get('shipping_method.repository'); @@ -770,17 +1003,27 @@ private function createShippingMethod(string $ruleId): string 'name' => 'test shipping method', 'bindShippingfree' => false, 'active' => true, - 'prices' => [ + 'priceRules' => [ [ - 'shippingMethodId' => Defaults::SHIPPING_METHOD, - 'quantityFrom' => 0, 'price' => '10.00', - 'factor' => 0, + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => 1, + 'quantityStart' => 1, ], ], 'availabilityRules' => [ [ - 'id' => $ruleId, + 'id' => $priceRuleId, 'name' => 'true', 'priority' => 0, 'conditions' => [ @@ -796,4 +1039,194 @@ private function createShippingMethod(string $ruleId): string return $shippingMethodId; } + + private function addSecondPriceRuleToShippingMethod(string $priceRuleId, string $shippingMethodId): ShippingMethodEntity + { + $repository = $this->getContainer()->get('shipping_method.repository'); + $data = [ + 'id' => $shippingMethodId, + 'type' => 0, + 'name' => 'test shipping method 2', + 'bindShippingfree' => false, + 'active' => true, + 'priceRules' => [ + [ + 'price' => '15.00', + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => 1, + 'quantityStart' => 0, + ], + [ + 'price' => '20.00', + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => 1, + 'quantityStart' => 1, + ], + ], + 'availabilityRules' => [ + [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + ], + ]; + + $repository->upsert([$data], $this->context); + + return $repository->search(new Criteria([$shippingMethodId]), $this->context)->get($shippingMethodId); + } + + private function addSecondShippingMethodPriceRule(string $priceRuleId, string $shippingMethodId): ShippingMethodEntity + { + $repository = $this->getContainer()->get('shipping_method.repository'); + $data = [ + 'id' => $shippingMethodId, + 'type' => 0, + 'name' => 'test shipping method 3', + 'bindShippingfree' => false, + 'active' => true, + 'priceRules' => [ + [ + 'price' => '15.00', + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => 1, + 'quantityStart' => 1, + 'quantityEnd' => 9, + ], + [ + 'price' => '10.00', + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => 1, + 'quantityStart' => 10, + 'quantityEnd' => 20, + ], + ], + 'availabilityRules' => [ + [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + ], + ]; + + $repository->upsert([$data], $this->context); + + return $repository->search(new Criteria([$shippingMethodId]), $this->context)->get($shippingMethodId); + } + + private function createTwoConditionsWithDifferentQuantities(string $priceRuleId, string $shippingMethodId, int $calculation): ShippingMethodEntity + { + $repository = $this->getContainer()->get('shipping_method.repository'); + + $data = [ + 'id' => $shippingMethodId, + 'type' => 0, + 'name' => 'test shipping method 4', + 'bindShippingfree' => false, + 'active' => true, + 'priceRules' => [ + [ + 'price' => '15.00', + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => $calculation, + 'quantityStart' => 0, + 'quantityEnd' => 70, + ], + [ + 'price' => '9.99', + 'currencyId' => Defaults::CURRENCY, + 'rule' => [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + 'calculation' => $calculation, + 'quantityStart' => 71, + ], + ], + 'availabilityRules' => [ + [ + 'id' => $priceRuleId, + 'name' => 'true', + 'priority' => 0, + 'conditions' => [ + [ + 'type' => 'true', + ], + ], + ], + ], + ]; + + $repository->upsert([$data], $this->context); + + return $repository->search(new Criteria([$shippingMethodId]), $this->context)->get($shippingMethodId); + } } diff --git a/src/Core/Checkout/Test/Shipping/DataAbstractionLayer/Indexer/ShippingMethodIndexerTest.php b/src/Core/Checkout/Test/Shipping/DataAbstractionLayer/Indexer/ShippingMethodIndexerTest.php index a2e0497149c..527332c7c99 100644 --- a/src/Core/Checkout/Test/Shipping/DataAbstractionLayer/Indexer/ShippingMethodIndexerTest.php +++ b/src/Core/Checkout/Test/Shipping/DataAbstractionLayer/Indexer/ShippingMethodIndexerTest.php @@ -223,7 +223,6 @@ private function createShippingMethod(): string 'shipping_method', [ 'id' => $id, 'active' => 1, - 'calculation' => 0, 'bind_shippingfree' => 0, 'created_at' => date('Y-m-d H:i:s'), ] diff --git a/src/Core/Content/Rule/RuleDefinition.php b/src/Core/Content/Rule/RuleDefinition.php index 8f6fcca91a8..80a5e237487 100644 --- a/src/Core/Content/Rule/RuleDefinition.php +++ b/src/Core/Content/Rule/RuleDefinition.php @@ -5,6 +5,7 @@ use Shopware\Core\Checkout\DiscountSurcharge\DiscountSurchargeDefinition; use Shopware\Core\Checkout\Payment\Aggregate\PaymentMethodRules\PaymentMethodRuleDefinition; use Shopware\Core\Checkout\Payment\PaymentMethodDefinition; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleDefinition; use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodRules\ShippingMethodRuleDefinition; use Shopware\Core\Checkout\Shipping\ShippingMethodDefinition; use Shopware\Core\Content\Product\Aggregate\ProductPriceRule\ProductPriceRuleDefinition; @@ -65,6 +66,7 @@ protected static function defineFields(): FieldCollection (new OneToManyAssociationField('conditions', RuleConditionDefinition::class, 'rule_id', false, 'id'))->addFlags(new CascadeDelete()), (new OneToManyAssociationField('discountSurcharges', DiscountSurchargeDefinition::class, 'rule_id', false, 'id'))->addFlags(new CascadeDelete()), (new OneToManyAssociationField('productPriceRules', ProductPriceRuleDefinition::class, 'rule_id', false, 'id'))->addFlags(new CascadeDelete()), + (new OneToManyAssociationField('shippingMethodPriceRules', ShippingMethodPriceRuleDefinition::class, 'rule_id', false, 'id'))->addFlags(new CascadeDelete()), (new ManyToManyAssociationField('shippingMethods', ShippingMethodDefinition::class, ShippingMethodRuleDefinition::class, false, 'rule_id', 'shipping_method_id'))->addFlags(new CascadeDelete()), (new ManyToManyAssociationField('paymentMethods', PaymentMethodDefinition::class, PaymentMethodRuleDefinition::class, false, 'rule_id', 'payment_method_id'))->addFlags(new CascadeDelete()), ]); diff --git a/src/Core/Content/Rule/RuleEntity.php b/src/Core/Content/Rule/RuleEntity.php index 5ad7bcb3516..faa87420103 100644 --- a/src/Core/Content/Rule/RuleEntity.php +++ b/src/Core/Content/Rule/RuleEntity.php @@ -4,6 +4,7 @@ use Shopware\Core\Checkout\DiscountSurcharge\DiscountSurchargeCollection; use Shopware\Core\Checkout\Payment\PaymentMethodCollection; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleCollection; use Shopware\Core\Checkout\Shipping\ShippingMethodCollection; use Shopware\Core\Content\Product\Aggregate\ProductPriceRule\ProductPriceRuleCollection; use Shopware\Core\Content\Rule\Aggregate\RuleCondition\RuleConditionCollection; @@ -85,6 +86,11 @@ class RuleEntity extends Entity */ protected $attributes; + /** + * @var ShippingMethodPriceRuleCollection|null + */ + protected $shippingMethodPriceRules; + public function getName(): string { return $this->name; @@ -224,4 +230,14 @@ public function setAttributes(?array $attributes): void { $this->attributes = $attributes; } + + public function getShippingMethodPriceRules(): ?ShippingMethodPriceRuleCollection + { + return $this->shippingMethodPriceRules; + } + + public function setShippingMethodPriceRules(?ShippingMethodPriceRuleCollection $shippingMethodPriceRules): void + { + $this->shippingMethodPriceRules = $shippingMethodPriceRules; + } } diff --git a/src/Core/Framework/Command/DemodataCommand.php b/src/Core/Framework/Command/DemodataCommand.php index 1153a6df2e9..e5a16d19f98 100644 --- a/src/Core/Framework/Command/DemodataCommand.php +++ b/src/Core/Framework/Command/DemodataCommand.php @@ -7,7 +7,7 @@ use function Flag\next754; use Shopware\Core\Checkout\Customer\CustomerDefinition; use Shopware\Core\Checkout\Order\OrderDefinition; -use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPrice\ShippingMethodPriceDefinition; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleDefinition; use Shopware\Core\Content\Category\CategoryDefinition; use Shopware\Core\Content\Cms\CmsPageDefinition; use Shopware\Core\Content\Configuration\ConfigurationGroupDefinition; @@ -105,7 +105,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $request->add(RuleDefinition::class, 5); $request->add(CustomerDefinition::class, (int) $input->getOption('customers')); $request->add(ConfigurationGroupDefinition::class, (int) $input->getOption('properties')); - $request->add(ShippingMethodPriceDefinition::class, 1); + $request->add(ShippingMethodPriceRuleDefinition::class, 1); $request->add(CategoryDefinition::class, (int) $input->getOption('categories')); $request->add(ProductManufacturerDefinition::class, (int) $input->getOption('manufacturers')); $request->add(ProductDefinition::class, (int) $input->getOption('products'), $this->getProductOptions($input)); diff --git a/src/Core/Framework/Demodata/Generator/ShippingMethodPriceGenerator.php b/src/Core/Framework/Demodata/Generator/ShippingMethodPriceGenerator.php index 1bf46c35585..10fd1281fcc 100644 --- a/src/Core/Framework/Demodata/Generator/ShippingMethodPriceGenerator.php +++ b/src/Core/Framework/Demodata/Generator/ShippingMethodPriceGenerator.php @@ -2,7 +2,8 @@ namespace Shopware\Core\Framework\Demodata\Generator; -use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPrice\ShippingMethodPriceDefinition; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleDefinition; +use Shopware\Core\Content\Rule\RuleDefinition; use Shopware\Core\Defaults; use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; use Shopware\Core\Framework\Demodata\DemodataContext; @@ -22,21 +23,25 @@ public function __construct(EntityRepositoryInterface $shippingMethodPriceReposi public function getDefinition(): string { - return ShippingMethodPriceDefinition::class; + return ShippingMethodPriceRuleDefinition::class; } public function generate(int $numberOfItems, DemodataContext $context, array $options = []): void { + $rules = $context->getIds(RuleDefinition::class); $data = [ 'id' => '572decf9581e4de0acd52f80499f0e9b', 'shippingMethodId' => Defaults::SHIPPING_METHOD, - 'quantityFrom' => 0, 'price' => '10.00', - 'factor' => 0, + 'currencyId' => Defaults::CURRENCY, + 'ruleId' => $rules[0], + 'calculation' => 1, + 'quantityStart' => 1, + '$createdAt' => 0, ]; $this->shippingMethodPriceRepository->upsert([$data], $context->getContext()); - $context->add(ShippingMethodPriceDefinition::class, $data['id']); + $context->add(ShippingMethodPriceRuleDefinition::class, $data['id']); } } diff --git a/src/Core/Framework/DependencyInjection/demodata.xml b/src/Core/Framework/DependencyInjection/demodata.xml index b258013a529..b19c00302f9 100644 --- a/src/Core/Framework/DependencyInjection/demodata.xml +++ b/src/Core/Framework/DependencyInjection/demodata.xml @@ -30,7 +30,7 @@ - + diff --git a/src/Core/Framework/Test/Api/Serializer/fixtures/testPayloadShouldNotBeInIncludedExpectation.php b/src/Core/Framework/Test/Api/Serializer/fixtures/testPayloadShouldNotBeInIncludedExpectation.php index 0a8355bbb15..050624cc1f6 100644 --- a/src/Core/Framework/Test/Api/Serializer/fixtures/testPayloadShouldNotBeInIncludedExpectation.php +++ b/src/Core/Framework/Test/Api/Serializer/fixtures/testPayloadShouldNotBeInIncludedExpectation.php @@ -33,6 +33,10 @@ 'data' => [], 'links' => ['related' => '/api/rule/f343a3c119cf42a7841aa0ac5094908c/shipping-methods'], ], + 'shippingMethodPriceRules' => [ + 'data' => [], + 'links' => ['related' => '/api/rule/f343a3c119cf42a7841aa0ac5094908c/shipping-method-price-rules'], + ], 'paymentMethods' => [ 'data' => [], 'links' => ['related' => '/api/rule/f343a3c119cf42a7841aa0ac5094908c/payment-methods'], @@ -59,6 +63,7 @@ 'attributes' => null, 'shippingMethods' => null, 'paymentMethods' => null, + 'shippingMethodPriceRules' => null, '_class' => 'Shopware\Core\Content\Rule\RuleEntity', ], ], diff --git a/src/Core/Migration/Migration1551962861ShippingMethodPriceRule.php b/src/Core/Migration/Migration1551962861ShippingMethodPriceRule.php new file mode 100644 index 00000000000..fa2e58618e4 --- /dev/null +++ b/src/Core/Migration/Migration1551962861ShippingMethodPriceRule.php @@ -0,0 +1,54 @@ +executeQuery(' + CREATE TABLE `shipping_method_price_rule` ( + `id` BINARY(16) NOT NULL, + `shipping_method_id` BINARY(16) NOT NULL, + `calculation` INT(1) unsigned NOT NULL DEFAULT 1, + `rule_id` BINARY(16) NOT NULL, + `currency_id` BINARY(16) NOT NULL, + `price` DECIMAL(10,2) NOT NULL, + `quantity_start` INT(11) NOT NULL, + `quantity_end` INT(11) NULL, + `attributes` JSON NULL, + `created_at` DATETIME(3) NOT NULL, + `updated_at` DATETIME(3) NULL, + PRIMARY KEY (`id`), + CONSTRAINT `uniq.shipping_method_quantity_start` UNIQUE KEY (`shipping_method_id`, `rule_id`, `currency_id`, `quantity_start`), + CONSTRAINT `json.attributes` CHECK (JSON_VALID(`attributes`)), + CONSTRAINT `fk.shipping_method_price_rule.shipping_method_id` FOREIGN KEY (`shipping_method_id`) + REFERENCES `shipping_method` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk.shipping_method_price_rule.currency_id` FOREIGN KEY (`currency_id`) + REFERENCES `currency` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk.shipping_method_price_rule.rule_id` FOREIGN KEY (`rule_id`) + REFERENCES `rule` (`id`) ON DELETE CASCADE ON UPDATE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + '); + } + + public function updateDestructive(Connection $connection): void + { + $connection->executeQuery(' + ALTER TABLE `shipping_method` + DROP COLUMN `calculation`; + '); + + $connection->exec(' + DROP TABLE shipping_method_price; + '); + } +} diff --git a/src/Core/System/Currency/CurrencyDefinition.php b/src/Core/System/Currency/CurrencyDefinition.php index 52e03bb24fe..4eefa8518bf 100644 --- a/src/Core/System/Currency/CurrencyDefinition.php +++ b/src/Core/System/Currency/CurrencyDefinition.php @@ -3,6 +3,7 @@ namespace Shopware\Core\System\Currency; use Shopware\Core\Checkout\Order\OrderDefinition; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleDefinition; use Shopware\Core\Content\Product\Aggregate\ProductPriceRule\ProductPriceRuleDefinition; use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition; use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField; @@ -62,6 +63,7 @@ protected static function defineFields(): FieldCollection (new TranslationsAssociationField(CurrencyTranslationDefinition::class, 'currency_id'))->addFlags(new Required()), (new OneToManyAssociationField('orders', OrderDefinition::class, 'currency_id', false, 'id'))->addFlags(new RestrictDelete()), new OneToManyAssociationField('productPriceRules', ProductPriceRuleDefinition::class, 'currency_id', false, 'id'), + new OneToManyAssociationField('shippingMethodPriceRules', ShippingMethodPriceRuleDefinition::class, 'currency_id', false, 'id'), new ManyToManyAssociationField('salesChannels', SalesChannelDefinition::class, SalesChannelCurrencyDefinition::class, false, 'currency_id', 'sales_channel_id'), (new OneToManyAssociationField('salesChannelDomains', SalesChannelDomainDefinition::class, 'currency_id', false))->addFlags(new RestrictDelete()), ]); diff --git a/src/Core/System/Currency/CurrencyEntity.php b/src/Core/System/Currency/CurrencyEntity.php index 2a0505316af..95bb80769cb 100644 --- a/src/Core/System/Currency/CurrencyEntity.php +++ b/src/Core/System/Currency/CurrencyEntity.php @@ -3,6 +3,7 @@ namespace Shopware\Core\System\Currency; use Shopware\Core\Checkout\Order\OrderCollection; +use Shopware\Core\Checkout\Shipping\Aggregate\ShippingMethodPriceRule\ShippingMethodPriceRuleCollection; use Shopware\Core\Content\Product\Aggregate\ProductPriceRule\ProductPriceRuleCollection; use Shopware\Core\Framework\DataAbstractionLayer\Entity; use Shopware\Core\Framework\DataAbstractionLayer\EntityIdTrait; @@ -98,6 +99,11 @@ class CurrencyEntity extends Entity */ protected $attributes; + /** + * @var ShippingMethodPriceRuleCollection|null + */ + protected $shippingMethodPriceRules; + public function getFactor(): float { return $this->factor; @@ -267,4 +273,14 @@ public function setDecimalPrecision(int $decimalPrecision): void { $this->decimalPrecision = $decimalPrecision; } + + public function getShippingMethodPriceRules(): ?ShippingMethodPriceRuleCollection + { + return $this->shippingMethodPriceRules; + } + + public function setShippingMethodPriceRules(?ShippingMethodPriceRuleCollection $shippingMethodPriceRules): void + { + $this->shippingMethodPriceRules = $shippingMethodPriceRules; + } }