Repository: shopware/shopware
Pull request #7338: fix: added missing cart errors after recalculation
Author: login: En0Ma1259
Labels: name: component/promotions
Created: 2025-03-06T13:01:31Z | Merged: 2025-03-10T10:26:57Z

### 1. Why is this change necessary?
When an error occurs while adding promotion codes to an order, such as when they are not eligible, the response does not include the associated errors. Consequently, no error notification is generated.

When adding a promotion to an order that is not eligible (e.g., due to rule validation), an error or informational notification should be included in the response. Errors like PromotionNotEligibleError are collected during cart processing, but due to the missing setErrors() statement, they are not included in the final response.

### 2. What does this change do, exactly?
Set errors to the validated cart in `Shopware\Core\Checkout\Cart\Order\RecalculationService::recalculateCart`

### 3. Describe each step to reproduce the issue or behaviour.

1. Create an order
2. Create a rule that will validate as false for the created order
3. Create promotion with the rule as “order rule” + discount + fixed promotion code
4. Try to add the promoition code to the order
5. Result: nothing really happends because the error is not displayed (check via debug in the src/Core/Checkout/Cart/Processor::proceses() there you can see the filled error collection on the $original cart, which is not transfered to the outgoing $cart)

### 4. Please link to the relevant issues (if any).
https://github.com/shopware/shopware/issues/7020

### 5. Checklist

