Add Shopify delta order import
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once __DIR__ . '/../../../modules/shared/db.php';
|
||||
require_once __DIR__ . '/shopify-api.php';
|
||||
require_once __DIR__ . '/../bestellungen/shopify-projection.php';
|
||||
|
||||
function shopify_delta_argument(string $name, array $argv, ?string $default = null): ?string
|
||||
{
|
||||
$prefix = '--' . $name . '=';
|
||||
foreach ($argv as $argument) {
|
||||
if (str_starts_with($argument, $prefix)) {
|
||||
return substr($argument, strlen($prefix));
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
|
||||
function shopify_delta_start_run(PDO $pdo, string $cutoff, bool $dryRun): int
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
"INSERT INTO process_runs (process_name, status, scope_json, requested_by)
|
||||
VALUES ('erp.import-integration.shopify_delta_order_import', 'running', :scope_json::jsonb, 'cli')
|
||||
RETURNING id"
|
||||
);
|
||||
$stmt->execute([':scope_json' => json_encode([
|
||||
'cutoff_timestamp' => $cutoff,
|
||||
'dry_run' => $dryRun,
|
||||
], JSON_THROW_ON_ERROR)]);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function shopify_delta_finish_run(PDO $pdo, int $runId, string $status, array $result, ?Throwable $error = null): void
|
||||
{
|
||||
$stmt = $pdo->prepare(
|
||||
'UPDATE process_runs
|
||||
SET status = :status, result_json = :result_json::jsonb, finished_at = NOW(),
|
||||
error_code = :error_code, error_message = :error_message
|
||||
WHERE id = :id'
|
||||
);
|
||||
$stmt->execute([
|
||||
':status' => $status,
|
||||
':result_json' => json_encode($result, JSON_THROW_ON_ERROR),
|
||||
':error_code' => $error === null ? null : 'shopify_delta_failed',
|
||||
':error_message' => $error?->getMessage(),
|
||||
':id' => $runId,
|
||||
]);
|
||||
}
|
||||
|
||||
function shopify_delta_load_orders(array $env, string $searchQuery): array
|
||||
{
|
||||
$query = <<<'GQL'
|
||||
query ($query: String!, $after: String) {
|
||||
orders(first: 100, after: $after, query: $query, sortKey: CREATED_AT) {
|
||||
nodes { id name createdAt cancelledAt displayFinancialStatus displayFulfillmentStatus }
|
||||
pageInfo { hasNextPage endCursor }
|
||||
}
|
||||
}
|
||||
GQL;
|
||||
|
||||
$orders = [];
|
||||
$after = null;
|
||||
do {
|
||||
$data = shopify_graphql_query($env, $query, ['query' => $searchQuery, 'after' => $after]);
|
||||
$connection = $data['orders'] ?? [];
|
||||
foreach (($connection['nodes'] ?? []) as $order) {
|
||||
$orders[] = $order;
|
||||
}
|
||||
$after = ($connection['pageInfo']['hasNextPage'] ?? false) ? ($connection['pageInfo']['endCursor'] ?? null) : null;
|
||||
} while ($after !== null);
|
||||
|
||||
return $orders;
|
||||
}
|
||||
|
||||
function shopify_delta_load_order(array $env, string $orderGid): array
|
||||
{
|
||||
$query = <<<'GQL'
|
||||
query ($id: ID!) {
|
||||
order(id: $id) {
|
||||
id name createdAt updatedAt cancelledAt displayFinancialStatus displayFulfillmentStatus
|
||||
currentSubtotalPriceSet { shopMoney { amount currencyCode } }
|
||||
totalShippingPriceSet { shopMoney { amount currencyCode } }
|
||||
totalTaxSet { shopMoney { amount currencyCode } }
|
||||
totalPriceSet { shopMoney { amount currencyCode } }
|
||||
customer { id firstName lastName email phone }
|
||||
billingAddress { firstName lastName company address1 address2 zip city provinceCode country countryCodeV2 phone }
|
||||
shippingAddress { firstName lastName company address1 address2 zip city provinceCode country countryCodeV2 phone }
|
||||
lineItems(first: 50) {
|
||||
nodes { id sku name quantity originalUnitPriceSet { shopMoney { amount currencyCode } } variant { id sku product { id } } }
|
||||
}
|
||||
}
|
||||
}
|
||||
GQL;
|
||||
$data = shopify_graphql_query($env, $query, ['id' => $orderGid]);
|
||||
$order = $data['order'] ?? null;
|
||||
if (!is_array($order)) {
|
||||
throw new RuntimeException("Shopify-Bestellung nicht gefunden: {$orderGid}");
|
||||
}
|
||||
return $order;
|
||||
}
|
||||
|
||||
function run_shopify_delta(array $env, PDO $pdo, string $cutoff, bool $dryRun): array
|
||||
{
|
||||
$cutoffDate = new DateTimeImmutable($cutoff);
|
||||
$searchQuery = 'created_at:>' . $cutoffDate->format('Y-m-d');
|
||||
$counters = [
|
||||
'status' => 'done',
|
||||
'cutoff_timestamp' => $cutoffDate->format(DATE_ATOM),
|
||||
'dry_run' => $dryRun,
|
||||
'checked_orders' => 0,
|
||||
'skipped_cancelled_or_refunded' => 0,
|
||||
'already_imported' => 0,
|
||||
'handed_off' => 0,
|
||||
'clarification_order_gids' => [],
|
||||
];
|
||||
|
||||
foreach (shopify_delta_load_orders($env, $searchQuery) as $summaryOrder) {
|
||||
$createdAt = new DateTimeImmutable((string) ($summaryOrder['createdAt'] ?? ''));
|
||||
if ($createdAt <= $cutoffDate) {
|
||||
continue;
|
||||
}
|
||||
$counters['checked_orders']++;
|
||||
if (($summaryOrder['cancelledAt'] ?? null) !== null || strtoupper((string) ($summaryOrder['displayFinancialStatus'] ?? '')) === 'REFUNDED') {
|
||||
$counters['skipped_cancelled_or_refunded']++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$order = shopify_delta_load_order($env, (string) $summaryOrder['id']);
|
||||
$result = project_shopify_order($pdo, $order, $env, $dryRun);
|
||||
if (($result['status'] ?? '') === 'already_imported') {
|
||||
$counters['already_imported']++;
|
||||
} else {
|
||||
$counters['handed_off']++;
|
||||
}
|
||||
} catch (Throwable $error) {
|
||||
$counters['status'] = 'partial_success';
|
||||
$counters['clarification_order_gids'][] = (string) $summaryOrder['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return $counters;
|
||||
}
|
||||
|
||||
if (PHP_SAPI === 'cli' && realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) {
|
||||
$argv = $_SERVER['argv'] ?? [];
|
||||
$cutoff = shopify_delta_argument('cutoff', $argv);
|
||||
$execute = in_array('--execute', $argv, true);
|
||||
$dryRun = !$execute;
|
||||
if ($cutoff === null || $cutoff === '') {
|
||||
fwrite(STDERR, "Usage: php shopify-delta-run.php --cutoff=ISO-8601 [--execute]\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
$env = expand_env_values(parse_env_file(__DIR__ . '/../../../.env'));
|
||||
$pdo = connect_database($env);
|
||||
$runId = shopify_delta_start_run($pdo, $cutoff, $dryRun);
|
||||
try {
|
||||
$result = run_shopify_delta($env, $pdo, $cutoff, $dryRun);
|
||||
$result['technical_run_id'] = $runId;
|
||||
shopify_delta_finish_run($pdo, $runId, $result['status'], $result);
|
||||
echo json_encode($result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
|
||||
exit($result['status'] === 'done' ? 0 : 1);
|
||||
} catch (Throwable $error) {
|
||||
shopify_delta_finish_run($pdo, $runId, 'failed', ['status' => 'failed', 'technical_run_id' => $runId], $error);
|
||||
fwrite(STDERR, $error->getMessage() . PHP_EOL);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user