Add Orders and Order Status repositories with pagination and management features

- Implemented OrdersRepository for handling order data with pagination, filtering, and sorting capabilities.
- Added methods for retrieving order status options, quick stats, and detailed order information.
- Created OrderStatusRepository for managing order status groups and statuses, including CRUD operations and sorting.
- Introduced a bootstrap file for test environment setup and autoloading.
This commit is contained in:
2026-03-03 01:32:28 +01:00
parent d1576bc4ab
commit c489891d15
106 changed files with 11669 additions and 5091 deletions

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace App\Modules\Settings;
use PDO;
final class AppSettingsRepository
{
public function __construct(private readonly PDO $pdo)
{
}
public function get(string $key, ?string $default = null): ?string
{
$statement = $this->pdo->prepare(
'SELECT setting_value
FROM app_settings
WHERE setting_key = :setting_key
LIMIT 1'
);
$statement->execute(['setting_key' => trim($key)]);
$value = $statement->fetchColumn();
if ($value === false || $value === null) {
return $default;
}
$text = trim((string) $value);
return $text === '' ? $default : $text;
}
public function getBool(string $key, bool $default = false): bool
{
$value = $this->get($key);
if ($value === null) {
return $default;
}
return in_array(strtolower(trim($value)), ['1', 'true', 'yes', 'on'], true);
}
public function getInt(string $key, int $default = 0): int
{
$value = $this->get($key);
if ($value === null || !is_numeric($value)) {
return $default;
}
return (int) $value;
}
public function set(string $key, string $value): void
{
$statement = $this->pdo->prepare(
'INSERT INTO app_settings (setting_key, setting_value, created_at, updated_at)
VALUES (:setting_key, :setting_value, :created_at, :updated_at)
ON DUPLICATE KEY UPDATE
setting_value = VALUES(setting_value),
updated_at = VALUES(updated_at)'
);
$now = date('Y-m-d H:i:s');
$statement->execute([
'setting_key' => trim($key),
'setting_value' => trim($value),
'created_at' => $now,
'updated_at' => $now,
]);
}
}

View File

