|
@@ -2,7 +2,10 @@
|
|
|
|
|
|
|
|
namespace Webkul\Product\Helpers\Indexers;
|
|
namespace Webkul\Product\Helpers\Indexers;
|
|
|
|
|
|
|
|
|
|
+use Illuminate\Database\Eloquent\Model;
|
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Carbon;
|
|
|
|
|
+use Illuminate\Support\Collection;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
use Webkul\Customer\Repositories\CustomerGroupRepository;
|
|
use Webkul\Customer\Repositories\CustomerGroupRepository;
|
|
|
use Webkul\Product\Repositories\ProductPriceIndexRepository;
|
|
use Webkul\Product\Repositories\ProductPriceIndexRepository;
|
|
|
use Webkul\Product\Repositories\ProductRepository;
|
|
use Webkul\Product\Repositories\ProductRepository;
|
|
@@ -10,9 +13,66 @@ use Webkul\Product\Repositories\ProductRepository;
|
|
|
class Price extends AbstractIndexer
|
|
class Price extends AbstractIndexer
|
|
|
{
|
|
{
|
|
|
/**
|
|
/**
|
|
|
|
|
+ * Index table.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected const TABLE = 'product_price_indices';
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Decimal columns are compared at a fixed scale because MySQL hands them back as
|
|
|
|
|
+ * strings ("10.0000") while the indexers compute floats (10.0).
|
|
|
|
|
+ */
|
|
|
|
|
+ protected const DECIMAL_COLUMNS = [
|
|
|
|
|
+ 'min_price',
|
|
|
|
|
+ 'regular_min_price',
|
|
|
|
|
+ 'max_price',
|
|
|
|
|
+ 'regular_max_price',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Columns backing the `ppi_priceable_group_channel_unique` index.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected const UNIQUE_COLUMNS = [
|
|
|
|
|
+ 'priceable_id',
|
|
|
|
|
+ 'priceable_type',
|
|
|
|
|
+ 'customer_group_id',
|
|
|
|
|
+ 'channel_id',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Relations every price indexer needs. Without them each product triggers its own
|
|
|
|
|
+ * queries for attribute values, group prices and catalog rules.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected const EAGER_LOADS = [
|
|
|
|
|
+ 'variants',
|
|
|
|
|
+ 'attribute_family',
|
|
|
|
|
+ 'attribute_values',
|
|
|
|
|
+ 'variants.attribute_family',
|
|
|
|
|
+ 'variants.attribute_values',
|
|
|
|
|
+ 'price_indices',
|
|
|
|
|
+ 'inventory_indices',
|
|
|
|
|
+ 'variants.price_indices',
|
|
|
|
|
+ 'variants.inventory_indices',
|
|
|
|
|
+ 'customer_group_prices',
|
|
|
|
|
+ 'variants.customer_group_prices',
|
|
|
|
|
+ 'catalog_rule_prices',
|
|
|
|
|
+ 'variants.catalog_rule_prices',
|
|
|
|
|
+ 'flexibleVariants',
|
|
|
|
|
+ 'flexibleVariants.price_indices',
|
|
|
|
|
+ 'flexibleVariants.customer_group_prices',
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Rows sent per upsert statement.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected const WRITE_CHUNK_SIZE = 500;
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Products per chunk. Memory is driven by the variants and index rows hanging off each
|
|
|
|
|
+ * product, so this stays conservative and is tunable through `--batch-size`.
|
|
|
|
|
+ *
|
|
|
* @var int
|
|
* @var int
|
|
|
*/
|
|
*/
|
|
|
- private $batchSize;
|
|
|
|
|
|
|
+ private $batchSize = self::BATCH_SIZE;
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Channels
|
|
* Channels
|
|
@@ -28,6 +88,13 @@ class Price extends AbstractIndexer
|
|
|
*/
|
|
*/
|
|
|
protected $customerGroups;
|
|
protected $customerGroups;
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Price indexer per product type.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @var array
|
|
|
|
|
+ */
|
|
|
|
|
+ protected $typeIndexers = [];
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Create a new indexer instance.
|
|
* Create a new indexer instance.
|
|
|
*
|
|
*
|
|
@@ -37,8 +104,18 @@ class Price extends AbstractIndexer
|
|
|
protected CustomerGroupRepository $customerGroupRepository,
|
|
protected CustomerGroupRepository $customerGroupRepository,
|
|
|
protected ProductRepository $productRepository,
|
|
protected ProductRepository $productRepository,
|
|
|
protected ProductPriceIndexRepository $productPriceIndexRepository
|
|
protected ProductPriceIndexRepository $productPriceIndexRepository
|
|
|
- ) {
|
|
|
|
|
- $this->batchSize = self::BATCH_SIZE;
|
|
|
|
|
|
|
+ ) {}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Number of products loaded per chunk.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return $this
|
|
|
|
|
+ */
|
|
|
|
|
+ public function setBatchSize(int $batchSize)
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->batchSize = $batchSize;
|
|
|
|
|
+
|
|
|
|
|
+ return $this;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -48,38 +125,9 @@ class Price extends AbstractIndexer
|
|
|
*/
|
|
*/
|
|
|
public function reindexFull()
|
|
public function reindexFull()
|
|
|
{
|
|
{
|
|
|
- while (true) {
|
|
|
|
|
- $paginator = $this->productRepository
|
|
|
|
|
- ->with([
|
|
|
|
|
- 'variants',
|
|
|
|
|
- 'attribute_family',
|
|
|
|
|
- 'attribute_values',
|
|
|
|
|
- 'variants.attribute_family',
|
|
|
|
|
- 'variants.attribute_values',
|
|
|
|
|
- 'price_indices',
|
|
|
|
|
- 'inventory_indices',
|
|
|
|
|
- 'variants.price_indices',
|
|
|
|
|
- 'variants.inventory_indices',
|
|
|
|
|
- 'customer_group_prices',
|
|
|
|
|
- 'variants.customer_group_prices',
|
|
|
|
|
- 'catalog_rule_prices',
|
|
|
|
|
- 'variants.catalog_rule_prices',
|
|
|
|
|
- 'flexibleVariants',
|
|
|
|
|
- 'flexibleVariants.price_indices',
|
|
|
|
|
- 'flexibleVariants.customer_group_prices',
|
|
|
|
|
- ])
|
|
|
|
|
- ->cursorPaginate($this->batchSize);
|
|
|
|
|
-
|
|
|
|
|
- $this->reindexBatch($paginator->items());
|
|
|
|
|
-
|
|
|
|
|
- if (! $cursor = $paginator->nextCursor()) {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- request()->query->add(['cursor' => $cursor->encode()]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- request()->query->remove('cursor');
|
|
|
|
|
|
|
+ $this->productQuery()
|
|
|
|
|
+ ->with(self::EAGER_LOADS)
|
|
|
|
|
+ ->chunkById($this->batchSize, fn ($products) => $this->reindexBatch($products));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -89,52 +137,30 @@ class Price extends AbstractIndexer
|
|
|
*/
|
|
*/
|
|
|
public function reindexSelective()
|
|
public function reindexSelective()
|
|
|
{
|
|
{
|
|
|
- while (true) {
|
|
|
|
|
- $paginator = $this->productRepository
|
|
|
|
|
- ->distinct()
|
|
|
|
|
- ->select('products.*')
|
|
|
|
|
- ->with([
|
|
|
|
|
- 'variants',
|
|
|
|
|
- 'attribute_values',
|
|
|
|
|
- 'variants.attribute_values',
|
|
|
|
|
- 'price_indices',
|
|
|
|
|
- 'inventory_indices',
|
|
|
|
|
- 'variants.price_indices',
|
|
|
|
|
- 'variants.inventory_indices',
|
|
|
|
|
- 'customer_group_prices',
|
|
|
|
|
- 'variants.customer_group_prices',
|
|
|
|
|
- 'catalog_rule_prices',
|
|
|
|
|
- 'variants.catalog_rule_prices',
|
|
|
|
|
- 'flexibleVariants',
|
|
|
|
|
- 'flexibleVariants.price_indices',
|
|
|
|
|
- 'flexibleVariants.customer_group_prices',
|
|
|
|
|
- ])
|
|
|
|
|
- ->join('product_attribute_values as special_price_from_pav', function ($join) {
|
|
|
|
|
- $join->on('products.id', '=', 'special_price_from_pav.product_id')
|
|
|
|
|
- ->where('special_price_from_pav.attribute_id', self::SPECIAL_PRICE_FROM_ATTRIBUTE_ID);
|
|
|
|
|
- })
|
|
|
|
|
- ->join('product_attribute_values as special_price_to_pav', function ($join) {
|
|
|
|
|
- $join->on('products.id', '=', 'special_price_to_pav.product_id')
|
|
|
|
|
- ->where('special_price_to_pav.attribute_id', self::SPECIAL_PRICE_TO_ATTRIBUTE_ID);
|
|
|
|
|
- })
|
|
|
|
|
- ->leftJoin('catalog_rule_product_prices', 'products.id', '=', 'catalog_rule_product_prices.product_id')
|
|
|
|
|
- ->where(function ($query) {
|
|
|
|
|
- return $query->orWhere('special_price_from_pav.date_value', Carbon::now()->format('Y-m-d'))
|
|
|
|
|
- ->orWhere('special_price_to_pav.date_value', Carbon::now()->subDays(1)->format('Y-m-d'))
|
|
|
|
|
- ->orWhere('catalog_rule_product_prices.rule_date', Carbon::now()->subDays(1)->format('Y-m-d'));
|
|
|
|
|
- })
|
|
|
|
|
- ->cursorPaginate($this->batchSize);
|
|
|
|
|
-
|
|
|
|
|
- $this->reindexBatch($paginator->items());
|
|
|
|
|
-
|
|
|
|
|
- if (! $cursor = $paginator->nextCursor()) {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- request()->query->add(['cursor' => $cursor->encode()]);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- request()->query->remove('cursor');
|
|
|
|
|
|
|
+ $this->productQuery()
|
|
|
|
|
+ ->distinct()
|
|
|
|
|
+ ->select('products.*')
|
|
|
|
|
+ ->with(self::EAGER_LOADS)
|
|
|
|
|
+ ->join('product_attribute_values as special_price_from_pav', function ($join) {
|
|
|
|
|
+ $join->on('products.id', '=', 'special_price_from_pav.product_id')
|
|
|
|
|
+ ->where('special_price_from_pav.attribute_id', self::SPECIAL_PRICE_FROM_ATTRIBUTE_ID);
|
|
|
|
|
+ })
|
|
|
|
|
+ ->join('product_attribute_values as special_price_to_pav', function ($join) {
|
|
|
|
|
+ $join->on('products.id', '=', 'special_price_to_pav.product_id')
|
|
|
|
|
+ ->where('special_price_to_pav.attribute_id', self::SPECIAL_PRICE_TO_ATTRIBUTE_ID);
|
|
|
|
|
+ })
|
|
|
|
|
+ ->leftJoin('catalog_rule_product_prices', 'products.id', '=', 'catalog_rule_product_prices.product_id')
|
|
|
|
|
+ ->where(function ($query) {
|
|
|
|
|
+ return $query->orWhere('special_price_from_pav.date_value', Carbon::now()->format('Y-m-d'))
|
|
|
|
|
+ ->orWhere('special_price_to_pav.date_value', Carbon::now()->subDays(1)->format('Y-m-d'))
|
|
|
|
|
+ ->orWhere('catalog_rule_product_prices.rule_date', Carbon::now()->subDays(1)->format('Y-m-d'));
|
|
|
|
|
+ })
|
|
|
|
|
+ ->chunkById(
|
|
|
|
|
+ $this->batchSize,
|
|
|
|
|
+ fn ($products) => $this->reindexBatch($products),
|
|
|
|
|
+ 'products.id',
|
|
|
|
|
+ 'id'
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -144,7 +170,15 @@ class Price extends AbstractIndexer
|
|
|
*/
|
|
*/
|
|
|
public function reindexBatch($products)
|
|
public function reindexBatch($products)
|
|
|
{
|
|
{
|
|
|
- $newIndices = [];
|
|
|
|
|
|
|
+ $products = $products instanceof Collection ? $products : collect($products);
|
|
|
|
|
+
|
|
|
|
|
+ if ($products->isEmpty()) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $catalogRuleProducts = $this->getCatalogRuleProducts($products);
|
|
|
|
|
+
|
|
|
|
|
+ $pending = [];
|
|
|
|
|
|
|
|
foreach ($products as $product) {
|
|
foreach ($products as $product) {
|
|
|
$indexer = $this->getTypeIndexer($product)
|
|
$indexer = $this->getTypeIndexer($product)
|
|
@@ -152,67 +186,63 @@ class Price extends AbstractIndexer
|
|
|
|
|
|
|
|
$isFlexibleVariant = $product->type === 'flexible_variant';
|
|
$isFlexibleVariant = $product->type === 'flexible_variant';
|
|
|
|
|
|
|
|
|
|
+ $variants = collect();
|
|
|
|
|
+
|
|
|
|
|
+ if ($isFlexibleVariant) {
|
|
|
|
|
+ $indexer->setCatalogRuleProducts($catalogRuleProducts[$product->id] ?? []);
|
|
|
|
|
+
|
|
|
|
|
+ $variants = $this->getSaleableVariants($product);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
foreach ($this->getChannels() as $channel) {
|
|
foreach ($this->getChannels() as $channel) {
|
|
|
foreach ($this->getCustomerGroups() as $customerGroup) {
|
|
foreach ($this->getCustomerGroups() as $customerGroup) {
|
|
|
$indexer->setChannel($channel)->setCustomerGroup($customerGroup);
|
|
$indexer->setChannel($channel)->setCustomerGroup($customerGroup);
|
|
|
|
|
|
|
|
- // Index each flexible variant individually
|
|
|
|
|
- if ($isFlexibleVariant) {
|
|
|
|
|
- $variants = $product->relationLoaded('flexibleVariants')
|
|
|
|
|
- ? $product->flexibleVariants
|
|
|
|
|
- : $product->flexibleVariants()->get();
|
|
|
|
|
-
|
|
|
|
|
- foreach ($variants as $variant) {
|
|
|
|
|
- if (! $variant->isSaleable()) {
|
|
|
|
|
- continue;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- $variantIndex = $indexer->setVariant($variant)->getVariantIndices();
|
|
|
|
|
-
|
|
|
|
|
- $this->upsertIndex(
|
|
|
|
|
- $variant->price_indices ?? collect(),
|
|
|
|
|
- $variantIndex,
|
|
|
|
|
- $newIndices,
|
|
|
|
|
- $channel->id,
|
|
|
|
|
- $customerGroup->id,
|
|
|
|
|
- $variant->id,
|
|
|
|
|
- get_class($variant)
|
|
|
|
|
- );
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $variantIndices = [];
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($variants as $variant) {
|
|
|
|
|
+ $variantIndices[] = $variantIndex = $indexer->setVariant($variant)->getVariantIndices();
|
|
|
|
|
+
|
|
|
|
|
+ $this->collectIndex(
|
|
|
|
|
+ $variant->price_indices ?? collect(),
|
|
|
|
|
+ $variantIndex,
|
|
|
|
|
+ $pending,
|
|
|
|
|
+ $channel->id,
|
|
|
|
|
+ $customerGroup->id,
|
|
|
|
|
+ $variant->id
|
|
|
|
|
+ );
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Product-level index (aggregated for flexible_variant, normal for others)
|
|
// Product-level index (aggregated for flexible_variant, normal for others)
|
|
|
- $newIndex = $indexer->getIndices();
|
|
|
|
|
|
|
+ $newIndex = $isFlexibleVariant
|
|
|
|
|
+ ? $indexer->aggregateIndices($variantIndices)
|
|
|
|
|
+ : $indexer->getIndices();
|
|
|
|
|
|
|
|
- $this->upsertIndex(
|
|
|
|
|
|
|
+ $this->collectIndex(
|
|
|
$product->price_indices,
|
|
$product->price_indices,
|
|
|
$newIndex,
|
|
$newIndex,
|
|
|
- $newIndices,
|
|
|
|
|
|
|
+ $pending,
|
|
|
$channel->id,
|
|
$channel->id,
|
|
|
$customerGroup->id,
|
|
$customerGroup->id,
|
|
|
- $product->id,
|
|
|
|
|
- get_class($product)
|
|
|
|
|
|
|
+ $product->id
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if (! empty($newIndices)) {
|
|
|
|
|
- $this->productPriceIndexRepository->insert($newIndices);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ $this->writeIndices($pending);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Upsert a single price index entry: update if exists, or queue for bulk insert.
|
|
|
|
|
|
|
+ * Queue a price index row for writing when it is new or its values moved.
|
|
|
*/
|
|
*/
|
|
|
- protected function upsertIndex(
|
|
|
|
|
|
|
+ protected function collectIndex(
|
|
|
$existingIndices,
|
|
$existingIndices,
|
|
|
array $newIndex,
|
|
array $newIndex,
|
|
|
- array &$newIndices,
|
|
|
|
|
|
|
+ array &$pending,
|
|
|
int $channelId,
|
|
int $channelId,
|
|
|
int $customerGroupId,
|
|
int $customerGroupId,
|
|
|
- int $priceableId,
|
|
|
|
|
- string $priceableType
|
|
|
|
|
|
|
+ int $priceableId
|
|
|
): void {
|
|
): void {
|
|
|
$existing = $existingIndices
|
|
$existing = $existingIndices
|
|
|
->where('channel_id', $channelId)
|
|
->where('channel_id', $channelId)
|
|
@@ -220,16 +250,31 @@ class Price extends AbstractIndexer
|
|
|
->where('priceable_id', $priceableId)
|
|
->where('priceable_id', $priceableId)
|
|
|
->first();
|
|
->first();
|
|
|
|
|
|
|
|
- if ($existing) {
|
|
|
|
|
- $oldIndex = collect($existing->toArray())
|
|
|
|
|
- ->except('id', 'created_at', 'updated_at')
|
|
|
|
|
- ->toArray();
|
|
|
|
|
|
|
+ if ($existing && ! $this->isIndexChanged($existing->getAttributes(), $newIndex)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if ($this->isIndexChanged($oldIndex, $newIndex)) {
|
|
|
|
|
- $this->productPriceIndexRepository->update($newIndex, $existing->id);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- $newIndices[] = $newIndex;
|
|
|
|
|
|
|
+ $pending[] = $newIndex;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Insert new rows and refresh changed ones in bulk.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function writeIndices(array $indices): void
|
|
|
|
|
+ {
|
|
|
|
|
+ if (empty($indices)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $update = array_values(array_diff(
|
|
|
|
|
+ array_keys($indices[0]),
|
|
|
|
|
+ self::UNIQUE_COLUMNS
|
|
|
|
|
+ ));
|
|
|
|
|
+
|
|
|
|
|
+ $model = $this->productPriceIndexRepository->getModel();
|
|
|
|
|
+
|
|
|
|
|
+ foreach (array_chunk($indices, self::WRITE_CHUNK_SIZE) as $chunk) {
|
|
|
|
|
+ $model->newQuery()->upsert($chunk, self::UNIQUE_COLUMNS, $update);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -240,23 +285,123 @@ class Price extends AbstractIndexer
|
|
|
*/
|
|
*/
|
|
|
public function isIndexChanged($oldIndex, $newIndex)
|
|
public function isIndexChanged($oldIndex, $newIndex)
|
|
|
{
|
|
{
|
|
|
- return (bool) count(array_diff_assoc($oldIndex, $newIndex));
|
|
|
|
|
|
|
+ foreach ($newIndex as $column => $value) {
|
|
|
|
|
+ $oldValue = $oldIndex[$column] ?? null;
|
|
|
|
|
+
|
|
|
|
|
+ if (in_array($column, self::DECIMAL_COLUMNS, true)) {
|
|
|
|
|
+ if (number_format((float) $oldValue, 4, '.', '') !== number_format((float) $value, 4, '.', '')) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if ((string) $oldValue !== (string) $value) {
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Drop index rows whose product or variant no longer exists. Polymorphic rows have
|
|
|
|
|
+ * no foreign key to cascade from, so deletions leave them behind forever.
|
|
|
|
|
+ */
|
|
|
|
|
+ public function pruneOrphanIndices(): int
|
|
|
|
|
+ {
|
|
|
|
|
+ $deleted = 0;
|
|
|
|
|
+
|
|
|
|
|
+ $types = DB::table(self::TABLE)
|
|
|
|
|
+ ->select('priceable_type')
|
|
|
|
|
+ ->distinct()
|
|
|
|
|
+ ->pluck('priceable_type');
|
|
|
|
|
+
|
|
|
|
|
+ foreach ($types as $type) {
|
|
|
|
|
+ if (! is_string($type) || ! is_subclass_of($type, Model::class)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $owner = new $type;
|
|
|
|
|
+
|
|
|
|
|
+ while (true) {
|
|
|
|
|
+ $ids = DB::table(self::TABLE)
|
|
|
|
|
+ ->leftJoin($owner->getTable(), $owner->getQualifiedKeyName(), '=', self::TABLE.'.priceable_id')
|
|
|
|
|
+ ->where(self::TABLE.'.priceable_type', $type)
|
|
|
|
|
+ ->whereNull($owner->getQualifiedKeyName())
|
|
|
|
|
+ ->limit(self::WRITE_CHUNK_SIZE)
|
|
|
|
|
+ ->pluck(self::TABLE.'.id');
|
|
|
|
|
+
|
|
|
|
|
+ if ($ids->isEmpty()) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $deleted += DB::table(self::TABLE)->whereIn('id', $ids)->delete();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return $deleted;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Catalog rule entries for every flexible variant product in the batch, keyed by
|
|
|
|
|
+ * product id, so the variant indexer does not query them one product at a time.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function getCatalogRuleProducts(Collection $products): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $productIds = $products
|
|
|
|
|
+ ->where('type', 'flexible_variant')
|
|
|
|
|
+ ->pluck('id')
|
|
|
|
|
+ ->all();
|
|
|
|
|
+
|
|
|
|
|
+ if (empty($productIds)) {
|
|
|
|
|
+ return [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return DB::table('catalog_rule_products')
|
|
|
|
|
+ ->whereIn('product_id', $productIds)
|
|
|
|
|
+ ->orderBy('sort_order')
|
|
|
|
|
+ ->orderBy('catalog_rule_id')
|
|
|
|
|
+ ->get()
|
|
|
|
|
+ ->groupBy('product_id')
|
|
|
|
|
+ ->map(fn ($rules) => $rules->all())
|
|
|
|
|
+ ->all();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Saleable flexible variants of a product.
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function getSaleableVariants($product): Collection
|
|
|
|
|
+ {
|
|
|
|
|
+ $variants = $product->relationLoaded('flexibleVariants')
|
|
|
|
|
+ ? $product->flexibleVariants
|
|
|
|
|
+ : $product->flexibleVariants()->get();
|
|
|
|
|
+
|
|
|
|
|
+ return $variants->filter(fn ($variant) => $variant->isSaleable())->values();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Fresh query builder on the product model.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return \Illuminate\Database\Eloquent\Builder
|
|
|
|
|
+ */
|
|
|
|
|
+ protected function productQuery()
|
|
|
|
|
+ {
|
|
|
|
|
+ return $this->productRepository->getModel()->newQuery();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
* Returns indexer for product type
|
|
* Returns indexer for product type
|
|
|
*
|
|
*
|
|
|
- * @return string
|
|
|
|
|
|
|
+ * @return \Webkul\Product\Helpers\Indexers\Price\AbstractType
|
|
|
*/
|
|
*/
|
|
|
public function getTypeIndexer($product)
|
|
public function getTypeIndexer($product)
|
|
|
{
|
|
{
|
|
|
- static $typeIndexers = [];
|
|
|
|
|
-
|
|
|
|
|
- if (isset($typeIndexers[$product->type])) {
|
|
|
|
|
- return $typeIndexers[$product->type];
|
|
|
|
|
|
|
+ if (isset($this->typeIndexers[$product->type])) {
|
|
|
|
|
+ return $this->typeIndexers[$product->type];
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return $typeIndexers[$product->type] = $product->getTypeInstance()->getPriceIndexer();
|
|
|
|
|
|
|
+ return $this->typeIndexers[$product->type] = $product->getTypeInstance()->getPriceIndexer();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|