perf: implement batch-fetching for variant prices to optimize query performance in OrderService and HomeController

This commit is contained in:
Yoga Pangestu 2026-06-22 12:40:58 +07:00
parent 747836be91
commit f83def0e8a
3 changed files with 95 additions and 26 deletions

View File

@ -26,20 +26,24 @@ public function index(): Response
$products = Product::getActiveWithVariantsAndCategories();
$products = $products->map(function ($product) {
$product->variants->each(function ($variant) {
$allVariantIds = $products
->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'));
$variantPrices = $allPricesByVariant->get($variant->id, collect());
$variant->setAttribute(
'prices',
collect($this->cuttingResultPriceResolver->latestPricesForVariant($variant->id))
->map(fn ($price) => [
'type' => $price->price_type->value,
'type_label' => $price->price_type->label(),
'price' => (int) $price->price,
'price_formatted' => $price->price_formatted,
])
->values()
->all(),
$variantPrices->map(fn ($price) => [
'type' => $price->price_type->value,
'type_label' => $price->price_type->label(),
'price' => (int) $price->price,
'price_formatted' => $price->price_formatted,
])->values()->all(),
);
});

View File

@ -4,6 +4,7 @@
use App\Enums\PriceType;
use App\Models\CuttingResultPrice;
use Illuminate\Support\Collection;
class CuttingResultPriceResolver
{
@ -36,4 +37,31 @@ public function latestPricesForVariant(int $productVariantId): array
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');
}
}

View File

@ -136,7 +136,7 @@ public function catalogItems(?Order $order = null, ?User $user = null): Collecti
? $order->items()->pluck('product_variant_id')->all()
: ($user ? $this->draftItemsQuery($user)->pluck('product_variant_id')->all() : []);
return Product::query()
$products = Product::query()
->with([
'variants' => fn ($query) => $query
->with('media')
@ -153,19 +153,29 @@ public function catalogItems(?Order $order = null, ?User $user = null): Collecti
}
})
->orderBy('name')
->get()
->each(function (Product $product): void {
$product->variants->each(function (ProductVariant $variant): void {
$variant->setAttribute(
'images',
MediaPresenter::collection($variant, 'images'),
);
$variant->setAttribute(
'prices',
$this->presentVariantPrices($variant->id),
);
});
->get();
$allVariantIds = $products
->flatMap(fn (Product $product) => $product->variants->pluck('id'))
->all();
$allPricesByVariant = $this->cuttingResultPriceResolver->latestPricesForVariants($allVariantIds);
return $products->each(function (Product $product) use ($allPricesByVariant): void {
$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
@ -175,7 +185,14 @@ public function findForEdit(Order $order): Order
'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;
if ($variant) {
@ -187,7 +204,10 @@ public function findForEdit(Order $order): Order
);
$variant->setAttribute(
'prices',
$this->presentVariantPrices($variant->id),
$this->presentVariantPricesFromCollection(
$variant->id,
$allPricesByVariant,
),
);
}
});
@ -767,4 +787,21 @@ private function presentVariantPrices(int $variantId): array
->values()
->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();
}
}