Upgrade the Discount module

Edit on GitHub

Upgrading from version 7.* to version 9.0.0

In order to dismantle the Horizontal Barrier and enable partial module updates on projects, a Technical Release took place. Public API of source and target major versions are equal. No migration efforts are required. Please contact us if you have any questions.

Upgrading from version 6.* to version 7.*

The seventh version of the Discount module introduces the Minimum Quantity of Items value for discounts. This functionality allows you to define discounts that will be applied only when the number of items which satisfies the conditions, is equal to or greater than the defined amount.

To achieve this, we have added a new database field: spy_discount.minimum_item_amount.

To upgrade the Discount module with this change, run:

console propel:install

This will generate a propel migration file as well update the database and the model for the Minimum Quantity of Items functionality to take effect.

Upgrading from version 5.* to version 6.*

  1. Update/install spryker/discount to at least 6.0.0 version.
  2. Run vendor/bin/console transfer:generate to generate the new transfer objects.
  3. Install the new database tables by running vendor/bin/console propel:diff. Propel should generate a migration file with the changes.
  4. Run vendor/bin/console propel:migrate to apply the database changes.
  5. Generate ORM models by running the command:
vendor/bin/console propel:model:build

This command will generate some new classes in your project under the \Orm\Zed\Discount\Persistence namespace. It is important to make sure that they extend the base classes from the Spryker core, e.g.:

  • \Orm\Zed\Discount\Persistence\SpyDiscountStore extends \Spryker\Zed\Discount\Persistence\Propel\AbstractSpyDiscountStore
  • \Orm\Zed\Discount\Persistence\SpyDiscountStoreQuery extends \Spryker\Zed\Discount\Persistence\Propel\AbstractSpyDiscountStoreQuery
  1. Each row in the newly created spy_discount_store table represents a connection between a Store and a Discount, meaning that a specific discount is available in that specific Store.

To migrate the spy_discount_store table, create connections between your discounts and the desired stores.

Example migration for multiple (or single) stores

PostgreSQL:

    INSERT INTO spy_discount_store (id_discount_store, fk_discount, fk_store)
    SELECT nextval('id_discount_store_pk_seq'), id_discount, id_store FROM spy_discount, spy_store;

MySQL:

    INSERT INTO spy_discount_store (fk_discount, fk_store)
    SELECT id_discount, id_store FROM spy_discount, spy_store;
  1. To populate current Store information into the Quote transfer object, the StoreQuoteTransferExpanderPlugin has to be provided through the QuoteDependencyProvider::getQuoteTransferExpanderPlugins().

Example plugin registration

<?php
namespace Pyz\Client\Quote;

use Spryker\Client\Quote\QuoteDependencyProvider as SprykerQuoteDependencyProvider;
use Spryker\Client\Store\Plugin\StoreQuoteTransferExpanderPlugin;

class QuoteDependencyProvider extends SprykerQuoteDependencyProvider
{
    /**
     * @param \Spryker\Client\Kernel\Container $container
     *
     * @return \Spryker\Client\Quote\Dependency\Plugin\QuoteTransferExpanderPluginInterface[]
     */
    protected function getQuoteTransferExpanderPlugins($container)
    {
        return [
            new StoreQuoteTransferExpanderPlugin(),
        ];
    }
}    
  1. To allow Discount in the Back Office to handle multi-store concept (even if you are using single-store), FormTypeInterface has to be provided through DiscountDependencyProvider::getStoreRelationFormTypePlugin() to handle store relation. You can use the already implemented StoreRelationToggleFormTypePlugin.

Example plugin registration

<?php
namespace Pyz\Zed\Discount;

use Spryker\Zed\Discount\DiscountDependencyProvider as SprykerDiscountDependencyProvider;
use Spryker\Zed\Store\Communication\Plugin\Form\StoreRelationToggleFormTypePlugin;

class DiscountDependencyProvider extends SprykerDiscountDependencyProvider
{
    /**
     * @return \Spryker\Zed\Kernel\Communication\Form\FormTypeInterface
     */
    protected function getStoreRelationFormTypePlugin()
    {
        return new StoreRelationToggleFormTypePlugin();
    }
}
  1. A bug was fixed in our Demoshop implementation when displaying promotion items using DiscountPromotion/Theme/default/discount-promotion/item-list.twig. In case you used it, please amend your implementation to check the same variable for number of elements and iterating through.

