Skip to content

Commit fe72c40

Browse files
authored
Merge pull request #10002 from magento-gl/spartans_pr_11082025
[Spartans] BugFixes Delivery
2 parents d15c409 + 98159c0 commit fe72c40

File tree

6 files changed

+917
-20
lines changed

6 files changed

+917
-20
lines changed

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

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Copyright 2020 Adobe.
3+
* Copyright 2020 Adobe
44
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
@@ -10,18 +10,20 @@
1010
use Magento\Catalog\Api\Data\ProductInterface;
1111
use Magento\Catalog\Api\ProductRepositoryInterface;
1212
use Magento\Framework\Api\SearchCriteriaBuilder;
13+
use Magento\Framework\App\ObjectManager;
1314
use Magento\Sales\Api\Data\OrderInterface;
1415
use Magento\Sales\Api\Data\OrderItemInterface;
1516
use Magento\Sales\Api\OrderItemRepositoryInterface;
1617
use Magento\Sales\Api\OrderRepositoryInterface;
17-
use Magento\Framework\App\ObjectManager;
1818
use Magento\Tax\Helper\Data as TaxHelper;
1919

2020
/**
2121
* Data provider for order items
2222
*/
2323
class DataProvider
2424
{
25+
public const APPLIED_TO_ITEM = 'ITEM';
26+
public const APPLIED_TO_SHIPPING = 'SHIPPING';
2527
/**
2628
* @var OrderItemRepositoryInterface
2729
*/
@@ -240,12 +242,28 @@ private function getDiscountDetails(OrderInterface $associatedOrder, OrderItemIn
240242
} else {
241243
$discounts [] = [
242244
'label' => $associatedOrder->getDiscountDescription() ?? __('Discount'),
245+
'applied_to' => $this->getAppliedTo($associatedOrder),
243246
'amount' => [
244247
'value' => abs((float) $orderItem->getDiscountAmount()),
245248
'currency' => $associatedOrder->getOrderCurrencyCode()
246-
]
249+
],
250+
'order_model' => $associatedOrder,
247251
];
248252
}
249253
return $discounts;
250254
}
255+
256+
/**
257+
* Get entity type the discount is applied to
258+
*
259+
* @param OrderInterface $order
260+
* @return string
261+
*/
262+
private function getAppliedTo($order)
263+
{
264+
if ((float) $order->getShippingDiscountAmount() > 0) {
265+
return self::APPLIED_TO_SHIPPING;
266+
}
267+
return self::APPLIED_TO_ITEM;
268+
}
251269
}

