Fix shipping adjustment removal to clear shipment reference

This commit is contained in:
Marek Rzytki 2026-06-19 10:34:19 +02:00
parent e98c819ea4
commit 209750e309
4 changed files with 201 additions and 0 deletions

View file

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Sylius\Component\Core\Model;
use Sylius\Component\Order\Model\AdjustableInterface;
use Sylius\Component\Order\Model\Adjustment as BaseAdjustment;
class Adjustment extends BaseAdjustment implements AdjustmentInterface
@ -20,6 +21,15 @@ class Adjustment extends BaseAdjustment implements AdjustmentInterface
/** @var ShipmentInterface|null */
protected $shipment;
public function setAdjustable(?AdjustableInterface $adjustable): void
{
parent::setAdjustable($adjustable);
if ($adjustable === null && $this->shipment !== null) {
$this->setShipment(null);
}
}
public function getShipment(): ?ShipmentInterface
{
return $this->shipment;

View file

@ -0,0 +1,56 @@
Sylius\Component\Currency\Model\Currency:
currency:
code: "EUR"
Sylius\Component\Locale\Model\Locale:
locale:
code: "en_US"
Sylius\Component\Addressing\Model\Zone:
zone:
code: "TEST_ZONE"
name: "Test Zone"
type: "country"
scope: "all"
Sylius\Component\Core\Model\Channel:
channel:
code: "TEST"
name: "Test Channel"
hostname: "localhost"
baseCurrency: "@currency"
defaultLocale: "@locale"
enabled: true
taxCalculationStrategy: "order_items_based"
currencies: ["@currency"]
locales: ["@locale"]
Sylius\Component\Core\Model\ShippingMethod:
shipping_method:
code: "TEST_FLAT_RATE"
enabled: true
calculator: "flat_rate"
configuration:
TEST:
amount: 1000
zone: "@zone"
channels: ["@channel"]
Sylius\Component\Core\Model\Adjustment:
shipping_adjustment:
type: "shipping"
amount: 1000
label: "Shipping"
Sylius\Component\Core\Model\Order:
order_with_shipping:
currencyCode: "EUR"
localeCode: "en_US"
channel: "@channel"
Sylius\Component\Core\Model\Shipment:
order_shipment:
method: "@shipping_method"
order: "@order_with_shipping"
__calls:
- addAdjustment: ["@shipping_adjustment"]

View file

@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Tests\Functional;
use Doctrine\ORM\EntityManagerInterface;
use Fidry\AliceDataFixtures\LoaderInterface;
use Fidry\AliceDataFixtures\Persistence\PurgeMode;
use PHPUnit\Framework\Attributes\Test;
use Sylius\Component\Core\Model\AdjustmentInterface;
use Sylius\Component\Core\Model\Order;
use Sylius\Component\Core\Model\Shipment;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
/**
* Reproduces GitHub issue #16347:
* Doctrine error when flush() is called twice after removing shipping adjustments
* (e.g. event listener flush + API Platform flush during order complete transition).
*/
final class OrderShippingAdjustmentDoubleFlushTest extends KernelTestCase
{
private array $fixtures;
private EntityManagerInterface $entityManager;
protected function setUp(): void
{
self::bootKernel();
/** @var LoaderInterface $fixtureLoader */
$fixtureLoader = self::$kernel->getContainer()->get('fidry_alice_data_fixtures.loader.doctrine');
$this->fixtures = $fixtureLoader->load(
[__DIR__ . '/../DataFixtures/ORM/resources/order_with_shipping_adjustment.yml'],
[],
[],
PurgeMode::createDeleteMode(),
);
$this->entityManager = self::$kernel->getContainer()->get('doctrine.orm.entity_manager');
}
#[Test]
public function it_allows_multiple_flushes_after_removing_shipping_adjustment_from_order(): void
{
/** @var Order $order */
$order = $this->fixtures['order_with_shipping'];
/** @var Shipment $shipment */
$shipment = $this->fixtures['order_shipment'];
$this->assertFalse(
$order->getAdjustments(AdjustmentInterface::SHIPPING_ADJUSTMENT)->isEmpty(),
'Order should have a shipping adjustment before removal',
);
$this->assertFalse(
$shipment->getAdjustments(AdjustmentInterface::SHIPPING_ADJUSTMENT)->isEmpty(),
'Shipment should have a shipping adjustment before removal',
);
$order->removeAdjustmentsRecursively(AdjustmentInterface::SHIPPING_ADJUSTMENT);
$this->entityManager->flush();
$this->entityManager->flush();
$this->assertTrue(
$order->getAdjustments(AdjustmentInterface::SHIPPING_ADJUSTMENT)->isEmpty(),
'Order should have no shipping adjustments after removal',
);
$this->assertTrue(
$shipment->getAdjustments(AdjustmentInterface::SHIPPING_ADJUSTMENT)->isEmpty(),
'Shipment should have no shipping adjustments after removal',
);
}
}

View file

@ -0,0 +1,50 @@
<?php
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Sylius\Tests\Model;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Sylius\Component\Core\Model\Adjustment;
use Sylius\Component\Core\Model\AdjustmentInterface;
use Sylius\Component\Core\Model\Order;
use Sylius\Component\Core\Model\Shipment;
final class OrderShippingAdjustmentRemovalTest extends TestCase
{
#[Test]
public function it_clears_shipment_reference_when_shipping_adjustment_is_removed_from_order(): void
{
$order = new Order();
$shipment = new Shipment();
$shipment->setOrder($order);
$adjustment = new Adjustment();
$adjustment->setType(AdjustmentInterface::SHIPPING_ADJUSTMENT);
$adjustment->setAmount(500);
$shipment->addAdjustment($adjustment);
$this->assertSame($order, $adjustment->getAdjustable(), 'Adjustment should be assigned to order after addAdjustment on shipment');
$this->assertSame($shipment, $adjustment->getShipment(), 'Adjustment should reference the shipment');
$this->assertTrue($order->hasAdjustment($adjustment), 'Order should contain the adjustment');
$this->assertTrue($shipment->hasAdjustment($adjustment), 'Shipment should contain the adjustment');
$order->removeAdjustment($adjustment);
$this->assertNull($adjustment->getAdjustable(), 'Adjustable reference should be cleared');
$this->assertNull($adjustment->getShipment(), 'Shipment reference should be cleared after removal from order');
$this->assertFalse($order->hasAdjustment($adjustment), 'Order should not contain the adjustment');
$this->assertFalse($shipment->hasAdjustment($adjustment), 'Shipment should not contain the adjustment after removal from order');
}
}