Skip to content

Commit 5a09115

Browse files
🔃 [Magento Community Engineering] Community Contributions - 2.4-develop expedited
Accepted Community Pull Requests: - #27414: Resolved Cannot create shipment for remaining items (by @niravkrish) - #27876: Add GraphQL mutations for Reset password for MyAccount (by @Usik2203) Fixed GitHub Issues: - #27141: Cannot create shipment for remaining items (reported by @MLucaciu) has been fixed in #27414 by @niravkrish in 2.4-develop branch Related commits: 1. 8e4c3bf 2. ec55a63 3. b7e99de 4. cbc352e 5. c85bcd0 6. 01aeae6 7. 20e30c6 8. 2d34f05 9. f6938af 10. f5c4dc4
2 parents 0e743f2 + f98ef04 commit 5a09115

File tree

9 files changed

+665
-3
lines changed

9 files changed

+665
-3
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Resolver;
9+
10+
use Magento\Customer\Api\AccountManagementInterface;
11+
use Magento\Customer\Api\CustomerRepositoryInterface;
12+
use Magento\Customer\Model\AccountManagement;
13+
use Magento\Customer\Model\AuthenticationInterface;
14+
use Magento\Framework\Exception\LocalizedException;
15+
use Magento\Framework\GraphQl\Config\Element\Field;
16+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
17+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
18+
use Magento\Framework\GraphQl\Query\Resolver\Value;
19+
use Magento\Framework\GraphQl\Query\ResolverInterface;
20+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
21+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
22+
23+
/**
24+
* Class Resolver for RequestPasswordResetEmail
25+
*/
26+
class RequestPasswordResetEmail implements ResolverInterface
27+
{
28+
/**
29+
* @var AuthenticationInterface
30+
*/
31+
private $authentication;
32+
33+
/**
34+
* @var CustomerRepositoryInterface
35+
*/
36+
private $customerRepository;
37+
38+
/**
39+
* @var AccountManagementInterface
40+
*/
41+
private $customerAccountManagement;
42+
43+
/**
44+
* @var EmailValidator
45+
*/
46+
private $emailValidator;
47+
48+
/**
49+
* RequestPasswordResetEmail constructor.
50+
*
51+
* @param AuthenticationInterface $authentication
52+
* @param CustomerRepositoryInterface $customerRepository
53+
* @param AccountManagementInterface $customerAccountManagement
54+
* @param EmailValidator $emailValidator
55+
*/
56+
public function __construct(
57+
AuthenticationInterface $authentication,
58+
CustomerRepositoryInterface $customerRepository,
59+
AccountManagementInterface $customerAccountManagement,
60+
EmailValidator $emailValidator
61+
) {
62+
$this->authentication = $authentication;
63+
$this->customerRepository = $customerRepository;
64+
$this->customerAccountManagement = $customerAccountManagement;
65+
$this->emailValidator = $emailValidator;
66+
}
67+
68+
/**
69+
* Send password email request
70+
*
71+
* @param Field $field
72+
* @param ContextInterface $context
73+
* @param ResolveInfo $info
74+
* @param array|null $value
75+
* @param array|null $args
76+
*
77+
* @return bool|Value|mixed
78+
*
79+
* @throws GraphQlInputException
80+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
81+
*/
82+
public function resolve(
83+
Field $field,
84+
$context,
85+
ResolveInfo $info,
86+
array $value = null,
87+
array $args = null
88+
) {
89+
if (empty($args['email'])) {
90+
throw new GraphQlInputException(__('You must specify an email address.'));
91+
}
92+
93+
if (!$this->emailValidator->isValid($args['email'])) {
94+
throw new GraphQlInputException(__('The email address has an invalid format.'));
95+
}
96+
97+
try {
98+
$customer = $this->customerRepository->get($args['email']);
99+
} catch (LocalizedException $e) {
100+
throw new GraphQlInputException(__('Cannot reset the customer\'s password'), $e);
101+
}
102+
103+
if (true === $this->authentication->isLocked($customer->getId())) {
104+
throw new GraphQlInputException(__('The account is locked'));
105+
}
106+
107+
try {
108+
return $this->customerAccountManagement->initiatePasswordReset(
109+
$args['email'],
110+
AccountManagement::EMAIL_RESET
111+
);
112+
} catch (LocalizedException $e) {
113+
throw new GraphQlInputException(__('Cannot reset the customer\'s password'), $e);
114+
}
115+
}
116+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CustomerGraphQl\Model\Resolver;
9+
10+
use Magento\Customer\Api\AccountManagementInterface;
11+
use Magento\Customer\Api\CustomerRepositoryInterface;
12+
use Magento\Customer\Model\AuthenticationInterface;
13+
use Magento\Framework\Exception\LocalizedException;
14+
use Magento\Framework\GraphQl\Config\Element\Field;
15+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
16+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
17+
use Magento\Framework\GraphQl\Query\Resolver\Value;
18+
use Magento\Framework\GraphQl\Query\ResolverInterface;
19+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
20+
use Magento\Framework\Validator\EmailAddress as EmailValidator;
21+
22+
/**
23+
* Class Resolver for ResetPassword
24+
*/
25+
class ResetPassword implements ResolverInterface
26+
{
27+
/**
28+
* @var AccountManagementInterface
29+
*/
30+
private $customerAccountManagement;
31+
32+
/**
33+
* @var EmailValidator
34+
*/
35+
private $emailValidator;
36+
37+
/**
38+
* @var AuthenticationInterface
39+
*/
40+
private $authentication;
41+
42+
/**
43+
* @var CustomerRepositoryInterface
44+
*/
45+
private $customerRepository;
46+
47+
/**
48+
* ResetPassword constructor.
49+
*
50+
* @param AuthenticationInterface $authentication
51+
* @param CustomerRepositoryInterface $customerRepository
52+
* @param AccountManagementInterface $customerAccountManagement
53+
* @param EmailValidator $emailValidator
54+
*/
55+
public function __construct(
56+
AuthenticationInterface $authentication,
57+
CustomerRepositoryInterface $customerRepository,
58+
AccountManagementInterface $customerAccountManagement,
59+
EmailValidator $emailValidator
60+
) {
61+
$this->authentication = $authentication;
62+
$this->customerRepository = $customerRepository;
63+
$this->customerAccountManagement = $customerAccountManagement;
64+
$this->emailValidator = $emailValidator;
65+
}
66+
67+
/**
68+
* Reset old password and set new
69+
*
70+
* @param Field $field
71+
* @param ContextInterface $context
72+
* @param ResolveInfo $info
73+
* @param array|null $value
74+
* @param array|null $args
75+
*
76+
* @return bool|Value|mixed
77+
*
78+
* @throws GraphQlInputException
79+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
80+
*/
81+
public function resolve(
82+
Field $field,
83+
$context,
84+
ResolveInfo $info,
85+
array $value = null,
86+
array $args = null
87+
) {
88+
if (empty($args['email'])) {
89+
throw new GraphQlInputException(__('You must specify an email address.'));
90+
}
91+
92+
if (!$this->emailValidator->isValid($args['email'])) {
93+
throw new GraphQlInputException(__('The email address has an invalid format.'));
94+
}
95+
96+
if (empty($args['resetPasswordToken'])) {
97+
throw new GraphQlInputException(__('resetPasswordToken must be specified'));
98+
}
99+
100+
if (empty($args['newPassword'])) {
101+
throw new GraphQlInputException(__('newPassword must be specified'));
102+
}
103+
104+
try {
105+
$customer = $this->customerRepository->get($args['email']);
106+
} catch (LocalizedException $e) {
107+
throw new GraphQlInputException(__('Cannot set the customer\'s password'), $e);
108+
}
109+
110+
if (true === $this->authentication->isLocked($customer->getId())) {
111+
throw new GraphQlInputException(__('The account is locked'));
112+
}
113+
114+
try {
115+
return $this->customerAccountManagement->resetPassword(
116+
$args['email'],
117+
$args['resetPasswordToken'],
118+
$args['newPassword']
119+
);
120+
} catch (LocalizedException $e) {
121+
throw new GraphQlInputException(__('Cannot set the customer\'s password'), $e);
122+
}
123+
}
124+
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,13 @@
2020
</argument>
2121
</arguments>
2222
</type>
23+
<type name="Magento\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider">
24+
<arguments>
25+
<argument name="extendedConfigData" xsi:type="array">
26+
<item name="required_character_classes_number" xsi:type="string">customer/password/required_character_classes_number</item>
27+
<item name="minimum_password_length" xsi:type="string">customer/password/minimum_password_length</item>
28+
<item name="autocomplete_on_storefront" xsi:type="string">customer/password/autocomplete_on_storefront</item>
29+
</argument>
30+
</arguments>
31+
</type>
2332
</config>

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

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# Copyright © Magento, Inc. All rights reserved.
22
# See COPYING.txt for license details.
33

4+
type StoreConfig {
5+
required_character_classes_number : String @doc(description: "The number of different character classes required in a password (lowercase, uppercase, digits, special characters).")
6+
minimum_password_length : String @doc(description: "The minimum number of characters required for a valid password.")
7+
autocomplete_on_storefront : Boolean @doc(description: "Enable autocomplete on login and forgot password forms")
8+
}
9+
410
type Query {
511
customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account") @cache(cacheable: false)
612
isEmailAvailable (
@@ -17,6 +23,8 @@ type Mutation {
1723
createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create customer address")
1824
updateCustomerAddress(id: Int!, input: CustomerAddressInput): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update customer address")
1925
deleteCustomerAddress(id: Int!): Boolean @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\DeleteCustomerAddress") @doc(description: "Delete customer address")
26+
requestPasswordResetEmail(email: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RequestPasswordResetEmail") @doc(description: "Request an email with a reset password token for the registered customer identified by the specified email.")
27+
resetPassword(email: String!, resetPasswordToken: String!, newPassword: String!): Boolean @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ResetPassword") @doc(description: "Reset a customer's password using the reset password token that the customer received in an email after requesting it using requestPasswordResetEmail.")
2028
}
2129

2230
input CustomerAddressInput {

app/code/Magento/Sales/Model/Order/Item.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ public function getQtyToShip()
232232
*/
233233
public function getSimpleQtyToShip()
234234
{
235-
$qty = $this->getQtyOrdered() - $this->getQtyShipped() - $this->getQtyRefunded() - $this->getQtyCanceled();
235+
$qty = $this->getQtyOrdered() - max($this->getQtyShipped(), $this->getQtyRefunded()) - $this->getQtyCanceled();
236236
return max(round($qty, 8), 0);
237237
}
238238

app/code/Magento/Sales/Test/Unit/Model/Order/ItemTest.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ public function getItemQtyVariants()
321321
'qty_ordered' => 12, 'qty_invoiced' => 12, 'qty_refunded' => 5, 'qty_shipped' => 4,
322322
'qty_canceled' => 0
323323
],
324-
'expectedResult' => ['to_ship' => 3.0, 'to_invoice' => 0.0]
324+
'expectedResult' => ['to_ship' => 7.0, 'to_invoice' => 0.0]
325325
],
326326
'complete' => [
327327
'options' => [
@@ -342,7 +342,7 @@ public function getItemQtyVariants()
342342
'qty_ordered' => 4.4, 'qty_invoiced' => 0.4, 'qty_refunded' => 0.4, 'qty_shipped' => 4,
343343
'qty_canceled' => 0,
344344
],
345-
'expectedResult' => ['to_ship' => 0.0, 'to_invoice' => 4.0]
345+
'expectedResult' => ['to_ship' => 0.4, 'to_invoice' => 4.0]
346346
],
347347
'completely_invoiced_using_decimals' => [
348348
'options' => [

0 commit comments

Comments
 (0)