diff --git a/Helper/Product.php b/Helper/Product.php index 290d1a7..a171988 100644 --- a/Helper/Product.php +++ b/Helper/Product.php @@ -1,6 +1,6 @@ galleryReadHandler = $galleryReadHandler; $this->catalogProductMediaConfig = $catalogProductMediaConfig; - $this->catalogHelper = $catalogHelper; $this->productImageHelper = $productImageHelper; - $this->ruleFactory = $ruleFactory; $this->eavConfig = $eavConfig; $this->filter = $filter; $this->attributeSet = $attributeSet; $this->catalogProductTypeConfigurable = $catalogProductTypeConfigurable; $this->catalogProductTypeGrouped = $catalogProductTypeGrouped; $this->catalogProductTypeBundle = $catalogProductTypeBundle; - $this->commonPriceModel = $commonPriceModel; $this->inventoryData = $inventoryData; $this->mediaData = $mediaData; + $this->priceData = $priceData; $this->logger = $logger; parent::__construct($context); } @@ -217,7 +180,8 @@ public function getDataRow($product, $parent, $config) $value = $this->getCondition( $attribute['condition'], $productData, - $attribute + $attribute, + $value ); } @@ -376,7 +340,7 @@ public function getAttributeValue($type, $attribute, $config, $product, $simple) $value = $product->getCategoryIds(); break; case 'tier_price': - $value = $this->processTierPrice($product, $config); + $value = $this->priceData->processTierPrice($product, $config); break; default: $value = $this->getValue($attribute, $product); @@ -883,7 +847,7 @@ public function getFormat($value, $attribute, array $config, $product): string $priceConfig = $config['price_config']; $priceConfig['currency'] = explode('_', $actions[0])[1]; $priceConfig['exchange_rate'] = $priceConfig['exchange_rate_' . $priceConfig['currency']] ?? 1; - $value = $this->processPrice($product, (float)$value, $priceConfig); + $value = $this->priceData->processPrice($product, (float)$value, $priceConfig); } } if (!empty($attribute['max'])) { @@ -900,10 +864,10 @@ public function getFormat($value, $attribute, array $config, $product): string * * @return string */ - public function getCondition($conditions, $product, $attribute) + public function getCondition($conditions, $product, $attribute, $value = null) { $data = null; - $value = $product->getData($attribute['source']); + $value = $value ?? $product->getData($attribute['source']); if ($attribute['source'] == 'is_in_stock') { $value = $this->getAvailability($product); } @@ -938,309 +902,12 @@ public function getCondition($conditions, $product, $attribute) public function getAttributeCollection($type, $config, $product) { if ($type == 'price') { - return $this->getPriceCollection($config, $product); + return $this->priceData->execute($config, $product); } return []; } - /** - * @param $config - * @param \Magento\Catalog\Model\Product $product - * - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function getPriceCollection($config, $product) - { - switch ($product->getTypeId()) { - case 'configurable': - /** - * Check if config has a final_price (data catalog_product_index_price) - * If final_price === null product is not salable (out of stock) - */ - if ($product->getData('final_price') === null) { - $price = 0; - $finalPrice = 0; - } else { - $price = $product->getData('price'); - $finalPrice = $product->getData('final_price'); - $specialPrice = $product->getSpecialPrice(); - $product['min_price'] = $product['min_price'] >= 0 ? $product['min_price'] : null; - $product['max_price'] = $product['max_price'] >= 0 ? $product['max_price'] : null; - } - break; - case 'grouped': - $groupedPriceType = null; - if (!empty($config['price_config']['grouped_price_type'])) { - $groupedPriceType = $config['price_config']['grouped_price_type']; - } - - $groupedPrices = $this->getGroupedPrices($product, $config); - $price = $groupedPrices['min_price']; - $finalPrice = $groupedPrices['min_price']; - $product['min_price'] = $groupedPrices['min_price']; - $product['max_price'] = $groupedPrices['max_price']; - $product['total_price'] = $groupedPrices['total_price']; - - if ($groupedPriceType == 'max') { - $price = $groupedPrices['max_price']; - $finalPrice = $price; - } - - if ($groupedPriceType == 'total') { - $price = $groupedPrices['total_price']; - $finalPrice = $price; - } - - break; - case 'bundle': - $price = $product->getPrice(); - $finalPrice = $product->getFinalPrice(); - $specialPrice = $product->getSpecialPrice(); - $rulePrice = $this->ruleFactory->create()->getRulePrice( - $config['timestamp'], - $config['website_id'], - '', - $product->getId() - ); - if ($rulePrice !== null && $rulePrice !== false) { - $finalPrice = min($finalPrice, $rulePrice); - } - break; - default: - if (intval($product->getFinalPrice()) !== 0) { - $price = $product->getPrice(); - $finalPrice = $product->getFinalPrice(); - $specialPrice = $product->getSpecialPrice(); - } else { - $finalPrice = $product->getPriceInfo()->getPrice('final_price')->getValue(); - $price = $product->getPriceInfo()->getPrice('regular_price')->getValue(); - $product['min_price'] = $product->getPriceInfo()->getPrice('final_price')->getMinimalPrice()->getBaseAmount(); - $product['max_price'] = $product->getPriceInfo()->getPrice('final_price')->getMaximalPrice()->getBaseAmount(); - } - - $rulePrice = $this->ruleFactory->create()->getRulePrice( - $config['timestamp'], - $config['website_id'], - '', - $product->getId() - ); - - if ($rulePrice !== null && $rulePrice !== false) { - $finalPrice = min($finalPrice, $rulePrice); - } - - break; - } - $prices = []; - $attributes = $config['attributes']; - $config = $config['price_config']; - $prices[$config['price']] = $this->processPrice($product, $price, $config); - - if (!empty($config['tax_include_both'])) { - $prices[$config['price_excl']] = $this->processPrice($product, $price, $config, false); - $prices[$config['price_incl']] = $this->processPrice($product, $price, $config, true); - } - - if (isset($finalPrice) && !empty($config['final_price'])) { - $prices[$config['final_price']] = $this->processPrice($product, $finalPrice, $config); - } - - if (isset($finalPrice) && ($price > $finalPrice) && !empty($config['sales_price'])) { - $prices[$config['sales_price']] = $this->processPrice($product, $finalPrice, $config); - if (!empty($config['tax_include_both'])) { - $prices[$config['sales_price_excl']] = $this->processPrice($product, $finalPrice, $config, false); - $prices[$config['sales_price_incl']] = $this->processPrice($product, $finalPrice, $config, true); - } - } - - if (isset($specialPrice) && ($specialPrice == $finalPrice) && !empty($config['sales_date_range'])) { - if ($product->getSpecialFromDate() && $product->getSpecialToDate()) { - $from = date('Y-m-d', strtotime($product->getSpecialFromDate())); - $to = date('Y-m-d', strtotime($product->getSpecialToDate())); - $prices[$config['sales_date_range']] = $from . '/' . $to; - } - } - - if ($price <= 0) { - if (!empty($product['min_price'])) { - $minPrice = $product['min_price']; - $prices[$config['price']] = $this->processPrice($product, $minPrice, $config); - if (!empty($config['tax_include_both'])) { - $prices[$config['price_excl']] = $this->processPrice($product, $minPrice, $config, false); - $prices[$config['price_incl']] = $this->processPrice($product, $minPrice, $config, true); - } - } - } - - if (!empty($product['min_price']) && !empty($config['min_price'])) { - if (($finalPrice > 0) && $finalPrice < $product['min_price']) { - $prices[$config['min_price']] = $this->processPrice($product, $finalPrice, $config); - } else { - $prices[$config['min_price']] = $this->processPrice($product, $product['min_price'], $config); - } - } - - if (!empty($product['max_price']) && !empty($config['max_price'])) { - $prices[$config['max_price']] = $this->processPrice($product, $product['max_price'], $config); - } - - if (!empty($product['total_price']) && !empty($config['total_price'])) { - $prices[$config['total_price']] = $this->processPrice($product, $product['total_price'], $config); - } - - if (!empty($config['discount_perc']) && isset($prices[$config['sales_price']])) { - if ($prices[$config['price']] > 0) { - $discount = ($prices[$config['sales_price']] - $prices[$config['price']]) / $prices[$config['price']]; - $discount = $discount * -100; - if ($discount > 0) { - $prices[$config['discount_perc']] = round($discount, 1) . '%'; - } - } - } - - if ($extraRenderedPriceFields = preg_grep('/^rendered_price__/', array_keys($attributes))) { - foreach ($extraRenderedPriceFields as $label) { - $field = $attributes[$label]; - $renderCurrency = $field['actions'][0] ? explode('_', $field['actions'][0])[1] : null; - if ($renderCurrency !== $config['currency']) { - $newConfig = $config; - $newConfig['currency'] = $renderCurrency; - $newConfig['exchange_rate'] = $config['exchange_rate_' . $renderCurrency] ?? 1; - switch ($field['price_source']) { - case 'price': - $prices[$field['label']] = $this->processPrice($product, $price, $newConfig); - break; - case 'min_price': - $price = $minPrice ?? $price; - $prices[$field['label']] = $this->processPrice($product, $price, $newConfig); - break; - case 'max_price': - $price = $maxPrice ?? $price; - $prices[$field['label']] = $this->processPrice($product, $price, $newConfig); - break; - } - } else { - $prices[$field['label']] = $prices[$field['price_source']] ?? null; - } - } - } - - return $prices; - } - - /** - * @param \Magento\Catalog\Model\Product $product - * @param $config - * - * @return array|null - */ - public function getGroupedPrices($product, $config) - { - $subProducts = $product->getTypeInstance()->getAssociatedProducts($product); - - $minPrice = null; - $maxPrice = null; - $totalPrice = null; - - foreach ($subProducts as $subProduct) { - $subProduct->setWebsiteId($config['website_id']); - if ($subProduct->isSalable()) { - $price = $this->commonPriceModel->getCatalogPrice($subProduct); - if ($price < $minPrice || $minPrice === null) { - $minPrice = $this->commonPriceModel->getCatalogPrice($subProduct); - $product->setTaxClassId($subProduct->getTaxClassId()); - } - if ($price > $maxPrice || $maxPrice === null) { - $maxPrice = $this->commonPriceModel->getCatalogPrice($subProduct); - $product->setTaxClassId($subProduct->getTaxClassId()); - } - if ($subProduct->getQty() > 0) { - $totalPrice += $price * $subProduct->getQty(); - } else { - $totalPrice += $price; - } - } - } - - return ['min_price' => $minPrice, 'max_price' => $maxPrice, 'total_price' => $totalPrice]; - } - - /** - * @param $product - * @param array $config - * @return array|null - */ - public function processTierPrice($product, array $config): ?array - { - if (!$product->getData('tier_price')) { - return null; - } - - $reformattedTierPriced = []; - foreach ($product->getData('tier_price') as $priceTier) { - $price = $priceTier['percentage_value'] - ? $product->getPrice() * ($priceTier['percentage_value'] / 100) - : $priceTier['value']; - - $reformattedTierPriced[] = [ - 'price_id' => $priceTier['value_id'], - 'website_id' => $priceTier['website_id'], - 'all_groups' => $priceTier['all_groups'], - 'cust_group' => $priceTier['customer_group_id'], - 'qty' => $priceTier['qty'], - 'price' => $this->formatPrice($price, $config) - ]; - } - - return $reformattedTierPriced; - } - - /** - * @param \Magento\Catalog\Model\Product $product - * @param $price - * @param $config - * @param $includingTax - * - * @return float|string - */ - public function processPrice($product, $price, $config, $includingTax = null) - { - if (!empty($config['exchange_rate'])) { - $price = $price * $config['exchange_rate']; - } - - if ($includingTax !== null) { - return $this->formatPrice( - $this->catalogHelper->getTaxPrice($product, $price, $includingTax), - $config - ); - } - - if (isset($config['incl_vat'])) { - $price = $this->catalogHelper->getTaxPrice($product, $price, ['incl_vat']); - } - - return $this->formatPrice($price, $config); - } - - /** - * @param $price - * @param $config - * - * @return string - */ - public function formatPrice($price, $config) - { - $decimal = isset($config['decimal_point']) ? $config['decimal_point'] : '.'; - $price = number_format(floatval(str_replace(',', '.', $price)), 2, $decimal, ''); - if (!empty($config['use_currency']) && ($price >= 0)) { - $price .= ' ' . $config['currency']; - } - return $price; - } - /** * @param $attributes * @param array $filters diff --git a/Helper/Source.php b/Helper/Source.php index b4d5e3a..db9512e 100755 --- a/Helper/Source.php +++ b/Helper/Source.php @@ -515,6 +515,10 @@ public function getAttributes($type, $filters = [], $storeId = null) ]; if ($type != 'api') { + $attributes['updated_at'] = [ + 'label' => 'updated_at', + 'source' => 'updated_at', + ]; $attributes['created_at'] = [ 'label' => 'created_at', 'source' => 'created_at', diff --git a/Plugin/AddDiscountToInvoice.php b/Plugin/AddDiscountToInvoice.php new file mode 100644 index 0000000..283eae8 --- /dev/null +++ b/Plugin/AddDiscountToInvoice.php @@ -0,0 +1,38 @@ +getOrder(); + if ($order->getPayment()->getMethod() == 'channable') { + $discountAmount = abs((float)$order->getDiscountAmount()); + if ($discountAmount > 0) { + $invoice->setDiscountAmount(-$discountAmount); + $invoice->setBaseDiscountAmount(-$discountAmount); + $invoice->setGrandTotal($invoice->getGrandTotal() - $discountAmount); + $invoice->setBaseGrandTotal($invoice->getBaseGrandTotal() - $discountAmount); + } + } + + return $invoice; + } +} diff --git a/Service/Category/CategoryData.php b/Service/Category/CategoryData.php index ac389e1..673d162 100644 --- a/Service/Category/CategoryData.php +++ b/Service/Category/CategoryData.php @@ -10,6 +10,7 @@ use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory as CategoryCollectionFactory; use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Exception\LocalizedException; use Magento\Store\Model\StoreManagerInterface; class CategoryData @@ -44,6 +45,7 @@ public function __construct( * @param ProductCollection $parents * @param int $storeId * @return array + * @throws LocalizedException */ public function load(ProductCollection $products, ProductCollection $parents, int $storeId): array { @@ -68,13 +70,20 @@ public function load(ProductCollection $products, ProductCollection $parents, in private function getCategoryIdsForCollection(array $productIds): array { $connection = $this->resourceConnection->getConnection(); - $tableName = $this->resourceConnection->getTableName('catalog_category_product'); + $ccpTable = $this->resourceConnection->getTableName('catalog_category_product'); $select = $connection->select() - ->from($tableName, 'category_id') + ->from($ccpTable, 'category_id') ->where('product_id IN (?)', $productIds) ->group('category_id'); + $categoryIds = $connection->fetchCol($select); + + $cceTable = $this->resourceConnection->getTableName('catalog_category_entity'); + $select = $connection->select() + ->from($cceTable, 'entity_id') + ->where('entity_id IN (?) OR parent_id IN (?)', $categoryIds); + return $connection->fetchCol($select); } @@ -82,13 +91,17 @@ private function getCategoryIdsForCollection(array $productIds): array * @param array $categoryIds * @param int $storeId * @return array + * @throws LocalizedException */ private function getCategoryTree(array $categoryIds, int $storeId): array { $collection = $this->categoryCollectionFactory->create() ->setStoreId($storeId) ->addAttributeToSelect(['name', 'level', 'path', 'url_path', self::EXCLUDE_ATTRIBUTE]) - ->addFieldToFilter('entity_id', ['in' => $categoryIds]) + ->addFieldToFilter([ + ['attribute' => 'entity_id', 'in' => $categoryIds], + ['attribute' => 'parent_id', 'in' => $categoryIds] + ]) ->addFieldToFilter('is_active', ['eq' => 1]); try { diff --git a/Service/Order/Import.php b/Service/Order/Import.php index a9c9e44..06d89e2 100755 --- a/Service/Order/Import.php +++ b/Service/Order/Import.php @@ -259,9 +259,9 @@ public function execute(ChannableOrderData $orderData): OrderInterface if (isset($orderData['price']['discount']) && !empty((float)$orderData['price']['discount'])) { $discountAmount = abs((float)$orderData['price']['discount']); - $order->setDiscountDescription(__('Channable discount')); - $order->setBaseDiscountAmount($discountAmount); - $order->setDiscountAmount($discountAmount); + $order->setDiscountDescription($orderData['channel_name']); + $order->setBaseDiscountAmount($discountAmount * -1); + $order->setDiscountAmount($discountAmount * -1); $order->setGrandTotal($order->getGrandTotal() - $discountAmount); $order->setBaseGrandTotal($order->getBaseGrandTotal() - $discountAmount); } diff --git a/Service/Order/ImportSimulator.php b/Service/Order/ImportSimulator.php index cea2389..d8b3fb4 100755 --- a/Service/Order/ImportSimulator.php +++ b/Service/Order/ImportSimulator.php @@ -174,7 +174,6 @@ public function getTestData(array $params): array "middle_name" => "From", "last_name" => "Channable", "company" => "Do Not Ship", - "vat_id" => 'NL0001', "email" => "dontemail@me.net", "address_line_1" => "Billing Line 1", "address_line_2" => "Billing Line 2", diff --git a/Service/Order/Items/Add.php b/Service/Order/Items/Add.php index b186c77..83a4158 100644 --- a/Service/Order/Items/Add.php +++ b/Service/Order/Items/Add.php @@ -212,11 +212,10 @@ private function getProductPrice( Quote $quote, bool $isBusinessOrder ): float { + $price = (float)$item['price']; if ($isBusinessOrder) { - return (float)$item['price']; + return $price; } - $price = (float)$item['price'] - $this->getProductWeeTax($product, $quote); - if (!$this->configProvider->getNeedsTaxCalulcation('price', (int)$store->getId())) { $request = $this->taxCalculation->getRateRequest( $quote->getShippingAddress(), @@ -231,7 +230,7 @@ private function getProductPrice( $price = $price / (100 + $percent) * 100; } - return $price; + return $price - $this->getProductWeeTax($product, $quote); } /** diff --git a/Service/Order/Quote/AddressHandler.php b/Service/Order/Quote/AddressHandler.php index b05508e..1d06082 100644 --- a/Service/Order/Quote/AddressHandler.php +++ b/Service/Order/Quote/AddressHandler.php @@ -135,12 +135,9 @@ public function getAddressData(string $type, array $orderData, Quote $quote): ar : null, 'postcode' => $address['zip_code'], 'telephone' => $telephone, - 'vat_id' => !empty($address['vat_id']) ? $address['vat_id'] : null, + 'vat_id' => $this->getVatId($type, $orderData, $storeId), '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); @@ -149,6 +146,35 @@ public function getAddressData(string $type, array $orderData, Quote $quote): ar return $addressData; } + /** + * Channable only sets VAT ID on billing address + * In some cases we also need this on shipping address (due to OSS/MOSS) + * + * @param string $type + * @param array $orderData + * @param int $storeId + * @return string|null + */ + private function getVatId(string $type, array $orderData, int $storeId): ?string + { + if (!$this->configProvider->isBusinessOrderEnabled($storeId)) { + return null; + } + + $vatId = !empty($orderData['billing']['vat_number']) ? $orderData['billing']['vat_number'] : null; + if ($type == 'billing' || !$vatId) { + return $vatId; + } + + if (empty($orderData['customer']['business_order']) || !$this->configProvider->importCompanyName($storeId)) { + return null; + } + + return $orderData['billing']['company'] == $orderData['shipping']['company'] + ? $vatId + : null; + } + /** * Removed unwanted characters from email. * Some Marketplaces add ":" to email what can cause import to fail. diff --git a/Service/Product/PriceData.php b/Service/Product/PriceData.php new file mode 100644 index 0000000..2f55ebf --- /dev/null +++ b/Service/Product/PriceData.php @@ -0,0 +1,352 @@ +commonPriceModel = $commonPriceModel; + $this->ruleFactory = $ruleFactory; + $this->catalogHelper = $catalogHelper; + } + + /** + * @param array $config + * @param Product $product + * + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function execute(array $config, Product$product): array + { + switch ($product->getTypeId()) { + case 'configurable': + /** + * Check if config has a final_price (data catalog_product_index_price) + * If final_price === null product is not salable (out of stock) + */ + if ($product->getData('final_price') === null) { + $price = 0; + $finalPrice = 0; + } else { + $price = $product->getData('price'); + $finalPrice = $product->getData('final_price'); + $specialPrice = $product->getSpecialPrice(); + $product['min_price'] = $product['min_price'] >= 0 ? $product['min_price'] : null; + $product['max_price'] = $product['max_price'] >= 0 ? $product['max_price'] : null; + } + break; + case 'grouped': + $groupedPriceType = null; + if (!empty($config['price_config']['grouped_price_type'])) { + $groupedPriceType = $config['price_config']['grouped_price_type']; + } + + $groupedPrices = $this->getGroupedPrices($product, $config); + $price = $groupedPrices['min_price']; + $finalPrice = $groupedPrices['min_price']; + $product['min_price'] = $groupedPrices['min_price']; + $product['max_price'] = $groupedPrices['max_price']; + $product['total_price'] = $groupedPrices['total_price']; + + if ($groupedPriceType == 'max') { + $price = $groupedPrices['max_price']; + $finalPrice = $price; + } + + if ($groupedPriceType == 'total') { + $price = $groupedPrices['total_price']; + $finalPrice = $price; + } + + break; + case 'bundle': + $price = $product->getPrice(); + $finalPrice = $product->getFinalPrice(); + $specialPrice = $product->getSpecialPrice(); + $rulePrice = $this->ruleFactory->create()->getRulePrice( + $config['timestamp'], + $config['website_id'], + '', + $product->getId() + ); + if ($rulePrice !== null && $rulePrice !== false) { + $finalPrice = min($finalPrice, $rulePrice); + } + break; + default: + if ($product->getFinalPrice() !== null) { + $price = $product->getPrice(); + $finalPrice = $product->getFinalPrice(); + $specialPrice = $product->getSpecialPrice(); + } else { + $finalPrice = $product->getPriceInfo()->getPrice('final_price')->getValue(); + $price = $product->getPriceInfo()->getPrice('regular_price')->getValue(); + $product['min_price'] = $product->getPriceInfo()->getPrice('final_price')->getMinimalPrice()->getBaseAmount(); + $product['max_price'] = $product->getPriceInfo()->getPrice('final_price')->getMaximalPrice()->getBaseAmount(); + } + + $rulePrice = $this->ruleFactory->create()->getRulePrice( + $config['timestamp'], + $config['website_id'], + '', + $product->getId() + ); + + if ($rulePrice !== null && $rulePrice !== false) { + $finalPrice = min($finalPrice, $rulePrice); + } + + break; + } + $prices = []; + $attributes = $config['attributes']; + $config = $config['price_config']; + $prices[$config['price']] = $this->processPrice($product, $price, $config); + + if (!empty($config['tax_include_both'])) { + $prices[$config['price_excl']] = $this->processPrice($product, $price, $config, false); + $prices[$config['price_incl']] = $this->processPrice($product, $price, $config, true); + } + + if (isset($finalPrice) && !empty($config['final_price'])) { + $prices[$config['final_price']] = $this->processPrice($product, $finalPrice, $config); + } + + if (isset($finalPrice) && ($price > $finalPrice) && !empty($config['sales_price'])) { + $prices[$config['sales_price']] = $this->processPrice($product, $finalPrice, $config); + if (!empty($config['tax_include_both'])) { + $prices[$config['sales_price_excl']] = $this->processPrice($product, $finalPrice, $config, false); + $prices[$config['sales_price_incl']] = $this->processPrice($product, $finalPrice, $config, true); + } + } + + if (isset($specialPrice) && ($specialPrice == $finalPrice) && !empty($config['sales_date_range'])) { + if ($product->getSpecialFromDate() && $product->getSpecialToDate()) { + $from = date('Y-m-d', strtotime($product->getSpecialFromDate())); + $to = date('Y-m-d', strtotime($product->getSpecialToDate())); + $prices[$config['sales_date_range']] = $from . '/' . $to; + } + } + + if ($price <= 0) { + if (!empty($product['min_price'])) { + $minPrice = $product['min_price']; + $prices[$config['price']] = $this->processPrice($product, $minPrice, $config); + if (!empty($config['tax_include_both'])) { + $prices[$config['price_excl']] = $this->processPrice($product, $minPrice, $config, false); + $prices[$config['price_incl']] = $this->processPrice($product, $minPrice, $config, true); + } + } + } + + if (!empty($product['min_price']) && !empty($config['min_price'])) { + if (($finalPrice > 0) && $finalPrice < $product['min_price']) { + $prices[$config['min_price']] = $this->processPrice($product, $finalPrice, $config); + } else { + $prices[$config['min_price']] = $this->processPrice($product, $product['min_price'], $config); + } + } + + if (!empty($product['max_price']) && !empty($config['max_price'])) { + $prices[$config['max_price']] = $this->processPrice($product, $product['max_price'], $config); + } + + if (!empty($product['total_price']) && !empty($config['total_price'])) { + $prices[$config['total_price']] = $this->processPrice($product, $product['total_price'], $config); + } + + if (!empty($config['discount_perc']) && isset($prices[$config['sales_price']])) { + if ($prices[$config['price']] > 0) { + $discount = ($prices[$config['sales_price']] - $prices[$config['price']]) / $prices[$config['price']]; + $discount = $discount * -100; + if ($discount > 0) { + $prices[$config['discount_perc']] = round($discount, 1) . '%'; + } + } + } + + if ($extraRenderedPriceFields = preg_grep('/^rendered_price__/', array_keys($attributes))) { + foreach ($extraRenderedPriceFields as $label) { + $field = $attributes[$label]; + $renderCurrency = $field['actions'][0] ? explode('_', $field['actions'][0])[1] : null; + if ($renderCurrency !== $config['currency']) { + $newConfig = $config; + $newConfig['currency'] = $renderCurrency; + $newConfig['exchange_rate'] = $config['exchange_rate_' . $renderCurrency] ?? 1; + switch ($field['price_source']) { + case 'price': + $prices[$field['label']] = $this->processPrice($product, $price, $newConfig); + break; + case 'min_price': + $price = $minPrice ?? $price; + $prices[$field['label']] = $this->processPrice($product, $price, $newConfig); + break; + case 'max_price': + $price = $maxPrice ?? $price; + $prices[$field['label']] = $this->processPrice($product, $price, $newConfig); + break; + } + } else { + $prices[$field['label']] = $prices[$field['price_source']] ?? null; + } + } + } + + return $prices; + } + + /** + * @param Product $product + * @param array $config + * + * @return array|null + */ + public function getGroupedPrices(Product $product, array $config): ?array + { + $subProducts = $product->getTypeInstance()->getAssociatedProducts($product); + + $minPrice = null; + $maxPrice = null; + $totalPrice = null; + + foreach ($subProducts as $subProduct) { + $subProduct->setWebsiteId($config['website_id']); + if ($subProduct->isSalable()) { + $price = $this->commonPriceModel->getCatalogPrice($subProduct); + if ($price < $minPrice || $minPrice === null) { + $minPrice = $this->commonPriceModel->getCatalogPrice($subProduct); + $product->setTaxClassId($subProduct->getTaxClassId()); + } + if ($price > $maxPrice || $maxPrice === null) { + $maxPrice = $this->commonPriceModel->getCatalogPrice($subProduct); + $product->setTaxClassId($subProduct->getTaxClassId()); + } + if ($subProduct->getQty() > 0) { + $totalPrice += $price * $subProduct->getQty(); + } else { + $totalPrice += $price; + } + } + } + + return ['min_price' => $minPrice, 'max_price' => $maxPrice, 'total_price' => $totalPrice]; + } + + /** + * @param Product $product + * @param $price + * @param array $config + * @param bool|null $includingTax + * + * @return string + */ + public function processPrice(Product $product, $price, array $config, ?bool $includingTax = null): string + { + if (!empty($config['exchange_rate'])) { + $price = $price * $config['exchange_rate']; + } + + if ($includingTax !== null) { + return $this->formatPrice( + $this->catalogHelper->getTaxPrice($product, $price, $includingTax), + $config + ); + } + + if (isset($config['incl_vat'])) { + $price = $this->catalogHelper->getTaxPrice($product, $price, ['incl_vat']); + } + + return $this->formatPrice($price, $config); + } + + /** + * Format a price based on the provided configuration. + * + * @param float|string $price The price value to be formatted. + * @param array $config Configuration options for formatting. + * @return string The formatted price. + */ + public function formatPrice($price, array $config): string + { + $decimalPoint = $config['decimal_point'] ?? '.'; + $currency = $config['currency'] ?? ''; + $useCurrency = !empty($config['use_currency']); + + // Ensure the price is converted to a float. + $formattedPrice = number_format( + floatval(str_replace(',', '.', (string)$price)), + 2, + $decimalPoint, + '' + ); + + // Append currency if configured. + if ($useCurrency && $formattedPrice >= 0) { + $formattedPrice .= ' ' . $currency; + } + + return $formattedPrice; + } + + /** + * Process tier pricing for a product. + * + * @param Product $product The product instance. + * @param array $config Configuration for price formatting. + * @return array|null An array of reformatted tier prices or null if none exist. + */ + public function processTierPrice(Product $product, array $config): ?array + { + if (!$tierPrices = $product->getData('tier_price')) { + return null; + } + + $reformattedTierPrices = []; + foreach ($tierPrices as $priceTier) { + $price = !empty($priceTier['percentage_value']) + ? $product->getPrice() * (1 - ($priceTier['percentage_value'] / 100)) // Subtract discount + : $priceTier['value']; + + $reformattedTierPrices[] = [ + 'price_id' => $priceTier['value_id'] ?? null, + 'website_id' => $priceTier['website_id'] ?? null, + 'all_groups' => $priceTier['all_groups'] ?? 0, + 'cust_group' => $priceTier['customer_group_id'] ?? null, + 'qty' => $priceTier['qty'] ?? 0, + 'price' => $this->formatPrice($price, $config) + ]; + } + + return $reformattedTierPrices; + } +} \ No newline at end of file diff --git a/Service/Product/TierPriceData.php b/Service/Product/TierPriceData.php index 340579f..69d8c7c 100644 --- a/Service/Product/TierPriceData.php +++ b/Service/Product/TierPriceData.php @@ -43,6 +43,7 @@ public function __construct( /** * @param ProductCollection $products * @param ProductCollection $parents + * @param $websiteId * @return void */ public function load(ProductCollection $products, ProductCollection $parents, $websiteId) diff --git a/Service/Returns/CreateCreditmemo.php b/Service/Returns/CreateCreditmemo.php index e00df8a..ed8c06f 100644 --- a/Service/Returns/CreateCreditmemo.php +++ b/Service/Returns/CreateCreditmemo.php @@ -43,27 +43,25 @@ class CreateCreditmemo * @var CreditmemoRepositoryInterface */ private $creditmemoRepositoryInterface; - /** - * ProcessReturn constructor. - * @param ReturnsRepository $returnsRepository - * @param ResourceConnection $resource - * @param RefundOrder $refundOrder - * @param ItemCreationFactory $itemCreationFactory - * @param CreditmemoRepositoryInterface $creditmemoRepositoryInterface + * @var GetSkuFromGtin */ + private $getSkuFromGtin; + public function __construct( ReturnsRepository $returnsRepository, ResourceConnection $resource, RefundOrder $refundOrder, ItemCreationFactory $itemCreationFactory, - CreditmemoRepositoryInterface $creditmemoRepositoryInterface + CreditmemoRepositoryInterface $creditmemoRepositoryInterface, + GetSkuFromGtin $getSkuFromGtin ) { $this->returnsRepository = $returnsRepository; $this->resource = $resource; $this->refundOrder = $refundOrder; $this->itemCreationFactory = $itemCreationFactory; $this->creditmemoRepositoryInterface = $creditmemoRepositoryInterface; + $this->getSkuFromGtin = $getSkuFromGtin; } /** @@ -81,9 +79,14 @@ public function execute(ReturnsData $return, ?string $status): string } $item = $return->getItem(); - $itemId = $this->findOrderItemId($item, $orderId); + $sku = $this->getSkuFromGtin->execute($item['gtin'] ?? null, (int)$return->getStoreId()); + if (!$sku) { + throw new InputException(__('Unable to find SKU for GTIN.')); + } + + $itemId = $this->findOrderItemId($sku, $orderId); if (!$itemId) { - throw new InputException(__('Unable to locate the order Item-ID if imported return.')); + throw new InputException(__('Unable to locate the order Item-ID for imported return.')); } $creditmemoItem = $this->itemCreationFactory->create(); @@ -99,11 +102,11 @@ public function execute(ReturnsData $return, ?string $status): string } /** - * @param array $item + * @param string $sku * @param int $orderId * @return ?int */ - private function findOrderItemId(array $item, int $orderId): ?int + private function findOrderItemId(string $sku, int $orderId): ?int { $connection = $this->resource->getConnection(); @@ -113,7 +116,7 @@ private function findOrderItemId(array $item, int $orderId): ?int ->where('order_id = :order_id'); $bind = [ - ':sku' => $item['gtin'], + ':sku' => $sku, ':order_id' => $orderId ]; diff --git a/Service/Returns/GetByOrder.php b/Service/Returns/GetByOrder.php index b8f1ba9..5b7cf8f 100644 --- a/Service/Returns/GetByOrder.php +++ b/Service/Returns/GetByOrder.php @@ -7,60 +7,34 @@ namespace Magmodules\Channable\Service\Returns; -use Magento\Catalog\Model\ProductFactory; use Magento\Framework\Serialize\Serializer\Json; use Magento\Sales\Model\Order; -use Magmodules\Channable\Api\Config\RepositoryInterface as ConfigProvider; -use Magmodules\Channable\Api\Log\RepositoryInterface as LogRepository; use Magmodules\Channable\Api\Returns\Data\DataInterface as ReturnsDataInterface; use Magmodules\Channable\Api\Returns\RepositoryInterface as ReturnsRepository; class GetByOrder { - /** - * @var string - */ - private $gtinAttribute; /** * @var ReturnsRepository */ private $returnsRepository; - /** - * @var ProductFactory - */ - private $productFactory; - /** - * @var ConfigProvider - */ - private $configProvider; - /** - * @var LogRepository - */ - private $logRepository; /** * @var Json */ private $json; - /** - * @param ReturnsRepository $returnsRepository - * @param ProductFactory $productFactory - * @param ConfigProvider $configProvider - * @param LogRepository $logRepository - * @param Json $json + * @var GetSkuFromGtin */ + private $getSkuFromGtin; + public function __construct( ReturnsRepository $returnsRepository, - ProductFactory $productFactory, - ConfigProvider $configProvider, - LogRepository $logRepository, + GetSkuFromGtin $getSkuFromGtin, Json $json ) { $this->returnsRepository = $returnsRepository; - $this->productFactory = $productFactory; - $this->configProvider = $configProvider; - $this->logRepository = $logRepository; + $this->getSkuFromGtin = $getSkuFromGtin; $this->json = $json; } @@ -101,55 +75,14 @@ public function execute(Order $order): ?array private function getSkuFromReturnData($itemData, int $storeId): ?string { if (is_array($itemData)) { - return $this->getSkuFromGtin($itemData['gtin'] ?? null, $storeId); + return $this->getSkuFromGtin->execute($itemData['gtin'] ?? null, $storeId); } try { $itemData = $this->json->unserialize($itemData); - return $this->getSkuFromGtin($itemData['gtin'] ?? null, $storeId); + return $this->getSkuFromGtin->execute($itemData['gtin'] ?? null, $storeId); } catch (\Exception $exception) { return null; } } - - /** - * @param string $gtin - * @param int $storeId - * @return string|null - */ - private function getSkuFromGtin(string $gtin, int $storeId): ?string - { - $gtinAttribute = $this->getGtinAttributeCode($storeId); - if ($gtinAttribute == 'sku' || $gtinAttribute == null) { - return $gtin; - } - - try { - if ($gtinAttribute == 'id') { - if ($product = $this->productFactory->create()->load($gtin)) { - return $product->getSku(); - } - } - if ($product = $this->productFactory->create()->loadByAttribute($gtinAttribute, $gtin)) { - return $product->getSku(); - } - } catch (\Exception $exception) { - $this->logRepository->addErrorLog('getSkuFromGtin', $exception->getMessage()); - } - - return $gtin; - } - - /** - * @param int $storeId - * @return string - */ - private function getGtinAttributeCode(int $storeId): string - { - if (!$this->gtinAttribute) { - $this->gtinAttribute = $this->configProvider->getGtinAttribute($storeId); - } - - return $this->gtinAttribute; - } } diff --git a/Service/Returns/GetSkuFromGtin.php b/Service/Returns/GetSkuFromGtin.php new file mode 100644 index 0000000..9431cd6 --- /dev/null +++ b/Service/Returns/GetSkuFromGtin.php @@ -0,0 +1,88 @@ +productFactory = $productFactory; + $this->configProvider = $configProvider; + $this->logRepository = $logRepository; + } + + /** + * @param string|null $gtin + * @param int $storeId + * @return string|null + */ + public function execute(?string $gtin, int $storeId): ?string + { + $gtinAttribute = $this->getGtinAttributeCode($storeId); + if ($gtinAttribute == 'sku' || $gtinAttribute == null || $gtin == null) { + return $gtin; + } + + try { + if ($gtinAttribute == 'id') { + if ($product = $this->productFactory->create()->load($gtin)) { + return $product->getSku(); + } + } + if ($product = $this->productFactory->create()->loadByAttribute($gtinAttribute, $gtin)) { + return $product->getSku(); + } + } catch (\Exception $exception) { + $this->logRepository->addErrorLog('getSkuFromGtin', $exception->getMessage()); + } + + return null; + } + + /** + * @param int $storeId + * @return string + */ + private function getGtinAttributeCode(int $storeId): string + { + if (!$this->gtinAttribute) { + $this->gtinAttribute = $this->configProvider->getGtinAttribute($storeId); + } + + return $this->gtinAttribute; + } +} diff --git a/composer.json b/composer.json index 760acb0..1dc0849 100755 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magmodules/magento2-channable", "description": "Channable integration for Magento 2", "type": "magento2-module", - "version": "1.19.1", + "version": "1.20.0", "license": "BSD-2-Clause", "homepage": "https://github.com/magmodules/magento2-channable", "require": { diff --git a/etc/config.xml b/etc/config.xml index f663b10..3356f09 100644 --- a/etc/config.xml +++ b/etc/config.xml @@ -12,7 +12,7 @@ 0 250 - v1.19.1 + v1.20.0 name diff --git a/etc/di.xml b/etc/di.xml index bdaaa39..79f2456 100755 --- a/etc/di.xml +++ b/etc/di.xml @@ -86,6 +86,10 @@ + + + +