Abo Einmalprodukte und Bestätigung abschließen
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2bdc9ada3c
commit
2269ce031f
57 changed files with 3647 additions and 371 deletions
181
app/Services/AboOneTimeService.php
Normal file
181
app/Services/AboOneTimeService.php
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue