feat(06-sonarqube-quality): extract long methods to fix S138 violations (06-06)
ShopproOrdersSyncService: sync() 195→44 lines via syncOneIntegration, fetchOrdersPage, processPageCandidates, importOneOrder; mapAddresses() 166→34 lines via buildCustomerAddress, buildDeliveryAddress. OrdersRepository: paginate() 183→69 lines via buildPaginateFilters, buildListSql, transformOrderRow; findDetails() 101→40 lines via loadOrderAddresses/Items/Payments/Shipments/Documents/Notes/StatusHistory. SonarQube S138 violations: 4 → 0. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,21 +24,78 @@ final class OrdersRepository
|
|||||||
$page = max(1, (int) ($filters['page'] ?? 1));
|
$page = max(1, (int) ($filters['page'] ?? 1));
|
||||||
$perPage = max(1, min(100, (int) ($filters['per_page'] ?? 20)));
|
$perPage = max(1, min(100, (int) ($filters['per_page'] ?? 20)));
|
||||||
$offset = ($page - 1) * $perPage;
|
$offset = ($page - 1) * $perPage;
|
||||||
|
|
||||||
$where = [];
|
|
||||||
$params = [];
|
|
||||||
$effectiveStatusSql = $this->effectiveStatusSql('o', 'asm');
|
$effectiveStatusSql = $this->effectiveStatusSql('o', 'asm');
|
||||||
$effectiveOrderedAtSql = $this->effectiveOrderedAtSql('o');
|
$effectiveOrderedAtSql = $this->effectiveOrderedAtSql('o');
|
||||||
|
|
||||||
|
['where' => $where, 'params' => $params] = $this->buildPaginateFilters($filters, $effectiveStatusSql, $effectiveOrderedAtSql);
|
||||||
|
$whereSql = $where === [] ? '' : (' WHERE ' . implode(' AND ', $where));
|
||||||
|
|
||||||
|
$sort = (string) ($filters['sort'] ?? 'ordered_at');
|
||||||
|
$sortDir = strtoupper((string) ($filters['sort_dir'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
||||||
|
$sortColumn = match ($sort) {
|
||||||
|
'source_order_id' => 'o.source_order_id',
|
||||||
|
'external_order_id' => 'o.external_order_id',
|
||||||
|
'external_status_id' => 'o.external_status_id',
|
||||||
|
'payment_status' => 'o.payment_status',
|
||||||
|
'total_with_tax' => 'o.total_with_tax',
|
||||||
|
'total_paid' => 'o.total_paid',
|
||||||
|
'source_updated_at' => 'o.source_updated_at',
|
||||||
|
'fetched_at' => 'o.fetched_at',
|
||||||
|
'id' => 'o.id',
|
||||||
|
default => $effectiveOrderedAtSql,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
$countSql = 'SELECT COUNT(*) FROM orders o '
|
||||||
|
. 'LEFT JOIN order_addresses a ON a.order_id = o.id AND a.address_type = "customer" '
|
||||||
|
. 'LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code'
|
||||||
|
. $whereSql;
|
||||||
|
$countStmt = $this->pdo->prepare($countSql);
|
||||||
|
$countStmt->execute($params);
|
||||||
|
$total = (int) $countStmt->fetchColumn();
|
||||||
|
|
||||||
|
$listSql = $this->buildListSql($effectiveStatusSql, $effectiveOrderedAtSql, $whereSql, $sortColumn, $sortDir);
|
||||||
|
$stmt = $this->pdo->prepare($listSql);
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
$stmt->bindValue(':' . $key, $value, is_int($value) ? PDO::PARAM_INT : PDO::PARAM_STR);
|
||||||
|
}
|
||||||
|
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
|
||||||
|
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
||||||
|
$stmt->execute();
|
||||||
|
|
||||||
|
$rows = $stmt->fetchAll();
|
||||||
|
if (!is_array($rows)) {
|
||||||
|
$rows = [];
|
||||||
|
}
|
||||||
|
$itemPreviewsByOrderId = $this->loadOrderItemsPreviews(array_map(
|
||||||
|
static fn (array $row): int => (int) ($row['id'] ?? 0),
|
||||||
|
$rows
|
||||||
|
));
|
||||||
|
|
||||||
|
return [
|
||||||
|
'items' => array_map(fn (array $row) => $this->transformOrderRow($row, $itemPreviewsByOrderId), $rows),
|
||||||
|
'total' => $total,
|
||||||
|
'page' => $page,
|
||||||
|
'per_page' => $perPage,
|
||||||
|
'error' => '',
|
||||||
|
];
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
return ['items' => [], 'total' => 0, 'page' => $page, 'per_page' => $perPage, 'error' => $exception->getMessage()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $filters
|
||||||
|
* @return array{where:array<int,string>,params:array<string,mixed>}
|
||||||
|
*/
|
||||||
|
private function buildPaginateFilters(array $filters, string $effectiveStatusSql, string $effectiveOrderedAtSql): array
|
||||||
|
{
|
||||||
|
$where = [];
|
||||||
|
$params = [];
|
||||||
|
|
||||||
$search = trim((string) ($filters['search'] ?? ''));
|
$search = trim((string) ($filters['search'] ?? ''));
|
||||||
if ($search !== '') {
|
if ($search !== '') {
|
||||||
$where[] = '('
|
$where[] = '(o.source_order_id LIKE :search OR o.external_order_id LIKE :search OR o.customer_login LIKE :search OR a.name LIKE :search OR a.email LIKE :search)';
|
||||||
. 'o.source_order_id LIKE :search '
|
|
||||||
. 'OR o.external_order_id LIKE :search '
|
|
||||||
. 'OR o.customer_login LIKE :search '
|
|
||||||
. 'OR a.name LIKE :search '
|
|
||||||
. 'OR a.email LIKE :search'
|
|
||||||
. ')';
|
|
||||||
$params['search'] = '%' . $search . '%';
|
$params['search'] = '%' . $search . '%';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,33 +129,12 @@ final class OrdersRepository
|
|||||||
$params['date_to'] = $dateTo . ' 23:59:59';
|
$params['date_to'] = $dateTo . ' 23:59:59';
|
||||||
}
|
}
|
||||||
|
|
||||||
$whereSql = $where === [] ? '' : (' WHERE ' . implode(' AND ', $where));
|
return ['where' => $where, 'params' => $params];
|
||||||
|
}
|
||||||
|
|
||||||
$sort = (string) ($filters['sort'] ?? 'ordered_at');
|
private function buildListSql(string $effectiveStatusSql, string $effectiveOrderedAtSql, string $whereSql, string $sortColumn, string $sortDir): string
|
||||||
$sortDir = strtoupper((string) ($filters['sort_dir'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
|
{
|
||||||
$sortColumn = match ($sort) {
|
return 'SELECT
|
||||||
'source_order_id' => 'o.source_order_id',
|
|
||||||
'external_order_id' => 'o.external_order_id',
|
|
||||||
'external_status_id' => 'o.external_status_id',
|
|
||||||
'payment_status' => 'o.payment_status',
|
|
||||||
'total_with_tax' => 'o.total_with_tax',
|
|
||||||
'total_paid' => 'o.total_paid',
|
|
||||||
'source_updated_at' => 'o.source_updated_at',
|
|
||||||
'fetched_at' => 'o.fetched_at',
|
|
||||||
'id' => 'o.id',
|
|
||||||
default => $effectiveOrderedAtSql,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
$countSql = 'SELECT COUNT(*) FROM orders o '
|
|
||||||
. 'LEFT JOIN order_addresses a ON a.order_id = o.id AND a.address_type = "customer" '
|
|
||||||
. 'LEFT JOIN allegro_order_status_mappings asm ON o.source = "allegro" AND LOWER(o.external_status_id) = asm.allegro_status_code'
|
|
||||||
. $whereSql;
|
|
||||||
$countStmt = $this->pdo->prepare($countSql);
|
|
||||||
$countStmt->execute($params);
|
|
||||||
$total = (int) $countStmt->fetchColumn();
|
|
||||||
|
|
||||||
$listSql = 'SELECT
|
|
||||||
o.id,
|
o.id,
|
||||||
o.internal_order_number,
|
o.internal_order_number,
|
||||||
o.source,
|
o.source,
|
||||||
@@ -132,31 +168,17 @@ final class OrdersRepository
|
|||||||
. $whereSql
|
. $whereSql
|
||||||
. ' ORDER BY ' . $sortColumn . ' ' . $sortDir
|
. ' ORDER BY ' . $sortColumn . ' ' . $sortDir
|
||||||
. ' LIMIT :limit OFFSET :offset';
|
. ' LIMIT :limit OFFSET :offset';
|
||||||
|
|
||||||
$stmt = $this->pdo->prepare($listSql);
|
|
||||||
foreach ($params as $key => $value) {
|
|
||||||
if (is_int($value)) {
|
|
||||||
$stmt->bindValue(':' . $key, $value, PDO::PARAM_INT);
|
|
||||||
} else {
|
|
||||||
$stmt->bindValue(':' . $key, $value);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
|
|
||||||
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
|
|
||||||
$stmt->execute();
|
|
||||||
|
|
||||||
$rows = $stmt->fetchAll();
|
/**
|
||||||
if (!is_array($rows)) {
|
* @param array<string, mixed> $row
|
||||||
$rows = [];
|
* @param array<int, array<int, array{name:string,quantity:float,media_url:string}>> $itemPreviewsByOrderId
|
||||||
}
|
* @return array<string, mixed>
|
||||||
$itemPreviewsByOrderId = $this->loadOrderItemsPreviews(array_map(
|
*/
|
||||||
static fn (array $row): int => (int) ($row['id'] ?? 0),
|
private function transformOrderRow(array $row, array $itemPreviewsByOrderId): array
|
||||||
$rows
|
{
|
||||||
));
|
|
||||||
|
|
||||||
return [
|
|
||||||
'items' => array_map(static function (array $row) use ($itemPreviewsByOrderId): array {
|
|
||||||
$orderId = (int) ($row['id'] ?? 0);
|
$orderId = (int) ($row['id'] ?? 0);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $orderId,
|
'id' => $orderId,
|
||||||
'internal_order_number' => (string) ($row['internal_order_number'] ?? ''),
|
'internal_order_number' => (string) ($row['internal_order_number'] ?? ''),
|
||||||
@@ -186,21 +208,6 @@ final class OrdersRepository
|
|||||||
'documents_count' => (int) ($row['documents_count'] ?? 0),
|
'documents_count' => (int) ($row['documents_count'] ?? 0),
|
||||||
'items_preview' => (array) ($itemPreviewsByOrderId[$orderId] ?? []),
|
'items_preview' => (array) ($itemPreviewsByOrderId[$orderId] ?? []),
|
||||||
];
|
];
|
||||||
}, $rows),
|
|
||||||
'total' => $total,
|
|
||||||
'page' => $page,
|
|
||||||
'per_page' => $perPage,
|
|
||||||
'error' => '',
|
|
||||||
];
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
return [
|
|
||||||
'items' => [],
|
|
||||||
'total' => 0,
|
|
||||||
'page' => $page,
|
|
||||||
'per_page' => $perPage,
|
|
||||||
'error' => $exception->getMessage(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -414,25 +421,52 @@ final class OrdersRepository
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$addressesStmt = $this->pdo->prepare('SELECT * FROM order_addresses WHERE order_id = :order_id ORDER BY FIELD(address_type, "customer", "invoice", "delivery"), id ASC');
|
return [
|
||||||
$addressesStmt->execute(['order_id' => $orderId]);
|
'order' => $order,
|
||||||
$addresses = $addressesStmt->fetchAll(PDO::FETCH_ASSOC);
|
'addresses' => $this->loadOrderAddresses($orderId),
|
||||||
if (!is_array($addresses)) {
|
'items' => $this->loadOrderItems($orderId),
|
||||||
$addresses = [];
|
'payments' => $this->loadOrderPayments($orderId),
|
||||||
|
'shipments' => $this->loadOrderShipments($orderId),
|
||||||
|
'documents' => $this->loadOrderDocuments($orderId),
|
||||||
|
'notes' => $this->loadOrderNotes($orderId),
|
||||||
|
'status_history' => $this->loadOrderStatusHistory($orderId),
|
||||||
|
'activity_log' => $this->loadActivityLog($orderId),
|
||||||
|
];
|
||||||
|
} catch (Throwable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function loadOrderAddresses(int $orderId): array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare('SELECT * FROM order_addresses WHERE order_id = :order_id ORDER BY FIELD(address_type, "customer", "invoice", "delivery"), id ASC');
|
||||||
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return is_array($rows) ? $rows : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function loadOrderItems(int $orderId): array
|
||||||
|
{
|
||||||
$itemsMediaSql = $this->resolvedMediaUrlSql('oi', 'o.source');
|
$itemsMediaSql = $this->resolvedMediaUrlSql('oi', 'o.source');
|
||||||
$itemsStmt = $this->pdo->prepare('SELECT oi.*, ' . $itemsMediaSql . ' AS resolved_media_url
|
$stmt = $this->pdo->prepare('SELECT oi.*, ' . $itemsMediaSql . ' AS resolved_media_url
|
||||||
FROM order_items oi
|
FROM order_items oi
|
||||||
INNER JOIN orders o ON o.id = oi.order_id
|
INNER JOIN orders o ON o.id = oi.order_id
|
||||||
WHERE oi.order_id = :order_id
|
WHERE oi.order_id = :order_id
|
||||||
ORDER BY oi.sort_order ASC, oi.id ASC');
|
ORDER BY oi.sort_order ASC, oi.id ASC');
|
||||||
$itemsStmt->execute(['order_id' => $orderId]);
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
$items = $itemsStmt->fetchAll(PDO::FETCH_ASSOC);
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
if (!is_array($items)) {
|
if (!is_array($rows)) {
|
||||||
$items = [];
|
return [];
|
||||||
}
|
}
|
||||||
$items = array_map(static function (array $row): array {
|
|
||||||
|
return array_map(static function (array $row): array {
|
||||||
$resolvedMediaUrl = trim((string) ($row['resolved_media_url'] ?? ''));
|
$resolvedMediaUrl = trim((string) ($row['resolved_media_url'] ?? ''));
|
||||||
if ($resolvedMediaUrl !== '') {
|
if ($resolvedMediaUrl !== '') {
|
||||||
$row['media_url'] = $resolvedMediaUrl;
|
$row['media_url'] = $resolvedMediaUrl;
|
||||||
@@ -440,59 +474,67 @@ final class OrdersRepository
|
|||||||
unset($row['resolved_media_url']);
|
unset($row['resolved_media_url']);
|
||||||
|
|
||||||
return $row;
|
return $row;
|
||||||
}, $items);
|
}, $rows);
|
||||||
|
|
||||||
$paymentsStmt = $this->pdo->prepare('SELECT * FROM order_payments WHERE order_id = :order_id ORDER BY payment_date ASC, id ASC');
|
|
||||||
$paymentsStmt->execute(['order_id' => $orderId]);
|
|
||||||
$payments = $paymentsStmt->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
if (!is_array($payments)) {
|
|
||||||
$payments = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$shipmentsStmt = $this->pdo->prepare('SELECT * FROM order_shipments WHERE order_id = :order_id ORDER BY posted_at ASC, id ASC');
|
/**
|
||||||
$shipmentsStmt->execute(['order_id' => $orderId]);
|
* @return array<int, array<string, mixed>>
|
||||||
$shipments = $shipmentsStmt->fetchAll(PDO::FETCH_ASSOC);
|
*/
|
||||||
if (!is_array($shipments)) {
|
private function loadOrderPayments(int $orderId): array
|
||||||
$shipments = [];
|
{
|
||||||
|
$stmt = $this->pdo->prepare('SELECT * FROM order_payments WHERE order_id = :order_id ORDER BY payment_date ASC, id ASC');
|
||||||
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return is_array($rows) ? $rows : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$documentsStmt = $this->pdo->prepare('SELECT * FROM order_documents WHERE order_id = :order_id ORDER BY source_created_at ASC, id ASC');
|
/**
|
||||||
$documentsStmt->execute(['order_id' => $orderId]);
|
* @return array<int, array<string, mixed>>
|
||||||
$documents = $documentsStmt->fetchAll(PDO::FETCH_ASSOC);
|
*/
|
||||||
if (!is_array($documents)) {
|
private function loadOrderShipments(int $orderId): array
|
||||||
$documents = [];
|
{
|
||||||
|
$stmt = $this->pdo->prepare('SELECT * FROM order_shipments WHERE order_id = :order_id ORDER BY posted_at ASC, id ASC');
|
||||||
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return is_array($rows) ? $rows : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$notesStmt = $this->pdo->prepare('SELECT * FROM order_notes WHERE order_id = :order_id ORDER BY created_at_external DESC, id DESC');
|
/**
|
||||||
$notesStmt->execute(['order_id' => $orderId]);
|
* @return array<int, array<string, mixed>>
|
||||||
$notes = $notesStmt->fetchAll(PDO::FETCH_ASSOC);
|
*/
|
||||||
if (!is_array($notes)) {
|
private function loadOrderDocuments(int $orderId): array
|
||||||
$notes = [];
|
{
|
||||||
|
$stmt = $this->pdo->prepare('SELECT * FROM order_documents WHERE order_id = :order_id ORDER BY source_created_at ASC, id ASC');
|
||||||
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return is_array($rows) ? $rows : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$historyStmt = $this->pdo->prepare('SELECT * FROM order_status_history WHERE order_id = :order_id ORDER BY changed_at DESC, id DESC');
|
/**
|
||||||
$historyStmt->execute(['order_id' => $orderId]);
|
* @return array<int, array<string, mixed>>
|
||||||
$history = $historyStmt->fetchAll(PDO::FETCH_ASSOC);
|
*/
|
||||||
if (!is_array($history)) {
|
private function loadOrderNotes(int $orderId): array
|
||||||
$history = [];
|
{
|
||||||
|
$stmt = $this->pdo->prepare('SELECT * FROM order_notes WHERE order_id = :order_id ORDER BY created_at_external DESC, id DESC');
|
||||||
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
return is_array($rows) ? $rows : [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$activityLog = $this->loadActivityLog($orderId);
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function loadOrderStatusHistory(int $orderId): array
|
||||||
|
{
|
||||||
|
$stmt = $this->pdo->prepare('SELECT * FROM order_status_history WHERE order_id = :order_id ORDER BY changed_at DESC, id DESC');
|
||||||
|
$stmt->execute(['order_id' => $orderId]);
|
||||||
|
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
return [
|
return is_array($rows) ? $rows : [];
|
||||||
'order' => $order,
|
|
||||||
'addresses' => $addresses,
|
|
||||||
'items' => $items,
|
|
||||||
'payments' => $payments,
|
|
||||||
'shipments' => $shipments,
|
|
||||||
'documents' => $documents,
|
|
||||||
'notes' => $notes,
|
|
||||||
'status_history' => $history,
|
|
||||||
'activity_log' => $activityLog,
|
|
||||||
];
|
|
||||||
} catch (Throwable) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -60,6 +60,19 @@ final class ShopproOrdersSyncService
|
|||||||
}
|
}
|
||||||
|
|
||||||
$result['checked_integrations'] = (int) $result['checked_integrations'] + 1;
|
$result['checked_integrations'] = (int) $result['checked_integrations'] + 1;
|
||||||
|
$this->syncOneIntegration($integration, $maxPages, $pageLimit, $maxOrders, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $integration
|
||||||
|
* @param array<string, mixed> $result
|
||||||
|
*/
|
||||||
|
private function syncOneIntegration(array $integration, int $maxPages, int $pageLimit, int $maxOrders, array &$result): void
|
||||||
|
{
|
||||||
|
$integrationId = (int) ($integration['id'] ?? 0);
|
||||||
$state = $this->syncState->getState($integrationId);
|
$state = $this->syncState->getState($integrationId);
|
||||||
$this->syncState->markRunStarted($integrationId, new DateTimeImmutable('now'));
|
$this->syncState->markRunStarted($integrationId, new DateTimeImmutable('now'));
|
||||||
|
|
||||||
@@ -85,17 +98,66 @@ final class ShopproOrdersSyncService
|
|||||||
$shouldStop = false;
|
$shouldStop = false;
|
||||||
|
|
||||||
for ($page = 1; $page <= $maxPages; $page++) {
|
for ($page = 1; $page <= $maxPages; $page++) {
|
||||||
|
$items = $this->fetchOrdersPage($baseUrl, (string) $apiKey, $timeout, $page, $pageLimit, $startDate);
|
||||||
|
if ($items === []) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$candidates = $this->buildCandidates($items, $cursorUpdatedAt, $cursorOrderId);
|
||||||
|
$this->processPageCandidates(
|
||||||
|
$candidates, $integrationId, $baseUrl, (string) $apiKey, $timeout,
|
||||||
|
$statusMap, $maxOrders, $result, $productImageCache, $shouldStop,
|
||||||
|
$latestUpdatedAt, $latestOrderId
|
||||||
|
);
|
||||||
|
if ($shouldStop || count($items) < $pageLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->syncState->markRunSuccess($integrationId, new DateTimeImmutable('now'), $latestUpdatedAt, $latestOrderId);
|
||||||
|
} catch (Throwable $exception) {
|
||||||
|
$this->syncState->markRunFailed($integrationId, new DateTimeImmutable('now'), $exception->getMessage());
|
||||||
|
$result['failed'] = (int) $result['failed'] + 1;
|
||||||
|
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
||||||
|
if (count($errors) < 20) {
|
||||||
|
$errors[] = ['integration_id' => $integrationId, 'error' => $exception->getMessage()];
|
||||||
|
}
|
||||||
|
$result['errors'] = $errors;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function fetchOrdersPage(string $baseUrl, string $apiKey, int $timeout, int $page, int $pageLimit, ?string $startDate): array
|
||||||
|
{
|
||||||
$orders = $this->apiClient->fetchOrders($baseUrl, $apiKey, $timeout, $page, $pageLimit, $startDate);
|
$orders = $this->apiClient->fetchOrders($baseUrl, $apiKey, $timeout, $page, $pageLimit, $startDate);
|
||||||
if (($orders['ok'] ?? false) !== true) {
|
if (($orders['ok'] ?? false) !== true) {
|
||||||
throw new \RuntimeException((string) ($orders['message'] ?? 'Blad pobierania listy zamowien.'));
|
throw new \RuntimeException((string) ($orders['message'] ?? 'Blad pobierania listy zamowien.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
$items = is_array($orders['items'] ?? null) ? $orders['items'] : [];
|
return is_array($orders['items'] ?? null) ? $orders['items'] : [];
|
||||||
if ($items === []) {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$candidates = $this->buildCandidates($items, $cursorUpdatedAt, $cursorOrderId);
|
/**
|
||||||
|
* @param array<int, array<string, mixed>> $candidates
|
||||||
|
* @param array<string, string> $statusMap
|
||||||
|
* @param array<string, mixed> $result
|
||||||
|
* @param array<int, string> $productImageCache
|
||||||
|
*/
|
||||||
|
private function processPageCandidates(
|
||||||
|
array $candidates,
|
||||||
|
int $integrationId,
|
||||||
|
string $baseUrl,
|
||||||
|
string $apiKey,
|
||||||
|
int $timeout,
|
||||||
|
array $statusMap,
|
||||||
|
int $maxOrders,
|
||||||
|
array &$result,
|
||||||
|
array &$productImageCache,
|
||||||
|
bool &$shouldStop,
|
||||||
|
?string &$latestUpdatedAt,
|
||||||
|
?string &$latestOrderId
|
||||||
|
): void {
|
||||||
foreach ($candidates as $candidate) {
|
foreach ($candidates as $candidate) {
|
||||||
if ((int) $result['processed'] >= $maxOrders) {
|
if ((int) $result['processed'] >= $maxOrders) {
|
||||||
$shouldStop = true;
|
$shouldStop = true;
|
||||||
@@ -109,21 +171,9 @@ final class ShopproOrdersSyncService
|
|||||||
$details = $this->apiClient->fetchOrderById($baseUrl, $apiKey, $timeout, $sourceOrderId);
|
$details = $this->apiClient->fetchOrderById($baseUrl, $apiKey, $timeout, $sourceOrderId);
|
||||||
if (($details['ok'] ?? false) === true && is_array($details['order'] ?? null)) {
|
if (($details['ok'] ?? false) === true && is_array($details['order'] ?? null)) {
|
||||||
$detailsOrder = (array) $details['order'];
|
$detailsOrder = (array) $details['order'];
|
||||||
foreach ([
|
foreach (['products', 'summary', 'paid', 'transport_cost', 'transport', 'transport_description',
|
||||||
'products',
|
'client_name', 'client_surname', 'client_email', 'client_phone', 'client_city',
|
||||||
'summary',
|
'client_street', 'client_postal_code'] as $protectedKey) {
|
||||||
'paid',
|
|
||||||
'transport_cost',
|
|
||||||
'transport',
|
|
||||||
'transport_description',
|
|
||||||
'client_name',
|
|
||||||
'client_surname',
|
|
||||||
'client_email',
|
|
||||||
'client_phone',
|
|
||||||
'client_city',
|
|
||||||
'client_street',
|
|
||||||
'client_postal_code',
|
|
||||||
] as $protectedKey) {
|
|
||||||
if (array_key_exists($protectedKey, $rawOrder)) {
|
if (array_key_exists($protectedKey, $rawOrder)) {
|
||||||
unset($detailsOrder[$protectedKey]);
|
unset($detailsOrder[$protectedKey]);
|
||||||
}
|
}
|
||||||
@@ -131,21 +181,44 @@ final class ShopproOrdersSyncService
|
|||||||
$rawOrder = array_replace($rawOrder, $detailsOrder);
|
$rawOrder = array_replace($rawOrder, $detailsOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->importOneOrder(
|
||||||
|
$integrationId, $sourceOrderId, $sourceUpdatedAt, $rawOrder,
|
||||||
|
$baseUrl, $apiKey, $timeout, $statusMap, $result, $productImageCache
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($latestUpdatedAt === null || $sourceUpdatedAt > $latestUpdatedAt) {
|
||||||
|
$latestUpdatedAt = $sourceUpdatedAt;
|
||||||
|
$latestOrderId = $sourceOrderId;
|
||||||
|
} elseif ($latestUpdatedAt === $sourceUpdatedAt && ($latestOrderId === null || strcmp($sourceOrderId, $latestOrderId) > 0)) {
|
||||||
|
$latestOrderId = $sourceOrderId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $rawOrder
|
||||||
|
* @param array<string, string> $statusMap
|
||||||
|
* @param array<string, mixed> $result
|
||||||
|
* @param array<int, string> $productImageCache
|
||||||
|
*/
|
||||||
|
private function importOneOrder(
|
||||||
|
int $integrationId,
|
||||||
|
string $sourceOrderId,
|
||||||
|
string $sourceUpdatedAt,
|
||||||
|
array $rawOrder,
|
||||||
|
string $baseUrl,
|
||||||
|
string $apiKey,
|
||||||
|
int $timeout,
|
||||||
|
array $statusMap,
|
||||||
|
array &$result,
|
||||||
|
array &$productImageCache
|
||||||
|
): void {
|
||||||
try {
|
try {
|
||||||
$productImages = $this->resolveProductImagesForOrder(
|
$productImages = $this->resolveProductImagesForOrder(
|
||||||
$baseUrl,
|
$baseUrl, $apiKey, $timeout, $rawOrder, $productImageCache
|
||||||
(string) $apiKey,
|
|
||||||
$timeout,
|
|
||||||
$rawOrder,
|
|
||||||
$productImageCache
|
|
||||||
);
|
);
|
||||||
$aggregate = $this->mapOrderAggregate(
|
$aggregate = $this->mapOrderAggregate(
|
||||||
$integrationId,
|
$integrationId, $rawOrder, $statusMap, $sourceOrderId, $sourceUpdatedAt, $productImages
|
||||||
$rawOrder,
|
|
||||||
$statusMap,
|
|
||||||
$sourceOrderId,
|
|
||||||
$sourceUpdatedAt,
|
|
||||||
$productImages
|
|
||||||
);
|
);
|
||||||
$save = $this->orderImportRepository->upsertOrderAggregate(
|
$save = $this->orderImportRepository->upsertOrderAggregate(
|
||||||
$aggregate['order'],
|
$aggregate['order'],
|
||||||
@@ -162,15 +235,11 @@ final class ShopproOrdersSyncService
|
|||||||
} else {
|
} else {
|
||||||
$result['imported_updated'] = (int) $result['imported_updated'] + 1;
|
$result['imported_updated'] = (int) $result['imported_updated'] + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->orders->recordActivity(
|
$this->orders->recordActivity(
|
||||||
(int) ($save['order_id'] ?? 0),
|
(int) ($save['order_id'] ?? 0),
|
||||||
'import',
|
'import',
|
||||||
'Import zamowienia z shopPRO',
|
'Import zamowienia z shopPRO',
|
||||||
[
|
['integration_id' => $integrationId, 'source_order_id' => $sourceOrderId],
|
||||||
'integration_id' => $integrationId,
|
|
||||||
'source_order_id' => $sourceOrderId,
|
|
||||||
],
|
|
||||||
'import',
|
'import',
|
||||||
'shopPRO'
|
'shopPRO'
|
||||||
);
|
);
|
||||||
@@ -186,41 +255,6 @@ final class ShopproOrdersSyncService
|
|||||||
}
|
}
|
||||||
$result['errors'] = $errors;
|
$result['errors'] = $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($latestUpdatedAt === null || $sourceUpdatedAt > $latestUpdatedAt) {
|
|
||||||
$latestUpdatedAt = $sourceUpdatedAt;
|
|
||||||
$latestOrderId = $sourceOrderId;
|
|
||||||
} elseif ($latestUpdatedAt === $sourceUpdatedAt && ($latestOrderId === null || strcmp($sourceOrderId, $latestOrderId) > 0)) {
|
|
||||||
$latestOrderId = $sourceOrderId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($shouldStop || count($items) < $pageLimit) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->syncState->markRunSuccess(
|
|
||||||
$integrationId,
|
|
||||||
new DateTimeImmutable('now'),
|
|
||||||
$latestUpdatedAt,
|
|
||||||
$latestOrderId
|
|
||||||
);
|
|
||||||
} catch (Throwable $exception) {
|
|
||||||
$this->syncState->markRunFailed($integrationId, new DateTimeImmutable('now'), $exception->getMessage());
|
|
||||||
$result['failed'] = (int) $result['failed'] + 1;
|
|
||||||
$errors = is_array($result['errors']) ? $result['errors'] : [];
|
|
||||||
if (count($errors) < 20) {
|
|
||||||
$errors[] = [
|
|
||||||
'integration_id' => $integrationId,
|
|
||||||
'error' => $exception->getMessage(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
$result['errors'] = $errors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -486,6 +520,38 @@ final class ShopproOrdersSyncService
|
|||||||
{
|
{
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
|
$customerData = $this->buildCustomerAddress($payload);
|
||||||
|
$result[] = $customerData['address'];
|
||||||
|
|
||||||
|
$invoiceAddress = $this->buildInvoiceAddress(
|
||||||
|
$payload,
|
||||||
|
$customerData['name'],
|
||||||
|
$customerData['email'],
|
||||||
|
$customerData['phone']
|
||||||
|
);
|
||||||
|
if ($invoiceAddress !== null) {
|
||||||
|
$result[] = $invoiceAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
$deliveryAddress = $this->buildDeliveryAddress(
|
||||||
|
$payload,
|
||||||
|
$customerData['name'],
|
||||||
|
$customerData['email'],
|
||||||
|
$customerData['phone']
|
||||||
|
);
|
||||||
|
if ($deliveryAddress !== null) {
|
||||||
|
$result[] = $deliveryAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $payload
|
||||||
|
* @return array{address:array<string,mixed>,name:?string,email:?string,phone:?string}
|
||||||
|
*/
|
||||||
|
private function buildCustomerAddress(array $payload): array
|
||||||
|
{
|
||||||
$customerFirstName = StringHelper::nullableString((string) $this->readPath($payload, [
|
$customerFirstName = StringHelper::nullableString((string) $this->readPath($payload, [
|
||||||
'buyer.first_name', 'buyer.firstname', 'customer.first_name', 'customer.firstname',
|
'buyer.first_name', 'buyer.firstname', 'customer.first_name', 'customer.firstname',
|
||||||
'client.first_name', 'client.firstname', 'billing_address.first_name', 'billing_address.firstname',
|
'client.first_name', 'client.firstname', 'billing_address.first_name', 'billing_address.firstname',
|
||||||
@@ -499,10 +565,7 @@ final class ShopproOrdersSyncService
|
|||||||
$customerName = StringHelper::nullableString((string) $this->readPath($payload, [
|
$customerName = StringHelper::nullableString((string) $this->readPath($payload, [
|
||||||
'buyer_name', 'buyer.name', 'customer.name', 'client.name', 'billing_address.name',
|
'buyer_name', 'buyer.name', 'customer.name', 'client.name', 'billing_address.name',
|
||||||
'receiver.name', 'client', 'customer_full_name', 'client_full_name',
|
'receiver.name', 'client', 'customer_full_name', 'client_full_name',
|
||||||
]));
|
])) ?? $this->composeName($customerFirstName, $customerLastName, 'Klient');
|
||||||
if ($customerName === null) {
|
|
||||||
$customerName = $this->composeName($customerFirstName, $customerLastName, 'Klient');
|
|
||||||
}
|
|
||||||
|
|
||||||
$customerEmail = StringHelper::nullableString((string) $this->readPath($payload, [
|
$customerEmail = StringHelper::nullableString((string) $this->readPath($payload, [
|
||||||
'buyer_email', 'buyer.email', 'customer.email', 'client.email', 'billing_address.email',
|
'buyer_email', 'buyer.email', 'customer.email', 'client.email', 'billing_address.email',
|
||||||
@@ -514,7 +577,7 @@ final class ShopproOrdersSyncService
|
|||||||
'phone_number', 'client_phone_number',
|
'phone_number', 'client_phone_number',
|
||||||
]));
|
]));
|
||||||
|
|
||||||
$result[] = [
|
$address = [
|
||||||
'address_type' => 'customer',
|
'address_type' => 'customer',
|
||||||
'name' => $customerName ?? 'Klient',
|
'name' => $customerName ?? 'Klient',
|
||||||
'phone' => $customerPhone,
|
'phone' => $customerPhone,
|
||||||
@@ -550,11 +613,15 @@ final class ShopproOrdersSyncService
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
$invoiceAddress = $this->buildInvoiceAddress($payload, $customerName, $customerEmail, $customerPhone);
|
return ['address' => $address, 'name' => $customerName, 'email' => $customerEmail, 'phone' => $customerPhone];
|
||||||
if ($invoiceAddress !== null) {
|
|
||||||
$result[] = $invoiceAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $payload
|
||||||
|
* @return array<string, mixed>|null
|
||||||
|
*/
|
||||||
|
private function buildDeliveryAddress(array $payload, ?string $customerName, ?string $customerEmail, ?string $customerPhone): ?array
|
||||||
|
{
|
||||||
$deliveryFirstName = StringHelper::nullableString((string) $this->readPath($payload, [
|
$deliveryFirstName = StringHelper::nullableString((string) $this->readPath($payload, [
|
||||||
'delivery.address.first_name', 'delivery.address.firstname', 'shipping.address.first_name', 'shipping.address.firstname',
|
'delivery.address.first_name', 'delivery.address.firstname', 'shipping.address.first_name', 'shipping.address.firstname',
|
||||||
'delivery_address.first_name', 'delivery_address.firstname', 'shipping_address.first_name', 'shipping_address.firstname',
|
'delivery_address.first_name', 'delivery_address.firstname', 'shipping_address.first_name', 'shipping_address.firstname',
|
||||||
@@ -568,14 +635,11 @@ final class ShopproOrdersSyncService
|
|||||||
$deliveryName = StringHelper::nullableString((string) $this->readPath($payload, [
|
$deliveryName = StringHelper::nullableString((string) $this->readPath($payload, [
|
||||||
'delivery.address.name', 'shipping.address.name', 'delivery_address.name', 'shipping_address.name',
|
'delivery.address.name', 'shipping.address.name', 'delivery_address.name', 'shipping_address.name',
|
||||||
'receiver.name', 'delivery_name', 'shipping_name',
|
'receiver.name', 'delivery_name', 'shipping_name',
|
||||||
]));
|
])) ?? $this->composeName($deliveryFirstName, $deliveryLastName, null);
|
||||||
if ($deliveryName === null) {
|
|
||||||
$deliveryName = $this->composeName($deliveryFirstName, $deliveryLastName, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
$pickupData = $this->parsePickupPoint((string) $this->readPath($payload, ['inpost_paczkomat', 'orlen_point', 'pickup_point']));
|
$pickupData = $this->parsePickupPoint((string) $this->readPath($payload, ['inpost_paczkomat', 'orlen_point', 'pickup_point']));
|
||||||
$deliveryAddress = [
|
$fields = [
|
||||||
'name' => $deliveryName,
|
'name' => $deliveryName ?? StringHelper::nullableString($this->buildDeliveryMethodLabel($payload)),
|
||||||
'phone' => StringHelper::nullableString((string) $this->readPath($payload, [
|
'phone' => StringHelper::nullableString((string) $this->readPath($payload, [
|
||||||
'delivery.address.phone', 'shipping.address.phone', 'delivery_address.phone', 'shipping_address.phone',
|
'delivery.address.phone', 'shipping.address.phone', 'delivery_address.phone', 'shipping_address.phone',
|
||||||
'receiver.phone', 'delivery_phone', 'shipping_phone',
|
'receiver.phone', 'delivery_phone', 'shipping_phone',
|
||||||
@@ -620,31 +684,26 @@ final class ShopproOrdersSyncService
|
|||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
if (($deliveryAddress['name'] ?? null) === null) {
|
if (!$this->hasAddressData($fields)) {
|
||||||
$deliveryAddress['name'] = StringHelper::nullableString($this->buildDeliveryMethodLabel($payload));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$hasDeliveryData = $this->hasAddressData($deliveryAddress);
|
return [
|
||||||
if ($hasDeliveryData) {
|
|
||||||
$result[] = [
|
|
||||||
'address_type' => 'delivery',
|
'address_type' => 'delivery',
|
||||||
'name' => $deliveryAddress['name'] ?? $customerName ?? 'Dostawa',
|
'name' => $fields['name'] ?? $customerName ?? 'Dostawa',
|
||||||
'phone' => $deliveryAddress['phone'] ?? null,
|
'phone' => $fields['phone'] ?? null,
|
||||||
'email' => $deliveryAddress['email'] ?? $customerEmail,
|
'email' => $fields['email'] ?? $customerEmail,
|
||||||
'street_name' => $deliveryAddress['street_name'] ?? null,
|
'street_name' => $fields['street_name'] ?? null,
|
||||||
'street_number' => $deliveryAddress['street_number'] ?? null,
|
'street_number' => $fields['street_number'] ?? null,
|
||||||
'city' => $deliveryAddress['city'] ?? null,
|
'city' => $fields['city'] ?? null,
|
||||||
'zip_code' => $deliveryAddress['zip_code'] ?? null,
|
'zip_code' => $fields['zip_code'] ?? null,
|
||||||
'country' => $deliveryAddress['country'] ?? null,
|
'country' => $fields['country'] ?? null,
|
||||||
'parcel_external_id' => $deliveryAddress['parcel_external_id'] ?? null,
|
'parcel_external_id' => $fields['parcel_external_id'] ?? null,
|
||||||
'parcel_name' => $deliveryAddress['parcel_name'] ?? null,
|
'parcel_name' => $fields['parcel_name'] ?? null,
|
||||||
'payload_json' => is_array($deliveryAddress['payload_json'] ?? null) ? $deliveryAddress['payload_json'] : null,
|
'payload_json' => is_array($fields['payload_json'] ?? null) ? $fields['payload_json'] : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed> $payload
|
* @param array<string, mixed> $payload
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user