Modified version

{% if promotionStorageProducts|length > 0 %}
    <div class="small-12 columns">
        <h1> {{ 'cart.promotion.items' | trans }}</h1>
        {% for promotionStorageProduct in promotionStorageProducts %}
            {% include '@DiscountPromotion/discount-promotion/item.twig' %}
        {% endfor %}
</div>
{% endif %}
  1. The following classes’ constructor dependencies were altered, please check if you have customized any of them or their constructor method:
  • Calculator/Discount
  • DiscountConfigurationHydrate
  • DiscountPersist
  • GeneralForm
  • DiscountsTable
  1. The following methods were enhanced, please check if you have customized any of them:
  • Calculator/Discount::retrieveActiveCartAndVoucherDiscounts()
  • DiscountFormDataProvider::createDiscountGeneralTransferDefaults()
  • DiscountConfigurationHydrate::getByIdDiscount()
  • DiscountCommunicationFactory::getVoucherForm()
  1. The following methods/classes were removed or renamed, please check if you have customized any of them:
  • DiscountConfigurationHydrate::setDiscountConfigurationExpanderPlugins()
  • DiscountPersist::setDiscountPostCreatePlugins()
  • DiscountPersist::setDiscountPostUpdatePlugins()
  • DiscountQueryContainerInterface::queryDiscountsBySpecifiedVouchers()
  • DiscountQueryContainerInterface::queryActiveCartRules()
  • Business/Persistence/DiscountOrderSaver
  • Business/Persistence/DiscountOrderSaverInterface
  • Communication/Plugin/Sales/DiscountOrderSavePlugin
  • DiscountFacadeInterface::saveOrderDiscounts()
  • DiscountFacade::saveOrderDiscounts()
  • DiscountCommunicationFactory::createGeneralFormType()
  • DiscountCommunicationFactory::createCalculatorFormType()
  • DiscountCommunicationFactory::createConditionsFormType()
  • DiscountCommunicationFactory::createVoucherFormType()
  • DiscountCommunicationFactory::createVoucherForm()
  • CalculatorForm::getName()
  • ConditionsForm::getName()
  • DiscountForm::getName()
  • GeneralForm::getName()
  • VoucherForm::getName()
  • DiscountCommunicationFactory::createDiscountForm()
  • DiscountCommunicationFactory::createVoucherForm()
  1. You can find additional information on the Discount module release page or by checking out our Demoshop implementation for implementation example and idea.
  2. You are ready now to use Discount Zed Admin UI and manage discounts per Store.

Upgrading from version 4.* to version 5.*

In the Discount module version 5, we have introduced multi-currency support for fixed discount calculation. This update also includes:

  • Support for net/gross amounts.
  • Currency decision rule - to filter discounts by currency.
  • PriceMode decision rule - to filter discounts by price mode(net/gross).
  • Database schema changes to store discount amounts and fk_store for later multi store support.
  • Sales table changed deprecated column type from decimal to int as discount amounts were already stored as integers.
  • CalculatorInterface renamed to CalculatorTypeInterface, concrete calculators Fixed and Percentage rename to FixedType and PercentageType accordingly.
  1. Run the following command:
composer update spryker/discount spryker/currency spryker/store spryker/money spryker/calculation spryker/cart spryker/kernel.

Install the new module to be able to use the new currency plugin.

composer require spryker/cart-currency-connector
  1. Run schema migration:
CREATE SEQUENCE "spy_discount_amount_pk_seq";

CREATE TABLE "spy_discount_amount"
(
  "id_discount_amount" INTEGER NOT NULL,
  "fk_currency" INTEGER NOT NULL,
  "fk_discount" INTEGER NOT NULL,
  "gross_amount" INTEGER,
  "net_amount" INTEGER,
  PRIMARY KEY ("id_discount_amount")
);

