361 lines
12 KiB
PHP
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;
|
|
}
|
|
}
|