| @@ -1,5 +1,5 @@ | |||
| **Possible issues** | |||
| - The plugin only reacts to the state 'shipped', partially shipped and returned items are not influencing the customer tag | |||
| - The plugin only reacts to the order state 'completed' | |||
| - Plugin only works with a boolean custom field, because it is looking for a truthy value | |||
| - Delete limit set to 999 | |||
| @@ -1,7 +1,7 @@ | |||
| { | |||
| "name": "atl/discount-extension", | |||
| "description": "Discount Extension", | |||
| "version": "1.0.2", | |||
| "version": "1.0.3", | |||
| "type": "shopware-platform-plugin", | |||
| "license": "proprietary", | |||
| "authors": [ | |||
| @@ -15,7 +15,7 @@ | |||
| <service id="Atl\DiscountExtension\Subscriber\StateMachineTransitionSubscriber"> | |||
| <argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService" /> | |||
| <argument type="service" id="order_delivery.repository"/> | |||
| <argument type="service" id="order.repository"/> | |||
| <argument type="service" id="custom_field.repository" /> | |||
| <argument type="service" id="customer.repository" /> | |||
| <tag name="kernel.event_subscriber"/> | |||
| @@ -6,9 +6,9 @@ namespace Atl\DiscountExtension\Subscriber; | |||
| use Atl\DiscountExtension\Core\System\RepeatDiscount\RepeatDiscountEntity; | |||
| use Shopware\Core\Checkout\Customer\CustomerEntity; | |||
| use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryEntity; | |||
| use Shopware\Core\Checkout\Order\Aggregate\OrderDelivery\OrderDeliveryStates; | |||
| use Shopware\Core\Checkout\Order\Aggregate\OrderDeliveryPosition\OrderDeliveryPositionCollection; | |||
| use Shopware\Core\Checkout\Order\Aggregate\OrderLineItem\OrderLineItemCollection; | |||
| use Shopware\Core\Checkout\Order\OrderEntity; | |||
| use Shopware\Core\Checkout\Order\OrderStates; | |||
| use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface; | |||
| use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; | |||
| use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; | |||
| @@ -27,7 +27,7 @@ class StateMachineTransitionSubscriber implements EventSubscriberInterface | |||
| /** | |||
| * @var EntityRepositoryInterface | |||
| */ | |||
| private $orderDeliveryRepository; | |||
| private $orderRepository; | |||
| /** | |||
| * @var EntityRepositoryInterface | |||
| @@ -42,13 +42,13 @@ class StateMachineTransitionSubscriber implements EventSubscriberInterface | |||
| public function __construct | |||
| ( | |||
| SystemConfigService $systemConfigService, | |||
| EntityRepositoryInterface $orderDeliveryRepository, | |||
| EntityRepositoryInterface $orderRepository, | |||
| EntityRepositoryInterface $customFieldRepository, | |||
| EntityRepositoryInterface $customerRepository | |||
| ) | |||
| { | |||
| $this->systemConfigService = $systemConfigService; | |||
| $this->orderDeliveryRepository = $orderDeliveryRepository; | |||
| $this->orderRepository = $orderRepository; | |||
| $this->customFieldRepository = $customFieldRepository; | |||
| $this->customerRepository = $customerRepository; | |||
| } | |||
| @@ -69,50 +69,51 @@ class StateMachineTransitionSubscriber implements EventSubscriberInterface | |||
| */ | |||
| public function onStateTransition(StateMachineTransitionEvent $event): void | |||
| { | |||
| // Early return if transition state is not 'shipped' | |||
| if ($event->getToPlace()->getTechnicalName() !== OrderDeliveryStates::STATE_SHIPPED) { | |||
| // Early return if order state is not 'completed' | |||
| if ($event->getToPlace()->getTechnicalName() !== OrderStates::STATE_COMPLETED) { | |||
| return; | |||
| } | |||
| $orderDeliveryId = $event->getEntityId(); | |||
| $orderDelivery = $this->getOrderDelivery($orderDeliveryId, $event); | |||
| $salesChannelId = $orderDelivery->getOrder()->getSalesChannelId(); | |||
| $orderId = $event->getEntityId(); | |||
| $order = $this->getOrder($orderId, $event); | |||
| $salesChannelId = $order->getSalesChannelId(); | |||
| // In case no order delivery could be found - return | |||
| if (empty($orderDelivery)) { | |||
| // In case no order could be found - return | |||
| if (empty($order)) { | |||
| return; | |||
| } | |||
| $minAmountDiscountableLineItems = $this->systemConfigService->get('AtlDiscountExtension.config.minAmountDiscountableLineItems', $salesChannelId); | |||
| $amountDiscountableLineItems = $this->getAmountDiscountableLineItems($orderDelivery->getPositions(), $minAmountDiscountableLineItems, $salesChannelId, $event); | |||
| $amountDiscountableLineItems = $this->getAmountDiscountableLineItems($order->getLineItems(), $minAmountDiscountableLineItems, $salesChannelId, $event); | |||
| // In case it is not enough - return | |||
| if ($amountDiscountableLineItems < $minAmountDiscountableLineItems) { | |||
| return; | |||
| } | |||
| $customer = $orderDelivery->getOrder()->getOrderCustomer()->getCustomer(); | |||
| $customer = $order->getOrderCustomer()->getCustomer(); | |||
| // Tag the customer and trace last order date in 'spwn_repeat_discount' table | |||
| $this->tagCustomer($customer, $salesChannelId, $event); | |||
| } | |||
| /** | |||
| * @param string $orderDeliveryId | |||
| * @param string $orderId | |||
| * @param StateMachineTransitionEvent $event | |||
| * @return OrderDeliveryEntity | |||
| * @return OrderEntity | |||
| */ | |||
| private function getOrderDelivery(string $orderDeliveryId, StateMachineTransitionEvent $event): OrderDeliveryEntity | |||
| private function getOrder(string $orderId, StateMachineTransitionEvent $event): OrderEntity | |||
| { | |||
| $criteria = new Criteria(); | |||
| $criteria->addFilter(new EqualsFilter('id', $orderDeliveryId)); | |||
| $criteria->addAssociation('positions.orderLineItem'); | |||
| $criteria->addAssociation('order.orderCustomer.customer'); | |||
| $criteria->addFilter(new EqualsFilter('id', $orderId)); | |||
| $criteria->addAssociation('lineItems'); | |||
| $criteria->addAssociation('orderCustomer.customer'); | |||
| return $this->orderDeliveryRepository->search($criteria, $event->getContext())->first(); | |||
| return $this->orderRepository->search($criteria, $event->getContext())->first(); | |||
| } | |||
| /** | |||
| * @param OrderDeliveryPositionCollection $positions | |||
| * @param OrderLineItemCollection $lineItems | |||
| * @param int $minAmountDiscountableLineItems | |||
| * @param string $salesChannelId | |||
| * @param StateMachineTransitionEvent $event | |||
| @@ -120,7 +121,7 @@ class StateMachineTransitionSubscriber implements EventSubscriberInterface | |||
| */ | |||
| private function getAmountDiscountableLineItems | |||
| ( | |||
| OrderDeliveryPositionCollection $positions, | |||
| OrderLineItemCollection $lineItems, | |||
| int $minAmountDiscountableLineItems, | |||
| string $salesChannelId, | |||
| StateMachineTransitionEvent $event | |||
| @@ -134,12 +135,12 @@ class StateMachineTransitionSubscriber implements EventSubscriberInterface | |||
| } | |||
| $amountDiscountableLineItems = 0; | |||
| foreach($positions as $position) { | |||
| $customFields = $position->getOrderLineItem()->getPayload()['customFields']; | |||
| foreach($lineItems as $lineItem) { | |||
| $customFields = $lineItem->getPayload()['customFields']; | |||
| // Checks if the custom field exists and is set to true | |||
| if (!empty($customFields[$customFieldName]) && $customFields[$customFieldName] === true) { | |||
| $amountDiscountableLineItems += $position->getQuantity(); | |||
| $amountDiscountableLineItems += $lineItem->getQuantity(); | |||
| } | |||
| // Break if min amount is already reached | |||
| @@ -35,13 +35,15 @@ $(document).ready(function() { | |||
| changeDelivery(fp); | |||
| } | |||
| var sticky = $(".thumbnail-container"), | |||
| parentContainer = sticky.parents(".aku-cms-factory-element"), | |||
| stickyTop = sticky.offset().top; | |||
| tagBar(sticky, parentContainer, stickyTop); | |||
| $(window).on('resize scroll', function() { | |||
| var sticky = $(".thumbnail-container"); | |||
| if (sticky.length) { | |||
| var parentContainer = sticky.parents(".aku-cms-factory-element"), | |||
| stickyTop = sticky.offset().top; | |||
| tagBar(sticky, parentContainer, stickyTop); | |||
| }); | |||
| $(window).on('resize scroll', function() { | |||
| tagBar(sticky, parentContainer, stickyTop); | |||
| }); | |||
| } | |||
| }); | |||
| function manipulateDateInputValue() { | |||
| @@ -34,7 +34,7 @@ | |||
| } | |||
| .product-price-unit, | |||
| .product-cheapest-price { | |||
| display: none; | |||
| //display: none; | |||
| } | |||
| } | |||
| @@ -56,6 +56,22 @@ | |||
| {% endif %} | |||
| {% endblock %} | |||
| {% block component_offcanvas_product_label %} | |||
| <div class="cart-item-details"> | |||
| {% if type == PRODUCT_LINE_ITEM_TYPE %} | |||
| <a href="{{ seoUrl('frontend.detail.page', {'productId': referencedId}) }}" | |||
| class="cart-item-label" | |||
| title="{{ label|replace({'&':'&'}) }}"> | |||
| {{ quantity }}{{ "checkout.quantityTimes"|trans|sw_sanitize }} {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </a> | |||
| {% else %} | |||
| <div class="cart-item-label"> | |||
| {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </div> | |||
| {% endif %} | |||
| </div> | |||
| {% endblock %} | |||
| {# coming from AtlProductConfigurator #} | |||
| {% block cart_item_variant_characteristics %} | |||
| <div class="cart-item-characteristics"> | |||
| @@ -0,0 +1,5 @@ | |||
| {% sw_extends '@Storefront/storefront/component/product/description.html.twig' %} | |||
| {% block component_product_description_title %} | |||
| {% endblock %} | |||
| @@ -14,18 +14,18 @@ | |||
| {% if lineItem.type == PRODUCT_LINE_ITEM_TYPE %} | |||
| <a href="{{ seoUrl('frontend.detail.page', {'productId': lineItem.referencedId}) }}" | |||
| class="cart-item-label" | |||
| title="{{ label }}" | |||
| title="{{ label|replace({'&':'&'}) }}" | |||
| {% if controllerAction is same as('confirmPage') %} | |||
| data-toggle="modal" | |||
| data-modal-class="quickview-modal" | |||
| data-url="{{ path('widgets.quickview.minimal',{ 'productId': lineItem.referencedId }) }}" | |||
| {% endif %} | |||
| > | |||
| {{ label|u.truncate(60, '...', false) }} | |||
| {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </a> | |||
| {% else %} | |||
| <div class="cart-item-label"> | |||
| {{ label|u.truncate(60, '...', false) }} | |||
| {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </div> | |||
| {% endif %} | |||
| {% endblock %} | |||
| @@ -1,7 +1,5 @@ | |||
| {% sw_extends '@Storefront/storefront/page/product-detail/description.html.twig' %} | |||
| {% block page_product_detail_description_title %} | |||
| <div class="h3 product-detail-description-title"> | |||
| {{ "detail.descriptionTitle"|trans|sw_sanitize }} {{ page.product.translated.name }} | |||
| </div> | |||
| {% endblock %} | |||
| @@ -1,107 +1,103 @@ | |||
| .top-bar-country { | |||
| transform: translateY(4px); | |||
| .top-bar-nav-btn.btn { | |||
| color: #222; | |||
| &:hover { | |||
| background: transparent; | |||
| color: $sw-color-brand-primary; | |||
| transform: translateY(4px); | |||
| .top-bar-nav-btn.btn { | |||
| color: #222; | |||
| &:hover { | |||
| background: transparent; | |||
| color: $sw-color-brand-primary; | |||
| } | |||
| } | |||
| } | |||
| @media (min-width: 992px) { | |||
| .header-main .header-inner { | |||
| padding-top: 15px; | |||
| padding-bottom: 25px; | |||
| } | |||
| } | |||
| } | |||
| .header-row { | |||
| padding-top: 10px; | |||
| border-bottom: 1px solid #B1C3D9; | |||
| padding-top: 10px; | |||
| padding-bottom: 15px; | |||
| } | |||
| .header-main { | |||
| .header-actions-btn { | |||
| &:hover { | |||
| background: transparent; | |||
| .header-actions-btn { | |||
| &:hover { | |||
| background: transparent; | |||
| } | |||
| } | |||
| } | |||
| .header-cart-btn { | |||
| .header-cart-total { | |||
| margin-right: 0; | |||
| .header-cart-btn { | |||
| .header-cart-total { | |||
| margin-right: 0; | |||
| } | |||
| &:hover { | |||
| background: transparent; | |||
| } | |||
| } | |||
| &:hover { | |||
| background: transparent; | |||
| } | |||
| } | |||
| } | |||
| .header-actions-col { | |||
| @media (min-width: 992px) { | |||
| position: absolute; | |||
| top: 0; | |||
| right: 0; | |||
| z-index: 10; | |||
| } | |||
| } | |||
| .header-nav-col { | |||
| max-width: 950px; | |||
| margin-top: 45px; | |||
| margin-left: auto; | |||
| max-width: 950px; | |||
| margin-top: 45px; | |||
| margin-left: auto; | |||
| } | |||
| .header-single-line { | |||
| .navigation-flyout { | |||
| top: 36px; | |||
| &.is-open { | |||
| @media (min-width: 992px) { | |||
| padding-top: 0; | |||
| } | |||
| .navigation-flyout { | |||
| top: 36px; | |||
| &.is-open { | |||
| @media (min-width: 992px) { | |||
| padding-top: 0; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .nav-header > .container { | |||
| padding-left: 0; | |||
| padding-right: 0; | |||
| .nav-header>.container { | |||
| padding-left: 0; | |||
| padding-right: 0; | |||
| } | |||
| .main-navigation-menu { | |||
| justify-content: space-between; | |||
| justify-content: space-around; | |||
| } | |||
| .main-navigation-divider { | |||
| display: none; | |||
| display: none; | |||
| } | |||
| .main-navigation-link { | |||
| font-size: 16px; | |||
| font-weight: 500; | |||
| letter-spacing: 1.6px; | |||
| line-height: 18px; | |||
| text-transform: uppercase; | |||
| padding: 0; | |||
| .main-navigation-link-text { | |||
| &:after { | |||
| height: 3px; | |||
| left: 0; | |||
| right: 0; | |||
| bottom: -18px; | |||
| } | |||
| } | |||
| &.active { | |||
| font-size: 18px; | |||
| font-weight: 500; | |||
| letter-spacing: 0.5px; | |||
| line-height: 18px; | |||
| padding: 0; | |||
| .main-navigation-link-text { | |||
| color: $sw-color-brand-primary; | |||
| &:after { | |||
| height: 3px; | |||
| left: 0; | |||
| right: 0; | |||
| bottom: -18px; | |||
| } | |||
| } | |||
| &.active { | |||
| .main-navigation-link-text { | |||
| color: $sw-color-brand-primary; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| .navigation-offcanvas-actions { | |||
| .top-bar-country { | |||
| z-index: 10; | |||
| } | |||
| .top-bar-country { | |||
| z-index: 10; | |||
| } | |||
| } | |||
| .navigation-offcanvas-link { | |||
| &.is-home-link, | |||
| &.is-current-category { | |||
| background: #f3f4f5; | |||
| color: #222; | |||
| } | |||
| } | |||
| &.is-home-link, | |||
| &.is-current-category { | |||
| background: #f3f4f5; | |||
| color: #222; | |||
| } | |||
| } | |||
| @@ -8,11 +8,12 @@ https://getbootstrap.com/docs/4.0/getting-started/theming/#variable-defaults | |||
| */ | |||
| html { | |||
| scroll-behavior: smooth; | |||
| scroll-behavior: smooth; | |||
| } | |||
| body { | |||
| background: #fff; | |||
| border-top: 5px solid $sw-color-brand-primary !important; | |||
| } | |||
| h1 { | |||
| @@ -40,7 +41,7 @@ h1 { | |||
| } | |||
| .btn-outline-primary { | |||
| border: 2px solid #33548C !important; | |||
| border: 2px solid #86B04B !important; | |||
| border-radius: 3px; | |||
| font-size: 16px; | |||
| font-weight: 600; | |||
| @@ -49,6 +50,13 @@ h1 { | |||
| padding: 5px 32px !important; | |||
| } | |||
| .aku-cms-factory-element { | |||
| .btn-outline-primary { | |||
| background-color: $sw-color-brand-primary; | |||
| color: #fff; | |||
| } | |||
| } | |||
| input.custom-number[type=number] { | |||
| border-bottom: 2px solid $sw-color-brand-primary; | |||
| } | |||
| @@ -26,9 +26,9 @@ | |||
| {# ... add short description #} | |||
| {% block zen_page_product_detail_short_description %} | |||
| {% if page.product.translated.metaDescription and theme_config('zen-product-details-short-description') %} | |||
| {% if page.product.customFields.custom_description_small_description and theme_config('zen-product-details-short-description') %} | |||
| <div class="product-detail-short-description"> | |||
| {{ page.product.translated.metaDescription|raw }} | |||
| {{ page.product.customFields.custom_description_small_description|trans|sw_sanitize }} | |||
| </div> | |||
| {% endif %} | |||
| {% endblock %} | |||
| @@ -22,11 +22,11 @@ | |||
| {% sw_icon 'checkmark-circle' %} | |||
| <div class="spwn-customized-products--text"> | |||
| {% if customizedProductsCount == 1 %} | |||
| <p>Ihr Abnehmplan wurde erfolgreich zum Warenkorb hinzugefügt – ein guter Start!</p> | |||
| <p>Wählen Sie bis zu 2 weitere Pläne für Ihr optimales Abnehmprogramm und sparen Sie ab 150 € Bestellsumme die Versandkosten.</p> | |||
| <p>Ihr Paket wurde erfolgreich zum Warenkorb hinzugefügt – ein guter Start!</p> | |||
| <p>Wählen Sie bis zu 2 weitere Pläne für Ihr optimales Abnehmprogramm und sparen Sie ab 100 € Bestellsumme die Versandkosten.</p> | |||
| {% elseif customizedProductsCount == 2 %} | |||
| <p>Ihr zweiter Abnehmplan wurde zum Warenkorb hinzugefügt - Sie sparen die Versandkosten!</p> | |||
| <p>Wählen Sie einen weiteren Plan für Ihr optimales, mehrwöchiges Abnehmprogramm.</p> | |||
| <p>Ihr zweites Paket wurde zum Warenkorb hinzugefügt - Sie sparen die Versandkosten!</p> | |||
| <p>Wählen Sie ein weiteres Paket für Ihr optimales, mehrwöchiges Abnehmprogramm.</p> | |||
| {% endif %} | |||
| </div> | |||
| </div> | |||
| @@ -48,14 +48,30 @@ | |||
| {% if lineItem.payload is defined and lineItem.payload.customFields is defined and | |||
| lineItem.payload.customFields.spwn_discountable_box is defined and addCustomizedProducts !== 0 %} | |||
| <div class="spwn-add-customized-products"> | |||
| <a href="/Abnehmprogramme/6-Tage-Abnehmprogramme/" class="spwn-add-customized-products-detail">Fügen Sie einen weiteren Plan hinzu für ihr optimales Abnehmprogramm</a> | |||
| <a href="/6-Tage-Pakete/" class="spwn-add-customized-products-detail">Fügen Sie ein weiteres Paket hinzu für ihr optimales Abnehmprogramm</a> | |||
| {% if addCustomizedProducts == 1 %} | |||
| <a href="/Abnehmprogramme/6-Tage-Abnehmprogramme/" class="spwn-add-customized-products-detail">Fügen Sie einen weiteren Plan hinzu für ihr optimales Abnehmprogramm</a> | |||
| <a href="/6-Tage-Pakete//" class="spwn-add-customized-products-detail">Fügen Sie einen weiteres Paket hinzu für ihr optimales Abnehmprogramm</a> | |||
| {% endif %} | |||
| </div> | |||
| {% endif %} | |||
| {% endblock %} | |||
| {% block component_offcanvas_product_label %} | |||
| <div class="cart-item-details"> | |||
| {% if type == PRODUCT_LINE_ITEM_TYPE %} | |||
| <a href="{{ seoUrl('frontend.detail.page', {'productId': referencedId}) }}" | |||
| class="cart-item-label" | |||
| title="{{ label|replace({'&':'&'}) }}"> | |||
| {{ quantity }}{{ "checkout.quantityTimes"|trans|sw_sanitize }} {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </a> | |||
| {% else %} | |||
| <div class="cart-item-label"> | |||
| {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </div> | |||
| {% endif %} | |||
| </div> | |||
| {% endblock %} | |||
| {# coming from AtlProductConfigurator #} | |||
| {% block cart_item_variant_characteristics %} | |||
| <div class="cart-item-characteristics"> | |||
| @@ -0,0 +1,5 @@ | |||
| {% sw_extends '@Storefront/storefront/component/product/description.html.twig' %} | |||
| {% block component_product_description_title %} | |||
| {% endblock %} | |||
| @@ -14,18 +14,18 @@ | |||
| {% if lineItem.type == PRODUCT_LINE_ITEM_TYPE %} | |||
| <a href="{{ seoUrl('frontend.detail.page', {'productId': lineItem.referencedId}) }}" | |||
| class="cart-item-label" | |||
| title="{{ label }}" | |||
| title="{{ label|replace({'&':'&'}) }}" | |||
| {% if controllerAction is same as('confirmPage') %} | |||
| data-toggle="modal" | |||
| data-modal-class="quickview-modal" | |||
| data-url="{{ path('widgets.quickview.minimal',{ 'productId': lineItem.referencedId }) }}" | |||
| {% endif %} | |||
| > | |||
| {{ label|u.truncate(60, '...', false) }} | |||
| {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </a> | |||
| {% else %} | |||
| <div class="cart-item-label"> | |||
| {{ label|u.truncate(60, '...', false) }} | |||
| {{ label|replace({'&':'&'})|u.truncate(60, '...', false) }} | |||
| </div> | |||
| {% endif %} | |||
| {% endblock %} | |||
| @@ -12,7 +12,7 @@ | |||
| data-url="{{ path('frontend.cms.page',{ id: config('core.basicInformation.shippingPaymentInfoPage') }) }}"> | |||
| {{ "checkout.summaryShipping"|trans|sw_sanitize }} | |||
| </a> | |||
| <span>{{ "checkout.freeShipping"|trans|sw_sanitize }}</span> | |||
| <span>{{ "checkout.freeShippingEasyfit"|trans|sw_sanitize }}</span> | |||
| </dt> | |||
| {% endblock %} | |||
| @@ -1,7 +1,5 @@ | |||
| {% sw_extends '@Storefront/storefront/page/product-detail/description.html.twig' %} | |||
| {% block page_product_detail_description_title %} | |||
| <div class="h3 product-detail-description-title"> | |||
| {{ "detail.descriptionTitle"|trans|sw_sanitize }} {{ page.product.translated.name }} | |||
| </div> | |||
| {% endblock %} | |||