Files
erp_naurua/modules/erp/import-integration/order-import.php
T

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(),
]);
}