Files
esun3d/app/code/Esun/SeventeenTrack/Model/TrackingRepository.php
T
2026-09-15 10:35:56 +08:00

213 lines
8.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\SeventeenTrack\Model;
use Magento\Framework\App\ResourceConnection;
use Magento\Sales\Model\Order\Shipment\Track;
class TrackingRepository
{
private const TABLE = 'esun_seventeen_track';
public function __construct(private readonly ResourceConnection $resource)
{
}
public function enqueue(Track $track): void
{
if (!$track->getId() || !$track->getTrackNumber() || !$track->getParentId() || !$track->getOrderId()) {
return;
}
$connection = $this->connection();
$existing = $connection->fetchRow($connection->select()->from($this->table())
->where('shipment_track_id = ?', (int)$track->getId())->limit(1));
$number = trim((string)$track->getTrackNumber());
$data = [
'shipment_track_id' => (int)$track->getId(),
'shipment_id' => (int)$track->getParentId(),
'order_id' => (int)$track->getOrderId(),
'tracking_number' => $number,
];
if ($existing && (string)$existing['tracking_number'] !== $number) {
$data += [
'carrier_code' => null,
'sync_status' => 'pending',
'error_code' => null,
'attempt_count' => 0,
'next_attempt_at' => null,
'package_status' => null,
'latest_event' => null,
'delivered_at' => null,
'last_error' => null,
'response_data' => null,
'last_synced_at' => null,
];
}
$connection->insertOnDuplicate($this->table(), $data, array_keys($data));
}
public function backfill(): void
{
$connection = $this->connection();
$track = $this->resource->getTableName('sales_shipment_track');
$select = $connection->select()->from(['track' => $track], [
'shipment_track_id' => 'entity_id',
'shipment_id' => 'parent_id',
'order_id' => 'order_id',
'tracking_number' => 'track_number',
])->where('track.track_number IS NOT NULL')->where('track.track_number != ?', '');
$connection->query($connection->insertFromSelect(
$select,
$this->table(),
['shipment_track_id', 'shipment_id', 'order_id', 'tracking_number'],
\Magento\Framework\DB\Adapter\AdapterInterface::INSERT_IGNORE
));
}
public function getPending(int $limit): array
{
return $this->connection()->fetchAll($this->connection()->select()->from($this->table())
->where('sync_status IN (?)', ['pending', 'error'])
->where('next_attempt_at IS NULL OR next_attempt_at <= UTC_TIMESTAMP()')
->order('updated_at ASC')->limit($limit));
}
public function getTracking(int $limit): array
{
return $this->connection()->fetchAll($this->connection()->select()->from($this->table())
->where('sync_status = ?', 'tracking')->where('package_status IS NULL OR package_status != ?', 'Delivered')
->where('next_attempt_at IS NULL OR next_attempt_at <= UTC_TIMESTAMP()')
->order('last_synced_at ASC')->limit($limit));
}
public function markRegistered(string $number, ?int $carrier): void
{
$this->connection()->update($this->table(), [
'sync_status' => 'tracking',
'carrier_code' => $carrier,
'error_code' => null,
'attempt_count' => 0,
'next_attempt_at' => null,
'last_error' => null,
], ['tracking_number = ?' => $number]);
}
public function markError(string $number, string $message, ?int $code, string $nextAttemptAt): void
{
$this->connection()->update($this->table(), [
'sync_status' => 'error',
'error_code' => $code,
'attempt_count' => new \Zend_Db_Expr('attempt_count + 1'),
'next_attempt_at' => $nextAttemptAt,
'last_error' => mb_substr($message, 0, 2000),
], ['tracking_number = ?' => $number]);
}
public function markQueryError(string $number, string $message, ?int $code, string $nextAttemptAt): void
{
$this->connection()->update($this->table(), [
'sync_status' => 'tracking',
'error_code' => $code,
'attempt_count' => new \Zend_Db_Expr('attempt_count + 1'),
'next_attempt_at' => $nextAttemptAt,
'last_error' => mb_substr($message, 0, 2000),
], ['tracking_number = ?' => $number]);
}
public function markPending(string $number, string $message, ?int $code, string $nextAttemptAt): void
{
$this->connection()->update($this->table(), [
'sync_status' => 'pending',
'carrier_code' => null,
'error_code' => $code,
'attempt_count' => new \Zend_Db_Expr('attempt_count + 1'),
'next_attempt_at' => $nextAttemptAt,
'last_error' => mb_substr($message, 0, 2000),
], ['tracking_number = ?' => $number]);
}
public function markRejected(string $number, string $message, ?int $code): void
{
$this->connection()->update($this->table(), [
'sync_status' => 'rejected',
'error_code' => $code,
'attempt_count' => new \Zend_Db_Expr('attempt_count + 1'),
'next_attempt_at' => null,
'last_error' => mb_substr($message, 0, 2000),
], ['tracking_number = ?' => $number]);
}
public function saveTrackingInfo(string $number, array $info, string $serialized): array
{
$trackInfo = (array)($info['track_info'] ?? []);
$latestStatus = (array)($trackInfo['latest_status'] ?? []);
$latestEvent = (array)($trackInfo['latest_event'] ?? []);
$status = (string)($latestStatus['status'] ?? $info['package_status'] ?? '');
$event = (string)($latestEvent['description'] ?? $latestEvent['event_description'] ?? $info['latest_event_info'] ?? '');
$deliveredAt = null;
if ($status === 'Delivered') {
$deliveredAt = $trackInfo['time_metrics']['delivery_time']
?? $trackInfo['time_metrics']['delievery_time']
?? $latestEvent['time_utc'] ?? gmdate('Y-m-d H:i:s');
}
$rows = $this->connection()->fetchAll($this->connection()->select()->from($this->table(), ['entity_id', 'order_id'])
->where('tracking_number = ?', $number));
$this->connection()->update($this->table(), [
'carrier_code' => isset($info['carrier']) ? (int)$info['carrier'] : null,
'sync_status' => 'tracking',
'error_code' => null,
'attempt_count' => 0,
'next_attempt_at' => null,
'package_status' => $status !== '' ? $status : null,
'latest_event' => $event !== '' ? mb_substr($event, 0, 1024) : null,
'delivered_at' => $deliveredAt ? $this->toMysqlDate((string)$deliveredAt) : null,
'response_data' => $serialized,
'last_error' => null,
'last_synced_at' => gmdate('Y-m-d H:i:s'),
], ['tracking_number = ?' => $number]);
return array_values(array_unique(array_map('intval', array_column($rows, 'order_id'))));
}
public function saveStoppedInfo(string $number, string $serialized): void
{
$this->connection()->update($this->table(), [
'sync_status' => 'stopped',
'error_code' => null,
'next_attempt_at' => null,
'response_data' => $serialized,
'last_error' => null,
'last_synced_at' => gmdate('Y-m-d H:i:s'),
], ['tracking_number = ?' => $number]);
}
public function isOrderDelivered(int $orderId): bool
{
$select = $this->connection()->select()->from($this->table(), [
'total' => new \Zend_Db_Expr('COUNT(*)'),
'delivered' => new \Zend_Db_Expr("SUM(package_status = 'Delivered')"),
])->where('order_id = ?', $orderId);
$row = $this->connection()->fetchRow($select) ?: [];
return (int)($row['total'] ?? 0) > 0 && (int)($row['total'] ?? 0) === (int)($row['delivered'] ?? 0);
}
private function toMysqlDate(string $value): string
{
try {
return (new \DateTimeImmutable($value))->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i:s');
} catch (\Throwable) {
return gmdate('Y-m-d H:i:s');
}
}
private function connection(): \Magento\Framework\DB\Adapter\AdapterInterface
{
return $this->resource->getConnection('sales');
}
private function table(): string
{
return $this->resource->getTableName(self::TABLE);
}
}