@@ -0,0 +1,679 @@
<?php
declare(strict_types=1);
namespace App\Modules\Settings;
use PDO;
use RuntimeException;
final class IntegrationRepository
{
private ?bool $ordersFetchColumnsAvailable = null;
private ?bool $orderStatusSyncDirectionColumnAvailable = null;
private const ORDER_STATUS_SYNC_DIRECTION_DEFAULT = 'shoppro_to_orderpro';
public function __construct(
private readonly PDO $pdo,
private readonly string $secret
) {
}
/**
* @return array<int, array<string, mixed>>
*/
public function listByType(string $type): array
{
$statement = $this->pdo->prepare(
'SELECT id, type, name, base_url, timeout_seconds, is_active'
. $this->ordersFetchSelectFragment()
. $this->orderStatusSyncDirectionSelectFragment() . ',
last_test_status, last_test_http_code, last_test_message, last_test_at,
created_at, updated_at,
CASE WHEN api_key_encrypted IS NULL OR api_key_encrypted = "" THEN 0 ELSE 1 END AS has_api_key
FROM integrations
WHERE type = :type
ORDER BY id DESC'
);
$statement->execute(['type' => $type]);
$rows = $statement->fetchAll();
if (!is_array($rows)) {
return [];
}
return array_map([$this, 'mapRow'], $rows);
}
/**
* @return array<string, mixed>|null
*/
public function findById(int $id): ?array
{
$statement = $this->pdo->prepare(
'SELECT id, type, name, base_url, timeout_seconds, is_active'
. $this->ordersFetchSelectFragment()
. $this->orderStatusSyncDirectionSelectFragment() . ',
last_test_status, last_test_http_code, last_test_message, last_test_at,
created_at, updated_at,
CASE WHEN api_key_encrypted IS NULL OR api_key_encrypted = "" THEN 0 ELSE 1 END AS has_api_key
FROM integrations
WHERE id = :id
LIMIT 1'
);
$statement->execute(['id' => $id]);
$row = $statement->fetch();
if (!is_array($row)) {
return null;
}
return $this->mapRow($row);
}
/**
* @return array<string, mixed>|null
*/
public function findApiCredentials(int $id): ?array
{
$statement = $this->pdo->prepare(
'SELECT id, name, base_url, timeout_seconds, api_key_encrypted'
. $this->ordersFetchSelectFragment()
. $this->orderStatusSyncDirectionSelectFragment() . '
FROM integrations
WHERE id = :id
LIMIT 1'
);
$statement->execute(['id' => $id]);
$row = $statement->fetch();
if (!is_array($row)) {
return null;
}
return [
'id' => (int) ($row['id'] ?? 0),
'name' => (string) ($row['name'] ?? ''),
'base_url' => (string) ($row['base_url'] ?? ''),
'timeout_seconds' => (int) ($row['timeout_seconds'] ?? 10),
'api_key' => $this->decryptApiKey((string) ($row['api_key_encrypted'] ?? '')),
'orders_fetch_enabled' => (int) ($row['orders_fetch_enabled'] ?? 0) === 1,
'orders_fetch_start_date' => $row['orders_fetch_start_date'] === null ? null : (string) $row['orders_fetch_start_date'],
'order_status_sync_direction' => $this->normalizeOrderStatusSyncDirection((string) ($row['order_status_sync_direction'] ?? self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT)),
];
}
/**
* @return array<string, mixed>|null
*/
public function findActiveApiCredentialsByType(string $type): ?array
{
$statement = $this->pdo->prepare(
'SELECT id, name, base_url, timeout_seconds, api_key_encrypted'
. $this->ordersFetchSelectFragment()
. $this->orderStatusSyncDirectionSelectFragment() . '
FROM integrations
WHERE type = :type AND is_active = 1
ORDER BY id DESC
LIMIT 1'
);
$statement->execute(['type' => $type]);
$row = $statement->fetch();
if (!is_array($row)) {
return null;
}
return [
'id' => (int) ($row['id'] ?? 0),
'name' => (string) ($row['name'] ?? ''),
'base_url' => (string) ($row['base_url'] ?? ''),
'timeout_seconds' => (int) ($row['timeout_seconds'] ?? 10),
'api_key' => $this->decryptApiKey((string) ($row['api_key_encrypted'] ?? '')),
'orders_fetch_enabled' => (int) ($row['orders_fetch_enabled'] ?? 0) === 1,
'orders_fetch_start_date' => $row['orders_fetch_start_date'] === null ? null : (string) $row['orders_fetch_start_date'],
'order_status_sync_direction' => $this->normalizeOrderStatusSyncDirection((string) ($row['order_status_sync_direction'] ?? self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT)),
];
}
public function create(
string $type,
string $name,
string $baseUrl,
int $timeoutSeconds,
bool $isActive,
string $apiKey,
bool $ordersFetchEnabled = false,
?string $ordersFetchStartDate = null,
string $orderStatusSyncDirection = self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT
): int {
$normalizedSyncDirection = $this->normalizeOrderStatusSyncDirection($orderStatusSyncDirection);
if ($this->hasOrdersFetchColumns() && $this->hasOrderStatusSyncDirectionColumn()) {
$statement = $this->pdo->prepare(
'INSERT INTO integrations (
type, name, base_url, api_key_encrypted, timeout_seconds, is_active,
orders_fetch_enabled, orders_fetch_start_date, order_status_sync_direction,
created_at, updated_at
) VALUES (
:type, :name, :base_url, :api_key_encrypted, :timeout_seconds, :is_active,
:orders_fetch_enabled, :orders_fetch_start_date, :order_status_sync_direction,
:created_at, :updated_at
)'
);
$statement->execute([
'type' => $type,
'name' => $name,
'base_url' => $baseUrl,
'api_key_encrypted' => $this->encryptApiKey($apiKey),
'timeout_seconds' => $timeoutSeconds,
'is_active' => $isActive ? 1 : 0,
'orders_fetch_enabled' => $ordersFetchEnabled ? 1 : 0,
'orders_fetch_start_date' => $ordersFetchStartDate,
'order_status_sync_direction' => $normalizedSyncDirection,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
} elseif ($this->hasOrdersFetchColumns()) {
$statement = $this->pdo->prepare(
'INSERT INTO integrations (
type, name, base_url, api_key_encrypted, timeout_seconds, is_active,
orders_fetch_enabled, orders_fetch_start_date,
created_at, updated_at
) VALUES (
:type, :name, :base_url, :api_key_encrypted, :timeout_seconds, :is_active,
:orders_fetch_enabled, :orders_fetch_start_date,
:created_at, :updated_at
)'
);
$statement->execute([
'type' => $type,
'name' => $name,
'base_url' => $baseUrl,
'api_key_encrypted' => $this->encryptApiKey($apiKey),
'timeout_seconds' => $timeoutSeconds,
'is_active' => $isActive ? 1 : 0,
'orders_fetch_enabled' => $ordersFetchEnabled ? 1 : 0,
'orders_fetch_start_date' => $ordersFetchStartDate,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
} else {
$statement = $this->pdo->prepare(
'INSERT INTO integrations (
type, name, base_url, api_key_encrypted, timeout_seconds, is_active, created_at, updated_at
) VALUES (
:type, :name, :base_url, :api_key_encrypted, :timeout_seconds, :is_active, :created_at, :updated_at
)'
);
$statement->execute([
'type' => $type,
'name' => $name,
'base_url' => $baseUrl,
'api_key_encrypted' => $this->encryptApiKey($apiKey),
'timeout_seconds' => $timeoutSeconds,
'is_active' => $isActive ? 1 : 0,
'created_at' => date('Y-m-d H:i:s'),
'updated_at' => date('Y-m-d H:i:s'),
]);
}
return (int) $this->pdo->lastInsertId();
}
public function update(
int $id,
string $name,
string $baseUrl,
int $timeoutSeconds,
bool $isActive,
?string $apiKey,
bool $ordersFetchEnabled = false,
?string $ordersFetchStartDate = null,
string $orderStatusSyncDirection = self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT
): void {
$normalizedSyncDirection = $this->normalizeOrderStatusSyncDirection($orderStatusSyncDirection);
$params = [
'id' => $id,
'name' => $name,
'base_url' => $baseUrl,
'timeout_seconds' => $timeoutSeconds,
'is_active' => $isActive ? 1 : 0,
'orders_fetch_enabled' => $ordersFetchEnabled ? 1 : 0,
'orders_fetch_start_date' => $ordersFetchStartDate,
'order_status_sync_direction' => $normalizedSyncDirection,
'updated_at' => date('Y-m-d H:i:s'),
];
$sql = 'UPDATE integrations SET
name = :name,
base_url = :base_url,
timeout_seconds = :timeout_seconds,
is_active = :is_active,
updated_at = :updated_at';
if ($this->hasOrdersFetchColumns() && $this->hasOrderStatusSyncDirectionColumn()) {
$sql = 'UPDATE integrations SET
name = :name,
base_url = :base_url,
timeout_seconds = :timeout_seconds,
is_active = :is_active,
orders_fetch_enabled = :orders_fetch_enabled,
orders_fetch_start_date = :orders_fetch_start_date,
order_status_sync_direction = :order_status_sync_direction,
updated_at = :updated_at';
} elseif ($this->hasOrdersFetchColumns()) {
$sql = 'UPDATE integrations SET
name = :name,
base_url = :base_url,
timeout_seconds = :timeout_seconds,
is_active = :is_active,
orders_fetch_enabled = :orders_fetch_enabled,
orders_fetch_start_date = :orders_fetch_start_date,
updated_at = :updated_at';
unset($params['order_status_sync_direction']);
} else {
unset($params['orders_fetch_enabled'], $params['orders_fetch_start_date'], $params['order_status_sync_direction']);
}
if ($apiKey !== null && trim($apiKey) !== '') {
$sql .= ', api_key_encrypted = :api_key_encrypted';
$params['api_key_encrypted'] = $this->encryptApiKey($apiKey);
}
$sql .= ' WHERE id = :id';
$statement = $this->pdo->prepare($sql);
$statement->execute($params);
}
public function setTestResult(
int $id,
string $status,
?int $httpCode,
string $message,
string $testedAt
): void {
$statement = $this->pdo->prepare(
'UPDATE integrations SET
last_test_status = :status,
last_test_http_code = :http_code,
last_test_message = :message,
last_test_at = :tested_at,
updated_at = :updated_at
WHERE id = :id'
);
$statement->execute([
'id' => $id,
'status' => $status,
'http_code' => $httpCode,
'message' => mb_substr($message, 0, 255),
'tested_at' => $testedAt,
'updated_at' => date('Y-m-d H:i:s'),
]);
}
public function logTest(
int $integrationId,
string $status,
?int $httpCode,
string $message,
string $endpointUrl,
string $testedAt
): void {
$statement = $this->pdo->prepare(
'INSERT INTO integration_test_logs (
integration_id, status, http_code, message, endpoint_url, tested_at
) VALUES (
:integration_id, :status, :http_code, :message, :endpoint_url, :tested_at
)'
);
$statement->execute([
'integration_id' => $integrationId,
'status' => $status,
'http_code' => $httpCode,
'message' => mb_substr($message, 0, 255),
'endpoint_url' => mb_substr($endpointUrl, 0, 255),
'tested_at' => $testedAt,
]);
}
/**
* @return array<int, array<string, mixed>>
*/
public function recentTests(int $integrationId, int $limit = 5): array
{
$statement = $this->pdo->prepare(
'SELECT id, integration_id, status, http_code, message, endpoint_url, tested_at
FROM integration_test_logs
WHERE integration_id = :integration_id
ORDER BY tested_at DESC, id DESC
LIMIT :limit'
);
$statement->bindValue(':integration_id', $integrationId, PDO::PARAM_INT);
$statement->bindValue(':limit', max(1, $limit), PDO::PARAM_INT);
$statement->execute();
$rows = $statement->fetchAll();
if (!is_array($rows)) {
return [];
}
return array_map(
static fn (array $row): array => [
'id' => (int) ($row['id'] ?? 0),
'integration_id' => (int) ($row['integration_id'] ?? 0),
'status' => (string) ($row['status'] ?? ''),
'http_code' => $row['http_code'] === null ? null : (int) $row['http_code'],
'message' => (string) ($row['message'] ?? ''),
'endpoint_url' => (string) ($row['endpoint_url'] ?? ''),
'tested_at' => (string) ($row['tested_at'] ?? ''),
],
$rows
);
}
public function nameExists(string $type, string $name, ?int $excludeId = null): bool
{
$sql = 'SELECT 1 FROM integrations WHERE type = :type AND name = :name';
$params = [
'type' => $type,
'name' => $name,
];
if ($excludeId !== null) {
$sql .= ' AND id <> :exclude_id';
$params['exclude_id'] = $excludeId;
}
$sql .= ' LIMIT 1';
$statement = $this->pdo->prepare($sql);
$statement->execute($params);
return $statement->fetchColumn() !== false;
}
public function ensureSalesChannelsSeeded(): void
{
$rows = [
['code' => 'shoppro', 'name' => 'shopPRO', 'type' => 'shop_instance'],
['code' => 'allegro', 'name' => 'Allegro', 'type' => 'marketplace'],
['code' => 'erli', 'name' => 'Erli', 'type' => 'marketplace'],
];
$statement = $this->pdo->prepare(
'INSERT INTO sales_channels (code, name, type, status, created_at, updated_at)
VALUES (:code, :name, :type, 1, :created_at, :updated_at)
ON DUPLICATE KEY UPDATE
name = VALUES(name),
type = VALUES(type),
updated_at = VALUES(updated_at)'
);
$now = date('Y-m-d H:i:s');
foreach ($rows as $row) {
$statement->execute([
'code' => $row['code'],
'name' => $row['name'],
'type' => $row['type'],
'created_at' => $now,
'updated_at' => $now,
]);
}
}
public function findMappedProductId(string $channelCode, string $externalProductId, ?int $integrationId = null): ?int
{
$sql = 'SELECT pcm.product_id
FROM product_channel_map pcm
INNER JOIN sales_channels sc ON sc.id = pcm.channel_id
WHERE sc.code = :channel_code
AND pcm.external_product_id = :external_product_id';
$params = [
'channel_code' => $channelCode,
'external_product_id' => $externalProductId,
];
if ($integrationId !== null && $integrationId > 0) {
$sql .= ' AND pcm.integration_id = :integration_id';
$params['integration_id'] = $integrationId;
}
$sql .= ' LIMIT 1';
$statement = $this->pdo->prepare($sql);
$statement->execute($params);
$value = $statement->fetchColumn();
if ($value === false) {
return null;
}
return (int) $value;
}
public function upsertProductChannelMap(
int $productId,
string $channelCode,
string $syncState,
string $externalProductId = '',
string $externalVariantId = '',
?int $integrationId = null
): void {
$channelId = $this->findChannelIdByCode($channelCode);
if ($channelId === null) {
throw new RuntimeException('Brak kanalu sprzedazy: ' . $channelCode);
}
$externalProductId = trim($externalProductId);
$externalVariantId = trim($externalVariantId);
$normalizedIntegrationId = $integrationId !== null && $integrationId > 0
? $integrationId
: null;
$linkType = 'manual';
$linkStatus = $externalProductId !== '' ? 'active' : 'unverified';
$linkedAt = $externalProductId !== '' ? date('Y-m-d H:i:s') : null;
$statement = $this->pdo->prepare(
'INSERT INTO product_channel_map (
product_id, channel_id, integration_id, external_product_id, external_variant_id,
sync_state, link_type, link_status, linked_at, last_sync_at, created_at, updated_at
) VALUES (
:product_id, :channel_id, :integration_id, :external_product_id, :external_variant_id,
:sync_state, :link_type, :link_status, :linked_at, :last_sync_at, :created_at, :updated_at
) ON DUPLICATE KEY UPDATE
integration_id = VALUES(integration_id),
sync_state = VALUES(sync_state),
last_sync_at = VALUES(last_sync_at),
external_product_id = VALUES(external_product_id),
external_variant_id = VALUES(external_variant_id),
link_type = VALUES(link_type),
link_status = VALUES(link_status),
linked_at = VALUES(linked_at),
updated_at = VALUES(updated_at)'
);
$now = date('Y-m-d H:i:s');
$statement->execute([
'product_id' => $productId,
'channel_id' => $channelId,
'integration_id' => $normalizedIntegrationId,
'external_product_id' => $externalProductId,
'external_variant_id' => $externalVariantId !== '' ? $externalVariantId : null,
'sync_state' => $syncState,
'link_type' => $linkType,
'link_status' => $linkStatus,
'linked_at' => $linkedAt,
'last_sync_at' => $now,
'created_at' => $now,
'updated_at' => $now,
]);
}
/**
* @param array<string, mixed> $row
* @return array<string, mixed>
*/
private function mapRow(array $row): array
{
return [
'id' => (int) ($row['id'] ?? 0),
'type' => (string) ($row['type'] ?? ''),
'name' => (string) ($row['name'] ?? ''),
'base_url' => (string) ($row['base_url'] ?? ''),
'timeout_seconds' => (int) ($row['timeout_seconds'] ?? 10),
'is_active' => (int) ($row['is_active'] ?? 0) === 1,
'orders_fetch_enabled' => (int) ($row['orders_fetch_enabled'] ?? 0) === 1,
'orders_fetch_start_date' => $row['orders_fetch_start_date'] === null ? null : (string) $row['orders_fetch_start_date'],
'order_status_sync_direction' => $this->normalizeOrderStatusSyncDirection((string) ($row['order_status_sync_direction'] ?? self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT)),
'last_test_status' => (string) ($row['last_test_status'] ?? ''),
'last_test_http_code' => $row['last_test_http_code'] === null ? null : (int) $row['last_test_http_code'],
'last_test_message' => (string) ($row['last_test_message'] ?? ''),
'last_test_at' => (string) ($row['last_test_at'] ?? ''),
'has_api_key' => (int) ($row['has_api_key'] ?? 0) === 1,
'created_at' => (string) ($row['created_at'] ?? ''),
'updated_at' => (string) ($row['updated_at'] ?? ''),
];
}
private function encryptApiKey(string $apiKey): string
{
$plain = trim($apiKey);
if ($plain === '') {
return '';
}
$secret = trim($this->secret);
if ($secret === '') {
throw new RuntimeException('Brak INTEGRATIONS_SECRET w konfiguracji .env.');
}
$iv = random_bytes(16);
$cipher = openssl_encrypt(
$plain,
'AES-256-CBC',
hash('sha256', $secret, true),
OPENSSL_RAW_DATA,
$iv
);
if ($cipher === false) {
throw new RuntimeException('Nie mozna zaszyfrowac klucza API.');
}
return base64_encode($iv) . ':' . base64_encode($cipher);
}
private function decryptApiKey(string $payload): string
{
$serialized = trim($payload);
if ($serialized === '') {
return '';
}
$secret = trim($this->secret);
if ($secret === '') {
throw new RuntimeException('Brak INTEGRATIONS_SECRET w konfiguracji .env.');
}
$parts = explode(':', $serialized, 2);
if (count($parts) !== 2) {
throw new RuntimeException('Niepoprawny format zapisanego klucza API.');
}
$iv = base64_decode($parts[0], true);
$cipher = base64_decode($parts[1], true);
if ($iv === false || $cipher === false || strlen($iv) !== 16) {
throw new RuntimeException('Nie mozna odczytac zapisanego klucza API.');
}
$plain = openssl_decrypt(
$cipher,
'AES-256-CBC',
hash('sha256', $secret, true),
OPENSSL_RAW_DATA,
$iv
);
if ($plain === false) {
throw new RuntimeException('Nie mozna odszyfrowac zapisanego klucza API.');
}
return $plain;
}
private function findChannelIdByCode(string $code): ?int
{
$statement = $this->pdo->prepare('SELECT id FROM sales_channels WHERE code = :code LIMIT 1');
$statement->execute(['code' => $code]);
$value = $statement->fetchColumn();
if ($value === false) {
return null;
}
return (int) $value;
}
private function ordersFetchSelectFragment(): string
{
if ($this->hasOrdersFetchColumns()) {
return ', orders_fetch_enabled, orders_fetch_start_date';
}
return ', 0 AS orders_fetch_enabled, NULL AS orders_fetch_start_date';
}
private function orderStatusSyncDirectionSelectFragment(): string
{
if ($this->hasOrderStatusSyncDirectionColumn()) {
return ', order_status_sync_direction';
}
return ", '" . self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT . "' AS order_status_sync_direction";
}
private function hasOrdersFetchColumns(): bool
{
if ($this->ordersFetchColumnsAvailable !== null) {
return $this->ordersFetchColumnsAvailable;
}
try {
$enabledStmt = $this->pdo->query("SHOW COLUMNS FROM integrations LIKE 'orders_fetch_enabled'");
$startDateStmt = $this->pdo->query("SHOW COLUMNS FROM integrations LIKE 'orders_fetch_start_date'");
$this->ordersFetchColumnsAvailable =
$enabledStmt !== false
&& $enabledStmt->fetch() !== false
&& $startDateStmt !== false
&& $startDateStmt->fetch() !== false;
} catch (\Throwable) {
$this->ordersFetchColumnsAvailable = false;
}
return $this->ordersFetchColumnsAvailable;
}
private function hasOrderStatusSyncDirectionColumn(): bool
{
if ($this->orderStatusSyncDirectionColumnAvailable !== null) {
return $this->orderStatusSyncDirectionColumnAvailable;
}
try {
$stmt = $this->pdo->query("SHOW COLUMNS FROM integrations LIKE 'order_status_sync_direction'");
$this->orderStatusSyncDirectionColumnAvailable =
$stmt !== false
&& $stmt->fetch() !== false;
} catch (\Throwable) {
$this->orderStatusSyncDirectionColumnAvailable = false;
}
return $this->orderStatusSyncDirectionColumnAvailable;
}
private function normalizeOrderStatusSyncDirection(string $value): string
{
$normalized = trim(mb_strtolower($value));
if ($normalized === 'orderpro_to_shoppro') {
return 'orderpro_to_shoppro';
}
return self::ORDER_STATUS_SYNC_DIRECTION_DEFAULT;
}
}