CREATE UNIQUE INDEX "spy_discount_amount-unique-currency-discount" ON "spy_discount_amount" ("fk_currency","fk_discount");

ALTER TABLE "spy_discount" ADD CONSTRAINT "spy_discount-fk_store"
    FOREIGN KEY ("fk_store")
    REFERENCES "spy_store" ("id_store");

ALTER TABLE "spy_sales_discount" ALTER COLUMN "amount" TYPE INTEGER;
  1. Then, run console commands:
vendor/bin/console propel:model:build
vendor/bin/console transfer:generate
  1. We have prepared a console command, discount migration script, to migrate old discount amounts to a new structure. Place it in your project Discount module and include in the Console module dependency provider. This console command will move all discount amount with fixed calculator plugin to a new discount amount tables. It won’t delete old values.

  2. Register a new currency plugin to reload cart items when currency is changed. Take \Spryker\Yves\CartCurrencyConnector\CurrencyChange\RebuildCartOnCurrencyChangePluginand place it to \Pyz\Yves\Currency\CurrencyDependencyProvider::getCurrencyPostChangePlugins plugin stack. This way we make sure that when currency in Yves is changed, we have updated product prices and discounts.

Discount Amounts Migration Console Command

<?php

/**

 * Copyright © 2017-present Spryker Systems GmbH. All rights reserved.
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
 */

namespace Pyz\Zed\Discount\Communication\Console;

use Orm\Zed\Currency\Persistence\SpyCurrencyQuery;
use Orm\Zed\Discount\Persistence\SpyDiscountAmount;
use Orm\Zed\Discount\Persistence\SpyDiscountQuery;
use Spryker\Shared\Kernel\Store;
use Spryker\Zed\Discount\DiscountDependencyProvider;
use Spryker\Zed\Kernel\Communication\Console\Console;
use Spryker\Zed\PropelOrm\Business\Runtime\ActiveQuery\Criteria;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ConfirmationQuestion;

class MigrateDiscountsConsole extends Console
{
    const COMMAND_NAME = 'discount:migrate';
    const COMMAND_DESCRIPTION = 'Console command to migrate discount amounts to multi currency implementation.';

    /**
     * @return void
     */
    protected function configure()
    {
        $this->setName(static::COMMAND_NAME);
        $this->setDescription(static::COMMAND_DESCRIPTION);

        parent::configure();
    }


    /**
     * @param \Symfony\Component\Console\Input\InputInterface $input
     * @param \Symfony\Component\Console\Output\OutputInterface $output
     *
     * @return void
     */
    protected function execute(InputInterface $input, OutputInterface $output)
    {
        $discounts = SpyDiscountQuery::create()
            ->filterByCalculatorPlugin(DiscountDependencyProvider::PLUGIN_CALCULATOR_FIXED)
            ->useDiscountAmountQuery(null, Criteria::LEFT_JOIN)
                ->filterByIdDiscountAmount(null,Criteria::EQUAL)
            ->endUse()
            ->find();

        if (count($discounts) === 0) {
            $output->writeln('There are no discounts to migrate.');
            return;
        }

        $helper = $this->getHelper('question');
        $question = new ConfirmationQuestion(
            sprintf('Migrate %s discounts? (y|n)', count($discounts)),
            false
        );

        if (!$helper->ask($input, $output, $question)) {
            $output->writeln('Aborted.');
            return;
        }

        $currencyIsoCode = Store::getInstance()->getCurrencyIsoCode();

        $currencyEntity = SpyCurrencyQuery::create()
            ->filterByCode($currencyIsoCode)
            ->findOne();

        foreach ($discounts as $discountEntity) {
            $amount = $discountEntity->getAmount();

            $discountAmountEntity = new SpyDiscountAmount();
            $discountAmountEntity->setGrossAmount($amount);
            $discountAmountEntity->setFkDiscount($discountEntity->getIdDiscount());
            $discountAmountEntity->setFkCurrency($currencyEntity->getIdCurrency());
            $discountAmountEntity->save();

            $output->writeln(sprintf('Discount with id %s updated.', $discountEntity->getIdDiscount()));
        }

        $output->writeln('done.');
    }

}
?>