From 2d59c19766b496e18a93c4e424dcc8c03221c6a3 Mon Sep 17 00:00:00 2001 From: Chris Date: Mon, 29 Jan 2024 22:03:26 +0100 Subject: [PATCH 01/18] Use referenceBlock instead of referenceContainer In some circumstances where the `order_totals` block is also modified by another module, using `referenceContainer` prevents the view from rendering due to using an incorrect reference. Using a `referenceBlock` fixes this. See the original Magento layout for reference: https://github.com/magento/magento2/blob/03621bbcd75cbac4ffa8266a51aa2606980f4830/app/code/Magento/Sales/view/adminhtml/layout/sales_order_view.xml#L71 --- view/adminhtml/layout/sales_order_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/view/adminhtml/layout/sales_order_view.xml b/view/adminhtml/layout/sales_order_view.xml index e5562be..f6794a6 100644 --- a/view/adminhtml/layout/sales_order_view.xml +++ b/view/adminhtml/layout/sales_order_view.xml @@ -7,8 +7,8 @@ --> - + - + From 98072e1db154fd2b70e7b11648df70e1334a3123 Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 7 Feb 2024 13:51:51 +0100 Subject: [PATCH 02/18] [inventory]:+ Prefetching MSI inventory, as a result it doesn't have to do 2 queries per product in the feed. If the page size would be 200 products, instead of 400 queries performed, only 2 remain --- Helper/Product.php | 8 ++++ Model/Generate.php | 15 ++++++++ Service/Product/InventoryData.php | 64 ++++++++++++++++++++++--------- 3 files changed, 68 insertions(+), 19 deletions(-) diff --git a/Helper/Product.php b/Helper/Product.php index f482e62..558ea38 100644 --- a/Helper/Product.php +++ b/Helper/Product.php @@ -1313,4 +1313,12 @@ public function getParentId($product, $filters) return array_unique($parentIds); } + + /** + * @return InventoryData + */ + public function getInventoryData() + { + return $this->inventoryData; + } } diff --git a/Model/Generate.php b/Model/Generate.php index 1511b77..7a791f7 100644 --- a/Model/Generate.php +++ b/Model/Generate.php @@ -120,6 +120,8 @@ public function generateByStore( $parentRelations = $this->productHelper->getParentsFromCollection($products, $config); $parents = $this->productModel->getParents($parentRelations, $config); + $this->prefetchData($products->getColumnValues('sku'), $config); + foreach ($products as $product) { /** @var Product $product */ $parent = null; @@ -208,4 +210,17 @@ public function getDataRow($product, $parent, $config): ?array return null; } + + /** + * Prefetches data to reduce amount of queries required. + * This increases performance by a lot for environments with >1ms latency to database. + * + * @param array $skus + * @param array $config + * @return void + */ + private function prefetchData(array $skus, array $config) + { + $this->productHelper->getInventoryData()->load($skus, $config); + } } diff --git a/Service/Product/InventoryData.php b/Service/Product/InventoryData.php index 57556bf..ab52a19 100644 --- a/Service/Product/InventoryData.php +++ b/Service/Product/InventoryData.php @@ -19,6 +19,16 @@ class InventoryData */ private $resourceConnection; + /** + * @var array + */ + private $inventory; + + /** + * @var array + */ + private $reservation; + /** * InventoryData constructor. * @@ -40,8 +50,8 @@ public function __construct( */ public function getSalableQty(ProductInterface $product, int $stockId): float { - $inventoryData = $this->getInventoryData($product->getSku(), $stockId); - $reservations = $this->getReservations($product->getSku(), $stockId); + $inventoryData = $this->inventory[$stockId][$product->getSku()] ?? []; + $reservations = $this->reservation[$stockId][$product->getSku()] ?? 0; $qty = isset($inventoryData['quantity']) ? $inventoryData['quantity'] - $reservations @@ -53,26 +63,28 @@ public function getSalableQty(ProductInterface $product, int $stockId): float /** * Get Inventory Data by SKU and StockID * - * @param string $sku + * @param array $skus * @param int $stockId * - * @return mixed + * @return void */ - private function getInventoryData(string $sku, int $stockId) + private function getInventoryData(array $skus, int $stockId): void { $connection = $this->resourceConnection->getConnection(); $tableName = $this->resourceConnection->getTableName('inventory_stock_' . $stockId); if (!$connection->isTableExists($tableName)) { - return []; + return; } $select = $connection->select() ->from($tableName) - ->where('sku = ?', $sku) - ->limit(1); + ->where('sku IN (?)', $skus); - return $connection->fetchRow($select); + $inventoryData = $connection->fetchAll($select); + foreach ($inventoryData as $data) { + $this->inventory[$stockId][$data['sku']] = $data; + } } /** @@ -83,24 +95,38 @@ private function getInventoryData(string $sku, int $stockId) * * @return float */ - private function getReservations(string $sku, int $stockId): float + private function getReservations(array $skus, int $stockId): void { $connection = $this->resourceConnection->getConnection(); $tableName = $this->resourceConnection->getTableName('inventory_reservation'); if (!$connection->isTableExists($tableName)) { - return 0; + return; } $select = $connection->select() - ->from($tableName, ['quantity' => 'SUM(quantity)']) - ->where('sku = ?', $sku) + ->from($tableName, ['sku', 'quantity' => 'SUM(quantity)']) + ->where('sku IN (?)', $skus) ->where('stock_id' . ' = ?', $stockId) - ->limit(1); + ->group('sku'); - return ($reservationQty = $connection->fetchOne($select)) - ? max(0, ($reservationQty * -1)) - : 0; + $reservations = $connection->fetchAll($select); + foreach ($reservations as $reservation) { + $this->reservation[$stockId][$reservation['sku']] = $reservation['quantity']; + } + } + + /** + * Loads all stock information into memory, only requiring 2 queries to the database + * instead of the page_size * 2 + * + * @param array $skus + * @param array $config + * @return void + */ + public function load(array $skus, array $config) { + $this->getInventoryData($skus, (int)$config['inventory']['stock_id']); + $this->getReservations($skus, (int)$config['inventory']['stock_id']); } /** @@ -119,8 +145,8 @@ public function addDataToProduct(Product $product, array $config): Product return $product; } - $inventoryData = $this->getInventoryData($product->getSku(), $config['inventory']['stock_id']); - $reservations = $this->getReservations($product->getSku(), $config['inventory']['stock_id']); + $inventoryData = $this->inventory[$config['inventory']['stock_id']][$product->getSku()] ?? []; + $reservations = $this->reservation[$config['inventory']['stock_id']][$product->getSku()] ?? 0; $qty = isset($inventoryData['quantity']) ? $inventoryData['quantity'] - $reservations : 0; $isSalable = $inventoryData['is_salable'] ?? 0; From 411a28f1513853780e5e3233a51dbac048443b44 Mon Sep 17 00:00:00 2001 From: Julian Date: Wed, 7 Feb 2024 13:56:48 +0100 Subject: [PATCH 03/18] [master]:+ Changed package name --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 77ac79c..85d9706 100755 --- a/composer.json +++ b/composer.json @@ -1,10 +1,10 @@ { - "name": "magmodules/magento2-channable", + "name": "loavies/magento2-channable", "description": "Channable integration for Magento 2", "type": "magento2-module", "version": "1.16.0", "license": "BSD-2-Clause", - "homepage": "https://github.com/magmodules/magento2-channable", + "homepage": "https://github.com/loavies/magento2-channable", "require": { "magento/framework": ">=102.0.3", "ext-json": "*" From b72f158574a1e07037a0344dc4b28589f1526217 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 8 Feb 2024 11:24:56 +0100 Subject: [PATCH 04/18] [media]:+ Prefetching media data instead of per product fetching --- Helper/Product.php | 24 +++++- Model/Generate.php | 18 +++-- Service/Product/MediaData.php | 146 ++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 Service/Product/MediaData.php diff --git a/Helper/Product.php b/Helper/Product.php index 558ea38..9dced35 100644 --- a/Helper/Product.php +++ b/Helper/Product.php @@ -23,6 +23,7 @@ use Magento\Bundle\Model\ResourceModel\Selection as BundleResource; use Magmodules\Channable\Api\Log\RepositoryInterface as LogRepository; use Magmodules\Channable\Service\Product\InventoryData; +use Magmodules\Channable\Service\Product\MediaData; /** * Class Product @@ -84,6 +85,12 @@ class Product extends AbstractHelper * @var CatalogPrice */ private $commonPriceModel; + + /** + * @var MediaData + */ + private $mediaData; + /** * @var LogRepository */ @@ -128,6 +135,7 @@ public function __construct( ConfigurableResource $catalogProductTypeConfigurable, CatalogPrice $commonPriceModel, InventoryData $inventoryData, + MediaData $mediaData, LogRepository $logger ) { $this->galleryReadHandler = $galleryReadHandler; @@ -143,6 +151,7 @@ public function __construct( $this->catalogProductTypeBundle = $catalogProductTypeBundle; $this->commonPriceModel = $commonPriceModel; $this->inventoryData = $inventoryData; + $this->mediaData = $mediaData; $this->logger = $logger; parent::__construct($context); } @@ -519,8 +528,13 @@ public function getImageData($attribute, $config, $product) } } } - $this->galleryReadHandler->execute($product); + $galleryImages = $product->getMediaGallery('images'); + if (!$galleryImages) { + $this->galleryReadHandler->execute($product); + $galleryImages = $product->getMediaGallery('images'); + } + foreach ($galleryImages as $image) { if (empty($image['disabled']) || !empty($config['inc_hidden_image'])) { $images[] = $this->catalogProductMediaConfig->getMediaUrl($image['file']); @@ -1321,4 +1335,12 @@ public function getInventoryData() { return $this->inventoryData; } + + /** + * @return MediaData + */ + public function getMediaData() + { + return $this->mediaData; + } } diff --git a/Model/Generate.php b/Model/Generate.php index 7a791f7..b8897ac 100644 --- a/Model/Generate.php +++ b/Model/Generate.php @@ -7,6 +7,7 @@ namespace Magmodules\Channable\Model; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Framework\Exception\LocalizedException; use Magmodules\Channable\Model\Collection\Products as ProductsModel; use Magmodules\Channable\Model\Item as ItemModel; @@ -120,7 +121,7 @@ public function generateByStore( $parentRelations = $this->productHelper->getParentsFromCollection($products, $config); $parents = $this->productModel->getParents($parentRelations, $config); - $this->prefetchData($products->getColumnValues('sku'), $config); + $this->prefetchData($products, $parents, $parentRelations, $config); foreach ($products as $product) { /** @var Product $product */ @@ -215,12 +216,19 @@ public function getDataRow($product, $parent, $config): ?array * Prefetches data to reduce amount of queries required. * This increases performance by a lot for environments with >1ms latency to database. * - * @param array $skus + * @param ProductCollection $products + * @param ProductCollection $parents + * @param array $parentRelations * @param array $config * @return void */ - private function prefetchData(array $skus, array $config) - { - $this->productHelper->getInventoryData()->load($skus, $config); + private function prefetchData( + ProductCollection $products, + ProductCollection $parents, + array $parentRelations, + array $config + ) { + $this->productHelper->getInventoryData()->load($products->getColumnValues('sku'), $config); + $this->productHelper->getMediaData()->load($products, $parents); } } diff --git a/Service/Product/MediaData.php b/Service/Product/MediaData.php new file mode 100644 index 0000000..5e65f05 --- /dev/null +++ b/Service/Product/MediaData.php @@ -0,0 +1,146 @@ +attributeRepository = $attributeRepository; + $this->resourceModel = $resourceModel; + $this->metadata = $metadataPool->getMetadata( + ProductInterface::class + ); + } + + /** + * Execute read handler for catalog product gallery + * + * @param ProductCollection $products + * @param ProductCollection $parents + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @since 101.0.0 + */ + public function load(ProductCollection $products, ProductCollection $parents) + { + /** @var Product $product */ + $product = $products->getFirstItem(); + + if ($product->isEmpty()) { + return; + } + + $select = $this->resourceModel->createBatchBaseSelect( + (int)$product->getStoreId(), + $this->getAttribute()->getAttributeId() + ); + + $productIds = array_merge( + $products->getColumnValues('entity_id'), + $parents->getColumnValues('entity_id') + ); + + $select->where( + 'entity.' . $this->metadata->getLinkField() . ' IN (?)', + $productIds + ); + + $result = $this->resourceModel->getConnection()->fetchAll($select); + + $mediaEntriesList = []; + foreach ($result as $record) { + $mediaEntriesList[$record[$this->metadata->getLinkField()]][] = $record; + } + + $this->addMediaDataToCollection($products, $mediaEntriesList); + $this->addMediaDataToCollection($parents, $mediaEntriesList); + } + + public function addMediaDataToCollection(ProductCollection $productCollection, array $mediaEntriesList) + { + foreach ($productCollection as $product) { + $this->addMediaDataToProduct($product, $mediaEntriesList[$product->getData($this->metadata->getLinkField())] ?? []); + } + } + + /** + * Add media data to product + * + * @param Product $product + * @param array $mediaEntries + * @return void + * @since 101.0.1 + */ + public function addMediaDataToProduct(Product $product, array $mediaEntries) + { + $product->setData( + $this->getAttribute()->getAttributeCode(), + [ + 'images' => array_column($mediaEntries, null, 'value_id'), + 'values' => [] + ] + ); + } + + /** + * Get attribute + * + * @return ProductAttributeInterface + * @since 101.0.0 + */ + public function getAttribute() + { + if (!$this->attribute) { + $this->attribute = $this->attributeRepository->get('media_gallery'); + } + + return $this->attribute; + } +} From 6ff681860bf98701640804bc7397d0b64eeea107 Mon Sep 17 00:00:00 2001 From: Julian Date: Thu, 8 Feb 2024 11:25:43 +0100 Subject: [PATCH 05/18] [media]:+ Removed version from composer.json file --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 85d9706..5670590 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,6 @@ "name": "loavies/magento2-channable", "description": "Channable integration for Magento 2", "type": "magento2-module", - "version": "1.16.0", "license": "BSD-2-Clause", "homepage": "https://github.com/loavies/magento2-channable", "require": { From 170f40057457b771aa1b6a3a31d1489e74eefb75 Mon Sep 17 00:00:00 2001 From: Julian Date: Fri, 9 Feb 2024 13:42:33 +0100 Subject: [PATCH 06/18] [attribute-set]:+ Improved fetching of attribute set name --- Helper/Product.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Helper/Product.php b/Helper/Product.php index 9dced35..f2f0ef3 100644 --- a/Helper/Product.php +++ b/Helper/Product.php @@ -622,9 +622,15 @@ public function getResizedImage($product, $source, $size) */ public function getAttributeSetName($product) { + static $attributeSets = []; + try { - $attributeSetRepository = $this->attributeSet->get($product->getAttributeSetId()); - return $attributeSetRepository->getAttributeSetName(); + if (!isset($attributeSets[$product->getAttributeSetId()])) { + $attributeSetName = $this->attributeSet->get($product->getAttributeSetId())->getAttributeSetName(); + $attributeSets[$product->getAttributeSetId()] = $attributeSetName; + } + + return $attributeSets[$product->getAttributeSetId()]; } catch (\Exception $e) { $this->logger->addErrorLog('getAttributeSetName', $e->getMessage()); } From 9621b91459d4b988a172fd66892631f8b4cd53d4 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:05:32 +0100 Subject: [PATCH 07/18] Feature: save returns meta data and add to returns grid --- Api/Returns/Data/DataInterface.php | 48 ++++++++++++++ Model/Returns/DataModel.php | 64 +++++++++++++++++++ .../Returns/ResourceModel/Grid/Collection.php | 2 +- Service/Returns/ImportReturn.php | 26 +++++++- Service/Returns/ImportSimulator.php | 14 +++- etc/db_schema.xml | 4 ++ .../ui_component/channable_returns_grid.xml | 28 ++++++++ 7 files changed, 183 insertions(+), 3 deletions(-) diff --git a/Api/Returns/Data/DataInterface.php b/Api/Returns/Data/DataInterface.php index 5808f2c..43a0b9a 100755 --- a/Api/Returns/Data/DataInterface.php +++ b/Api/Returns/Data/DataInterface.php @@ -33,6 +33,10 @@ interface DataInterface extends ExtensibleDataInterface public const REASON = 'reason'; public const COMMENT = 'comment'; public const STATUS = 'status'; + public const CHANNEL_RETURN_ID = 'channel_return_id'; + public const CHANNEL_ORDER_ID = 'channel_order_id'; + public const CHANNEL_ORDER_ID_INTERNAL = 'channel_order_id_internal'; + public const PLATFORM_ORDER_ID = 'platform_order_id'; public const CREATED_AT = 'created_at'; public const UPDATED_AT = 'updated_at'; @@ -217,6 +221,50 @@ public function getStatus(): string; */ public function setStatus(string $status): self; + /** + * @return string|null + */ + public function getChannelReturnId(): ?string; + + /** + * @param string|null $channelReturnId + * @return $this + */ + public function setChannelReturnId(?string $channelReturnId): self; + + /** + * @return string|null + */ + public function getChannelOrderId(): ?string; + + /** + * @param string|null $channelOrderId + * @return $this + */ + public function setChannelOrderId(?string $channelOrderId): self; + + /** + * @return string|null + */ + public function getChannelOrderIdInternal(): ?string; + + /** + * @param string|null $channelOrderIdInternal + * @return $this + */ + public function setChannelOrderIdInternal(?string $channelOrderIdInternal): self; + + /** + * @return string|null + */ + public function getPlatformOrderId(): ?string; + + /** + * @param string|null $platformOrderId + * @return $this + */ + public function setPlatformOrderId(?string $platformOrderId): self; + /** * @return string */ diff --git a/Model/Returns/DataModel.php b/Model/Returns/DataModel.php index 00214b3..b53c437 100755 --- a/Model/Returns/DataModel.php +++ b/Model/Returns/DataModel.php @@ -392,6 +392,70 @@ public function setStatus(string $status): ChannableReturnsData return $this->setData(self::STATUS, $status); } + /** + * @inheritDoc + */ + public function getChannelReturnId(): ?string + { + return $this->getData(self::CHANNEL_RETURN_ID); + } + + /** + * @inheritDoc + */ + public function setChannelReturnId(?string $channelReturnId): ChannableReturnsData + { + return $this->setData(self::CHANNEL_RETURN_ID, $channelReturnId); + } + + /** + * @inheritDoc + */ + public function getChannelOrderId(): ?string + { + return $this->getData(self::CHANNEL_ORDER_ID); + } + + /** + * @inheritDoc + */ + public function setChannelOrderId(?string $channelOrderId): ChannableReturnsData + { + return $this->setData(self::CHANNEL_ORDER_ID, $channelOrderId); + } + + /** + * @inheritDoc + */ + public function getChannelOrderIdInternal(): ?string + { + return $this->getData(self::CHANNEL_ORDER_ID_INTERNAL); + } + + /** + * @inheritDoc + */ + public function setChannelOrderIdInternal(?string $channelOrderIdInternal): ChannableReturnsData + { + return $this->setData(self::CHANNEL_ORDER_ID_INTERNAL, $channelOrderIdInternal); + } + + /** + * @inheritDoc + */ + public function getPlatformOrderId(): ?string + { + return $this->getData(self::PLATFORM_ORDER_ID); + } + + /** + * @inheritDoc + */ + public function setPlatformOrderId(?string $platformOrderId): ChannableReturnsData + { + return $this->setData(self::PLATFORM_ORDER_ID, $platformOrderId); + } + /** * @inheritDoc */ diff --git a/Model/Returns/ResourceModel/Grid/Collection.php b/Model/Returns/ResourceModel/Grid/Collection.php index a067d63..5a82307 100755 --- a/Model/Returns/ResourceModel/Grid/Collection.php +++ b/Model/Returns/ResourceModel/Grid/Collection.php @@ -31,6 +31,6 @@ protected function _initSelect() [ 'qty_creditmemos' => new Zend_Db_Expr('count(sales_creditmemo.entity_id)'), ] - )->group('main_table.magento_order_id'); + )->group('main_table.entity_id'); } } diff --git a/Service/Returns/ImportReturn.php b/Service/Returns/ImportReturn.php index b95765a..8354dfb 100755 --- a/Service/Returns/ImportReturn.php +++ b/Service/Returns/ImportReturn.php @@ -9,6 +9,7 @@ use Exception; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Stdlib\ArrayManager; use Magmodules\Channable\Api\Returns\RepositoryInterface as ReturnsRepository; class ImportReturn @@ -22,17 +23,24 @@ class ImportReturn * @var ResourceConnection */ private $resource; + /** + * @var ArrayManager + */ + private $arrayManager; /** * @param ResourceConnection $resource * @param ReturnsRepository $returnsRepository + * @param ArrayManager $arrayManager */ public function __construct( ResourceConnection $resource, - ReturnsRepository $returnsRepository + ReturnsRepository $returnsRepository, + ArrayManager $arrayManager ) { $this->returnsRepository = $returnsRepository; $this->resource = $resource; + $this->arrayManager = $arrayManager; } /** @@ -70,6 +78,22 @@ public function execute(array $returnData, int $storeId): array $returns->setMagentoOrderId((int)$salesOrderGridData['entity_id']); } + if ($channelReturnId = $this->arrayManager->get('meta/channel_return_id', $returnData)) { + $returns->setChannelReturnId($channelReturnId); + } + + if ($channelOrderId = $this->arrayManager->get('meta/channel_order_id', $returnData)) { + $returns->setChannelOrderId($channelOrderId); + } + + if ($channelOrderIdInternal = $this->arrayManager->get('meta/channel_order_id_internal', $returnData)) { + $returns->setChannelOrderIdInternal($channelOrderIdInternal); + } + + if ($platformOrderId = $this->arrayManager->get('meta/platform_order_id', $returnData)) { + $returns->setPlatformOrderId($platformOrderId); + } + try { $returns = $this->returnsRepository->save($returns); $response['validated'] = 'true'; diff --git a/Service/Returns/ImportSimulator.php b/Service/Returns/ImportSimulator.php index 6469e57..71bf315 100755 --- a/Service/Returns/ImportSimulator.php +++ b/Service/Returns/ImportSimulator.php @@ -140,7 +140,7 @@ public function getTestData(?array $params = []): array 'channable_id' => $random, 'item' => [ 'id' => $product['id'], - 'order_id' => 9999, + 'order_id' => null, 'gtin' => $product['sku'], 'title' => $product['name'], 'quantity' => 1, @@ -164,6 +164,12 @@ public function getTestData(?array $params = []): array 'city' => 'Test', 'country_code' => 'NL', 'zip_code' => '1234 AB', + ], + 'meta' => [ + 'channel_return_id' => $random . '-some-return', + 'channel_order_id' => $random . '-some-order', + 'channel_order_id_internal' => $random . '-order-id', + 'platform_order_id' => $random, ] ]; } @@ -211,6 +217,12 @@ public function getTestDataFromTestOrder(?array $params = []): array 'city' => $address->getCity(), 'country_code' => $address->getCountryId(), 'zip_code' => $address->getPostcode() + ], + 'meta' => [ + 'channel_return_id' => $order->getIncrementId() . '-some-return', + 'channel_order_id' => $order->getIncrementId() . '-some-order', + 'channel_order_id_internal' => $order->getIncrementId() . '-order-id', + 'platform_order_id' => $order->getIncrementId() . '-platform', ] ]; } diff --git a/etc/db_schema.xml b/etc/db_schema.xml index f482027..4569e19 100755 --- a/etc/db_schema.xml +++ b/etc/db_schema.xml @@ -98,6 +98,10 @@ + + + + diff --git a/view/adminhtml/ui_component/channable_returns_grid.xml b/view/adminhtml/ui_component/channable_returns_grid.xml index 52aee26..7674fd0 100644 --- a/view/adminhtml/ui_component/channable_returns_grid.xml +++ b/view/adminhtml/ui_component/channable_returns_grid.xml @@ -143,6 +143,34 @@ ui/grid/cells/html + + + text + + false + + + + + text + + false + + + + + text + + false + + + + + text + + false + + text From 1c652865fbe3a98d9a4796060ba7330a8504adad Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:09:46 +0100 Subject: [PATCH 08/18] Feature: added option to set zero tax on business orders --- Api/Config/System/OrderInterface.php | 10 ++++++++++ Model/Config/System/OrderRepository.php | 8 ++++++++ Service/Order/ImportSimulator.php | 2 ++ Service/Order/Items/Add.php | 19 ++++++++++++++++--- Service/Order/Quote/AddressHandler.php | 3 +++ Service/Order/Shipping/CalculatePrice.php | 4 ++++ etc/adminhtml/system/order.xml | 6 ++++++ 7 files changed, 49 insertions(+), 3 deletions(-) diff --git a/Api/Config/System/OrderInterface.php b/Api/Config/System/OrderInterface.php index dd821e9..0979738 100755 --- a/Api/Config/System/OrderInterface.php +++ b/Api/Config/System/OrderInterface.php @@ -42,6 +42,7 @@ interface OrderInterface extends ReturnsInterface const XML_PATH_LVB_SKIP_STOCK = 'magmodules_channable_marketplace/order/lvb_stock'; const XML_PATH_LVB_AUTO_SHIP = 'magmodules_channable_marketplace/order/lvb_ship'; const XML_PATH_DEDUCT_FPT = 'magmodules_channable_marketplace/order/deduct_fpt'; + const XML_PATH_BUSINESS_ORDER = 'magmodules_channable_marketplace/order/business_order'; const XML_PATH_TRANSACTION_FEE = 'magmodules_channable_marketplace/order/transaction_fee'; const XML_PATH_LOG = 'magmodules_channable_marketplace/order/log'; const XML_PATH_CARRIER_TITLE = 'carriers/channable/title'; @@ -285,6 +286,15 @@ public function autoShipLvbOrders(int $storeId = null): bool; */ public function deductFptTax(int $storeId = null): bool; + /** + * Check if business orders enabled. + * + * @param null|int $storeId + * + * @return bool + */ + public function isBusinessOrderEnabled(int $storeId = null): bool; + /** * Check if need to add transaction fee * diff --git a/Model/Config/System/OrderRepository.php b/Model/Config/System/OrderRepository.php index 57c5333..1b84ae0 100755 --- a/Model/Config/System/OrderRepository.php +++ b/Model/Config/System/OrderRepository.php @@ -128,6 +128,14 @@ public function deductFptTax(int $storeId = null): bool return $this->isSetFlag(self::XML_PATH_DEDUCT_FPT, $storeId); } + /** + * @inheritDoc + */ + public function isBusinessOrderEnabled(int $storeId = null): bool + { + return $this->isSetFlag(self::XML_PATH_BUSINESS_ORDER, $storeId); + } + /** * @inheritDoc */ diff --git a/Service/Order/ImportSimulator.php b/Service/Order/ImportSimulator.php index e6bcb4f..cea2389 100755 --- a/Service/Order/ImportSimulator.php +++ b/Service/Order/ImportSimulator.php @@ -167,6 +167,7 @@ public function getTestData(array $params): array "middle_name" => "From", "last_name" => "Channable", "company" => "TestCompany", + "business_order" => false, ], "billing" => [ "first_name" => "Test", @@ -185,6 +186,7 @@ public function getTestData(array $params): array "country_code" => $country, "state" => $country == "US" ? "Texas" : "", "state_code" => $country == "US" ? "TX" : "", + "vat_number" => "NL123456790B01" ], "shipping" => [ "first_name" => "Test", diff --git a/Service/Order/Items/Add.php b/Service/Order/Items/Add.php index 8171d2a..5661809 100644 --- a/Service/Order/Items/Add.php +++ b/Service/Order/Items/Add.php @@ -117,12 +117,17 @@ public function execute(Quote $quote, array $data, StoreInterface $store, bool $ $exceptionMsg = self::EMPTY_ITEMS_EXCEPTION; throw new CouldNotImportOrder(__($exceptionMsg)); } + $isBusinessOrder = $this->configProvider->isBusinessOrderEnabled((int)$store->getId()) && + isset($data['customer']['business_order']) && ($data['customer']['business_order'] == true); try { foreach ($data['products'] as $item) { $product = $this->getProductById((int)$item['id'], (int)$store->getStoreId()); - $price = $this->getProductPrice($item, $product, $store, $quote); + $price = $this->getProductPrice($item, $product, $store, $quote, $isBusinessOrder); $product = $this->setProductData($product, $price, $store, $lvbOrder); + if ($isBusinessOrder) { + $product->setTaxClassId(0); + } switch ($product->getTypeId()) { case 'grouped': @@ -200,8 +205,16 @@ private function getProductById(int $productId, int $storeId): ProductInterface * * @return float */ - private function getProductPrice(array $item, ProductInterface $product, StoreInterface $store, Quote $quote): float - { + private function getProductPrice( + array $item, + ProductInterface $product, + StoreInterface $store, + Quote $quote, + bool $isBusinessOrder + ): float { + if ($isBusinessOrder) { + return (float)$item['price']; + } $price = (float)$item['price'] - $this->getProductWeeTax($product, $quote); if (!$this->configProvider->getNeedsTaxCalulcation('price', (int)$store->getId())) { $request = $this->taxCalculation->getRateRequest( diff --git a/Service/Order/Quote/AddressHandler.php b/Service/Order/Quote/AddressHandler.php index e86f0fe..b05508e 100644 --- a/Service/Order/Quote/AddressHandler.php +++ b/Service/Order/Quote/AddressHandler.php @@ -138,6 +138,9 @@ public function getAddressData(string $type, array $orderData, Quote $quote): ar 'vat_id' => !empty($address['vat_id']) ? $address['vat_id'] : null, 'email' => $email ]; + if (isset($address['vat_number']) && $this->configProvider->isBusinessOrderEnabled()) { + $addressData['vat_id'] = $address['vat_number']; + } if ($this->configProvider->createCustomerOnImport((int)$storeId)) { $this->saveAddress($addressData, $customerId, $type); diff --git a/Service/Order/Shipping/CalculatePrice.php b/Service/Order/Shipping/CalculatePrice.php index e009f89..5128ca7 100644 --- a/Service/Order/Shipping/CalculatePrice.php +++ b/Service/Order/Shipping/CalculatePrice.php @@ -54,6 +54,10 @@ public function execute(Quote $quote, array $orderData, StoreInterface $store): { $taxCalculation = $this->configProvider->getNeedsTaxCalulcation('shipping', (int)$store->getId()); $shippingPriceCal = (float) $orderData['price']['shipping']; + $isBusinessOrder = isset($data['customer']['business_order']) && ($data['customer']['business_order'] == true); + if ($this->configProvider->isBusinessOrderEnabled((int)$store->getId()) && $isBusinessOrder) { + return $shippingPriceCal; + } if (empty($taxCalculation)) { $shippingAddress = $quote->getShippingAddress(); diff --git a/etc/adminhtml/system/order.xml b/etc/adminhtml/system/order.xml index 89c81a9..16773c3 100755 --- a/etc/adminhtml/system/order.xml +++ b/etc/adminhtml/system/order.xml @@ -197,6 +197,12 @@ magmodules_channable_marketplace/order/deduct_fpt + + + Magento\Config\Model\Config\Source\Yesno + magmodules_channable_marketplace/order/business_order + + Magmodules\Channable\Block\Adminhtml\Design\Heading From c2aeddce8c52562a0eef2346e39c54b47d2e5a7b Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:11:02 +0100 Subject: [PATCH 09/18] Added tier prices to output if selected in extra values --- Model/Collection/Products.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Model/Collection/Products.php b/Model/Collection/Products.php index d86c880..e2d56f7 100755 --- a/Model/Collection/Products.php +++ b/Model/Collection/Products.php @@ -116,6 +116,10 @@ public function getCollection($config, $page, $productIds) ->addUrlRewrite() ->setOrder('product_website.product_id', 'ASC'); + if (in_array('tier_price', $attributes)) { + $collection->addTierPriceData(); + } + if (!empty($filters['visibility'])) { $collection->addAttributeToFilter('visibility', ['in' => $filters['visibility']]); } From 342e18bb148915eec53a4edb12702bfaf574f1a6 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:17:29 +0100 Subject: [PATCH 10/18] Also convert shipping costs if needed --- Service/Order/Shipping/CalculatePrice.php | 41 ++++++++++++++++------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/Service/Order/Shipping/CalculatePrice.php b/Service/Order/Shipping/CalculatePrice.php index 5128ca7..27ea76d 100644 --- a/Service/Order/Shipping/CalculatePrice.php +++ b/Service/Order/Shipping/CalculatePrice.php @@ -9,8 +9,10 @@ use Magento\Quote\Model\Quote; use Magento\Store\Api\Data\StoreInterface; -use Magento\Tax\Model\Calculation as TaxCalculationn; +use Magento\Tax\Model\Calculation as TaxCalculation; use Magmodules\Channable\Api\Config\RepositoryInterface as ConfigProvider; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Framework\Pricing\PriceCurrencyInterface; /** * Get shipping price for quote @@ -22,23 +24,36 @@ class CalculatePrice * @var ConfigProvider */ private $configProvider; - /** - * @var TaxCalculationn + * @var TaxCalculation */ private $taxCalculation; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** + * @var PriceCurrencyInterface + */ + private $priceManager; /** * CalculatePrice constructor. * @param ConfigProvider $configProvider - * @param TaxCalculationn $taxCalculation + * @param TaxCalculation $taxCalculation + * @param StoreManagerInterface $storeManager + * @param PriceCurrencyInterface $priceManager */ public function __construct( ConfigProvider $configProvider, - TaxCalculationn $taxCalculation + TaxCalculation $taxCalculation, + StoreManagerInterface $storeManager, + PriceCurrencyInterface $priceManager ) { $this->configProvider = $configProvider; $this->taxCalculation = $taxCalculation; + $this->storeManager = $storeManager; + $this->priceManager = $priceManager; } /** @@ -53,10 +68,12 @@ public function __construct( public function execute(Quote $quote, array $orderData, StoreInterface $store): float { $taxCalculation = $this->configProvider->getNeedsTaxCalulcation('shipping', (int)$store->getId()); - $shippingPriceCal = (float) $orderData['price']['shipping']; - $isBusinessOrder = isset($data['customer']['business_order']) && ($data['customer']['business_order'] == true); - if ($this->configProvider->isBusinessOrderEnabled((int)$store->getId()) && $isBusinessOrder) { - return $shippingPriceCal; + + $amount = (float)$orderData['price']['shipping']; + $baseCurrency = $this->storeManager->getStore($quote->getStoreId())->getBaseCurrencyCode(); + if ($baseCurrency != $orderData['price']['currency']) { + $rate = $this->priceManager->convert($amount, $quote->getStoreId()) / $amount; + $amount = $amount / $rate; } if (empty($taxCalculation)) { @@ -65,9 +82,9 @@ public function execute(Quote $quote, array $orderData, StoreInterface $store): $taxRateId = $this->configProvider->getTaxClassShipping((int)$store->getId()); $request = $this->taxCalculation->getRateRequest($shippingAddress, $billingAddress, null, $store); $percent = $this->taxCalculation->getRate($request->setData('product_tax_class_id', $taxRateId)); - $shippingPriceCal = ($orderData['price']['shipping'] / (100 + $percent) * 100); + $amount = ($amount / (100 + $percent) * 100); } - return $shippingPriceCal; + return $amount; } -} +} \ No newline at end of file From 88e2b89b1e1b475f59ad1c3561a1fc8ac7141f88 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:18:31 +0100 Subject: [PATCH 11/18] Add "Not Visible Individually" products to feed when forced --- Helper/Product.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Helper/Product.php b/Helper/Product.php index f482e62..a170863 100644 --- a/Helper/Product.php +++ b/Helper/Product.php @@ -254,6 +254,11 @@ public function validateProduct($product, $parent, $config) } } + $visibilityFilter = $config['filters']['visibility'] ?? []; + if (!empty($visibilityFilter) && in_array($product->getVisibility(), $visibilityFilter)) { + return true; + } + if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE) { if (empty($parent)) { return false; From 0e022e4cff6f4e5f824f41996cea5bebb000d9da Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:19:24 +0100 Subject: [PATCH 12/18] Move returns config menu option to bottom --- etc/adminhtml/system/returns.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/adminhtml/system/returns.xml b/etc/adminhtml/system/returns.xml index c85ec3e..3115838 100755 --- a/etc/adminhtml/system/returns.xml +++ b/etc/adminhtml/system/returns.xml @@ -7,7 +7,7 @@ --> -
+
channable Magmodules_Channable::config_returns From 9afe0b50e042979792f2c8d705adeab361c6e3fd Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:47:24 +0100 Subject: [PATCH 13/18] Revert "[master]:+ Changed package name" This reverts commit 411a28f1513853780e5e3233a51dbac048443b44. --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 5670590..ebb7e7a 100755 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { - "name": "loavies/magento2-channable", + "name": "magmodules/magento2-channable", "description": "Channable integration for Magento 2", "type": "magento2-module", "license": "BSD-2-Clause", - "homepage": "https://github.com/loavies/magento2-channable", + "homepage": "https://github.com/magmodules/magento2-channable", "require": { "magento/framework": ">=102.0.3", "ext-json": "*" From 788ddfed15d273946be3c384c2cc49506797b936 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:50:11 +0100 Subject: [PATCH 14/18] Do not prefetch inventory data if option is disabled in config --- Service/Product/InventoryData.php | 36 ++++++++----------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/Service/Product/InventoryData.php b/Service/Product/InventoryData.php index ab52a19..a067ca9 100644 --- a/Service/Product/InventoryData.php +++ b/Service/Product/InventoryData.php @@ -40,26 +40,6 @@ public function __construct( $this->resourceConnection = $resourceConnection; } - /** - * Get Salable QTY for a product by StockID - * - * @param ProductInterface $product - * @param int $stockId - * - * @return float|int|mixed - */ - public function getSalableQty(ProductInterface $product, int $stockId): float - { - $inventoryData = $this->inventory[$stockId][$product->getSku()] ?? []; - $reservations = $this->reservation[$stockId][$product->getSku()] ?? 0; - - $qty = isset($inventoryData['quantity']) - ? $inventoryData['quantity'] - $reservations - : 0; - - return !empty($inventoryData['is_salable']) ? $qty : 0; - } - /** * Get Inventory Data by SKU and StockID * @@ -90,10 +70,10 @@ private function getInventoryData(array $skus, int $stockId): void /** * Returns number of reservations by SKU & StockId * - * @param string $sku + * @param array $skus * @param int $stockId * - * @return float + * @return void */ private function getReservations(array $skus, int $stockId): void { @@ -117,16 +97,18 @@ private function getReservations(array $skus, int $stockId): void } /** - * Loads all stock information into memory, only requiring 2 queries to the database - * instead of the page_size * 2 + * Loads all stock information into memory * * @param array $skus * @param array $config * @return void */ - public function load(array $skus, array $config) { - $this->getInventoryData($skus, (int)$config['inventory']['stock_id']); - $this->getReservations($skus, (int)$config['inventory']['stock_id']); + public function load(array $skus, array $config): void + { + if (isset($config['inventory']['stock_id'])) { + $this->getInventoryData($skus, (int)$config['inventory']['stock_id']); + $this->getReservations($skus, (int)$config['inventory']['stock_id']); + } } /** From b282f3ce554cc18d9df5c6de5147190360e0886e Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:51:11 +0100 Subject: [PATCH 15/18] Fix TypeError when parent relations are skipped --- Model/Collection/Products.php | 72 ++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/Model/Collection/Products.php b/Model/Collection/Products.php index d86c880..d7314a1 100755 --- a/Model/Collection/Products.php +++ b/Model/Collection/Products.php @@ -7,6 +7,7 @@ namespace Magmodules\Channable\Model\Collection; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as ProductAttributeCollectionFactory; use Magento\Eav\Model\Config as EavConfig; use Magento\Catalog\Model\Indexer\Product\Flat\StateFactory; @@ -27,6 +28,10 @@ class Products * @var ProductCollectionFactory */ private $productCollectionFactory; + /** + * @var ProductCollection + */ + private $productCollection; /** * @var ProductAttributeCollectionFactory */ @@ -70,6 +75,7 @@ class Products */ public function __construct( ProductCollectionFactory $productCollectionFactory, + ProductCollection $productCollection, ProductAttributeCollectionFactory $productAttributeCollectionFactory, EavConfig $eavConfig, StockHelper $stockHelper, @@ -79,6 +85,7 @@ public function __construct( ResourceConnection $resource ) { $this->productCollectionFactory = $productCollectionFactory; + $this->productCollection = $productCollection; $this->productAttributeCollectionFactory = $productAttributeCollectionFactory; $this->eavConfig = $eavConfig; $this->productFlatState = $productFlatState; @@ -373,49 +380,52 @@ public function joinPriceIndexLeft($collection, $websiteId) * @param $parentRelations * @param $config * - * @return \Magento\Catalog\Model\ResourceModel\Product\Collection + * @return ProductCollection * @throws \Magento\Framework\Exception\LocalizedException */ - public function getParents($parentRelations, $config) + public function getParents($parentRelations, $config): ProductCollection { - if (!empty($parentRelations)) { - $filters = $config['filters']; + if (empty($parentRelations)) { + return $this->productCollection; //return empty product collection + } - if (!$config['flat']) { - $productFlatState = $this->productFlatState->create(['isAvailable' => false]); - } else { - $productFlatState = $this->productFlatState->create(['isAvailable' => true]); - } + $filters = $config['filters']; - $entityField = $this->generalHelper->getLinkField(); - $attributes = $this->getAttributes($config['attributes']); + if (!$config['flat']) { + $productFlatState = $this->productFlatState->create(['isAvailable' => false]); + } else { + $productFlatState = $this->productFlatState->create(['isAvailable' => true]); + } - $collection = $this->productCollectionFactory - ->create(['catalogProductFlatState' => $productFlatState]) - ->addStoreFilter($config['store_id']) - ->addAttributeToFilter($entityField, ['in' => array_values($parentRelations)]) - ->addAttributeToSelect($attributes) - ->addUrlRewrite() - ->setRowIdFieldName($entityField); + $entityField = $this->generalHelper->getLinkField(); + $attributes = $this->getAttributes($config['attributes']); - if (!empty($filters['category_ids'])) { - if (!empty($filters['category_type'])) { - $collection->addCategoriesFilter([$filters['category_type'] => $filters['category_ids']]); - } - } + $collection = $this->productCollectionFactory + ->create(['catalogProductFlatState' => $productFlatState]) + ->addStoreFilter($config['store_id']) + ->addAttributeToFilter($entityField, ['in' => array_values($parentRelations)]) + ->addAttributeToSelect($attributes) + ->addUrlRewrite() + ->setRowIdFieldName($entityField); - if (!empty($filters['visibility'])) { - $collection->addAttributeToFilter('visibility', ['in' => $filters['visibility']]); + if (!empty($filters['category_ids'])) { + if (!empty($filters['category_type'])) { + $collection->addCategoriesFilter([$filters['category_type'] => $filters['category_ids']]); } + } - if (!empty($config['inventory']['attributes'])) { - $this->joinCatalogInventoryLeft($collection, $config); - } + if (!empty($filters['visibility'])) { + $collection->addAttributeToFilter('visibility', ['in' => $filters['visibility']]); + } - $this->addFilters($filters, $collection, 'parent'); - $this->joinPriceIndexLeft($collection, $config['website_id']); - return $collection->load(); + if (!empty($config['inventory']['attributes'])) { + $this->joinCatalogInventoryLeft($collection, $config); } + + $this->addFilters($filters, $collection, 'parent'); + $this->joinPriceIndexLeft($collection, $config['website_id']); + return $collection->load(); + } /** From 2b63cfea7bb576490cf9dd99dfe14cd3f79bdc43 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:54:10 +0100 Subject: [PATCH 16/18] Added category id to category output --- Helper/Source.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Helper/Source.php b/Helper/Source.php index 3108233..a420610 100755 --- a/Helper/Source.php +++ b/Helper/Source.php @@ -844,7 +844,11 @@ public function getCategoryData($product, $parent, $categories) if (!empty($categories[$catId])) { $category = $categories[$catId]; if (!empty($category['path'])) { - $path[] = ['level' => $category['level'], 'path' => implode(' > ', $category['path'])]; + $path[] = [ + 'level' => $category['level'], + 'id' => $catId, + 'path' => implode(' > ', $category['path']) + ]; } } } From 114067b3c747bfeef32cd68bd073840aac6ab687 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Mon, 26 Feb 2024 13:55:21 +0100 Subject: [PATCH 17/18] Version bump --- composer.json | 1 + etc/config.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index ebb7e7a..3325fe7 100755 --- a/composer.json +++ b/composer.json @@ -2,6 +2,7 @@ "name": "magmodules/magento2-channable", "description": "Channable integration for Magento 2", "type": "magento2-module", + "version": "1.17.0", "license": "BSD-2-Clause", "homepage": "https://github.com/magmodules/magento2-channable", "require": { diff --git a/etc/config.xml b/etc/config.xml index 68f38b1..bdf7d04 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -12,7 +12,7 @@ 0 250 - v1.16.0 + v1.17.0 name From 4c4ae9306563b602a500291224e4df8e5394cb32 Mon Sep 17 00:00:00 2001 From: Marvin Besselsen Date: Tue, 27 Feb 2024 13:55:56 +0100 Subject: [PATCH 18/18] Improved Return block on Credit-memo page --- .../order/creditmemo/view/returns.phtml | 61 +++++++++---------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/view/adminhtml/templates/order/creditmemo/view/returns.phtml b/view/adminhtml/templates/order/creditmemo/view/returns.phtml index 4b2b380..b5e88e4 100644 --- a/view/adminhtml/templates/order/creditmemo/view/returns.phtml +++ b/view/adminhtml/templates/order/creditmemo/view/returns.phtml @@ -1,43 +1,40 @@ autoUpdateReturnsOnCreditmemo(); ?> showOnCreditmemoCreation() && $returns = $block->checkForReturns()): ?>
-
- escapeHtml(__('Open Channable Returns')) ?> - - - These are auto matched ... +
+ + escapeHtml(__('Available Channable Returns')) ?> + + +

+ escapeHtml(__($autoAccTxt)) ?> +

+ +
+
+ $return): ?> + + + + Automatched Items
- -
-
- $return): ?> - - - - [ auto match ] - - - -
+ + +