191 lines
6.3 KiB
PHP
191 lines
6.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../../modules/shared/db.php';
|
|
require_once __DIR__ . '/../../../modules/shared/webhook_throttle.php';
|
|
require_once __DIR__ . '/../kontakte/service.php';
|
|
require_once __DIR__ . '/../bestellungen/service.php';
|
|
require_once __DIR__ . '/../artikel-mapping/service.php';
|
|
require_once __DIR__ . '/../lager/service.php';
|
|
require_once __DIR__ . '/service.php';
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
|
|
json_response(405, ['ok' => false, 'error' => 'Method Not Allowed']);
|
|
}
|
|
|
|
$env = parse_env_file(__DIR__ . '/../../../.env');
|
|
$env = expand_env_values($env);
|
|
|
|
$expectedSecret = env_value('N8N_WEBHOOK_SECRET', $env);
|
|
$providedSecret = (string) ($_SERVER['HTTP_X_WEBHOOK_SECRET'] ?? '');
|
|
|
|
if ($expectedSecret === '') {
|
|
json_response(500, ['ok' => false, 'error' => 'N8N_WEBHOOK_SECRET not configured']);
|
|
}
|
|
|
|
if ($providedSecret === '' || !hash_equals($expectedSecret, $providedSecret)) {
|
|
json_response(401, ['ok' => false, 'error' => 'Unauthorized']);
|
|
}
|
|
|
|
$rawPayload = file_get_contents('php://input');
|
|
if ($rawPayload === false || trim($rawPayload) === '') {
|
|
json_response(400, ['ok' => false, 'error' => 'Empty payload']);
|
|
}
|
|
|
|
try {
|
|
$data = json_decode($rawPayload, true, 512, JSON_THROW_ON_ERROR);
|
|
} catch (JsonException) {
|
|
json_response(400, ['ok' => false, 'error' => 'Invalid JSON payload']);
|
|
}
|
|
|
|
if (!is_array($data)) {
|
|
json_response(400, ['ok' => false, 'error' => 'JSON object expected']);
|
|
}
|
|
|
|
if (array_is_list($data)) {
|
|
if (!isset($data[0]) || !is_array($data[0])) {
|
|
json_response(400, ['ok' => false, 'error' => 'Array payload must contain one order object']);
|
|
}
|
|
$data = $data[0];
|
|
}
|
|
|
|
$externalRef = trim((string) ($data['BestellungNr'] ?? ''));
|
|
if ($externalRef === '') {
|
|
json_response(422, ['ok' => false, 'error' => 'BestellungNr is required']);
|
|
}
|
|
|
|
$lineItems = $data['lineItems'] ?? [];
|
|
if (!is_array($lineItems)) {
|
|
$lineItems = [];
|
|
}
|
|
|
|
try {
|
|
$pdo = connect_database($env);
|
|
ensure_required_tables_exist($pdo);
|
|
$pdo->beginTransaction();
|
|
|
|
$locations = get_default_location_ids($pdo);
|
|
$existingOrderId = find_existing_order_id($pdo, $externalRef);
|
|
|
|
$partyId = find_or_create_party($pdo, $data);
|
|
upsert_addresses($pdo, $partyId, $data);
|
|
|
|
$paymentMethodId = lookup_method_id($pdo, 'payment_method', map_payment_code((string) ($data['Zahlungsmethode'] ?? '')));
|
|
$shippingMethodId = lookup_method_id($pdo, 'shipping_method', map_shipping_code((string) ($data['Liefermethode'] ?? '')));
|
|
|
|
$orderId = upsert_sales_order($pdo, [
|
|
':external_ref' => $externalRef,
|
|
':party_id' => $partyId,
|
|
':order_source' => 'wix',
|
|
':order_status' => 'imported',
|
|
':payment_status' => 'paid',
|
|
':payment_method_id' => $paymentMethodId,
|
|
':shipping_method_id' => $shippingMethodId,
|
|
':amount_net' => parse_number($data['Netto'] ?? null),
|
|
':amount_shipping' => parse_number($data['Versandkosten'] ?? null),
|
|
':amount_tax' => parse_number($data['Mehrwertsteuer'] ?? null),
|
|
':amount_discount' => parse_number($data['Rabatt'] ?? null),
|
|
':total_amount' => parse_number($data['Gesamtsumme'] ?? null),
|
|
':currency' => 'CHF',
|
|
':webhook_payload' => json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
|
|
]);
|
|
|
|
$inventoryRollback = [
|
|
'reversedMoves' => 0,
|
|
'reversedQty' => 0.0,
|
|
];
|
|
if ($existingOrderId !== null) {
|
|
$inventoryRollback = reverse_existing_allocations_for_order($pdo, $existingOrderId, $locations['storage']);
|
|
}
|
|
|
|
delete_sales_order_lines($pdo, $orderId);
|
|
|
|
$insertedLines = 0;
|
|
$inventory = [
|
|
'linesMapped' => 0,
|
|
'linesUnmapped' => 0,
|
|
'allocationCount' => 0,
|
|
'warnings' => [],
|
|
];
|
|
|
|
foreach ($lineItems as $index => $lineItem) {
|
|
if (!is_array($lineItem)) {
|
|
continue;
|
|
}
|
|
|
|
$articleNumber = trim((string) ($lineItem['artikelnummer'] ?? ''));
|
|
$title = trim((string) ($lineItem['titel'] ?? ''));
|
|
$qty = parse_number($lineItem['artikelanzahl'] ?? null);
|
|
if ($qty === null || $qty <= 0) {
|
|
continue;
|
|
}
|
|
|
|
$unitPrice = parse_number($lineItem['preisEinheit'] ?? null);
|
|
$lineTotal = $unitPrice !== null ? round($qty * $unitPrice, 2) : null;
|
|
$sellableItemId = resolve_sellable_item_id($pdo, $articleNumber, $title);
|
|
$lineNo = $index + 1;
|
|
|
|
$lineId = insert_sales_order_line($pdo, [
|
|
':sales_order_id' => $orderId,
|
|
':line_no' => $lineNo,
|
|
':sellable_item_id' => $sellableItemId,
|
|
':article_number' => $articleNumber,
|
|
':title' => $title,
|
|
':qty' => $qty,
|
|
':unit_price' => $unitPrice,
|
|
':line_total' => $lineTotal,
|
|
]);
|
|
|
|
$insertedLines++;
|
|
|
|
if ($sellableItemId === null) {
|
|
$inventory['linesUnmapped']++;
|
|
$inventory['warnings'][] = "No sellable item mapping for line {$lineNo} (artikelnummer='{$articleNumber}', titel='{$title}')";
|
|
continue;
|
|
}
|
|
|
|
$inventory['linesMapped']++;
|
|
$allocationResult = allocate_line_inventory(
|
|
$pdo,
|
|
$orderId,
|
|
$lineId,
|
|
$lineNo,
|
|
(float) $qty,
|
|
$sellableItemId,
|
|
$locations
|
|
);
|
|
|
|
if (($allocationResult['allocated'] ?? false) === true) {
|
|
$inventory['allocationCount'] += (int) ($allocationResult['allocationCount'] ?? 0);
|
|
}
|
|
}
|
|
|
|
$pdo->commit();
|
|
|
|
$labelTrigger = trigger_shipping_label_flow($data, $env);
|
|
$excelTrigger = trigger_excel_webhook($externalRef, $env);
|
|
|
|
json_response(200, [
|
|
'ok' => true,
|
|
'orderId' => $orderId,
|
|
'externalRef' => $externalRef,
|
|
'lineItemsImported' => $insertedLines,
|
|
'inventory' => $inventory,
|
|
'inventoryRollback' => $inventoryRollback,
|
|
'labelTrigger' => $labelTrigger,
|
|
'excelTrigger' => $excelTrigger,
|
|
]);
|
|
} catch (Throwable $e) {
|
|
if (isset($pdo) && $pdo instanceof PDO && $pdo->inTransaction()) {
|
|
$pdo->rollBack();
|
|
}
|
|
|
|
json_response(500, [
|
|
'ok' => false,
|
|
'error' => 'Order import failed',
|
|
'detail' => $e->getMessage(),
|
|
]);
|
|
}
|