[CoreBundle] Do not return carts abandoned before the customer's last completed checkout

This commit is contained in:
Mateusz 2026-06-12 15:45:14 +02:00
parent 4d35ae1d75
commit 3b3242e0c5
2 changed files with 80 additions and 0 deletions

View file

@ -233,12 +233,20 @@ class OrderRepository extends BaseOrderRepository implements OrderRepositoryInte
ChannelInterface $channel, ChannelInterface $channel,
CustomerInterface $customer, CustomerInterface $customer,
): ?OrderInterface { ): ?OrderInterface {
$completedCheckoutsQueryBuilder = $this->createQueryBuilder('completedOrder')
->select('completedOrder.id')
->andWhere('completedOrder.channel = :channel')
->andWhere('completedOrder.customer = :customer')
->andWhere('completedOrder.checkoutCompletedAt > o.createdAt')
;
return $this->createQueryBuilder('o') return $this->createQueryBuilder('o')
->andWhere('o.state = :state') ->andWhere('o.state = :state')
->andWhere('o.channel = :channel') ->andWhere('o.channel = :channel')
->andWhere('o.customer = :customer') ->andWhere('o.customer = :customer')
->andWhere('o.items IS NOT EMPTY') ->andWhere('o.items IS NOT EMPTY')
->andWhere('o.createdByGuest = :createdByGuest') ->andWhere('o.createdByGuest = :createdByGuest')
->andWhere(sprintf('NOT EXISTS (%s)', $completedCheckoutsQueryBuilder->getDQL()))
->setParameter('state', OrderInterface::STATE_CART) ->setParameter('state', OrderInterface::STATE_CART)
->setParameter('channel', $channel) ->setParameter('channel', $channel)
->setParameter('customer', $customer) ->setParameter('customer', $customer)

View file

@ -110,6 +110,78 @@ final class CartTest extends JsonApiTestCase
self::assertSame([], $response['items']); self::assertSame([], $response['items']);
} }
#[Test]
public function it_does_not_return_a_cart_abandoned_before_the_customer_last_completed_checkout(): void
{
$this->setUpDefaultPostHeaders();
$this->setUpShopUserContext();
$this->loadFixturesFromFiles([
'authentication/shop_user.yaml',
'channel/channel.yaml',
'cart.yaml',
'country.yaml',
'shipping_method.yaml',
'payment_method.yaml',
]);
$this->placeOrder(
tokenValue: 'placedOrderToken',
email: 'shop@example.com',
checkoutCompletedAt: new \DateTimeImmutable(),
);
$abandonedCartToken = $this->pickUpCart(tokenValue: 'abandonedCartToken', email: 'shop@example.com');
$this->addItemToCart('MUG_BLUE', 3, $abandonedCartToken);
$abandonedCart = $this->orderRepository->findCartByTokenValue($abandonedCartToken);
$abandonedCart->setCreatedAt(new \DateTime('-1 hour'));
$this->get('doctrine.orm.entity_manager')->flush();
$this->requestPost(uri: '/api/v2/shop/orders', body: []);
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_CREATED);
$response = json_decode($this->client->getResponse()->getContent(), true, flags: \JSON_THROW_ON_ERROR);
self::assertNotSame($abandonedCartToken, $response['tokenValue']);
self::assertSame([], $response['items']);
}
#[Test]
public function it_returns_the_customer_cart_created_after_the_last_completed_checkout(): void
{
$this->setUpDefaultPostHeaders();
$this->setUpShopUserContext();
$this->loadFixturesFromFiles([
'authentication/shop_user.yaml',
'channel/channel.yaml',
'cart.yaml',
'country.yaml',
'shipping_method.yaml',
'payment_method.yaml',
]);
$this->placeOrder(
tokenValue: 'placedOrderToken',
email: 'shop@example.com',
checkoutCompletedAt: new \DateTimeImmutable('-1 hour'),
);
$newCartToken = $this->pickUpCart(tokenValue: 'newCartToken', email: 'shop@example.com');
$this->addItemToCart('MUG_BLUE', 3, $newCartToken);
$this->requestPost(uri: '/api/v2/shop/orders', body: []);
$this->assertResponseCode($this->client->getResponse(), Response::HTTP_CREATED);
$response = json_decode($this->client->getResponse()->getContent(), true, flags: \JSON_THROW_ON_ERROR);
self::assertSame($newCartToken, $response['tokenValue']);
self::assertCount(1, $response['items']);
}
#[Test] #[Test]
public function it_gets_an_empty_cart_as_guest(): void public function it_gets_an_empty_cart_as_guest(): void
{ {