From 5f3156174c75fc197ce0435ce115d33846b60483 Mon Sep 17 00:00:00 2001 From: EugeneShab Date: Wed, 27 Dec 2023 08:48:09 +0200 Subject: [PATCH] feat: Add subscription functionality on grouped products. Refs: #207 --- Block/Product/Grouped.php | 101 ++++++++++++++++++ Model/Product/Type/Grouped.php | 85 +++++++++++++++ ViewModel/Grouped.php | 31 ++++++ etc/frontend/di.xml | 2 + .../catalog_product_view_type_grouped.xml | 12 +++ .../product/grouped-subscription.phtml | 20 ++++ .../templates/product/view/type/grouped.phtml | 98 +++++++++++++++++ .../js/view/product/grouped-subscription.js | 67 ++++++++++++ .../product/grouped-subscription.html | 55 ++++++++++ 9 files changed, 471 insertions(+) create mode 100644 Block/Product/Grouped.php create mode 100644 Model/Product/Type/Grouped.php create mode 100644 ViewModel/Grouped.php create mode 100644 view/frontend/layout/catalog_product_view_type_grouped.xml create mode 100644 view/frontend/templates/product/grouped-subscription.phtml create mode 100644 view/frontend/templates/product/view/type/grouped.phtml create mode 100644 view/frontend/web/js/view/product/grouped-subscription.js create mode 100644 view/frontend/web/template/product/grouped-subscription.html diff --git a/Block/Product/Grouped.php b/Block/Product/Grouped.php new file mode 100644 index 00000000..cf52ee8b --- /dev/null +++ b/Block/Product/Grouped.php @@ -0,0 +1,101 @@ +helper = $helper; + parent::__construct( + $context, + $subscriptionDiscountConfig, + $platformProductManager, + $taxCalculation, + $priceConfigProvider, + $priceCurrency, + $productHelper, + $cart, + $quoteItemHelper, + $logger, + $data + ); + } + + /** + * @return void + * @throws NoSuchEntityException + */ + protected function initJsLayout(): void + { + try { + $platformProduct = $this->getPlatformProduct()->toArray(); + } catch (InvalidArgumentException|HttpException $e) { + $this->logger->debug('Could not load product from Subscribe Pro platform.'); + $this->logger->info($e->getMessage()); + $platformProduct = []; + } + + $data = [ + 'components' => [ + 'subscription-container-' . $this->getProduct()->getId() => [ + 'component' => 'Swarming_SubscribePro/js/view/product/grouped-subscription', + 'config' => [ + 'oneTimePurchaseOption' => ProductInterfaceAlias::SO_ONETIME_PURCHASE, + 'subscriptionOption' => ProductInterfaceAlias::SO_SUBSCRIPTION, + 'subscriptionOnlyMode' => ProductInterfaceAlias::SOM_SUBSCRIPTION_ONLY, + 'subscriptionAndOneTimePurchaseMode' => ProductInterfaceAlias::SOM_SUBSCRIPTION_AND_ONETIME_PURCHASE, + 'product' => $platformProduct, + 'product_id' => $this->getProduct()->getId(), + 'priceConfig' => $this->priceConfigProvider->getConfig(), + 'template' => 'Swarming_SubscribePro/product/grouped-subscription', + 'priceBoxSelector' => '.price-box', + 'messages' => [ + 'component' => 'Magento_Ui/js/view/messages', + 'displayArea' => 'messages', + ], + ] + ] + ] + ]; + + $jsLayout = array_replace_recursive($this->jsLayout, $data); + if ($this->isPriceHidden()) { + $class = 'Swarming_SubscribePro/js/view/product/subscription-msrp'; + $jsLayout['components']['subscription-container-' . $this->getProduct()->getId()]['component'] = $class; + $jsLayout['components']['subscription-container-' . $this->getProduct()->getId()]['config']['msrpPrice'] = $this->getMsrpPrice(); + } + + $this->jsLayout = $jsLayout; + } + + /** + * @param $item + * @return bool + */ + public function isSubscriptionEnabled($item): bool + { + return $this->helper->isSubscriptionEnabled($item); + } +} diff --git a/Model/Product/Type/Grouped.php b/Model/Product/Type/Grouped.php new file mode 100644 index 00000000..eaf744be --- /dev/null +++ b/Model/Product/Type/Grouped.php @@ -0,0 +1,85 @@ +_isStrictProcessMode($processMode); + $subscriptionConfig = $buyRequest->getData('subscription_option'); + $productsInfo = $this->getProductInfo($buyRequest, $product, $isStrictProcessMode); + if (is_string($productsInfo)) { + return $productsInfo; + } + $associatedProducts = !$isStrictProcessMode || !empty($productsInfo) + ? $this->getAssociatedProducts($product) + : false; + + foreach ($associatedProducts as $subProduct) { + $qty = $productsInfo[$subProduct->getId()]; + if (!is_numeric($qty) || empty($qty)) { + continue; + } + + $_result = $subProduct->getTypeInstance()->_prepareProduct($buyRequest, $subProduct, $processMode); + + if (is_string($_result)) { + return $_result; + } elseif (!isset($_result[0])) { + return __('Cannot process the item.')->render(); + } + + if ($isStrictProcessMode) { + $subscription = []; + if (isset($subscriptionConfig[$subProduct->getId()])) { + $subscription = $subscriptionConfig[$subProduct->getId()]; + } + $_result[0]->setCartQty($qty); + $_result[0]->addCustomOption('product_type', self::TYPE_CODE, $product); + $_result[0]->addCustomOption( + 'info_buyRequest', + $this->serializer->serialize( + [ + 'super_product_config' => [ + 'product_type' => self::TYPE_CODE, + 'product_id' => $product->getId(), + ], + 'subscription_option' => $subscription + ] + ) + ); + $products[] = $_result[0]; + } else { + $associatedProductsInfo[] = [$subProduct->getId() => $qty]; + $product->addCustomOption('associated_product_' . $subProduct->getId(), $qty); + } + } + + if (!$isStrictProcessMode || count($associatedProductsInfo)) { + $product->addCustomOption('product_type', self::TYPE_CODE, $product); + $product->addCustomOption('info_buyRequest', $this->serializer->serialize($buyRequest->getData())); + + $products[] = $product; + } + + if (count($products)) { + return $products; + } + + return __('Please specify the quantity of product(s).')->render(); + } +} diff --git a/ViewModel/Grouped.php b/ViewModel/Grouped.php new file mode 100644 index 00000000..e4e80df6 --- /dev/null +++ b/ViewModel/Grouped.php @@ -0,0 +1,31 @@ +helper = $helper; + } + + /** + * @param $item + * @return bool + */ + public function isSubscriptionEnabled($item): bool + { + return $this->helper->isSubscriptionEnabled($item); + } +} diff --git a/etc/frontend/di.xml b/etc/frontend/di.xml index 87da7091..05e8d91d 100644 --- a/etc/frontend/di.xml +++ b/etc/frontend/di.xml @@ -36,6 +36,8 @@ + + diff --git a/view/frontend/layout/catalog_product_view_type_grouped.xml b/view/frontend/layout/catalog_product_view_type_grouped.xml new file mode 100644 index 00000000..89b25048 --- /dev/null +++ b/view/frontend/layout/catalog_product_view_type_grouped.xml @@ -0,0 +1,12 @@ + + + + + + Swarming\SubscribePro\ViewModel\Grouped + + + + + + diff --git a/view/frontend/templates/product/grouped-subscription.phtml b/view/frontend/templates/product/grouped-subscription.phtml new file mode 100644 index 00000000..26841453 --- /dev/null +++ b/view/frontend/templates/product/grouped-subscription.phtml @@ -0,0 +1,20 @@ + + +
+ +
+ + + + diff --git a/view/frontend/templates/product/view/type/grouped.phtml b/view/frontend/templates/product/view/type/grouped.phtml new file mode 100644 index 00000000..2b46396e --- /dev/null +++ b/view/frontend/templates/product/view/type/grouped.phtml @@ -0,0 +1,98 @@ + +getData('view_model'); ?> +setPreconfiguredValue(); ?> +getProduct(); ?> +getAssociatedProducts($_product); ?> + 0; ?> + +
+ + + + + + isSaleable()) : ?> + + + + + + + + + + + + isSaleable()) : ?> + + + + getCanShowProductPrice($_product) + && $block->getCanShowProductPrice($_item) + && trim($block->getProductPriceHtml( + $_item, + \Magento\Catalog\Pricing\Price\TierPrice::PRICE_CODE + ))) : ?> + + + + + + + + + + + + + +
escapeHtml(__('Grouped product items')) ?>
escapeHtml(__('Product Name')) ?>escapeHtml(__('Qty')) ?>
+ escapeHtml($_item->getName()) ?> + getCanShowProductPrice($_product)) : ?> + getCanShowProductPrice($_item)) : ?> + getProductPrice($_item) ?> + + + isSubscriptionEnabled($_item)): ?> +
+ getChildBlock('product_info_subscription_options_grouped')->setProduct($_item)->toHtml()?> +
+ +
+ isSaleable()) : ?> +
+ +
+ +
+ escapeHtml(__('Out of stock')) ?> +
+ +
+ getProductPriceHtml( + $_item, + \Magento\Catalog\Pricing\Price\TierPrice::PRICE_CODE + ) ?> +
+ escapeHtml(__('No options of this product are available.')) ?> +
+
+
diff --git a/view/frontend/web/js/view/product/grouped-subscription.js b/view/frontend/web/js/view/product/grouped-subscription.js new file mode 100644 index 00000000..a56a4d3e --- /dev/null +++ b/view/frontend/web/js/view/product/grouped-subscription.js @@ -0,0 +1,67 @@ +define([ + 'Swarming_SubscribePro/js/view/product/subscription', + 'ko', + 'jquery', + 'Swarming_SubscribePro/js/model/product/price' +], function (Component, ko, $, productPriceModel) { + 'use strict'; + + return Component.extend({ + defaults: { + template: 'Swarming_SubscribePro/product/grouped-subscription', + oneTimePurchasePriceTemplate: 'Swarming_SubscribePro/product/price/default/one-time-purchase', + subscriptionPriceTemplate: 'Swarming_SubscribePro/product/price/default/subscription', + priceBoxSelector: '[data-role=priceBox]', + productPrice: {}, + }, + initialize: function () { + this._super(); + this.priceBoxElement = this.getPriceBoxElement(); + this.priceBoxElement.on('reloadPrice', this.onPriceChange.bind(this)); + this.initProductPrice(); + this.priceBoxElement.trigger('reloadPrice'); + }, + + initProductPrice: function () { + this.productPrice = productPriceModel.create(this.product, this.priceConfig); + }, + + onPriceChange: function () { + var priceBox = this.priceBoxElement.data('mage-priceBox'); + if (!priceBox || !priceBox.cache || !priceBox.cache.displayPrices) { + return; + } + + this.syncProductPrice(priceBox.cache.displayPrices); + }, + + syncProductPrice: function (prices) { + var frontendFinalPrice, frontendPrice; + var code = this.getFrontendPriceCode(); + if (prices[code]) { + frontendFinalPrice = frontendPrice = prices[code].amount; + } + if (prices.oldPrice) { + frontendPrice = prices.oldPrice.amount; + } + this.productPrice.setFrontendPrice(frontendFinalPrice); + this.productPrice.hasSpecialPrice(frontendPrice != frontendFinalPrice); + }, + + getPriceBoxElement: function () { + var priceBoxElement = _.find($(this.priceBoxSelector + ' [data-product-id="' + this.product_id + '"]'), function(el) { + return el && $(el).data('mage-priceBox'); + }); + return $(priceBoxElement); + }, + + getFrontendPriceCode: function () { + var code = 'finalPrice'; + if (this.priceConfig.displayPriceExcludingTax && this.priceConfig.priceIncludesTax) { + code = 'basePrice'; + } + + return code; + } + }); +}); diff --git a/view/frontend/web/template/product/grouped-subscription.html b/view/frontend/web/template/product/grouped-subscription.html new file mode 100644 index 00000000..bf77e38f --- /dev/null +++ b/view/frontend/web/template/product/grouped-subscription.html @@ -0,0 +1,55 @@ + +
+ + + + + +
+ + +
+
+ + +
+ + +
+ +
+ +
+
+
+ + + + +