Skip to content

Commit 8f4989a

Browse files
wip44850lucafuser
authored andcommitted
AC-7917: Order item should contain parent sku, to enable reorder functionality
1 parent e8937d3 commit 8f4989a

File tree

8 files changed

+274
-10
lines changed

8 files changed

+274
-10
lines changed

app/code/Magento/BundleGraphQl/etc/schema.graphqls

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# Copyright © Magento, Inc. All rights reserved.
2-
# See COPYING.txt for license details.
1+
# Copyright 2018 Adobe.
2+
# All Rights Reserved.
33

44
type Mutation {
55
addBundleProductsToCart(input: AddBundleProductsToCartInput @doc(description: "An input object that defines which bundle products to add to the cart.")): AddBundleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") @doc(description: "Add one or more bundle products to the specified cart. We recommend using `addProductsToCart` instead.")
@@ -103,6 +103,7 @@ enum ShipBundleItemsEnum @doc(description: "Defines whether bundle items must be
103103

104104
type BundleOrderItem implements OrderItemInterface @doc(description: "Defines bundle product options for `OrderItemInterface`.") {
105105
bundle_options: [ItemSelectedBundleOption] @doc(description: "A list of bundle options that are assigned to the bundle product.") @resolver(class: "Magento\\BundleGraphQl\\Model\\Resolver\\Order\\Item\\BundleOptions")
106+
parent_sku: String @doc(description: "The SKU of parent product.")
106107
}
107108

108109
type BundleInvoiceItem implements InvoiceItemInterface @doc(description: "Defines bundle product options for `InvoiceItemInterface`.") {

app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0"?>
22
<!--
33
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
4+
* Copyright 2017 Adobe.
5+
* All Rights Reserved.
66
*/
77
-->
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
@@ -85,4 +85,11 @@
8585
<type name="Magento\Quote\Model\Quote">
8686
<plugin name="update_customized_options" type="Magento\ConfigurableProductGraphQl\Plugin\Quote\UpdateCustomizedOptions"/>
8787
</type>
88+
<type name="Magento\SalesGraphQl\Model\TypeResolver\OrderItem">
89+
<arguments>
90+
<argument name="productTypeMap" xsi:type="array">
91+
<item name="configurable" xsi:type="string">ConfigurableOrderItem</item>
92+
</argument>
93+
</arguments>
94+
</type>
8895
</config>

app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
# Copyright © Magento, Inc. All rights reserved.
2-
# See COPYING.txt for license details.
1+
# Copyright 2018 Adobe.
2+
# All Rights Reserved.
33
type Mutation {
44
addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput @doc(description: "An input object that defines which configurable products to add to the cart.")): AddConfigurableProductsToCartOutput @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\AddConfigurableProductsToCart") @doc(description: "Add one or more configurable products to the specified cart. We recommend using `addProductsToCart` instead.")
55
}
@@ -111,3 +111,7 @@ type ConfigurableProductOptionValue @doc(description: "Defines a value for a con
111111
type StoreConfig {
112112
configurable_thumbnail_source : String @doc(description: "Indicates whether the `parent` or child (`itself`) thumbnail should be used in the cart for configurable products.")
113113
}
114+
115+
type ConfigurableOrderItem implements OrderItemInterface {
116+
parent_sku: String @doc(description: "The SKU of parent product.")
117+
}

app/code/Magento/SalesGraphQl/Model/OrderItem/DataProvider.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2020 Adobe.
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -147,6 +147,7 @@ private function fetch()
147147
'product_sku' => $orderItem->getSku(),
148148
'product_url_key' => $associatedProduct ? $associatedProduct->getUrlKey() : null,
149149
'product_type' => $orderItem->getProductType(),
150+
'parent_sku' => ($orderItem->getChildrenItems() && $associatedProduct) ? $associatedProduct->getSku() : null,
150151
'status' => $orderItem->getStatus(),
151152
'discounts' => $this->getDiscountDetails($associatedOrder, $orderItem),
152153
'product_sale_price' => [

dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/RetrieveOrdersWithBundleProductByOrderNumberTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2020 Adobe.
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

@@ -77,6 +77,10 @@ public function testGetCustomerOrderBundleProduct()
7777
'bundle-product-two-dropdown-options-simple1-simple2',
7878
$bundledItemInTheOrder['product_sku']
7979
);
80+
$this->assertEquals(
81+
'bundle-product-two-dropdown-options',
82+
$bundledItemInTheOrder['parent_sku']
83+
);
8084
$priceOfBundledItemInOrder = $bundledItemInTheOrder['product_sale_price']['value'];
8185
$this->assertEquals(15, $priceOfBundledItemInOrder);
8286
$this->assertArrayHasKey('bundle_options', $bundledItemInTheOrder);
@@ -266,6 +270,7 @@ private function getCustomerOrderQueryBundleProduct($orderNumber)
266270
quantity_ordered
267271
discounts{amount{value} label}
268272
... on BundleOrderItem{
273+
parent_sku
269274
bundle_options{
270275
__typename
271276
label
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe.
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\GraphQl\Sales;
9+
10+
use Magento\Framework\Exception\AuthenticationException;
11+
use Magento\GraphQl\GetCustomerAuthenticationHeader;
12+
use Magento\TestFramework\Helper\Bootstrap;
13+
use Magento\TestFramework\TestCase\GraphQlAbstract;
14+
15+
/**
16+
* Test for orders with configurable product
17+
*/
18+
class RetrieveOrdersWithConfigurableProductByOrderNumberTest extends GraphQlAbstract
19+
{
20+
/**
21+
* @var GetCustomerAuthenticationHeader
22+
*/
23+
private $customerAuthenticationHeader;
24+
25+
protected function setUp(): void
26+
{
27+
parent::setUp();
28+
$objectManager = Bootstrap::getObjectManager();
29+
$this->customerAuthenticationHeader = $objectManager->get(GetCustomerAuthenticationHeader::class);
30+
}
31+
32+
/**
33+
* Test customer order details with configurable product with child items
34+
*
35+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
36+
* @magentoApiDataFixture Magento/Sales/_files/customer_order_with_configurable_product.php
37+
*/
38+
public function testGetCustomerOrderConfigurableProduct(): void
39+
{
40+
$orderNumber = '100000001';
41+
$customerOrderResponse = $this->getCustomerOrderQueryConfigurableProduct($orderNumber);
42+
$customerOrderItems = $customerOrderResponse[0];
43+
$this->assertEquals('Pending Payment', $customerOrderItems['status']);
44+
$configurableItemInTheOrder = $customerOrderItems['items'][0];
45+
$this->assertEquals(
46+
'simple_10',
47+
$configurableItemInTheOrder['product_sku']
48+
);
49+
50+
$expectedConfigurableOptions = [
51+
'__typename' => 'ConfigurableOrderItem',
52+
'product_sku' => 'simple_10',
53+
'product_name' => 'Configurable Product',
54+
'parent_sku' => 'configurable',
55+
'product_url_key' => 'configurable-product',
56+
'quantity_ordered' => 2
57+
];
58+
$this->assertEquals($expectedConfigurableOptions, $configurableItemInTheOrder);
59+
}
60+
61+
/**
62+
* Get customer order query for configurable order items
63+
*
64+
* @param $orderNumber
65+
* @return array
66+
* @throws AuthenticationException
67+
*/
68+
private function getCustomerOrderQueryConfigurableProduct($orderNumber): array
69+
{
70+
$query =
71+
<<<QUERY
72+
{
73+
customer {
74+
orders(filter:{number:{eq:"{$orderNumber}"}}) {
75+
total_count
76+
items {
77+
id
78+
number
79+
order_date
80+
status
81+
items {
82+
__typename
83+
product_sku
84+
product_name
85+
product_url_key
86+
quantity_ordered
87+
... on ConfigurableOrderItem {
88+
parent_sku
89+
}
90+
}
91+
total {
92+
base_grand_total{value currency}
93+
grand_total{value currency}
94+
subtotal {value currency }
95+
total_tax{value currency}
96+
taxes {amount{value currency} title rate}
97+
total_shipping{value currency}
98+
shipping_handling
99+
{
100+
amount_including_tax{value}
101+
amount_excluding_tax{value}
102+
total_amount{value}
103+
discounts{amount{value}}
104+
taxes {amount{value} title rate}
105+
}
106+
discounts {amount{value currency} label}
107+
}
108+
}
109+
}
110+
}
111+
}
112+
QUERY;
113+
$currentEmail = 'customer@example.com';
114+
$currentPassword = 'password';
115+
$response = $this->graphQlQuery(
116+
$query,
117+
[],
118+
'',
119+
$this->customerAuthenticationHeader->execute($currentEmail, $currentPassword)
120+
);
121+
122+
$this->assertArrayHasKey('orders', $response['customer']);
123+
$this->assertArrayHasKey('items', $response['customer']['orders']);
124+
$customerOrderItemsInResponse = $response['customer']['orders']['items'];
125+
return $customerOrderItemsInResponse;
126+
}
127+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe.
4+
* All Rights Reserved.
5+
*/
6+
7+
use Magento\Catalog\Api\Data\ProductInterface;
8+
use Magento\Catalog\Api\ProductRepositoryInterface;
9+
use Magento\Customer\Model\CustomerRegistry;
10+
use Magento\Sales\Api\OrderItemRepositoryInterface;
11+
use Magento\Sales\Api\OrderRepositoryInterface;
12+
use Magento\Sales\Model\Order;
13+
use Magento\Sales\Model\Order\Address;
14+
use Magento\Sales\Model\Order\Item;
15+
use Magento\Sales\Model\Order\Payment;
16+
use Magento\Store\Model\StoreManagerInterface;
17+
use Magento\TestFramework\Helper\Bootstrap;
18+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
19+
20+
Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/product_configurable.php');
21+
22+
$objectManager = Bootstrap::getObjectManager();
23+
/** @var CustomerRegistry $customerRegistry */
24+
$customerRegistry = $objectManager->create(CustomerRegistry::class);
25+
$customer = $customerRegistry->retrieve(1);
26+
/** @var ProductRepositoryInterface $productRepository */
27+
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
28+
$configurableProduct = $productRepository->get('configurable');
29+
30+
$addressData = include __DIR__ . '/address_data.php';
31+
32+
/** @var Address $billingAddress */
33+
$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]);
34+
$billingAddress->setAddressType('billing');
35+
36+
$shippingAddress = clone $billingAddress;
37+
$shippingAddress->setId(null)->setAddressType('shipping');
38+
39+
/** @var Payment $payment */
40+
$payment = $objectManager->create(Payment::class);
41+
$payment->setMethod('checkmo')
42+
->setAdditionalInformation([
43+
'token_metadata' => [
44+
'token' => 'f34vjw',
45+
'customer_id' => $customer->getId(),
46+
],
47+
]);
48+
49+
/** @var Order $order */
50+
$order = $objectManager->create(Order::class);
51+
/** @var OrderRepositoryInterface $orderRepository */
52+
$orderRepository = $objectManager->create(OrderRepositoryInterface::class);
53+
54+
$order->setIncrementId('100000001')
55+
->setState(Order::STATE_PENDING_PAYMENT)
56+
->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PENDING_PAYMENT))
57+
->setSubtotal(100)
58+
->setGrandTotal(100)
59+
->setBaseSubtotal(100)
60+
->setBaseGrandTotal(100)
61+
->setCustomerIsGuest(false)
62+
->setCustomerId($customer->getId())
63+
->setCustomerEmail($customer->getEmail())
64+
->setCustomerFirstname($customer->getName())
65+
->setCustomerLastname($customer->getLastname())
66+
->setBillingAddress($billingAddress)
67+
->setShippingAddress($shippingAddress)
68+
->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId())
69+
->setPayment($payment);
70+
$orderRepository->save($order);
71+
72+
$qtyOrdered = 2;
73+
/** @var Item $orderItem */
74+
$orderConfigurableItem = $objectManager->create(Item::class);
75+
$orderConfigurableItem->setProductId($configurableProduct->getId())->setQtyOrdered($qtyOrdered);
76+
$orderConfigurableItem->setBasePrice($configurableProduct->getPrice());
77+
$orderConfigurableItem->setPrice($configurableProduct->getPrice());
78+
$orderConfigurableItem->setRowTotal($configurableProduct->getPrice());
79+
$orderConfigurableItem->setParentItemId(null);
80+
$orderConfigurableItem->setProductType('configurable');
81+
$orderConfigurableItem->setSku($configurableProduct->getSku());
82+
$orderConfigurableItem->setName($configurableProduct->getName());
83+
$orderConfigurableItem->setOrder($order);
84+
85+
/** @var OrderItemRepositoryInterface $orderItemsRepository */
86+
$orderItemsRepository = $objectManager->create(OrderItemRepositoryInterface::class);
87+
// Configurable item must be present in database to have its real ID to set parent id of simple product.
88+
$orderItemsRepository->save($orderConfigurableItem);
89+
90+
if ($configurableProduct->getExtensionAttributes()
91+
&& (array)$configurableProduct->getExtensionAttributes()->getConfigurableProductLinks()
92+
) {
93+
$simpleProductId = current($configurableProduct->getExtensionAttributes()->getConfigurableProductLinks());
94+
/** @var ProductInterface $simpleProduct */
95+
$simpleProduct = $productRepository->getById($simpleProductId);
96+
$orderItem = $objectManager->create(\Magento\Sales\Api\Data\OrderItemInterface::class);
97+
$orderItem->setBasePrice($simpleProduct->getPrice());
98+
$orderItem->setPrice($simpleProduct->getPrice());
99+
$orderItem->setRowTotal($simpleProduct->getPrice());
100+
$orderItem->setProductType('simple');
101+
$orderItem->setSku($simpleProduct->getSku());
102+
$orderItem->setName($simpleProduct->getName());
103+
// duplicate behavior with simple product associated with configurable one that happens during order process.
104+
$orderItem->setProductId($simpleProduct->getId())->setQtyOrdered($qtyOrdered);
105+
$orderItem->setParentItemId($orderConfigurableItem->getId());
106+
$orderItem->setOrder($order);
107+
$orderConfigurableItem->setSku($simpleProduct->getSku());
108+
$orderItemsRepository->save($orderItem);
109+
$orderItemsRepository->save($orderConfigurableItem);
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe.
4+
* All Rights Reserved.
5+
*/
6+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
7+
8+
Resolver::getInstance()->requireDataFixture('Magento/ConfigurableProduct/_files/product_configurable_rollback.php');
9+
Resolver::getInstance()->requireDataFixture('Magento/Sales/_files/order_rollback.php');

0 commit comments

Comments
 (0)