mivita/app/Services/AboItemHistoryService.php
2026-02-20 17:55:06 +01:00

361 lines
12 KiB
PHP

<?php
namespace App\Services;
use App\Models\Product;
use App\Models\UserAbo;
use App\Models\UserAboItem;
use App\Models\UserAboItemHistory;
use Illuminate\Support\Str;
class AboItemHistoryService
{
/**
* Log alle Items eines neu erstellten Abos als initial-Einträge.
*/
public static function logInitialCreation(UserAbo $userAbo, string $channel = 'system'): void
{
$batchId = Str::uuid()->toString();
$actor = self::resolveActor();
foreach ($userAbo->user_abo_items as $item) {
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_INITIAL,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => null,
'qty_after' => $item->qty,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
'batch_id' => $batchId,
'is_initial' => true,
]);
}
}
/**
* Log: Produkt hinzugefügt oder Menge erhöht (bei bereits vorhandenem Produkt).
*/
public static function logProductAdded(UserAbo $userAbo, UserAboItem $item, int $qtyBefore, string $view): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
$action = $qtyBefore === 0
? UserAboItemHistory::ACTION_ADDED
: UserAboItemHistory::ACTION_QTY_CHANGED;
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => $action,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $qtyBefore > 0 ? $qtyBefore : null,
'qty_after' => $item->qty,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: Menge eines Produkts geändert.
*/
public static function logQtyChanged(UserAbo $userAbo, UserAboItem $item, int $qtyBefore, int $qtyAfter, string $view): void
{
if ($qtyBefore === $qtyAfter) {
return;
}
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_QTY_CHANGED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $qtyAfter,
'qty_before' => $qtyBefore,
'qty_after' => $qtyAfter,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: Produkt entfernt (vor dem delete aufrufen!).
*/
public static function logProductRemoved(UserAbo $userAbo, UserAboItem $item, string $view): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_REMOVED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $item->qty,
'qty_after' => 0,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: Comp-Produkt getauscht.
*/
public static function logCompProductChanged(UserAbo $userAbo, UserAboItem $item, Product $oldProduct, Product $newProduct, string $view): void
{
// Item hat bereits das neue Produkt — getPrice() liefert den korrekten Anzeige-Preis
$unitPrice = self::resolveItemPrice($item);
$actor = self::resolveActor();
$channel = self::resolveChannel($view);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $newProduct->id,
'action' => UserAboItemHistory::ACTION_COMP_CHANGED,
'product_name' => $newProduct->getLang('name'),
'product_number' => $newProduct->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'old_product_id' => $oldProduct->id,
'old_product_name' => $oldProduct->getLang('name'),
'comp' => $item->comp,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => $channel,
]);
}
/**
* Log: System hat Comp-Produkt hinzugefügt.
*/
public static function logSystemCompAdded(UserAbo $userAbo, UserAboItem $item): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_COMP_ADDED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_after' => $item->qty,
'comp' => $item->comp,
'changed_by_user_id' => null,
'changed_by_name' => 'System',
'channel' => UserAboItemHistory::CHANNEL_SYSTEM,
]);
}
/**
* Log: System hat Comp-Produkt entfernt (vor dem delete aufrufen!).
*/
public static function logSystemCompRemoved(UserAbo $userAbo, UserAboItem $item): void
{
$product = $item->product;
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $product->id,
'action' => UserAboItemHistory::ACTION_COMP_REMOVED,
'product_name' => $product->getLang('name'),
'product_number' => $product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $item->qty,
'qty_after' => 0,
'comp' => $item->comp,
'changed_by_user_id' => null,
'changed_by_name' => 'System',
'channel' => UserAboItemHistory::CHANNEL_SYSTEM,
]);
}
/**
* Setzt das Abo auf den Ursprungszustand zurück.
*/
public static function rollbackToInitial(UserAbo $userAbo): bool
{
$initialItems = $userAbo->user_abo_item_histories()
->where('is_initial', true)
->get();
if ($initialItems->isEmpty()) {
return false;
}
$batchId = Str::uuid()->toString();
$actor = self::resolveActor();
// Aktuelle Items loggen und löschen
foreach ($userAbo->user_abo_items as $item) {
$unitPrice = self::resolveItemPrice($item);
UserAboItemHistory::create([
'user_abo_id' => $userAbo->id,
'user_abo_item_id' => $item->id,
'product_id' => $item->product_id,
'action' => UserAboItemHistory::ACTION_ROLLBACK,
'product_name' => $item->product->getLang('name'),
'product_number' => $item->product->number ?? null,
'unit_price' => $unitPrice,
'total_price' => $unitPrice * $item->qty,
'qty_before' => $item->qty,
'qty_after' => 0,
'comp' => $item->comp ?? 0,
'changed_by_user_id' => $actor['id'],
'changed_by_name' => $actor['name'],
'channel' => UserAboItemHistory::CHANNEL_ADMIN,
'batch_id' => $batchId,
]);
}
// Alle aktuellen Items löschen
UserAboItem::where('user_abo_id', $userAbo->id)->delete();
// Neue Items aus Initial-History erstellen
foreach ($initialItems as $historyItem) {
UserAboItem::create([
'user_abo_id' => $userAbo->id,
'product_id' => $historyItem->product_id,
'comp' => $historyItem->comp,
'qty' => $historyItem->qty_after,
'status' => 1,
]);
}
return true;
}
/**
* Ermittelt den exakten Anzeige-Preis des UserAboItems.
* Nutzt UserAboItem::getPrice() — das ist genau der Preis, der dem User
* in der Produktliste und im Warenkorb angezeigt wird (netto+UserFactor
* für Berater, brutto für Kunden, länderspezifisch).
*
* Fallback-Kette wenn Yard nicht verfügbar:
* 1. getPriceWith() ohne Country (netto/ufactor je nach is_for)
* 2. Rohpreis des Produkts
*/
private static function resolveItemPrice(UserAboItem $item): float
{
// 1. Exakter Anzeige-Preis via UserAboItem::getPrice() (nutzt Yard)
try {
return (float) $item->getPrice();
} catch (\Throwable $e) {
// Yard nicht initialisiert — weiter zum Fallback
}
// 2. Manuelle Berechnung ohne Yard (ohne Country-spezifische Preise)
try {
$isMe = $item->user_abo->is_for === 'me';
return (float) $item->product->getPriceWith($isMe, $isMe);
} catch (\Throwable $e) {
// Letzter Fallback
}
// 3. Roher Produktpreis
return (float) ($item->product->price ?? 0);
}
/**
* Aktuellen Benutzer ermitteln (CRM Auth oder Kunden-Guard).
*/
private static function resolveActor(): array
{
// CRM User (Admin/Berater)
$user = \Auth::user();
if ($user) {
$name = '';
if ($user->account) {
$name = trim($user->account->first_name.' '.$user->account->last_name);
}
if (! $name) {
$name = $user->email ?? 'User #'.$user->id;
}
return ['id' => $user->id, 'name' => $name];
}
// Portal Kunde
$customer = \Auth::guard('customers')->user();
if ($customer) {
return ['id' => null, 'name' => $customer->name ?? $customer->email ?? 'Kunde'];
}
return ['id' => null, 'name' => 'System'];
}
/**
* Channel aus View-Parameter und Auth-Kontext ableiten.
*/
private static function resolveChannel(string $view): string
{
if ($view === 'portal') {
return UserAboItemHistory::CHANNEL_PORTAL;
}
if ($view === 'admin') {
return UserAboItemHistory::CHANNEL_ADMIN;
}
// Bei user views: Prüfen ob Admin die Änderung vornimmt
$user = \Auth::user();
if ($user && $user->isAdmin()) {
return UserAboItemHistory::CHANNEL_ADMIN;
}
if ($view === 'me') {
return UserAboItemHistory::CHANNEL_USER_ME;
}
if ($view === 'ot') {
return UserAboItemHistory::CHANNEL_USER_OT;
}
return UserAboItemHistory::CHANNEL_SYSTEM;
}
}