perf: implement batch-fetching for variant prices to optimize query performance in OrderService and HomeController
This commit is contained in:
parent
747836be91
commit
f83def0e8a
@ -26,20 +26,24 @@ public function index(): Response
|
|||||||
|
|
||||||
$products = Product::getActiveWithVariantsAndCategories();
|
$products = Product::getActiveWithVariantsAndCategories();
|
||||||
|
|
||||||
$products = $products->map(function ($product) {
|
$allVariantIds = $products
|
||||||
$product->variants->each(function ($variant) {
|
->flatMap(fn ($product) => $product->variants->pluck('id'))
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$allPricesByVariant = $this->cuttingResultPriceResolver->latestPricesForVariants($allVariantIds);
|
||||||
|
|
||||||
|
$products = $products->map(function ($product) use ($allPricesByVariant) {
|
||||||
|
$product->variants->each(function ($variant) use ($allPricesByVariant) {
|
||||||
$variant->setAttribute('images', MediaPresenter::collection($variant, 'images'));
|
$variant->setAttribute('images', MediaPresenter::collection($variant, 'images'));
|
||||||
|
$variantPrices = $allPricesByVariant->get($variant->id, collect());
|
||||||
$variant->setAttribute(
|
$variant->setAttribute(
|
||||||
'prices',
|
'prices',
|
||||||
collect($this->cuttingResultPriceResolver->latestPricesForVariant($variant->id))
|
$variantPrices->map(fn ($price) => [
|
||||||
->map(fn ($price) => [
|
'type' => $price->price_type->value,
|
||||||
'type' => $price->price_type->value,
|
'type_label' => $price->price_type->label(),
|
||||||
'type_label' => $price->price_type->label(),
|
'price' => (int) $price->price,
|
||||||
'price' => (int) $price->price,
|
'price_formatted' => $price->price_formatted,
|
||||||
'price_formatted' => $price->price_formatted,
|
])->values()->all(),
|
||||||
])
|
|
||||||
->values()
|
|
||||||
->all(),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Enums\PriceType;
|
use App\Enums\PriceType;
|
||||||
use App\Models\CuttingResultPrice;
|
use App\Models\CuttingResultPrice;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
|
||||||
class CuttingResultPriceResolver
|
class CuttingResultPriceResolver
|
||||||
{
|
{
|
||||||
@ -36,4 +37,31 @@ public function latestPricesForVariant(int $productVariantId): array
|
|||||||
|
|
||||||
return $prices;
|
return $prices;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Batch-fetch latest prices for multiple variant IDs in a single query.
|
||||||
|
* Returns a Collection keyed by product_variant_id, where each value is
|
||||||
|
* a Collection of CuttingResultPrice (one per price type, the latest).
|
||||||
|
*
|
||||||
|
* @param list<int> $variantIds
|
||||||
|
* @return Collection<int, Collection<int, CuttingResultPrice>>
|
||||||
|
*/
|
||||||
|
public function latestPricesForVariants(array $variantIds): Collection
|
||||||
|
{
|
||||||
|
if (empty($variantIds)) {
|
||||||
|
return collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return CuttingResultPrice::query()
|
||||||
|
->whereIn('product_variant_id', $variantIds)
|
||||||
|
->whereHas('cutting', fn ($query) => $query->verified())
|
||||||
|
->join('cuttings', 'cutting_result_prices.cutting_id', '=', 'cuttings.id')
|
||||||
|
->orderByDesc('cuttings.created_at')
|
||||||
|
->select('cutting_result_prices.*')
|
||||||
|
->get()
|
||||||
|
->groupBy(fn (CuttingResultPrice $price) => $price->product_variant_id.'-'.$price->price_type->value)
|
||||||
|
->map(fn (Collection $group) => $group->first())
|
||||||
|
->values()
|
||||||
|
->groupBy('product_variant_id');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -136,7 +136,7 @@ public function catalogItems(?Order $order = null, ?User $user = null): Collecti
|
|||||||
? $order->items()->pluck('product_variant_id')->all()
|
? $order->items()->pluck('product_variant_id')->all()
|
||||||
: ($user ? $this->draftItemsQuery($user)->pluck('product_variant_id')->all() : []);
|
: ($user ? $this->draftItemsQuery($user)->pluck('product_variant_id')->all() : []);
|
||||||
|
|
||||||
return Product::query()
|
$products = Product::query()
|
||||||
->with([
|
->with([
|
||||||
'variants' => fn ($query) => $query
|
'variants' => fn ($query) => $query
|
||||||
->with('media')
|
->with('media')
|
||||||
@ -153,19 +153,29 @@ public function catalogItems(?Order $order = null, ?User $user = null): Collecti
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
->orderBy('name')
|
->orderBy('name')
|
||||||
->get()
|
->get();
|
||||||
->each(function (Product $product): void {
|
|
||||||
$product->variants->each(function (ProductVariant $variant): void {
|
$allVariantIds = $products
|
||||||
$variant->setAttribute(
|
->flatMap(fn (Product $product) => $product->variants->pluck('id'))
|
||||||
'images',
|
->all();
|
||||||
MediaPresenter::collection($variant, 'images'),
|
|
||||||
);
|
$allPricesByVariant = $this->cuttingResultPriceResolver->latestPricesForVariants($allVariantIds);
|
||||||
$variant->setAttribute(
|
|
||||||
'prices',
|
return $products->each(function (Product $product) use ($allPricesByVariant): void {
|
||||||
$this->presentVariantPrices($variant->id),
|
$product->variants->each(function (ProductVariant $variant) use ($allPricesByVariant): void {
|
||||||
);
|
$variant->setAttribute(
|
||||||
});
|
'images',
|
||||||
|
MediaPresenter::collection($variant, 'images'),
|
||||||
|
);
|
||||||
|
$variant->setAttribute(
|
||||||
|
'prices',
|
||||||
|
$this->presentVariantPricesFromCollection(
|
||||||
|
$variant->id,
|
||||||
|
$allPricesByVariant,
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findForEdit(Order $order): Order
|
public function findForEdit(Order $order): Order
|
||||||
@ -175,7 +185,14 @@ public function findForEdit(Order $order): Order
|
|||||||
'items.productVariant.media',
|
'items.productVariant.media',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$order->items->each(function (OrderItem $item): void {
|
$variantIds = $order->items
|
||||||
|
->filter(fn (OrderItem $item) => $item->productVariant !== null)
|
||||||
|
->map(fn (OrderItem $item) => $item->productVariant->id)
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$allPricesByVariant = $this->cuttingResultPriceResolver->latestPricesForVariants($variantIds);
|
||||||
|
|
||||||
|
$order->items->each(function (OrderItem $item) use ($allPricesByVariant): void {
|
||||||
$variant = $item->productVariant;
|
$variant = $item->productVariant;
|
||||||
|
|
||||||
if ($variant) {
|
if ($variant) {
|
||||||
@ -187,7 +204,10 @@ public function findForEdit(Order $order): Order
|
|||||||
);
|
);
|
||||||
$variant->setAttribute(
|
$variant->setAttribute(
|
||||||
'prices',
|
'prices',
|
||||||
$this->presentVariantPrices($variant->id),
|
$this->presentVariantPricesFromCollection(
|
||||||
|
$variant->id,
|
||||||
|
$allPricesByVariant,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -767,4 +787,21 @@ private function presentVariantPrices(int $variantId): array
|
|||||||
->values()
|
->values()
|
||||||
->all();
|
->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function presentVariantPricesFromCollection(int $variantId, Collection $allPricesByVariant): array
|
||||||
|
{
|
||||||
|
return $allPricesByVariant
|
||||||
|
->get($variantId, collect())
|
||||||
|
->map(fn ($price) => [
|
||||||
|
'type' => $price->price_type->value,
|
||||||
|
'type_label' => $price->price_type->label(),
|
||||||
|
'price' => (int) $price->price,
|
||||||
|
'price_formatted' => $price->price_formatted,
|
||||||
|
'price_input' => (string) $price->price,
|
||||||
|
'cost_per_unit' => (int) $price->cost_per_unit,
|
||||||
|
'cost_per_unit_formatted' => $price->cost_per_unit_formatted,
|
||||||
|
])
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user