$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(); } }