View File

@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace App\Modules\Settings;
use PDO;
final class OrderStatusMappingRepository
{
public function __construct(private readonly PDO $pdo)
{
}
/**
* @return array<string, array{orderpro_status_code:string, shoppro_status_name:string|null}>
*/
public function listByIntegration(int $integrationId): array
{
$stmt = $this->pdo->prepare(
'SELECT shoppro_status_code, shoppro_status_name, orderpro_status_code
FROM order_status_mappings
WHERE integration_id = :integration_id
ORDER BY shoppro_status_code ASC'
);
$stmt->execute(['integration_id' => $integrationId]);
$rows = $stmt->fetchAll();
if (!is_array($rows)) {
return [];
}
$result = [];
foreach ($rows as $row) {
if (!is_array($row)) {
continue;
}
$code = trim((string) ($row['shoppro_status_code'] ?? ''));
if ($code === '') {
continue;
}
$result[$code] = [
'orderpro_status_code' => trim((string) ($row['orderpro_status_code'] ?? '')),
'shoppro_status_name' => isset($row['shoppro_status_name']) ? trim((string) $row['shoppro_status_name']) : null,
];
}
return $result;
}
/**
* @param array<int, array{shoppro_status_code:string,shoppro_status_name:string|null,orderpro_status_code:string}> $mappings
*/
public function replaceForIntegration(int $integrationId, array $mappings): void
{
$deleteStmt = $this->pdo->prepare('DELETE FROM order_status_mappings WHERE integration_id = :integration_id');
$deleteStmt->execute(['integration_id' => $integrationId]);
if ($mappings === []) {
return;
}
$insertStmt = $this->pdo->prepare(
'INSERT INTO order_status_mappings (
integration_id, shoppro_status_code, shoppro_status_name, orderpro_status_code, created_at, updated_at
) VALUES (
:integration_id, :shoppro_status_code, :shoppro_status_name, :orderpro_status_code, :created_at, :updated_at
)'
);
$now = date('Y-m-d H:i:s');
foreach ($mappings as $mapping) {
$shopCode = trim((string) ($mapping['shoppro_status_code'] ?? ''));
$orderCode = trim((string) ($mapping['orderpro_status_code'] ?? ''));
if ($shopCode === '' || $orderCode === '') {
continue;
}
$shopNameRaw = isset($mapping['shoppro_status_name']) ? trim((string) $mapping['shoppro_status_name']) : '';
$shopName = $shopNameRaw === '' ? null : $shopNameRaw;
$insertStmt->execute([
'integration_id' => $integrationId,
'shoppro_status_code' => $shopCode,
'shoppro_status_name' => $shopName,
'orderpro_status_code' => $orderCode,
'created_at' => $now,
'updated_at' => $now,
]);
}
}
/**
* @return array<string, string>
*/
public function listOrderProToShopProMap(int $integrationId): array
{
$rows = $this->listByIntegration($integrationId);
if ($rows === []) {
return [];
}
$result = [];
foreach ($rows as $shopCode => $mapping) {
$orderProCode = trim((string) ($mapping['orderpro_status_code'] ?? ''));
$normalizedOrderProCode = $this->normalizeCode($orderProCode);
$normalizedShopCode = $this->normalizeCode((string) $shopCode);
if ($normalizedOrderProCode === '' || $normalizedShopCode === '') {
continue;
}
if (!isset($result[$normalizedOrderProCode])) {
$result[$normalizedOrderProCode] = $normalizedShopCode;
}
}
return $result;
}
private function normalizeCode(string $value): string
{
return trim(mb_strtolower($value));
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff