mivita/app/Services/AboOneTimeService.php
Kevin 2269ce031f Abo Einmalprodukte und Bestätigung abschließen
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 15:28:08 +00:00

181 lines
5.6 KiB
PHP

<?php
namespace App\Services;
use App\Models\Product;
use App\Models\UserAbo;
use App\Models\UserAboOneTimeItem;
class AboOneTimeService
{
/**
* Verarbeitet eine Einmal-Artikel-Aktion (add/update/remove) für ein Abo.
*
* Erwartet einen bereits initialisierten Yard (für die Snapshot-Berechnung beim
* Hinzufügen). Gibt eine Fehlermeldung zurück oder null bei Erfolg.
*
* @param array<string, mixed> $data
*/
public function handleAction(UserAbo $userAbo, array $data): ?string
{
$action = $data['action'] ?? null;
return match ($action) {
'add' => $this->add($userAbo, (int) ($data['product_id'] ?? 0)),
'update' => $this->update($userAbo, (int) ($data['one_time_item_id'] ?? 0), (int) ($data['qty'] ?? 1)),
'remove' => $this->remove($userAbo, (int) ($data['one_time_item_id'] ?? 0)),
'confirm' => $this->confirm($userAbo),
'discard' => $this->discard($userAbo),
default => __('abo.onetime_action_invalid'),
};
}
private function add(UserAbo $userAbo, int $productId): ?string
{
$product = Product::find($productId);
$allowedIds = ProductOrderContext::allowedShowOnIds(false, $userAbo->is_for === 'me' ? 'me' : 'ot-customer');
if (! $product || ! $product->active || ! ProductOrderContext::productMatchesShowOn($product, $allowedIds)) {
return __('abo.onetime_product_not_allowed');
}
if (AboOrderCart::exceedsMaxWeight($userAbo, (int) $product->weight)) {
return __('msg.cart_max_weight_reached');
}
$existing = UserAboOneTimeItem::withTrashed()
->where('user_abo_id', $userAbo->id)
->where('product_id', $product->id)
->first();
if ($existing) {
if ($existing->trashed()) {
$existing->restore();
$existing->qty = $existing->confirmed_at ? max(1, (int) $existing->confirmed_qty) + 1 : 1;
} else {
$existing->qty = min(100, $existing->qty + 1);
}
$existing->qty = min(100, $existing->qty);
$existing->status = 0;
$existing->save();
return null;
}
$snapshot = AboOrderCart::buildOneTimeSnapshot($product, $userAbo);
UserAboOneTimeItem::create(array_merge($snapshot, [
'user_abo_id' => $userAbo->id,
'product_id' => $product->id,
'comp' => 0,
'qty' => 1,
'status' => 0,
]));
return null;
}
private function update(UserAbo $userAbo, int $itemId, int $qty): ?string
{
$item = UserAboOneTimeItem::where('user_abo_id', $userAbo->id)->find($itemId);
if (! $item) {
return __('abo.abo_item_not_found');
}
$targetQty = max(1, min(100, $qty));
$product = Product::find($item->product_id);
if ($product) {
$additionalWeight = (int) $product->weight * ($targetQty - (int) $item->qty);
if ($additionalWeight > 0 && AboOrderCart::exceedsMaxWeight($userAbo, $additionalWeight)) {
return __('msg.cart_max_weight_reached');
}
}
$item->qty = $targetQty;
$item->status = $item->isConfirmed() ? 1 : 0;
$item->save();
return null;
}
private function remove(UserAbo $userAbo, int $itemId): ?string
{
$item = UserAboOneTimeItem::where('user_abo_id', $userAbo->id)->find($itemId);
if (! $item) {
return __('abo.abo_item_not_found');
}
if ($item->confirmed_at) {
$item->delete();
} else {
$item->forceDelete();
}
return null;
}
private function confirm(UserAbo $userAbo): ?string
{
UserAboOneTimeItem::withTrashed()
->where('user_abo_id', $userAbo->id)
->get()
->each(function (UserAboOneTimeItem $item): void {
if ($item->trashed()) {
$item->forceDelete();
return;
}
$item->confirmed_qty = $item->qty;
$item->confirmed_at = now();
$item->status = 1;
$item->save();
});
return null;
}
private function discard(UserAbo $userAbo): ?string
{
UserAboOneTimeItem::withTrashed()
->where('user_abo_id', $userAbo->id)
->get()
->each(function (UserAboOneTimeItem $item): void {
if (! $item->confirmed_at) {
$item->forceDelete();
return;
}
if ($item->trashed()) {
$item->restore();
}
$item->qty = max(1, (int) $item->confirmed_qty);
$item->status = 1;
$item->save();
});
return null;
}
public static function hasUnconfirmedChanges(UserAbo $userAbo): bool
{
return UserAboOneTimeItem::withTrashed()
->where('user_abo_id', $userAbo->id)
->get()
->contains(function (UserAboOneTimeItem $item): bool {
if ($item->trashed()) {
return $item->confirmed_at !== null;
}
return ! $item->isConfirmed();
});
}
public static function hasConfirmedItems(UserAbo $userAbo): bool
{
return UserAboOneTimeItem::where('user_abo_id', $userAbo->id)
->whereNotNull('confirmed_at')
->exists();
}
}