- [x] I have written tests and verified that they fail without my change
- [x] I have created a [changelog file](https://github.com/shopware/platform/blob/trunk/adr/2020-08-03-implement-new-changelog.md) with all necessary information about my changes
- [ ] I have written or adjusted the documentation according to my changes
- [ ] This change has comments for package types, values, functions, and non-obvious lines of code
- [x] I have read the contribution requirements and fulfill them.



Diff:
diff --git a/changelog/_unreleased/2025-03-06-added-cart-errors-after-recalculation.md b/changelog/_unreleased/2025-03-06-added-cart-errors-after-recalculation.md
new file mode 100644
index 00000000000..f525344f058
--- /dev/null
+++ b/changelog/_unreleased/2025-03-06-added-cart-errors-after-recalculation.md
@@ -0,0 +1,10 @@
+---
+title: Added cart errors after recalculation
+issue: https://github.com/shopware/shopware/issues/7020
+author_github: @En0Ma1259
+---
+# Core
+* Added cart errors to validated cart after recalculation in `src/Core/Checkout/Cart/Order/RecalculationService.php`
+___
+# Administration
+* Changed method from `NotificaitonSuccess` to `NotificationInfo` for promotion errors level "info" in `src/Administration/Resources/app/administration/src/module/sw-order/component/sw-order-promotion-field/index.js` 
diff --git a/src/Administration/Resources/app/administration/src/module/sw-order/component/sw-order-promotion-field/index.js b/src/Administration/Resources/app/administration/src/module/sw-order/component/sw-order-promotion-field/index.js
index f7cdb1cbc63..670d3b3871b 100644
--- a/src/Administration/Resources/app/administration/src/module/sw-order/component/sw-order-promotion-field/index.js
+++ b/src/Administration/Resources/app/administration/src/module/sw-order/component/sw-order-promotion-field/index.js
@@ -275,7 +275,7 @@ export default {
             Object.values(response.data.errors).forEach((value) => {
                 switch (value.level) {
                     case 0: {
-                        this.createNotificationSuccess({
+                        this.createNotificationInfo({
                             message: value.message,
                         });
                         break;
diff --git a/src/Core/Checkout/Cart/Order/RecalculationService.php b/src/Core/Checkout/Cart/Order/RecalculationService.php
index 7afafbcd541..8be72d5f40f 100644
--- a/src/Core/Checkout/Cart/Order/RecalculationService.php
+++ b/src/Core/Checkout/Cart/Order/RecalculationService.php
@@ -7,6 +7,7 @@
 use Shopware\Core\Checkout\Cart\CartException;
 use Shopware\Core\Checkout\Cart\CartRuleLoader;
 use Shopware\Core\Checkout\Cart\Delivery\Struct\DeliveryPosition;
+use Shopware\Core\Checkout\Cart\Error\Error;
 use Shopware\Core\Checkout\Cart\Exception\CustomerNotLoggedInException;
 use Shopware\Core\Checkout\Cart\LineItem\LineItem;
 use Shopware\Core\Checkout\Cart\Order\Transformer\AddressTransformer;
@@ -360,9 +361,10 @@ private function recalculateCart(Cart $cart, SalesChannelContext $context): Cart
             $cart = $this->processor->process($cart, $live, $behavior);
 
             // validate cart against the context rules
-            $validated = $this->cartRuleLoader->loadByCart($live, $cart, $behavior);
+            $validatedCart = $this->cartRuleLoader->loadByCart($live, $cart, $behavior)->getCart();
+            $validatedCart->addErrors(...$cart->getErrors()->filter(fn (Error $error) => !$error->isPersistent()));
 
-            return $validated->getCart();
+            return $validatedCart;
         });
     }
 
diff --git a/src/Core/Checkout/Promotion/Cart/PromotionCartDeletedInformationError.php b/src/Core/Checkout/Promotion/Cart/PromotionCartDeletedInformationError.php
index 8128d2b7309..ff35f3d8d0b 100644
--- a/src/Core/Checkout/Promotion/Cart/PromotionCartDeletedInformationError.php
+++ b/src/Core/Checkout/Promotion/Cart/PromotionCartDeletedInformationError.php
@@ -20,7 +20,7 @@ public function __construct(LineItem $discountLineItem)
         $this->name = $discountLineItem->getLabel() ?? '';
         $this->discountLineItemId = $discountLineItem->getId();
         $this->message = \sprintf(
-            'Discount %s has been added',
+            'Promotion %s has been deleted',
             $this->name
         );
         parent::__construct($this->message);
diff --git a/tests/unit/Core/Checkout/Cart/Order/RecalculationServiceTest.php b/tests/unit/Core/Checkout/Cart/Order/RecalculationServiceTest.php
index 433f81fac08..2cb6d384690 100644
--- a/tests/unit/Core/Checkout/Cart/Order/RecalculationServiceTest.php
+++ b/tests/unit/Core/Checkout/Cart/Order/RecalculationServiceTest.php
@@ -8,6 +8,8 @@
 use Shopware\Core\Checkout\Cart\Cart;
 use Shopware\Core\Checkout\Cart\CartBehavior;
 use Shopware\Core\Checkout\Cart\CartRuleLoader;
+use Shopware\Core\Checkout\Cart\Error\Error;
+use Shopware\Core\Checkout\Cart\Error\ErrorCollection;
 use Shopware\Core\Checkout\Cart\LineItem\LineItem;
 use Shopware\Core\Checkout\Cart\Order\OrderConversionContext;
 use Shopware\Core\Checkout\Cart\Order\OrderConverter;
@@ -456,6 +458,67 @@ public function testRecalculateOrderWithEmptyLineItems(): void
         $recalculationService->recalculateOrder($orderEntity->getId(), $this->context);
     }
 
+    public function testSetCartErrorToValidatedCart(): void
+    {
+        $order = $this->orderEntity();
+
+        $entityRepository = $this->createMock(EntityRepository::class);
+        $entityRepository->method('search')->willReturnOnConsecutiveCalls(
+            new EntitySearchResult('order', 1, new OrderCollection([$order]), null, new Criteria(), $this->salesChannelContext->getContext()),
+        );
+
+        $persistentError = $this->createMock(Error::class);
+        $persistentError
+            ->expects(static::once())
+            ->method('isPersistent')
+            ->willReturn(true);
+
+        $nonPersistentError = $this->createMock(Error::class);
+        $nonPersistentError
+            ->expects(static::once())
+            ->method('isPersistent')
+            ->willReturn(false);
+
+        $cart = new Cart('some-token');
+        $cart->setErrors(new ErrorCollection([$persistentError, $nonPersistentError]));
+
+        $processorMock = $this->createMock(Processor::class);
+        $processorMock
+            ->expects(static::once())
+            ->method('process')
+            ->willReturn($cart);
+
+        $this->cartRuleLoader
+            ->expects(static::once())
+            ->method('loadByCart')
+            ->willReturn(new RuleLoaderResult(new Cart('reloaded-cart'), new RuleCollection()));
+
+        $this->orderConverter
+            ->expects(static::once())
+            ->method('convertToOrder')
+            ->willReturnCallback(static function (Cart $validatedCart) {
+                static::assertCount(1, $validatedCart->getErrors());
+                static::assertInstanceOf(Error::class, $validatedCart->getErrors()->first());
+
+                return [];
+            });
+
+        $recalculationService = new RecalculationService(
+            $entityRepository,
+            $this->orderConverter,
+            $this->createMock(CartService::class),
+            $entityRepository,
+            $entityRepository,
+            $entityRepository,
+            $entityRepository,
+            $processorMock,
+            $this->cartRuleLoader,
+            $this->createMock(PromotionItemBuilder::class)
+        );
+
+        $recalculationService->addCustomLineItem($order->getId(), new LineItem(Uuid::randomHex(), LineItem::CUSTOM_LINE_ITEM_TYPE), $this->context);
+    }
+
     private function orderEntity(): OrderEntity
     {
         $order = new OrderEntity();


Reviews:
user: login: cyl3x
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T06:54:10Z
user: login: cyl3x
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T09:06:28Z
user: login: cyl3x
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T10:18:34Z
user: login: mstegmeyer
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-07T14:51:28Z

Review comments:
diff_hunk: @@ -360,9 +360,10 @@ private function recalculateCart(Cart $cart, SalesChannelContext $context): Cart
             $cart = $this->processor->process($cart, $live, $behavior);
 
             // validate cart against the context rules
-            $validated = $this->cartRuleLoader->loadByCart($live, $cart, $behavior);
+            $validatedCart = $this->cartRuleLoader->loadByCart($live, $cart, $behavior)->getCart();
+            $validatedCart->setErrors($cart->getErrors());
path: src/Core/Checkout/Cart/Order/RecalculationService.php
user: login: cyl3x
body: Errors should be merged so new ones don't get lost:
```suggestion
            $validatedCart->addErrors(...$cart->getErrors());
```
created_at: 2025-03-07T06:53:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -360,9 +360,10 @@ private function recalculateCart(Cart $cart, SalesChannelContext $context): Cart
             $cart = $this->processor->process($cart, $live, $behavior);
 
             // validate cart against the context rules
-            $validated = $this->cartRuleLoader->loadByCart($live, $cart, $behavior);
+            $validatedCart = $this->cartRuleLoader->loadByCart($live, $cart, $behavior)->getCart();
+            $validatedCart->addErrors(...$cart->getErrors());
path: src/Core/Checkout/Cart/Order/RecalculationService.php
user: login: cyl3x
body: sorry for not mentioning this earlier, but persistent errors will be taken over anyway, which could lead to duplication 😅:
```suggestion
            $validatedCart->addErrors(...$cart->getErrors()->filter(fn ($error) => !$error->isPersistent()));
```
created_at: 2025-03-07T09:06:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-06T13:03:28Z
body: ## [Codecov](https://app.codecov.io/gh/shopware/shopware/pull/7338?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) Report
Attention: Patch coverage is `60.00000%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 52.55%. Comparing base [(`b613057`)](https://app.codecov.io/gh/shopware/shopware/commit/b613057a928e44b43710bcc53e0e763feec5a226?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) to head [(`2c7a988`)](https://app.codecov.io/gh/shopware/shopware/commit/2c7a988eec4387037f03c408df5c2c4c3dab8384?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware).
> Report is 4 commits behind head on trunk.

:white_check_mark: All tests successful. No failed tests found.

| [Files with missing lines](https://app.codecov.io/gh/shopware/shopware/pull/7338?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) | Patch % | Lines |
|---|---|---|
| [...-order/component/sw-order-promotion-field/index.js](https://app.codecov.io/gh/shopware/shopware/pull/7338?src=pr&el=tree&filepath=src%2FAdministration%2FResources%2Fapp%2Fadministration%2Fsrc%2Fmodule%2Fsw-order%2Fcomponent%2Fsw-order-promotion-field%2Findex.js&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware#diff-c3JjL0FkbWluaXN0cmF0aW9uL1Jlc291cmNlcy9hcHAvYWRtaW5pc3RyYXRpb24vc3JjL21vZHVsZS9zdy1vcmRlci9jb21wb25lbnQvc3ctb3JkZXItcHJvbW90aW9uLWZpZWxkL2luZGV4Lmpz) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/shopware/shopware/pull/7338?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) |
| [...tion/Cart/PromotionCartDeletedInformationError.php](https://app.codecov.io/gh/shopware/shopware/pull/7338?src=pr&el=tree&filepath=src%2FCore%2FCheckout%2FPromotion%2FCart%2FPromotionCartDeletedInformationError.php&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware#diff-c3JjL0NvcmUvQ2hlY2tvdXQvUHJvbW90aW9uL0NhcnQvUHJvbW90aW9uQ2FydERlbGV0ZWRJbmZvcm1hdGlvbkVycm9yLnBocA==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/shopware/shopware/pull/7338?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) |

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##            trunk    #7338   +/-   ##
=======================================
  Coverage   52.55%   52.55%           
=======================================
  Files        4712     4712           
  Lines      159442   159439    -3     
  Branches     8713     8713           
=======================================
- Hits        83799    83798    -1     
+ Misses      73122    73120    -2     
  Partials     2521     2521           
```

| [Flag](https://app.codecov.io/gh/shopware/shopware/pull/7338/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) | Coverage Δ | |
|---|---|---|
| [jest-admin](https://app.codecov.io/gh/shopware/shopware/pull/7338/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) | `60.43% <0.00%> (+<0.01%)` | :arrow_up: |
| [jest-storefront](https://app.codecov.io/gh/shopware/shopware/pull/7338/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) | `69.68% <ø> (ø)` | |
| [phpunit-migration](https://app.codecov.io/gh/shopware/shopware/pull/7338/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) | `41.73% <ø> (ø)` | |
| [phpunit-unit](https://app.codecov.io/gh/shopware/shopware/pull/7338/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware) | `44.67% <75.00%> (-0.01%)` | :arrow_down: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/shopware/shopware/pull/7338?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=shopware).

<details><summary>🚀 New features to boost your workflow: </summary>

- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE