gruene-seele/app/Models/ApiOrderLog.php
Kevin Adametz 8ad0f65947 Warenwirtschaft: Review-Fix — API-Protokoll markiert verworfene Positionen korrekt
recordTransfer() löste die SKU erneut auf und markierte nur unbekannte
Artikel als Fehler — Zeilen mit gültiger SKU aber fehlender Menge/Preis
wurden verworfen und trotzdem grün protokolliert. prepareOrder() reicht
jetzt included/product_id/product_name durch; failed = Position nicht
übernommen. Entfernt zugleich die N+1-Produkt-Queries je Position.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 10:29:37 +00:00

105 lines
3.9 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.
* `failed` = Position wurde NICHT in die Bestellung übernommen (SKU unbekannt ODER
* Menge/Preis fehlt) — die Wahrheit kommt direkt aus prepareOrder() (`included`,
* `product_id`, `product_name`), damit Protokoll und Bestellung nie auseinanderlaufen
* (Review 20.07.2026: vorher wurde die SKU hier erneut aufgelöst, wodurch Zeilen mit
* fehlender Menge/Preis fälschlich als „OK" geloggt wurden — plus N+1-Queries).
*
* @param array<int, object> $lines Zeilen aus prepareOrder() (article/qty/price/message/included/product_id/product_name)
*/
public static function recordTransfer(mixed $wpOrderNumber, ShoppingUser $shoppingUser, ?ShoppingOrder $shoppingOrder, array $lines): self
{
$payloadLines = [];
$failedCount = 0;
foreach ($lines as $line) {
$line = (object) $line;
$failed = ! (bool) ($line->included ?? false);
if ($failed) {
$failedCount++;
}
$payloadLines[] = [
'article' => $line->article ?? null,
'qty' => $line->qty ?? null,
'price' => $line->price ?? null,
'product_id' => $line->product_id ?? null,
'product_name' => $line->product_name ?? null,
'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,
]);
}
}