app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,6 @@ public function asHtml()
156156
public function validate(AbstractModel $model)
157157
{
158158
$subSelectConditionsFlag = true;
159-
if (!$this->getConditions()) {
160-
return false;
161-
}
162159
$attr = $this->getAttribute();
163160
$total = 0;
164161
$isMultiShipping = (bool) $model->getQuote()->getIsMultiShipping();

app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/Product/SubselectTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,7 @@ public static function dataProviderForFixedBundleProduct(): array
412412
'baseRowTotal' => 100,
413413
'valueParsed' => 100
414414
],
415-
false,
415+
true,
416416
false
417417
],
418418
'validate true for bundle product
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
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 Exception;
11+
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
12+
use Magento\Checkout\Test\Fixture\PlaceOrder as PlaceOrderFixture;
13+
use Magento\Checkout\Test\Fixture\SetBillingAddress;
14+
use Magento\Checkout\Test\Fixture\SetDeliveryMethod as SetDeliveryMethodFixture;
15+
use Magento\Checkout\Test\Fixture\SetPaymentMethod as SetPaymentMethodFixture;
16+
use Magento\Checkout\Test\Fixture\SetShippingAddress;
17+
use Magento\Customer\Test\Fixture\Customer;
18+
use Magento\Framework\Exception\LocalizedException;
19+
use Magento\Quote\Test\Fixture\AddProductToCart;
20+
use Magento\Quote\Test\Fixture\ApplyCoupon as ApplyCouponFixture;
21+
use Magento\Quote\Test\Fixture\CustomerCart;
22+
use Magento\SalesRule\Model\Rule as SalesRule;
23+
use Magento\SalesRule\Test\Fixture\Rule as SalesRuleFixture;
24+
use Magento\TestFramework\Fixture\DataFixture;
25+
use Magento\TestFramework\Fixture\DataFixtureStorage;
26+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
27+
use Magento\TestFramework\Helper\Bootstrap;
28+
use Magento\TestFramework\TestCase\GraphQlAbstract;
29+
30+
/**
31+
* Test customer orders GraphQL query with coupon codes
32+
*/
33+
class CustomerOrdersCouponTest extends GraphQlAbstract
34+
{
35+
private const COUPON_CODE = 'TEST-COUPON-2025';
36+
private const DISCOUNT_AMOUNT = 10;
37+
38+
/**
39+
* @var DataFixtureStorage
40+
*/
41+
private $fixtures;
42+
43+
/**
44+
* @inheritDoc
45+
* @throws LocalizedException
46+
*/
47+
protected function setUp(): void
48+
{
49+
parent::setUp();
50+
$this->fixtures = Bootstrap::getObjectManager()->get(DataFixtureStorageManager::class)->getStorage();
51+
}
52+
53+
/**
54+
* Test that customer orders GraphQL query returns coupon code correctly for order items with discounts
55+
*
56+
* @throws Exception
57+
*/
58+
#[
59+
DataFixture(
60+
SalesRuleFixture::class,
61+
[
62+
'name' => 'Test Sales Rule with Coupon',
63+
'is_active' => 1,
64+
'coupon_type' => SalesRule::COUPON_TYPE_SPECIFIC,
65+
'coupon_code' => self::COUPON_CODE,
66+
'discount_amount' => self::DISCOUNT_AMOUNT,
67+
'simple_action' => 'by_percent',
68+
'stop_rules_processing' => false,
69+
'is_advanced' => 1
70+
],
71+
as: 'sales_rule'
72+
),
73+
DataFixture(
74+
ProductFixture::class,
75+
[
76+
'price' => 100.00,
77+
'sku' => 'test-product-coupon'
78+
],
79+
as: 'product'
80+
),
81+
DataFixture(Customer::class, as: 'customer'),
82+
DataFixture(CustomerCart::class, ['customer_id' => '$customer.id$'], as: 'cart'),
83+
DataFixture(
84+
AddProductToCart::class,
85+
[
86+
'cart_id' => '$cart.id$',
87+
'product_id' => '$product.id$',
88+
'qty' => 1
89+
]
90+
),
91+
DataFixture(
92+
ApplyCouponFixture::class,
93+
[
94+
'cart_id' => '$cart.id$',
95+
'coupon_codes' => [self::COUPON_CODE]
96+
]
97+
),
98+
DataFixture(SetBillingAddress::class, ['cart_id' => '$cart.id$']),
99+
DataFixture(SetShippingAddress::class, ['cart_id' => '$cart.id$']),
100+
DataFixture(SetDeliveryMethodFixture::class, ['cart_id' => '$cart.id$']),
101+
DataFixture(SetPaymentMethodFixture::class, ['cart_id' => '$cart.id$']),
102+
DataFixture(PlaceOrderFixture::class, ['cart_id' => '$cart.id$'], 'order')
103+
]
104+
public function testGetCustomerOrdersWithCouponCode()
105+
{
106+
$customer = $this->fixtures->get('customer');
107+
$currentEmail = $customer->getEmail();
108+
$currentPassword = 'password';
109+
110+
// Generate customer token
111+
$generateToken = $this->generateCustomerTokenMutation($currentEmail, $currentPassword);
112+
$tokenResponse = $this->graphQlMutation($generateToken);
113+
$customerToken = $tokenResponse['generateCustomerToken']['token'];
114+
115+
// Query customer orders with detailed item information including discounts and coupons
116+
$query = $this->getCustomerOrdersWithCouponQuery();
117+
$response = $this->graphQlQuery(
118+
$query,
119+
[],
120+
'',
121+
$this->getCustomerHeaders($customerToken)
122+
);
123+
124+
// Validate response structure
125+
$this->assertArrayHasKey('customer', $response);
126+
$this->assertArrayHasKey('orders', $response['customer']);
127+
$this->assertArrayHasKey('items', $response['customer']['orders']);
128+
$this->assertNotEmpty($response['customer']['orders']['items']);
129+
130+
$order = $response['customer']['orders']['items'][0];
131+
132+
// Validate order has items
133+
$this->assertArrayHasKey('items', $order);
134+
$this->assertNotEmpty($order['items']);
135+
136+
$orderItem = $order['items'][0];
137+
138+
// Validate order item structure
139+
$this->assertArrayHasKey('product_sku', $orderItem);
140+
$this->assertEquals('test-product-coupon', $orderItem['product_sku']);
141+
142+
// Validate discounts array exists
143+
$this->assertArrayHasKey('discounts', $orderItem);
144+
$this->assertNotEmpty($orderItem['discounts']);
145+
146+
$discount = $orderItem['discounts'][0];
147+
148+
// Validate discount structure and values
149+
$this->assertArrayHasKey('amount', $discount);
150+
$this->assertArrayHasKey('value', $discount['amount']);
151+
$this->assertEquals(10, $discount['amount']['value']);
152+
153+
// The key test: validate coupon code is returned without error
154+
$this->assertArrayHasKey('coupon', $discount);
155+
$this->assertArrayHasKey('code', $discount['coupon']);
156+
$this->assertEquals(self::COUPON_CODE, $discount['coupon']['code']);
157+
158+
// Validate order status and other basic properties
159+
$this->assertArrayHasKey('order_number', $order);
160+
$this->assertArrayHasKey('status', $order);
161+
}
162+
163+
/**
164+
* Test that the fix handles orders without coupons gracefully
165+
*
166+
* @throws Exception
167+
*/
168+
#[
169+
DataFixture(
170+
ProductFixture::class,
171+
[
172+
'price' => 50.00,
173+
'sku' => 'test-product-no-coupon'
174+
],
175+
as: 'product'
176+
),
177+
DataFixture(Customer::class, as: 'customer'),
178+
DataFixture(CustomerCart::class, ['customer_id' => '$customer.id$'], as: 'cart'),
179+
DataFixture(
180+
AddProductToCart::class,
181+
[
182+
'cart_id' => '$cart.id$',
183+
'product_id' => '$product.id$',
184+
'qty' => 1
185+
]
186+
),
187+
DataFixture(SetBillingAddress::class, ['cart_id' => '$cart.id$']),
188+
DataFixture(SetShippingAddress::class, ['cart_id' => '$cart.id$']),
189+
DataFixture(SetDeliveryMethodFixture::class, ['cart_id' => '$cart.id$']),
190+
DataFixture(SetPaymentMethodFixture::class, ['cart_id' => '$cart.id$']),
191+
DataFixture(PlaceOrderFixture::class, ['cart_id' => '$cart.id$'], 'order')
192+
]
193+
public function testGetCustomerOrdersWithoutCoupon()
194+
{
195+
$customer = $this->fixtures->get('customer');
196+
$currentEmail = $customer->getEmail();
197+
$currentPassword = 'password';
198+
199+
// Generate customer token
200+
$generateToken = $this->generateCustomerTokenMutation($currentEmail, $currentPassword);
201+
$tokenResponse = $this->graphQlMutation($generateToken);
202+
$customerToken = $tokenResponse['generateCustomerToken']['token'];
203+
204+
// Query customer orders
205+
$query = $this->getCustomerOrdersWithCouponQuery();
206+
$response = $this->graphQlQuery(
207+
$query,
208+
[],
209+
'',
210+
$this->getCustomerHeaders($customerToken)
211+
);
212+
213+
// Validate that orders without coupons don't cause errors
214+
$this->assertArrayHasKey('customer', $response);
215+
$this->assertArrayHasKey('orders', $response['customer']);
216+
$this->assertArrayHasKey('items', $response['customer']['orders']);
217+
$this->assertNotEmpty($response['customer']['orders']['items']);
218+
219+
$order = $response['customer']['orders']['items'][0];
220+
$this->assertArrayHasKey('items', $order);
221+
$this->assertNotEmpty($order['items']);
222+
223+
$orderItem = $order['items'][0];
224+
$this->assertEquals('test-product-no-coupon', $orderItem['product_sku']);
225+
226+
// Orders without discounts should have empty discounts array
227+
$this->assertArrayHasKey('discounts', $orderItem);
228+
// Discounts array may be empty for orders without coupons
229+
if (!empty($orderItem['discounts'])) {
230+
// If there are discounts, they should not have coupon codes
231+
foreach ($orderItem['discounts'] as $discount) {
232+
$this->assertArrayHasKey('coupon', $discount);
233+
$this->assertNull($discount['coupon']);
234+
}
235+
}
236+
}
237+
238+
/**
239+
* Get GraphQL query for customer orders with coupon information
240+
*
241+
* @return string
242+
*/
243+
private function getCustomerOrdersWithCouponQuery(): string
244+
{
245+
return <<<QUERY
246+
query {
247+
customer {
248+
firstname
249+
lastname
250+
orders {
251+
items {
252+
order_number
253+
status
254+
order_date
255+
items {
256+
product_sku
257+
product_name
258+
quantity_ordered
259+
discounts {
260+
coupon {
261+
code
262+
}
263+
amount {
264+
value
265+
currency
266+
}
267+
}
268+
}
269+
}
270+
page_info {
271+
current_page
272+
page_size
273+
total_pages
274+
}
275+
total_count
276+
}
277+
}
278+
}
279+
QUERY;
280+
}
281+
282+
/**
283+
* Generate customer token mutation
284+
*
285+
* @param string $email
286+
* @param string $password
287+
* @return string
288+
*/
289+
private function generateCustomerTokenMutation(string $email, string $password): string
290+
{
291+
return <<<MUTATION
292+
mutation {
293+
generateCustomerToken(
294+
email: "{$email}"
295+
password: "{$password}"
296+
) {
297+
token
298+
}
299+
}
300+
MUTATION;
301+
}
302+
303+
/**
304+
* Get customer authorization headers
305+
*
306+
* @param string $token
307+
* @return array
308+
*/
309+
private function getCustomerHeaders(string $token): array
310+
{
311+
return [
312+
'Authorization' => 'Bearer ' . $token,
313+
'Store' => 'default'
314+
];
315+
}
316+
}

0 commit comments

Comments
 (0)