gruene-seele/app/Models/ApiOrderLog.php
Kevin Adametz 50243a07b4 Warenwirtschaft: Review-Fix — Indizes + schlanke API-Bestellungen-Seite
Indizes auf shopping_orders.shipped/shipped_at/created_at (Board-Filter,
Tages-Kacheln) und products.wp_number (SKU-Auflösung). Die
API-Bestellungen-Seite lud das komplette JSON-payload für alle 500 Zeilen
und serialisierte es in Inline-Attribute; jetzt persistierte
positions_count, Liste ohne payload, Inhalt on-demand über show-Endpoint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 13:02:47 +00:00

108 lines
4.1 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',
'positions_count',
'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',
'positions_count' => 0,
'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',
'positions_count' => count($payloadLines),
'payload' => ['lines' => $payloadLines],
'has_errors' => $failedCount > 0,
'error_summary' => $failedCount > 0
? __(':failed von :total Positionen ohne Produktzuordnung', ['failed' => $failedCount, 'total' => count($payloadLines)])
: null,
]);
}
}