Skip to content

Commit b9bf867

Browse files
authored
Merge branch '2.4-develop' into GL_PR_Arrows_September_06_2023
2 parents c6bc2bf + ba41752 commit b9bf867

File tree

12 files changed

+996
-24
lines changed

12 files changed

+996
-24
lines changed

app/code/Magento/OrderCancellation/Block/Adminhtml/Form/Field/Reasons.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class Reasons extends AbstractFieldArray
1616
*/
1717
protected function _prepareToRender()
1818
{
19-
$this->addColumn('reason', ['label' => __('Reason'), 'class' => 'required-entry']);
19+
$this->addColumn('description', ['label' => __('Reason'), 'class' => 'required-entry']);
2020
$this->_addAfter = false;
2121
$this->_addButtonLabel = __('Add Reason');
2222
}
Lines changed: 124 additions & 0 deletions
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\OrderCancellation\Model;
9+
10+
use Magento\Framework\Escaper;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Sales\Api\Data\OrderPaymentInterface;
13+
use Magento\Sales\Api\OrderRepositoryInterface;
14+
use Magento\Sales\Exception\CouldNotRefundException;
15+
use Magento\Sales\Exception\DocumentValidationException;
16+
use Magento\Sales\Model\Order;
17+
use Magento\Sales\Model\RefundInvoice;
18+
use Magento\Sales\Model\RefundOrder;
19+
use Magento\Sales\Model\Order\Email\Sender\OrderCommentSender;
20+
21+
/**
22+
* Cancels an order including online or offline payment refund and updates status accordingly.
23+
*/
24+
class CancelOrder
25+
{
26+
private const EMAIL_NOTIFICATION_SUCCESS = "Order cancellation notification email was sent.";
27+
28+
private const EMAIL_NOTIFICATION_ERROR = "Email notification failed.";
29+
30+
/**
31+
* @var OrderCommentSender
32+
*/
33+
private OrderCommentSender $sender;
34+
35+
/**
36+
* @var RefundInvoice
37+
*/
38+
private RefundInvoice $refundInvoice;
39+
40+
/**
41+
* @var RefundOrder
42+
*/
43+
private RefundOrder $refundOrder;
44+
45+
/**
46+
* @var OrderRepositoryInterface
47+
*/
48+
private OrderRepositoryInterface $orderRepository;
49+
50+
/**
51+
* @var Escaper
52+
*/
53+
private Escaper $escaper;
54+
55+
/**
56+
* @param RefundInvoice $refundInvoice
57+
* @param RefundOrder $refundOrder
58+
* @param OrderRepositoryInterface $orderRepository
59+
* @param Escaper $escaper
60+
* @param OrderCommentSender $sender
61+
*/
62+
public function __construct(
63+
RefundInvoice $refundInvoice,
64+
RefundOrder $refundOrder,
65+
OrderRepositoryInterface $orderRepository,
66+
Escaper $escaper,
67+
OrderCommentSender $sender
68+
) {
69+
$this->refundInvoice = $refundInvoice;
70+
$this->refundOrder = $refundOrder;
71+
$this->orderRepository = $orderRepository;
72+
$this->escaper = $escaper;
73+
$this->sender = $sender;
74+
}
75+
76+
/**
77+
* Cancels and refund an order, if applicable.
78+
*
79+
* @param Order $order
80+
* @param string $reason
81+
* @return Order
82+
* @throws LocalizedException
83+
* @throws CouldNotRefundException
84+
* @throws DocumentValidationException
85+
*/
86+
public function execute(
87+
Order $order,
88+
string $reason
89+
): Order {
90+
/** @var OrderPaymentInterface $payment */
91+
$payment = $order->getPayment();
92+
if ($payment->getAmountPaid() === null) {
93+
$order->cancel();
94+
} else {
95+
if ($payment->getMethodInstance()->isOffline()) {
96+
$this->refundOrder->execute($order->getEntityId());
97+
} else {
98+
/** @var Order\Invoice $invoice */
99+
foreach ($order->getInvoiceCollection() as $invoice) {
100+
$this->refundInvoice->execute($invoice->getEntityId());
101+
}
102+
}
103+
// in this case order needs to be re-instantiated
104+
$order = $this->orderRepository->get($order->getId());
105+
}
106+
107+
$result = $this->sender->send(
108+
$order,
109+
true,
110+
__("Order %1 was cancelled", $order->getRealOrderId())
111+
);
112+
$order->addCommentToStatusHistory(
113+
$result ?
114+
__("%1", CancelOrder::EMAIL_NOTIFICATION_SUCCESS) : __("%1", CancelOrder::EMAIL_NOTIFICATION_ERROR)
115+
);
116+
117+
$order->addCommentToStatusHistory(
118+
$this->escaper->escapeHtml($reason),
119+
$order->getStatus()
120+
);
121+
122+
return $this->orderRepository->save($order);
123+
}
124+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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\OrderCancellation\Model\Config;
9+
10+
use Magento\Framework\App\Config\ScopeConfigInterface;
11+
use Magento\Store\Model\ScopeInterface as StoreScopeInterface;
12+
13+
/**
14+
* Config Model for order cancellation module
15+
*/
16+
class Config
17+
{
18+
private const SETTING_ENABLED = '1';
19+
20+
private const SETTING_ENABLE_PATH = 'sales/cancellation/enabled';
21+
22+
/**
23+
* @var ScopeConfigInterface
24+
*/
25+
private ScopeConfigInterface $scopeConfig;
26+
27+
/**
28+
* @param ScopeConfigInterface $scopeConfig
29+
*/
30+
public function __construct(ScopeConfigInterface $scopeConfig)
31+
{
32+
$this->scopeConfig = $scopeConfig;
33+
}
34+
35+
/**
36+
* Is order cancellation enabled for requested store
37+
*
38+
* @param int $storeId
39+
* @return bool
40+
*/
41+
public function isOrderCancellationEnabledForStore(int $storeId): bool
42+
{
43+
return $this->scopeConfig->getValue(
44+
self::SETTING_ENABLE_PATH,
45+
StoreScopeInterface::SCOPE_STORE,
46+
$storeId
47+
) === self::SETTING_ENABLED;
48+
}
49+
}

app/code/Magento/OrderCancellation/composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
"require": {
88
"php": "~8.1.0||~8.2.0",
99
"magento/framework": "*",
10-
"magento/module-config": "*"
10+
"magento/module-config": "*",
11+
"magento/module-store": "*",
12+
"magento/module-sales": "*"
1113
},
1214
"type": "magento2-module",
1315
"license": [

app/code/Magento/OrderCancellation/etc/config.xml

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,27 @@
55
* See COPYING.txt for license details.
66
*/
77
-->
8-
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
9-
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
109
<default>
1110
<sales>
1211
<cancellation>
1312
<enabled>0</enabled>
1413
<reasons>
15-
<item1>
16-
<reason>The item(s) are no longer needed</reason>
17-
</item1>
18-
<item2>
19-
<reason>The order was placed by mistake</reason>
20-
</item2>
21-
<item3>
22-
<reason>Item(s) not arriving within the expected timeframe</reason>
23-
</item3>
24-
<item4>
25-
<reason>Found a better price elsewhere</reason>
26-
</item4>
27-
<item5>
28-
<reason>Other</reason>
29-
</item5>
14+
<reason1>
15+
<description>The item(s) are no longer needed</description>
16+
</reason1>
17+
<reason2>
18+
<description>The order was placed by mistake</description>
19+
</reason2>
20+
<reason3>
21+
<description>Item(s) not arriving within the expected timeframe</description>
22+
</reason3>
23+
<reason4>
24+
<description>Found a better price elsewhere</description>
25+
</reason4>
26+
<reason5>
27+
<description>Other</description>
28+
</reason5>
3029
</reasons>
3130
</cancellation>
3231
</sales>
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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\OrderCancellationGraphQl\Model\Resolver;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
use Magento\Framework\GraphQl\Config\Element\Field;
12+
use Magento\Framework\GraphQl\Query\ResolverInterface;
13+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
14+
use Magento\OrderCancellation\Model\CancelOrder as CancelOrderAction;
15+
use Magento\OrderCancellation\Model\Config\Config;
16+
use Magento\OrderCancellationGraphQl\Model\ValidateRequest;
17+
use Magento\Sales\Api\OrderRepositoryInterface;
18+
use Magento\Sales\Model\Order;
19+
use Magento\SalesGraphQl\Model\Formatter\Order as OrderFormatter;
20+
21+
/**
22+
* Cancels an order
23+
*/
24+
class CancelOrder implements ResolverInterface
25+
{
26+
/**
27+
* @var ValidateRequest $validateRequest
28+
*/
29+
private ValidateRequest $validateRequest;
30+
31+
/**
32+
* @var CancelOrderAction $cancelOrderAction
33+
*/
34+
private CancelOrderAction $cancelOrderAction;
35+
36+
/**
37+
* @var OrderFormatter
38+
*/
39+
private OrderFormatter $orderFormatter;
40+
41+
/**
42+
* @var OrderRepositoryInterface
43+
*/
44+
private OrderRepositoryInterface $orderRepository;
45+
46+
/**
47+
* @var Config
48+
*/
49+
private Config $config;
50+
51+
/**
52+
* @param ValidateRequest $validateRequest
53+
* @param OrderFormatter $orderFormatter
54+
* @param OrderRepositoryInterface $orderRepository
55+
* @param Config $config
56+
* @param CancelOrderAction $cancelOrderAction
57+
*/
58+
public function __construct(
59+
ValidateRequest $validateRequest,
60+
OrderFormatter $orderFormatter,
61+
OrderRepositoryInterface $orderRepository,
62+
Config $config,
63+
CancelOrderAction $cancelOrderAction
64+
) {
65+
$this->validateRequest = $validateRequest;
66+
$this->orderFormatter = $orderFormatter;
67+
$this->orderRepository = $orderRepository;
68+
$this->config = $config;
69+
$this->cancelOrderAction = $cancelOrderAction;
70+
}
71+
72+
/**
73+
* @inheritdoc
74+
*/
75+
public function resolve(
76+
Field $field,
77+
$context,
78+
ResolveInfo $info,
79+
array $value = null,
80+
array $args = null
81+
) {
82+
$this->validateRequest->execute($context, $args['input'] ?? []);
83+
84+
try {
85+
/** @var Order $order */
86+
$order = $this->orderRepository->get($args['input']['order_id']);
87+
88+
if ((int) $order->getCustomerId() !== $context->getUserId()) {
89+
return [
90+
'error' => __('Current user is not authorized to cancel this order')
91+
];
92+
}
93+
94+
if ($order->getState() === order::STATE_CLOSED
95+
|| $order->getState() === order::STATE_CANCELED
96+
|| $order->getState() === order::STATE_HOLDED
97+
) {
98+
return [
99+
'error' => __('Order already closed, cancelled or on hold'),
100+
'order' => $this->orderFormatter->format($order)
101+
];
102+
}
103+
104+
if (!$this->config->isOrderCancellationEnabledForStore((int)$order->getStoreId())) {
105+
return [
106+
'error' => __('Order cancellation is not enabled for requested store.')
107+
];
108+
}
109+
110+
$order = $this->cancelOrderAction->execute($order, $args['input']['reason']);
111+
112+
return [
113+
'order' => $this->orderFormatter->format($order)
114+
];
115+
} catch (LocalizedException $e) {
116+
return [
117+
'error' => __($e->getMessage())
118+
];
119+
}
120+
}
121+
}

app/code/Magento/OrderCancellationGraphQl/Model/Resolver/CancellationReason.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,6 @@ public function resolve(
2626
array $value = null,
2727
array $args = null
2828
) {
29-
return $value['reason'];
29+
return $value['description'];
3030
}
3131
}

0 commit comments

Comments
 (0)