| @@ -373,9 +373,15 @@ | ||
| 373 | 373 | } |
| 374 | 374 | return false; |
| 375 | 375 | } |
| 376 | 376 | |
| 377 | - public function isStock(): bool | |
| 377 | + /** | |
| 378 | + * @param array|null $bundleChildren Bundle child rows keyed by id, from | |
| 379 | + * loadBundleChildren(), when the caller has several bundle variations | |
| 380 | + * to ask about and wants one query for all their children instead of | |
| 381 | + * one per variation. Null queries this variation's own children. | |
| 382 | + */ | |
| 383 | + public function isStock($bundleChildren = null): bool | |
| 378 | 384 | { |
| 379 | 385 | // Check if variation is active |
| 380 | 386 | if ($this->item_status !== 'active') { |
| 381 | 387 | return false; |
| @@ -387,9 +393,9 @@ | ||
| 387 | 393 | // If stock management is disabled for this variation |
| 388 | 394 | if (!$this->manage_stock) { |
| 389 | 395 | // For bundle products, still check child items |
| 390 | 396 | if ($isBundleProduct) { |
| 391 | - return $this->isBundleChildrenInStock(); | |
| 397 | + return $this->isBundleChildrenInStock($bundleChildren); | |
| 392 | 398 | } |
| 393 | 399 | // For regular products without stock management, check status |
| 394 | 400 | return $this->stock_status === Helper::IN_STOCK; |
| 395 | 401 | } |
| @@ -406,17 +412,53 @@ | ||
| 406 | 412 | if (!$hasStock) { |
| 407 | 413 | return false; |
| 408 | 414 | } |
| 409 | 415 | |
| 410 | - return $this->isBundleChildrenInStock(); | |
| 416 | + return $this->isBundleChildrenInStock($bundleChildren); | |
| 411 | 417 | } |
| 412 | 418 | |
| 413 | 419 | /** |
| 420 | + * The bundle children of several variations in one query, keyed by id, | |
| 421 | + * for isStock($bundleChildren). Only the columns the stock check reads. | |
| 422 | + * | |
| 423 | + * @param iterable $variations | |
| 424 | + * @return array<int, static> | |
| 425 | + */ | |
| 426 | + public static function loadBundleChildren($variations): array | |
| 427 | + { | |
| 428 | + $childIds = []; | |
| 429 | + foreach ($variations as $variation) { | |
| 430 | + $ids = Arr::get($variation->other_info, 'bundle_child_ids', []); | |
| 431 | + if (is_array($ids)) { | |
| 432 | + foreach ($ids as $id) { | |
| 433 | + $childIds[(int) $id] = true; | |
| 434 | + } | |
| 435 | + } | |
| 436 | + } | |
| 437 | + unset($childIds[0]); | |
| 438 | + | |
| 439 | + if (!$childIds) { | |
| 440 | + return []; | |
| 441 | + } | |
| 442 | + | |
| 443 | + $children = static::query() | |
| 444 | + ->whereIn('id', array_keys($childIds)) | |
| 445 | + ->get(['id', 'manage_stock', 'available', 'stock_status', 'item_status', 'post_id', 'other_info']); | |
| 446 | + | |
| 447 | + $byId = []; | |
| 448 | + foreach ($children as $child) { | |
| 449 | + $byId[(int) $child->id] = $child; | |
| 450 | + } | |
| 451 | + | |
| 452 | + return $byId; | |
| 453 | + } | |
| 454 | + | |
| 455 | + /** | |
| 414 | 456 | * Check if all bundle children are in stock |
| 415 | 457 | * |
| 416 | 458 | * @return bool |
| 417 | 459 | */ |
| 418 | - protected function isBundleChildrenInStock(): bool | |
| 460 | + protected function isBundleChildrenInStock($preloadedChildren = null): bool | |
| 419 | 461 | { |
| 420 | 462 | $childIds = Arr::get($this->other_info, 'bundle_child_ids', []); |
| 421 | 463 | |
| 422 | 464 | // No bundle children, consider as in stock |
| @@ -423,12 +465,33 @@ | ||
| 423 | 465 | if (empty($childIds) || !is_array($childIds)) { |
| 424 | 466 | return true; |
| 425 | 467 | } |
| 426 | 468 | |
| 427 | - // Get all bundle children variations | |
| 428 | - $children = static::query() | |
| 429 | - ->whereIn('id', $childIds) | |
| 430 | - ->get(['id', 'manage_stock', 'available', 'stock_status', 'item_status', 'post_id', 'other_info']); | |
| 469 | + $childIds = array_values(array_unique(array_map('intval', $childIds))); | |
| 470 | + | |
| 471 | + if (is_array($preloadedChildren)) { | |
| 472 | + // From loadBundleChildren(). | |
| 473 | + $children = []; | |
| 474 | + foreach ($childIds as $childId) { | |
| 475 | + if (isset($preloadedChildren[$childId])) { | |
| 476 | + $children[] = $preloadedChildren[$childId]; | |
| 477 | + } | |
| 478 | + } | |
| 479 | + } else { | |
| 480 | + // Get all bundle children variations | |
| 481 | + $children = static::query() | |
| 482 | + ->whereIn('id', $childIds) | |
| 483 | + ->get(['id', 'manage_stock', 'available', 'stock_status', 'item_status', 'post_id', 'other_info']) | |
| 484 | + ->all(); | |
| 485 | + } | |
| 486 | + | |
| 487 | + // A child the bundle names but that no longer exists cannot be | |
| 488 | + // supplied, so the bundle is not in stock. Same rule on both paths: | |
| 489 | + // a row missing from the map and a row missing from the query are | |
| 490 | + // the same deleted variation. | |
| 491 | + if (count($children) !== count($childIds)) { | |
| 492 | + return false; | |
| 493 | + } | |
| 431 | 494 | |
| 432 | 495 | // Check each child |
| 433 | 496 | foreach ($children as $child) { |
| 434 | 497 | // Child must be active |