Issue with Price Sorting After Implementing Custom Discount Provider and Exclude Product Decorator in Price Rules

How to configure and adapt Aimeos based shops as developer
Forum rules
Always add your Aimeos and PHP version as well as your environment (Linux/Mac/Win)
Spam and unrelated posts will be removed immediately!
User avatar
Paulus-Ragnarr
Posts: 16
Joined: 15 Oct 2024, 07:02

Issue with Price Sorting After Implementing Custom Discount Provider and Exclude Product Decorator in Price Rules

Post by Paulus-Ragnarr » 07 Nov 2024, 04:49

Hi Aimeos,

I’ve implemented a custom price rule using a provider and catalog decorator. The Discount Provider applies a discount as an integer value, and the Exclude Product Decorator excludes certain products based on criteria such as product.codes, catalog.code, and products that already include a rebate in the current price. While the discount application works as expected, we’re experiencing an issue with price sorting. After applying the price rule, the sorting behavior is no longer consistent with the previous functionality.

Could you advise on how to address this sorting issue?

Here's the codes

//Discount Provider

Code: Select all

<?php

namespace Aimeos\MShop\Rule\Provider\Catalog;

class Discount
    extends \Aimeos\MShop\Rule\Provider\Base
    implements \Aimeos\MShop\Rule\Provider\Catalog\Iface, \Aimeos\MShop\Rule\Provider\Factory\Iface
{
    private array $beConfig = [
		'discount' => [
			'code' => 'discount',
			'internalcode' => 'discount',
			'label' => 'Percentage discount',
			'type' => 'number',
			'default' => '0',
			'required' => true,
		],
	];

    public function checkConfigBE( array $attributes ) : array
    {
        $errors = parent::checkConfigBE( $attributes );
        return array_merge( $errors, $this->checkConfig( $this->beConfig, $attributes ) );
    }

    public function getConfigBE(): array
    {
        return array_replace( parent::getConfigBE(), $this->getConfigItems( $this->beConfig ) );
    }

    public function apply( \Aimeos\MShop\Product\Item\Iface $product ) : bool
    {
        $this->update( $product, (int) $this->getConfigValue( 'discount' ) );
        return $this->isLast();
    }

    protected function update( \Aimeos\MShop\Product\Item\Iface $product, float $discount )
    {
        foreach ( $product->getRefItems( 'price' ) as $price )
        {
            $value = $price->getValue();
            $diff = $value * $discount / 100;
            $price->setValue( $value - $diff )->setRebate( $diff );
        }

        foreach ( $product->getRefItems( 'product' ) as $subproduct ) {
            $this->update( $subproduct, $discount );
        }
    }
}
//Exclude Product Decorator

Code: Select all

<?php

namespace Aimeos\MShop\Rule\Provider\Catalog\Decorator;

use Illuminate\Support\Facades\Log;

class ExcludeProduct extends \Aimeos\MShop\Rule\Provider\Catalog\Decorator\Base implements \Aimeos\MShop\Rule\Provider\Catalog\Decorator\Iface
{
    private array $codes;
    private array $productCodes;
    private string $excludeCatalog = 'soidukid,kawasaki,polaris,triumph,indianvoge,kinkekaardid';

    private array $beConfig = [
        'category.code' => [
            'code' => 'category.code',
            'internalcode' => 'category.code',
            'label' => 'Except category codes',
            'type' => 'string',
            'default' => '',
            'required' => true,
        ],
        'product.code' => [
            'code' => 'product.code',
            'internalcode' => 'product.code',
            'label' => 'Except product codes',
            'default' => '',
            'required' => false,
        ]
    ];

    /**
	 * Initializes the rule instance
	 *
	 * @param \Aimeos\MShop\ContextIface $context Context object with required objects
	 * @param \Aimeos\MShop\Rule\Item\Iface $item Rule item object
	 * @param \Aimeos\MShop\Rule\Provider\Iface $provider Rule provider object
	 */
    public function __construct(
        \Aimeos\MShop\ContextIface $context,
        \Aimeos\MShop\Rule\Item\Iface $item,
        \Aimeos\MShop\Rule\Provider\Iface $provider
    ) {
        parent::__construct($context, $item, $provider);

        $this->beConfig['category.code']['default'] = $this->excludeCatalog;
        $this->codes = array_filter(explode(',', str_replace(' ', '', $this->getConfigValue('category.code', ''))));
        $this->productCodes = array_filter(explode(',', str_replace(' ', '', $this->getConfigValue('product.code', ''))));
    }

