Skip to content

Commit ffa5965

Browse files
authored
LYNX-551: Add order status date tracking and API coverage
1 parent 0aefc4b commit ffa5965

File tree

9 files changed

+387
-0
lines changed

9 files changed

+387
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Model;
9+
10+
use Magento\Sales\Model\ResourceModel\SalesOrderStatusChangeHistory
11+
as SalesOrderStatusChangeHistoryResourceModel;
12+
13+
class InsertOrderStatusChangeHistory
14+
{
15+
/**
16+
* @param SalesOrderStatusChangeHistoryResourceModel $salesOrderStatusChangeHistoryResourceModel
17+
*/
18+
public function __construct(
19+
private readonly SalesOrderStatusChangeHistoryResourceModel $salesOrderStatusChangeHistoryResourceModel,
20+
) {
21+
}
22+
23+
/**
24+
* Inserts latest status if status is changed
25+
*
26+
* @param Order $order
27+
* @return void
28+
*/
29+
public function execute(Order $order): void
30+
{
31+
$latestStatus = $this->salesOrderStatusChangeHistoryResourceModel->getLatestStatus((int)$order->getId());
32+
if ((!$latestStatus && $order->getStatus()) ||
33+
(isset($latestStatus['status']) && $latestStatus['status'] !== $order->getStatus())
34+
) {
35+
$this->salesOrderStatusChangeHistoryResourceModel->insert($order);
36+
}
37+
}
38+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Model\ResourceModel;
9+
10+
use Magento\Framework\App\ResourceConnection;
11+
use Magento\Sales\Model\Order;
12+
use Magento\Sales\Model\OrderRepository;
13+
14+
/**
15+
* Sales order status change history resource model.
16+
*/
17+
class SalesOrderStatusChangeHistory
18+
{
19+
/**
20+
* Sales order status change log table to store order status and timestamp
21+
*/
22+
private const TABLE_NAME = 'sales_order_status_change_history';
23+
24+
/**
25+
* Sales order table
26+
*/
27+
private const ORDER_TABLE_NAME = 'sales_order';
28+
29+
/**
30+
* @param ResourceConnection $resourceConnection
31+
* @param OrderRepository $orderRepository
32+
*/
33+
public function __construct(
34+
private readonly ResourceConnection $resourceConnection,
35+
private readonly OrderRepository $orderRepository,
36+
) {
37+
}
38+
39+
/**
40+
* Fetch recent row from table if entry exists against the order
41+
*
42+
* @param int $orderId
43+
* @return array|null
44+
*/
45+
public function getLatestStatus(int $orderId): ?array
46+
{
47+
$connection = $this->resourceConnection->getConnection();
48+
return $connection->fetchRow(
49+
$connection->select()->from(
50+
$connection->getTableName(self::TABLE_NAME),
51+
['status', 'created_at']
52+
)->where(
53+
'order_id = ?',
54+
$orderId
55+
)->order('created_at DESC')
56+
) ?: null;
57+
}
58+
59+
/**
60+
* Insert updated status against an order into the table
61+
*
62+
* @param Order $order
63+
* @return void
64+
*/
65+
public function insert(Order $order): void
66+
{
67+
if (!$this->isOrderExists((int)$order->getId()) || $order->getStatus() === null) {
68+
return;
69+
}
70+
71+
$connection = $this->resourceConnection->getConnection();
72+
$connection->insert(
73+
$connection->getTableName(self::TABLE_NAME),
74+
[
75+
'order_id' => (int)$order->getId(),
76+
'status' => $order->getStatus()
77+
]
78+
);
79+
}
80+
81+
/**
82+
* Check if order exists in db or is deleted
83+
*
84+
* @param int $orderId
85+
* @return bool
86+
*/
87+
private function isOrderExists(int $orderId): bool
88+
{
89+
$connection = $this->resourceConnection->getConnection();
90+
$entityId = $connection->fetchOne(
91+
$connection->select()->from(
92+
$connection->getTableName(self::ORDER_TABLE_NAME),
93+
['entity_id']
94+
)->where(
95+
'entity_id = ?',
96+
$orderId
97+
)
98+
);
99+
return (int) $entityId === $orderId;
100+
}
101+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Observer;
9+
10+
use Magento\Framework\Event\Observer;
11+
use Magento\Framework\Event\ObserverInterface;
12+
use Magento\Sales\Model\Order;
13+
use Magento\Sales\Model\InsertOrderStatusChangeHistory;
14+
15+
class StoreStatusChangeObserver implements ObserverInterface
16+
{
17+
/**
18+
* @param InsertOrderStatusChangeHistory $salesOrderStatusChangeHistory
19+
*/
20+
public function __construct(
21+
private readonly InsertOrderStatusChangeHistory $salesOrderStatusChangeHistory,
22+
) {
23+
}
24+
25+
/**
26+
* Store status in sales_order_status_change_history table if the status is updated
27+
*
28+
* @param Observer $observer
29+
* @return $this
30+
*/
31+
public function execute(Observer $observer)
32+
{
33+
/* @var $order Order */
34+
$order = $observer->getEvent()->getOrder();
35+
36+
if (!$order->getId()) {
37+
//order not saved in the database
38+
return $this;
39+
}
40+
41+
//Insert order status into sales_order_status_change_history table if the order status is changed
42+
$this->salesOrderStatusChangeHistory->execute($order);
43+
return $this;
44+
}
45+
}

app/code/Magento/Sales/etc/db_schema.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,4 +2068,20 @@
20682068
<column name="store_id"/>
20692069
</index>
20702070
</table>
2071+
<table name="sales_order_status_change_history" resource="default" engine="innodb" comment="Save the updated order status with timestamp">
2072+
<column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="true"
2073+
comment="Entity ID"/>
2074+
<column xsi:type="int" name="order_id" unsigned="true" nullable="false" identity="false"
2075+
comment="Order ID"/>
2076+
<column xsi:type="varchar" name="status" nullable="false" length="32"
2077+
comment="Order Status"/>
2078+
<column xsi:type="datetime" name="created_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP"
2079+
comment="Order status updated at"/>
2080+
<constraint xsi:type="primary" referenceId="PRIMARY">
2081+
<column name="entity_id"/>
2082+
</constraint>
2083+
<constraint xsi:type="foreign" referenceId="SALES_ORDER_STATUS_CHANGE_HISTORY_ORDER_ID_SALES_ORDER_ENTITY_ID"
2084+
table="sales_order_status_change_history" column="order_id"
2085+
referenceTable="sales_order" referenceColumn="entity_id" onDelete="CASCADE" />
2086+
</table>
20712087
</schema>

app/code/Magento/Sales/etc/db_schema_whitelist.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,5 +1246,18 @@
12461246
"SALES_ORDER_STATUS_LABEL_STATUS_SALES_ORDER_STATUS_STATUS": true,
12471247
"SALES_ORDER_STATUS_LABEL_STORE_ID_STORE_STORE_ID": true
12481248
}
1249+
},
1250+
"sales_order_status_change_history": {
1251+
"column": {
1252+
"entity_id": true,
1253+
"order_id": true,
1254+
"status": true,
1255+
"created_at": true,
1256+
"updated_at": true
1257+
},
1258+
"constraint": {
1259+
"PRIMARY": true,
1260+
"SALES_ORDER_STATUS_CHANGE_HISTORY_ORDER_ID_SALES_ORDER_ENTITY_ID": true
1261+
}
12491262
}
12501263
}

