gruene-seele/app/Models/ApiOrderLog.php
Kevin Adametz 71d5591e7b Warenwirtschaft: AP-13 API-Bestellungen — Protokoll mit SKU-Fehlermarkierung
Neuer Navigationspunkt „API-Bestellungen": jede wp/order-Übertragung wird
protokolliert (api_order_logs) — auch abgelehnte (Kunde unbekannt, Bestellung
existiert bereits, Speicherfehler). Positionen ohne Produktzuordnung
(unbekannte WP-Nummer) markieren den Eintrag rot; das Modal zeigt den Inhalt
der Übertragung je Position (SKU, Produkt, Menge, Preis, Meldung übersetzt).
Dazu SKU-Konsistenzprüfung auf der Seite: doppelte WP-Nummern aktiver Produkte
(Warnung) und aktive Produkte ohne WP-Nummer (aufklappbar). Behebt die stille
Lücke, dass „article not found" nur in der API-Antwort unterging und die
Position kommentarlos in Bestellung und Bestandsabzug fehlte (Plan §4).
ShoppingUserController per Pint formatiert.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 08:04:16 +00:00

103 lines
3.6 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* AP-13: Protokolleintrag einer Bestell-Übertragung über die Partnercenter-API (wp/order).
*
* payload['lines'] enthält je Position: article (WP-SKU), qty, price (Cent),
* product_id/product_name (Zuordnung im Partnercenter, null = nicht zugeordnet),
* messages (API-Rohmeldungen) und failed (Position wurde nicht übernommen).
*/
class ApiOrderLog extends Model
{
protected $fillable = [
'shopping_order_id',
'shopping_user_id',
'wp_order_number',
'endpoint',
'payload',
'has_errors',
'error_summary',
];
protected $casts = [
'payload' => 'array',
'has_errors' => 'bool',
];
public function shoppingOrder(): BelongsTo
{
return $this->belongsTo(ShoppingOrder::class, 'shopping_order_id');
}
public function shoppingUser(): BelongsTo
{
return $this->belongsTo(ShoppingUser::class, 'shopping_user_id');
}
/**
* Fehlgeschlagene Übertragung ohne verwertbare Positionen (Kunde unbekannt,
* Bestellung existiert bereits, Speicherfehler).
*
* @param mixed $rawOrder eingehender wp_order-Payload (String oder Array)
*/
public static function recordFailure(mixed $wpOrderNumber, ?ShoppingUser $shoppingUser, mixed $rawOrder, string $summary): self
{
return self::query()->create([
'shopping_user_id' => $shoppingUser?->id,
'wp_order_number' => is_numeric($wpOrderNumber) ? (int) $wpOrderNumber : null,
'endpoint' => 'order',
'payload' => ['raw' => is_string($rawOrder) ? $rawOrder : json_decode(json_encode($rawOrder), true)],
'has_errors' => true,
'error_summary' => $summary,
]);
}
/**
* Erfolgreich verarbeitete Übertragung: Positionen inkl. Zuordnungsergebnis protokollieren.
* Positionen ohne Produktzuordnung (SKU unbekannt / Parameter fehlen) markieren den Eintrag.
*
* @param array<int, object> $lines Zeilen aus prepareOrder() (article/qty/price/message)
*/
public static function recordTransfer(mixed $wpOrderNumber, ShoppingUser $shoppingUser, ?ShoppingOrder $shoppingOrder, array $lines): self
{
$payloadLines = [];
$failedCount = 0;
foreach ($lines as $line) {
$line = (object) $line;
$article = $line->article ?? null;
$product = $article !== null ? Product::whereWpNumber($article)->first() : null;
$failed = $product === null;
if ($failed) {
$failedCount++;
}
$payloadLines[] = [
'article' => $article,
'qty' => $line->qty ?? null,
'price' => $line->price ?? null,
'product_id' => $product?->id,
'product_name' => $product?->name,
'messages' => array_values((array) ($line->message ?? [])),
'failed' => $failed,
];
}
return self::query()->create([
'shopping_order_id' => $shoppingOrder?->id,
'shopping_user_id' => $shoppingUser->id,
'wp_order_number' => is_numeric($wpOrderNumber) ? (int) $wpOrderNumber : null,
'endpoint' => 'order',
'payload' => ['lines' => $payloadLines],
'has_errors' => $failedCount > 0,
'error_summary' => $failedCount > 0
? __(':failed von :total Positionen ohne Produktzuordnung', ['failed' => $failedCount, 'total' => count($payloadLines)])
: null,
]);
}
}