The requirement is to fix the final price export in Magento 2.4.2. The issue is as follows:
We want to export product data into a CSV. We also want to export the final prices of the products. For that, an after Plugin is supposed to take the exported array and add an additional column for the final price before it gets handed to the file writing logic of the export.
The Plugin works as follows:
use Magento\Catalog\Api\Data\ProductExtensionInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Type\Price;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Product as ResourceModel;
use Magento\Framework\Exception\NoSuchEntityException;
use Firebear\ImportExport\Model\Export\Product as FirebearProduct;
class AfterGetExportDataPlugin
{
public const FINAL_PRICE_EXPORT = 'export_finalPrice';
private ResourceModel $resourceModel;
private ProductRepositoryInterface $productRepository;
private Price $price;
public function __construct(
ResourceModel $resourceModel,
ProductRepositoryInterface $productRepository,
Price $price
) {
$this->resourceModel = $resourceModel;
$this->productRepository = $productRepository;
$this->price = $price;
}
/**
* #param FirebearProduct $subject
* #param array $result
* #return array
* #throws NoSuchEntityException
*/
public function afterGetExportData(FirebearProduct $subject, array $result)
{
// Evaluate if final price is a selected attribute for the export
$additionalColumns = $subject->getParameters()['list'];
if (false === in_array(self::FINAL_PRICE_EXPORT, $additionalColumns, true)) {
return $result;
}
// Evaluate if an alias for final price is set
$exportAttributeMapping = self::FINAL_PRICE_EXPORT;
for ($i = 0; $i < $additionalColumns; $i++) {
if ($additionalColumns[$i] === self::FINAL_PRICE_EXPORT) {
$exportAttributeMapping = $subject->getParameters()['replace_code'][$i];
break 1;
}
}
return array_map(function ($product) use ($exportAttributeMapping) {
/** #var Product $productEntity */
$productEntity = $this->productRepository->get((string) $product['sku']);
$product[$exportAttributeMapping] = (float) $this->price->getFinalPrice(1, $productEntity);
return $product;
}, $result);
}
}
I tested that with 5000 products on my development environment and the export went well. But when we exported 400000 products on our staging environment, the majority of final prices were missing.
I can't figure out why that is. Does anyone how or why that happens?
FYI: We use the Firebear export extension but I don't think it matters that much in this context.
Related
I implemented a price collector / processor exactly as described in the docs (https://developer.shopware.com/docs/guides/plugins/plugins/checkout/cart/change-price-of-item).
For testing purposes every line item product in the cart has a price of 100€.
When applying a discount (10%) that I have created in the admin, the discount is applied on the original price of the product but not on the actual new cart price.
What am I missing here? My OverwritePriceCollector.php looks as following:
<?php declare(strict_types=1);
namespace Test\TestPlugin\Core\Checkout\Cart;
use Shopware\Core\Checkout\Cart\Cart;
use Shopware\Core\Checkout\Cart\CartBehavior;
use Shopware\Core\Checkout\Cart\CartDataCollectorInterface;
use Shopware\Core\Checkout\Cart\CartProcessorInterface;
use Shopware\Core\Checkout\Cart\LineItem\CartDataCollection;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;
use Shopware\Core\Checkout\Cart\Price\AbsolutePriceCalculator;
use Shopware\Core\Checkout\Cart\Price\PercentagePriceCalculator;
use Shopware\Core\Checkout\Cart\Price\QuantityPriceCalculator;
use Shopware\Core\Checkout\Cart\Price\Struct\AbsolutePriceDefinition;
use Shopware\Core\Checkout\Cart\Price\Struct\PercentagePriceDefinition;
use Shopware\Core\Checkout\Cart\Price\Struct\QuantityPriceDefinition;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
class OverwritePriceCollector implements CartDataCollectorInterface, CartProcessorInterface
{
/**
* #var QuantityPriceCalculator
*/
private $calculator;
public function __construct(QuantityPriceCalculator $calculator) {
$this->calculator = $calculator;
}
public function collect(CartDataCollection $data, Cart $original, SalesChannelContext $context, CartBehavior $behavior): void
{
// get all product ids of current cart
$productIds = $original->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE)->getReferenceIds();
// remove all product ids which are already fetched from the database
$filtered = $this->filterAlreadyFetchedPrices($productIds, $data);
// Skip execution if there are no prices to be saved
if (empty($filtered)) {
return;
}
foreach ($filtered as $id) {
$key = $this->buildKey($id);
// Needs implementation, just an example
$newPrice = 100;
// we have to set a value for each product id to prevent duplicate queries in next calculation
$data->set($key, $newPrice);
}
}
public function process(CartDataCollection $data, Cart $original, Cart $toCalculate, SalesChannelContext $context, CartBehavior $behavior): void
{
// get all product line items
$products = $toCalculate->getLineItems()->filterType(LineItem::PRODUCT_LINE_ITEM_TYPE);
foreach ($products as $product) {
$key = $this->buildKey($product->getReferencedId());
// no overwritten price? continue with next product
if (!$data->has($key) || $data->get($key) === null) {
continue;
}
$newPrice = $data->get($key);
// build new price definition
$definition = new QuantityPriceDefinition(
$newPrice,
$product->getPrice()->getTaxRules(),
$product->getPrice()->getQuantity()
);
// build CalculatedPrice over calculator class for overwritten price
$calculated = $this->calculator->calculate($definition, $context);
// set new price into line item
$product->setPrice($calculated);
$product->setPriceDefinition($definition);
}
}
private function filterAlreadyFetchedPrices(array $productIds, CartDataCollection $data): array
{
$filtered = [];
foreach ($productIds as $id) {
$key = $this->buildKey($id);
// already fetched from database?
if ($data->has($key)) {
continue;
}
$filtered[] = $id;
}
return $filtered;
}
private function buildKey(string $id): string
{
return 'price-overwrite-'.$id;
}
}
I think your problem is that you registered your Collector with the same priority as in documentation (4500).
Shopware PromotionProcessor is registered with priority 4900, so your code is called after PromotionProcessor.
So what do you need is to register your OverwritePriceCollector with higher priority e.g. 5000.
I'm building the admin for a Magento2 store (currently on 2.1.7, they want to use the newest version until we go live and then want to stabilize a particular version). The module in question is supposed to display all existing orders, with an actionsColumn that contains links to cancel, edit, and open a detailed overview of the purchased items associated with that order. The order detail page contains a grid view that should display all order items associated with an order number passed in the URL.
In order to filter out Order Items that don't relate to the specific Order Number, I've extended the \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult class. This works except for one weird caveat. If, in the addFieldToFilter call, I replace $ordNum with, say, '10000', it grabs the correct data. When using $ordNum to call this dynamically, however, it returns no rows at all. This despite trying all sorts of casting and === checks to ensure that there's no difference between the hardcoded and dynamic values. Is this a Magento bug? I can't at all figure out why this would be the case.
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$ordNum = $request->getParam('order_num');
$this->addFieldToFilter('order_num', ['eq' => $ordNum]); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
I just fixed it by using mentioned below steps
Store param value in session in controller
public function execute() {
$this->_catalogSession->setTokenId($this->request->getParam('entity_id'));
$this->_view->loadLayout();
$this->_view->loadLayoutUpdates();
$this->_view->getPage()->getConfig()->getTitle()->set(__('Redeem Token History'));
$this->_view->renderLayout();
}
Use session value in dataprovider
$tokensCollection->addFieldToFilter('token_id', ['eq' => $this->_catalogSession->getTokenId()]);
Enjoy :)
Try this in place of the getParam statement:
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
Just to make sure we are on the same page, this is the full code:
<?php
class OrderItems extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
{
protected function _initSelect()
{
$this->filterByOrderNum();
parent::_initSelect();
return $this;
}
private function filterByOrderNum()
{
$request = \Magento\Framework\App\ObjectManager::getInstance()
->get('Magento\Framework\App\Request\Http');
$url = parse_url($request);
$path = explode('/',$url['path']);
$ordNum = $path[3];
$this->addFieldToFilter('order_num', $ordNum); //if I switch this to hardcoded 10000, this works. With the variable, no dice.
return $this;
}
}
We have solved this issue by doing the following :
/**
* CcCustompriceProductListingDataProvider constructor.
* #param string $name
* #param string $primaryFieldName
* #param string $requestFieldName
* #param \Magento\Framework\Api\Search\ReportingInterface $reporting
* #param \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder
* #param \Magento\Framework\App\RequestInterface $request
* #param \Magento\Framework\Api\FilterBuilder $filterBuilder
* #param array $meta
* #param array $data
* #throws \Exception
*/
public function __construct(
$name,
$primaryFieldName,
$requestFieldName,
ReportingInterface $reporting,
SearchCriteriaBuilder $searchCriteriaBuilder,
RequestInterface $request,
FilterBuilder $filterBuilder,
array $meta = [],
array $data = []
) {
$data['config']['filter_url_params']['product_id'] = $request->getParam('cppc_product_id', 0);
parent::__construct($name, $primaryFieldName, $requestFieldName, $reporting, $searchCriteriaBuilder, $request, $filterBuilder, $meta, $data);
}
You do not need to use any other function. The reason why this is is because it is also updated with an update URL and that does not have that parameter. By using adding that to the data it also parses that into the update url.
You can see that here (Parent function)
/**
* #return void
*/
protected function prepareUpdateUrl()
{
if (!isset($this->data['config']['filter_url_params'])) {
return;
}
foreach ($this->data['config']['filter_url_params'] as $paramName => $paramValue) {
if ('*' == $paramValue) {
$paramValue = $this->request->getParam($paramName);
}
if ($paramValue) {
$this->data['config']['update_url'] = sprintf(
'%s%s/%s/',
$this->data['config']['update_url'],
$paramName,
$paramValue
);
$this->addFilter(
$this->filterBuilder->setField($paramName)->setValue($paramValue)->setConditionType('eq')->create()
);
}
}
}
By using Magento 2.1.3 after importing tier price cvs file, I can't edit products in backend anymore.The error in backend is
Warning: Illegal string offset 'price_qty' in vendor/magento/module-catalog/Model/Product/Attribute/Backend/Tierprice.php on line 74
In the report, I found 25 reports, the newest is
#1/vendor/magento/modulecatalog/Controller/Adminhtml/Product/Attribute/Validate.php(108): Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate->checkUniqueOption(Object(Magento\Framew
ork\DataObject), Array).
Can someone help me to resolve this problem pls. Thank you so much.
Tierprice.php
namespace Magento\Catalog\Model\Product\Attribute\Backend;
class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice
{
protected $_productAttributeBackendTierprice;
/**
* #param \Magento\Directory\Model\CurrencyFactory $currencyFactory
* #param \Magento\Store\Model\StoreManagerInterface $storeManager
* #param \Magento\Catalog\Helper\Data $catalogData
* #param \Magento\Framework\App\Config\ScopeConfigInterface $config
* #param \Magento\Framework\Locale\FormatInterface $localeFormat
* #param \Magento\Catalog\Model\Product\Type $catalogProductType
* #param \Magento\Customer\Api\GroupManagementInterface $groupManagement
* #param \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $productAttributeTierprice
*/
public function __construct(
\Magento\Directory\Model\CurrencyFactory $currencyFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Helper\Data $catalogData,
\Magento\Framework\App\Config\ScopeConfigInterface $config,
\Magento\Framework\Locale\FormatInterface $localeFormat,
\Magento\Catalog\Model\Product\Type $catalogProductType,
\Magento\Customer\Api\GroupManagementInterface $groupManagement,
\Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\Tierprice $productAttributeTierprice
) {
$this->_productAttributeBackendTierprice = $productAttributeTierprice;
parent::__construct(
$currencyFactory,
$storeManager,
$catalogData,
$config,
$localeFormat,
$catalogProductType,
$groupManagement
);
}
protected function _getResource()
{
return $this->_productAttributeBackendTierprice;
}
protected function _getAdditionalUniqueFields($objectArray)
{
$uniqueFields = parent::_getAdditionalUniqueFields($objectArray);
$uniqueFields['qty'] = $objectArray['price_qty'] * 1;
return $uniqueFields;
}
protected function _getDuplicateErrorMessage()
{
return __('We found a duplicate website, tier price, customer group and quantity.');
}
protected function _isPriceFixed($priceObject)
{
return $priceObject->isTierPriceFixed();
}
public function isScalar()
{
return false;
}
}
I was also having the same issue with tier pricing while product save. I just checked if $objectArray['price_qty'] isset before using it and that resolved my issue temporarily. However, its a temporary fix but works for now
Just replace below function
protected function _getAdditionalUniqueFields($objectArray)
{
$uniqueFields = parent::_getAdditionalUniqueFields($objectArray);
$uniqueFields['qty'] = $objectArray['price_qty'] * 1;
return $uniqueFields;
}
with
protected function _getAdditionalUniqueFields($objectArray)
{
$uniqueFields = parent::_getAdditionalUniqueFields($objectArray);
if(isset($objectArray['price_qty'])){
$uniqueFields['qty'] = $objectArray['price_qty'] * 1;
}
return $uniqueFields;
}
I had the same issue in 2.2.5 EE
after following these steps I managed to get this fixed.
https://github.com/magento/magento2/issues/8426
Backup your database (optional)
Locate your "tier_price" attribute ID (Stores -> Attributes -> Product -> Search for "tier_price") or from eav_attribute table
Search catalog_product_entity_decimal, catalog_product_entity_int, catalog_product_entity_text, and catalog_product_entity_varchar for any records with the attribute_id of your tier_price attribute.
Delete these rows
Try saving product again.
I have the following magento helper class.
class CommissionJunction extends Mage_Core_Helper_Data
{
/**
* Get SKU, quantity, price and discount amount for each product in a given order
* #param object $order
* #return array
*/
private function _getOrderProductsList($order) {
$orderItems = $order->getAllItems();
$purchasedSkus = array();
$count_orderItems = count($orderItems);
for($i = 0; $i < $count_orderItems; $i++) {
$purchasedSkus[$i] = array(
'ITEM' => $orderItems[$i]['sku'],
'QTY' => number_format($orderItems[$i]['qty_ordered'],0), // no decimals
'AMT' => number_format($orderItems[$i]['price'],2) // 2 decimal places
'DCNT' => number_format(abs($orderItems[$i]['discount_amount']),2) */
);
}
return $purchasedSkus;
}
/**
* Get the Universal Data (JSON) Object for Commission Junction.
* This object contains the order details passed on to Commission Junction for reporting purposes
* on the Checkout Success / Order Confirmation page.
* Notes:
* - CID, TYPE AND CURRENCY are hard coded
* #param string $orderId
* #return JSON object Universal Data Object for Commission Junction $json_masterTmsUdp
*/
public function getCommissionJunctionUdo($orderId) {
$order = Mage::getModel('sales/order')->loadByIncrementId($orderId);
$udo = array();
$udo['CID'] = 'XXXX';
$udo['TYPE'] = 'XXXX';
$udo['CURRENCY'] = 'USD';
$udo['OID'] = $orderId;
$udo['DISCOUNT'] = number_format(abs($order->discount_amount),2);
$order_coupon_code = $order->coupon_code;
if(!is_null($order_coupon_code) && !empty($order_coupon_code)) {
$udo['COUPON'] = $order_coupon_code;
}
$udo['PRODUCTLIST'] = self::_getOrderProductsList($order);
if(Mage::getModel('core/cookie')->get('aff_commissionjunction') == 'cjafflx') {
$udo['FIRECJ'] = "TRUE";
}
else {
$udo['FIRECJ'] = "FALSE";
}
$masterTmsUdo['CJ'] = $udo;
$json_masterTmsUdo = json_encode($masterTmsUdo);
return $json_masterTmsUdo;
}
}
And I found this site where they explain how to register a helper class
http://codegento.com/2011/02/creating-a-helper-class/
The only thing I dont know its:
Where in the magento structure should I add this php class?
Where is the config.xml I should edit?
User created classes should be placed in this folder:
app/code/community
Another issue, you should use the same naming convention for your class as Magento uses i.e.
MOduleNameSpace_ComissionJunction_Helper_Data
Also the config XML should be placed in your app/code/community/ MOduleNameSpace/ComissionJunction/etc folder
I have a problem within the magento checkout. With integrating Billsafe Payment plugin I get the following error in checkout process:
HTTP-Error 500 (Internal Server Error):
The error log says:
mod_fcgid: stderr: PHP Fatal error: Call to a member function setName() on a non-object in /var/www/vhosts/domain.com/httpdocs/app/code/community/AwHh/PaymentFee/Helper/Data.php
Any Ideas?
The code of Data.php:
/**
* Check if the extension is active
*
* #return boolean
*/
public function isEnabled()
{
return (bool)Mage::getStoreConfig('payment_services/paymentfee/active');
}
/**
* Check if minimum fee amount, maximum fee amount or percentage rate is given
* #return boolean
*/
public function hasFeeValues()
{
$min = (bool)max(0, Mage::getStoreConfig('payment_services/paymentfee/min_fee_amount'));
$max = (bool)Mage::getStoreConfig('payment_services/paymentfee/max_fee_amount');
$rate = (bool)Mage::getStoreConfig('payment_services/paymentfee/relative_fee');
return ($min || $max || $rate);
}
public function getFeeProductSku()
{
return Mage::getStoreConfig('payment_services/paymentfee/sku');
}
/**
* if item represents fee product
*
* #param Mage_Catalog_Model_Product|Mage_Sales_Model_Item $product
* #return boolean
*/
public function isFeeProduct($product)
{
return ($product->getSku() == $this->getFeeProductSku());
}
public function setFeeProduct($feeProduct)
{
$this->feeProduct = $feeProduct;
}
public function getFeeProduct()
{
if (is_null($this->feeProduct)) {
$this->feeProduct = Mage::getModel('catalog/product')->loadByAttribute('sku', $this->getFeeProductSku());
}
return $this->feeProduct;
}
public function hasFeeProduct()
{
$feeProduct = $this->getFeeProduct();
return ($feeProduct && 0 < $feeProduct->getId());
}
/**
* Obtain the fee that is set for the current payment method
* #return float
*/
public function getPaymentFee()
{
if (!$this->isEnabled()) {
return 0;
}
if (!Mage::getModel('checkout/cart')->getQuote()->getPayment()->hasMethodInstance()) {
return 0;
}
// get the currently set payment method
$payment_model = Mage::getModel('checkout/cart')->getQuote()->getPayment()->getMethodInstance();
// check which methods are enabled for payment fee via backend
$enabled_methods = explode(',', Mage::getStoreConfig('payment_services/paymentfee/payment_methods'));
if (!$payment_model || !in_array($payment_model->getCode(), $enabled_methods)) {
return 0;
}
// return fee if
// (1) a payment method has been selected by the customer
// (2) the selected payment method is enabled for payment fee via backend
// (3) the payment method has a fee
return (float)$payment_model->getFee();
}
/**
* get quote item representing fee
*
* #return Mage_Sales_Model_Quote_Item
*/
protected function getFeeQuoteItem()
{
foreach (Mage::getSingleton('checkout/session')->getQuote()->getItemsCollection() as $item) {
if ($this->isFeeProduct($item->getProduct())) {
return $item;
}
}
}
/**
* Computed amount of payment fee based on backend configuration
* and grand total and attach it to fee product.
*/
public function getUpdatedFeeProduct($product=null, $grandTotal=null)
{
if (!$product) {
$product = $this->getFeeProduct();
}
$product->setName($product->getResource()->getAttributeRawValue($product->getId(), 'name', Mage::app()->getStore()->getId()));
if (!$grandTotal) {
$quote = Mage::getSingleton('checkout/session')->getQuote();
$grandTotal = $quote->getGrandTotal();
$feeAmount = 0;
foreach ($quote->getItemsCollection() as $quoteItem) {
if ($this->isFeeProduct($quoteItem->getProduct())) {
$feeAmount = $quoteItem->getBaseRowTotalInclTax();
continue;
}
}
$grandTotal -= $feeAmount;
}
$min = max(0, Mage::getStoreConfig('payment_services/paymentfee/min_fee_amount'));
$max = Mage::getStoreConfig('payment_services/paymentfee/max_fee_amount');
$rate = Mage::getStoreConfig('payment_services/paymentfee/relative_fee');
//$product->setName($this->__('Payment fee'));
if ($this->getFeeQuoteItem()) {
$product->setTaxPercent($this->getFeeQuoteItem()->getTaxPercent());
}
// first, set payment fee to the price configured in backend
$price = $max;
// If set to zero, do not limit the final fee
if (!$max) {
$max = INF;
}
$product->setCheckoutDescription($this->formatPrice($price))
->setExceedsMaxAmount(false)
->setExceedsMinAmount(false);
// calculate relative fee if given in backend
if ($rate) {
$price = $grandTotal * $rate / 100;
if ($max < $price) {
// calculated relative fee exceeds maximum charge
// -> use maximum charge
$product->setCheckoutDescription($this->formatPrice($max));
$product->setExceedsMaxAmount(true);
$price = $max;
} elseif ($price < $min) {
// calculated relative fee is below minimum charge
// -> use minimum charge
$product->setCheckoutDescription($this->formatPrice($min));
$product->setExceedsMinAmount(true);
$price = $min;
} else {
// calculated relative fee is between minimum and maximum charge
// -> use calculated relative fee
$msg = '%s (%s%% of Total %s)';
$product->setCheckoutDescription($this->__(
$msg,
$this->formatPrice($price),
$rate,
$this->formatPrice($grandTotal)
));
$msg = '%s %s (%s%% of Total %s)';
$product->setName($this->__(
$msg,
$product->getName(),
strip_tags($this->formatPrice($price)),
$rate,
strip_tags($this->formatPrice($grandTotal))
));
}
}
$product->setPriceInclTax($price)
->setPrice($price)
->setFinalPrice($price);
// Make sure fee product is "in stock"
$stockItem = Mage::getModel('cataloginventory/stock_item');
$stockItem->assignProduct($product);
$stockItem->setIsInStock(1);
$stockItem->setManageStock(1);
$stockItem->setQty(10000);
$stockItem->save();
return $product;
}
public function removeFeeFromQuote(Mage_Sales_Model_Quote $quote)
{
foreach ($quote->getItemsCollection() as $quoteItem) {
if ($this->isFeeProduct($quoteItem->getProduct())) {
$quote->removeItem($quoteItem->getId());
}
}
}
}
I had the same problem.
Solution:
You propably didnt add the virtual article.
Go to your Adminarea, choose catalog -> manage article
add a new one and choose virtual article
give some name for it and make sure its activce and NOT visible
set the SKU to something and copy it. you need it later!
set price zero and stock at least 1
you can choose if you give it a higher stock or just one and turn of the stock management.
after saving go to system->configuration->paymenttypes and there you have to
paste the SKU you just copied into the field where it says some with SKU.
after all you have to clear your cache's and there you go :)
A helper class extends extends Mage_Core_Helper_Abstract..
class Mage_Catalog_Helper_Data does not extend varien_object so getName() function will genertae this error.
instead of call getname from helper class object.
use
$model = getmodel('whatever') and then call
$model->getName() from there.
Alternatively you can disable Billsafe under Configuration (System->Configuration->payment) (set Active: no )