app/code/Magento/Sales/etc/events.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,7 @@
5656
name="sales_assign_order_to_customer"
5757
instance="Magento\Sales\Observer\AssignOrderToCustomerObserver" />
5858
</event>
59+
<event name="sales_order_save_after">
60+
<observer name="store_status_change_observer" instance="Magento\Sales\Observer\StoreStatusChangeObserver" />
61+
</event>
5962
</config>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
/**
3+
* Copyright 2024 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\SalesGraphQl\Model\Resolver;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
12+
use Magento\Framework\GraphQl\Config\Element\Field;
13+
use Magento\Framework\GraphQl\Query\ResolverInterface;
14+
use Magento\Framework\Stdlib\DateTime;
15+
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
16+
use Magento\Sales\Model\Order;
17+
use \Magento\Sales\Model\ResourceModel\SalesOrderStatusChangeHistory;
18+
19+
/**
20+
* Resolver for the OrderStatusChangeDate in CustomerOrder
21+
*/
22+
class OrderStatusChangeDate implements ResolverInterface
23+
{
24+
/**
25+
* @param SalesOrderStatusChangeHistory $salesOrderStatusChangeHistory
26+
* @param TimezoneInterface $localeDate
27+
*/
28+
public function __construct(
29+
private readonly SalesOrderStatusChangeHistory $salesOrderStatusChangeHistory,
30+
private readonly TimezoneInterface $localeDate,
31+
) {
32+
}
33+
34+
/**
35+
* @inheritDoc
36+
*/
37+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null): string
38+
{
39+
if (!isset($value['model']) || !($value['model'] instanceof Order)) {
40+
throw new LocalizedException(__('"model" value should be specified'));
41+
}
42+
$order = $value['model'];
43+
$latestStatus = $this->salesOrderStatusChangeHistory->getLatestStatus((int)$order->getId());
44+
return ($latestStatus)
45+
? $this->localeDate->convertConfigTimeToUtc($latestStatus['created_at'], DateTime::DATE_PHP_FORMAT)
46+
: '';
47+
}
48+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ type CustomerOrder @doc(description: "Contains details about each of the custome
8282
is_virtual: Boolean! @doc(description: "`TRUE` if the order is virtual") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\OrderIsVirtual")
8383
available_actions: [OrderActionType!]! @doc(description: "List of available order actions.") @resolver(class: "\\Magento\\SalesGraphQl\\Model\\Resolver\\OrderAvailableActions")
8484
customer_info: OrderCustomerInfo! @doc(description: "Returns customer information from order.") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\OrderCustomerInfo")
85+
order_status_change_date: String! @doc(description: "The date the order status was last updated.") @resolver(class: "Magento\\SalesGraphQl\\Model\\Resolver\\OrderStatusChangeDate")
8586
}
8687

8788
type OrderCustomerInfo {

0 commit comments

Comments
 (0)