    /**
	 * Checks the backend configuration attributes for validity.
	 *
	 * @param array $attributes Attributes added by the shop owner in the administraton interface
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
	 */
    public function checkConfigBE(array $attributes): array
    {
        $errors = parent::checkConfigBE($attributes);
        return array_merge($errors, $this->checkConfig($this->beConfig, $attributes));
    }

    /**
	 * Returns the configuration attribute definitions of the provider to generate a list of available fields and
	 * rules for the value of each field in the administration interface.
	 *
	 * @return array List of attribute definitions implementing \Aimeos\Base\Critera\Attribute\Iface
	 */
    public function getConfigBE(): array
    {
        return array_merge(parent::getConfigBE(), $this->getConfigItems($this->beConfig));
    }

    /**
	 * Applies the rule to the given products
	 *
	 * @param \Aimeos\MShop\Product\Item\Iface $product Product the rule should be applied to
	 * @return bool True if rule is the last one, false to continue with further rules
	 */
    public function apply(\Aimeos\MShop\Product\Item\Iface $product): bool
    {
        if (in_array($product->getCode(), $this->productCodes) || $product->getConfigValue('is_vehicle') == 1) {
            return false;
        }

        foreach ($product->getRefItems('price') as $priceItem) {
            if ($priceItem->getRebate() > 0) {
                return false;
            }
        }

        foreach ($product->getRefItems('catalog') as $catItem) {
            if (in_array($catItem->getCode(), $this->codes)) {
                return false;
            }
        }

        return $this->getProvider()->apply($product);
    }
}
// Jsonapi query

Code: Select all

http://localhost:8000/jsonapi/product?page%5Boffset%5D=0&page%5Blimit%5D=25&include=attribute,media,price,product,product/property,text,catalog,supplier,stock,tag,size&filter%5Bf_catid%5D=37&sort=-price
Attachments
Screenshot 2024-11-07 at 12.50.48 PM.png
Screenshot 2024-11-07 at 12.50.48 PM.png (164.55 KiB) Viewed 37838 times
Screenshot 2024-11-07 at 12.44.54 PM.png
Screenshot 2024-11-07 at 12.44.54 PM.png (40.52 KiB) Viewed 37839 times
Screenshot 2024-11-07 at 12.42.52 PM.png
Screenshot 2024-11-07 at 12.42.52 PM.png (203.71 KiB) Viewed 37839 times

User avatar
aimeos
Administrator
Posts: 8356
Joined: 01 Jan 1970, 00:00

Re: Issue with Price Sorting After Implementing Custom Discount Provider and Exclude Product Decorator in Price Rules

Post by aimeos » 08 Nov 2024, 07:36

Short answer: You can't!
You are manipulating the prices after they have been sorted by the database. The only thing you can do is to resort the products by the new price in the catalog list template for that page but then, the prices on the next page can still be higher/lower than the prices on the current page.
Professional support and custom implementation are available at Aimeos.com
If you like Aimeos, Image give us a star

User avatar
Paulus-Ragnarr
Posts: 16
Joined: 15 Oct 2024, 07:02

Re: Issue with Price Sorting After Implementing Custom Discount Provider and Exclude Product Decorator in Price Rules

Post by Paulus-Ragnarr » 11 Nov 2024, 04:53

Thank you for the reply.

Is there any other option for price sort to include discounted prices or for price rule functionality(discounts)?

This functionality is essential for us, as we need to apply discounts across all products without manually adjusting each product’s details. Our store has nearly 15,000 products with various discount specifications, and the price rule feature has been invaluable in managing these efficiently.

Thank you!

Update: We are considering implementing a custom price rule(discount) panel where an admin can create a discount that generates an additional price for the product with a "sale-price" type. We’re also exploring ways to add this "sale-price" to the index_price to enable sorting by discounted prices.

User avatar
aimeos
Administrator
Posts: 8356
Joined: 01 Jan 1970, 00:00

Re: Issue with Price Sorting After Implementing Custom Discount Provider and Exclude Product Decorator in Price Rules

Post by aimeos » 12 Nov 2024, 08:34

As long as you use dynamic price rules, sorting by price won't work as expected if the products are not all only in one category where you apply the same discount. Then, the relative order of the product prices is still fine.
Professional support and custom implementation are available at Aimeos.com
If you like Aimeos, Image give us a star

Post Reply