From e542265d0badc404cd68078d27f6b7eeca93d0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Thu, 16 Oct 2025 01:20:38 +0200 Subject: [PATCH 1/3] [WIP] Current status --- code_samples/discounts/config/services.yaml | 29 ++++ .../PurchaseParityValueFormatter.php | 17 ++ docs/discounts/discounts.md | 3 +- docs/discounts/extend_discounts.md | 152 ++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 code_samples/discounts/config/services.yaml create mode 100644 code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php create mode 100644 docs/discounts/extend_discounts.md diff --git a/code_samples/discounts/config/services.yaml b/code_samples/discounts/config/services.yaml new file mode 100644 index 0000000000..0706a557c9 --- /dev/null +++ b/code_samples/discounts/config/services.yaml @@ -0,0 +1,29 @@ +# This file is the entry point to configure your own services. +# Files in the packages/ subdirectory configure your dependencies. + +# Put parameters here that don't need to change on each machine where the app is deployed +# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration +parameters: + +services: + # default configuration for services in *this* file + _defaults: + autowire: true # Automatically injects dependencies in your services. + autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + + # makes classes in src/ available to be used as services + # this creates a service per class whose id is the fully-qualified class name + App\: + resource: '../src/' + exclude: + - '../src/DependencyInjection/' + - '../src/Entity/' + - '../src/Kernel.php' + + # add more service definitions when explicit configuration is needed + # please note that last definitions always *replace* previous ones + + App\Discounts\CustomDiscountValueFormatter: + tags: + - name: ibexa.discounts.value.formatter + rule_type: 'referral_rule' diff --git a/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php b/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php new file mode 100644 index 0000000000..79049bffb5 --- /dev/null +++ b/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php @@ -0,0 +1,17 @@ +`is_product_in_product_codes()` | `true/false`, depending if the product is part of the given list.| Conditions, rules | +| Variable | `cart` | [Cart object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-CartInterface.html) associated with current context.| Conditions, rules | +| Variable | `currency` | [Currency object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CurrencyInterface.html) of the current siteaccess. | Conditions, rules | +| Variable | `customer_group` | [Customer group object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-CustomerGroupInterface.html) associated with given price context or the current user.| Conditions, rules | +| Variable | `amount` | Original price of the product | Rules | +| Variable | `product` | [Product object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-ProductInterface.html)| Rules | + +### Custom expressions + +You can create your own variables and functions to make creating the conditions easier. +To create the condition checking the registration date, the following example will use an additinal variable and a function: + +- `current_user`, a variable with the current [User object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-User-User.html) + +To add it, create a class implementing the [`DiscountVariablesResolverInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountVariablesResolverInterface.html): + +``` php +todo: verify +``` + +And mark it as a service using the `ibexa.discounts.expression_language.variable_resolver` service tag: + +``` yaml +todo: verify +``` + +- `is_anniversary()`, a function returning a boolean value indicating if the two dates passed as arguments fall on the same day. + +``` php +todo: verify +``` + +Mark it as a service using the `ibexa.discounts.expression_language.function` service tag and specify the function name in the service definition. + +``` yaml +todo: verify +``` + +Two new expressions are now available for use in custom conditions and rules. + +### Implement custom condition + +Now, create the condition by creating a class implementing the [`DiscountConditionInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountConditionInterface.html). + +``` php +todo: verify +``` + +The expression can evaluate to `true` or `false` depending on the custom expressions values. +An additional variable, `today`, is defined to store the current date for comparison. + +For each condition class you must create a dedicated condition factory, a class implementing the `\Ibexa\Discounts\Repository\DiscountCondition\DiscountConditionFactoryInterface` inteface. + +This allows you to create conditions when working in the context of the Symfony service container. + +``` php +todo +``` + +Mark it as a service using the `ibexa.discounts.condition.factory` service tag and specify the condition's identifier. + +``` yaml +todo +``` + +## Create custom rules + +To implement a custom rule, create a class implementing the [`DiscountRuleInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountRuleInterface.html). + +The following example implements a [purchasing power parity discount](https://en.wikipedia.org/wiki/Purchasing_power_parity), adjusting product's price in the cart based on buyer's region. + +``` php +todo +``` + +As with conditions, create a dedicated rule factory. + +``` php +todo +``` + +Mark it as a service using the `ibexa.discounts.condition.factory` service tag and specify the rule's type. + +``` yaml +todo +``` + +### Custom discount formatting + +You can adjust how each discount type is displayed when using the [`ibexa_discounts_render_discount_badge` Twig function](discounts_twig_functions.md#ibexa_discounts_render_discount_badge) by implementing a custom formatter. + +To do it, create a class implementing the [`DiscountValueFormatterInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountValueFormatterInterface.html) and use the `ibexa.discounts.value.formatter` service tag: + +``` php +todo +``` + +``` yaml +todo +``` + +## Change discount priority + +You can change the [the defualt discount priority](discounts_guide.md#discounts-priority) by creating a class implementing the [`DiscountPrioritizationStrategyInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountPrioritizationStrategyInterface.html) and aliasing to it the default implementation. + +The example below decorates the default implementation to prioritize recently created discounts above all the others. + +``` php +todo +``` + +``` yaml +todo +``` + +## Form integration + +### Condition + +### Rules diff --git a/mkdocs.yml b/mkdocs.yml index 68158e1d8b..31d1649fe4 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -412,6 +412,7 @@ nav: - Install Discounts: discounts/install_discounts.md - Customize Discounts: discounts/configure_discounts.md - Discounts API: discounts/discounts_api.md + - Extend Discounts: discounts/extend_discounts.md - Customer management: - Customer Portal: customer_management/customer_portal.md - Customer Portal guide: customer_management/customer_portal_guide.md From d10add4d0fb2192e973422d5b7f766f7ca536ebd Mon Sep 17 00:00:00 2001 From: mnocon Date: Wed, 15 Oct 2025 23:35:55 +0000 Subject: [PATCH 2/3] PHP & JS CS Fixes --- .../discounts/src/Discounts/PurchaseParityValueFormatter.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php b/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php index 79049bffb5..a88ffceac5 100644 --- a/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php +++ b/code_samples/discounts/src/Discounts/PurchaseParityValueFormatter.php @@ -4,14 +4,13 @@ namespace App\Discounts; -use Ibexa\Contracts\Discounts\Value\DiscountInterface; use Ibexa\Contracts\Discounts\DiscountValueFormatterInterface; +use Ibexa\Contracts\Discounts\Value\DiscountInterface; use Money\Money; final class PurchaseParityValueFormatter implements DiscountValueFormatterInterface { public function format(DiscountInterface $discount, ?Money $money = null): string { - } } From 1eab7aeca874bdb04911d764932841b704958c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Noco=C5=84?= Date: Fri, 31 Oct 2025 10:11:20 +0100 Subject: [PATCH 3/3] Started working on Forms --- docs/discounts/extend_discounts.md | 24 ++++++++++++++--------- docs/discounts/extend_discounts_wizard.md | 19 ++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 docs/discounts/extend_discounts_wizard.md diff --git a/docs/discounts/extend_discounts.md b/docs/discounts/extend_discounts.md index 63de341b44..cf99cc9c41 100644 --- a/docs/discounts/extend_discounts.md +++ b/docs/discounts/extend_discounts.md @@ -11,6 +11,13 @@ month_change: true By extending [Discounts](discounts_guide.md), you can increase flexibility and control over how promotions are applied to suit your unique business rules. Together with the existing [events](event_reference.md) and the [Discounts PHP API](discounts_api.md), extending discounts gives you the ability to cover additional use cases related to selling products. +!!! tip + + If you prefer learning from videos, two presentations from Ibexa Summit 2025 cover the Discounts feature: + + - Konrad Oboza: [Introduction to the Discounts system in Ibexa DXP](https://www.youtube.com/watch?v=kTgtxY38srw) + - Paweł Niedzielski: [Extending new Discounts to suit your needs](https://www.youtube.com/watch?v=pDJxEKJLwPs) + ## Create custom conditions With custom [conditions](discounts_api.md#conditions) you can create more advanced discounts that apply only in specific scenarios. @@ -28,7 +35,7 @@ The following expressions are available for conditions and rules: | --- | --- | --- | --- | | Function | `get_current_region()` | [Region object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-ProductCatalog-Values-RegionInterface.html) of the current siteaccess.| Conditions, rules | | Function | `is_in_category()` | `true/false`, depending if a product belongs to given [product categories](pim_guide.md#product-categories).| Conditions, rules | -| Function | `is_user_in_customer_group()` | `true/false`, depending if a user belongs to given [customer groups](customer_groups.md). | Conditions, rules | +| Function | `is_user_in_customer_group()` | `true/false`, depending if an user belongs to given [customer groups](customer_groups.md). | Conditions, rules | | Function | `calculate_purchase_amount()` | Purchase amount, calculated for all products in the cart before the discounts are applied.| Conditions, rules | | Function | `is_product_in_product_codes()` | `true/false`, depending if the product is part of the given list.| Conditions, rules | | Variable | `cart` | [Cart object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Cart-Value-CartInterface.html) associated with current context.| Conditions, rules | @@ -40,7 +47,7 @@ The following expressions are available for conditions and rules: ### Custom expressions You can create your own variables and functions to make creating the conditions easier. -To create the condition checking the registration date, the following example will use an additinal variable and a function: +To create the condition checking the registration date, the following example uses an additional variable and a function: - `current_user`, a variable with the current [User object](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Core-Repository-Values-User-User.html) @@ -79,7 +86,7 @@ todo: verify ``` The expression can evaluate to `true` or `false` depending on the custom expressions values. -An additional variable, `today`, is defined to store the current date for comparison. +An additional variable, `date`, is defined to store the current date for comparison. For each condition class you must create a dedicated condition factory, a class implementing the `\Ibexa\Discounts\Repository\DiscountCondition\DiscountConditionFactoryInterface` inteface. @@ -95,11 +102,13 @@ Mark it as a service using the `ibexa.discounts.condition.factory` service tag a todo ``` +To learn how to integrate the custom conditions into the back office, see [Extend Discounts wizard](extend_discounts_wizard.md). + ## Create custom rules To implement a custom rule, create a class implementing the [`DiscountRuleInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-Value-DiscountRuleInterface.html). -The following example implements a [purchasing power parity discount](https://en.wikipedia.org/wiki/Purchasing_power_parity), adjusting product's price in the cart based on buyer's region. +The following example implements a [purchasing power parity](https://en.wikipedia.org/wiki/Purchasing_power_parity) discount, adjusting product's price in the cart based on buyer's region. ``` php todo @@ -117,6 +126,8 @@ Mark it as a service using the `ibexa.discounts.condition.factory` service tag a todo ``` +To learn how to integrate the custom rules into the back office, see [Extend Discounts wizard](extend_discounts_wizard.md). + ### Custom discount formatting You can adjust how each discount type is displayed when using the [`ibexa_discounts_render_discount_badge` Twig function](discounts_twig_functions.md#ibexa_discounts_render_discount_badge) by implementing a custom formatter. @@ -145,8 +156,3 @@ todo todo ``` -## Form integration - -### Condition - -### Rules diff --git a/docs/discounts/extend_discounts_wizard.md b/docs/discounts/extend_discounts_wizard.md new file mode 100644 index 0000000000..0ec4614ee8 --- /dev/null +++ b/docs/discounts/extend_discounts_wizard.md @@ -0,0 +1,19 @@ +--- +description: Integrate custom rules and conditions into the back office forms. +editions: + - lts-update + - commerce +month_change: true +--- + +## Extend Discounts wizard + +To allow using your [custom conditions and rules](extend_discounts.md#create-custom-conditions) by the store managers, you need to integrate them into the back office discounts creation form. + +The [`DiscountFormMapperInterface`](/api/php_api/php_api_reference/classes/Ibexa-Contracts-Discounts-DiscountFormMapperInterface.html) is the service responsible for translating the form data into structures used by the PHP API. + +The form uses a data driver approach, where the mapper provides all the data to the form and the form adjusts and created the fields as neccessary. + +### Condition + +### Rules