Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions app/Http/Controllers/ProductImportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ public function store(Request $request)
// (Though usually file is sorted, better safe)
$groupedData = collect($previewData)->groupBy('name');

DB::transaction(function () use ($groupedData, &$count) {
$allSkus = collect($previewData)->pluck('sku')->unique()->filter()->toArray();
$existingSkus = ProductVariant::whereIn('sku', $allSkus)->pluck('sku')->map(fn($sku) => strtolower($sku))->flip()->toArray();

DB::transaction(function () use ($groupedData, &$count, $existingSkus) {
foreach ($groupedData as $productName => $variants) {
// Use the FIRST valid row's creation data for the product
// Find a row that has valid product data? Or just take the first one?
Expand Down Expand Up @@ -218,7 +221,7 @@ public function store(Request $request)
if (!empty($row['errors'])) continue;

// Check duplicate SKU again to be safe
if (ProductVariant::where('sku', $row['sku'])->exists()) continue;
if (isset($existingSkus[strtolower($row['sku'])])) continue;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep SKU cache in sync during import loop

$existingSkus is initialized once before the transaction and never updated after a new variant is created, so later rows rely on stale duplicate information. If another writer inserts one of the pending SKUs during this import (or duplicated preview data reaches this action), isset($existingSkus[...]) will miss it and create() will hit the unique sku constraint, rolling back the whole transaction instead of skipping just the conflicting row as the previous per-row existence check could for later rows.

Useful? React with 👍 / 👎.


$variantImage = null; // Currently template supports 1 image per row, usually mapping to Product Image.
// If user provides specific variant image logic, we'd need another column.
Expand Down
96 changes: 96 additions & 0 deletions tests/Feature/ProductImportPerformanceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

namespace Tests\Feature;

use Tests\TestCase;
use App\Models\User;
use App\Models\Category;
use App\Models\SubCategory;
use App\Models\Unit;
use App\Models\Product;
use App\Models\ProductVariant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;

class ProductImportPerformanceTest extends TestCase
{
use RefreshDatabase;

public function test_import_performance()
{
// Setup
$user = User::factory()->create();
$this->actingAs($user);

$category = Category::create(['name' => 'Test Cat', 'code' => 'TC']);
$subCategory = SubCategory::create(['name' => 'Test Sub', 'category_id' => $category->id, 'code' => 'TS']);
$unit = Unit::create(['name' => 'Test Unit', 'short_name' => 'TU']);

// Seed some existing data to simulate a real-world scenario where check is necessary
$existingCount = 50;
for ($i = 0; $i < $existingCount; $i++) {
$p = Product::create([
'name' => "Existing Product $i",
'category_id' => $category->id,
'sub_category_id' => $subCategory->id,
'description' => 'Test',
]);
ProductVariant::create([
'product_id' => $p->id,
'unit_id' => $unit->id,
'sku' => "SKU-$i",
'selling_price' => 100,
'quantity' => 10,
'unit_value' => 1,
]);
}

// Prepare import data
$importCount = 500;
$previewData = [];

for ($i = 0; $i < $importCount; $i++) {
// First 50 are duplicates (SKU-0 to SKU-49)
// Next are new
$sku = "SKU-$i";

$previewData[] = [
'row_id' => $i,
'name' => "Import Product $i",
'category_id' => $category->id,
'sub_category_id' => $subCategory->id,
'description' => 'Desc',
'unit_id' => $unit->id,
'unit_value' => 1,
'sku' => $sku,
'selling_price' => 120,
'limit_price' => 110,
'quantity' => 20,
'alert_quantity' => 5,
'image_url' => null,
'errors' => []
];
}

session(['product_import_preview_data' => $previewData]);

// Enable query logging to count queries
DB::enableQueryLog();

$startTime = microtime(true);

$response = $this->post(route('products.import.store'));

$endTime = microtime(true);
$duration = $endTime - $startTime;
$queryLog = DB::getQueryLog();
$queryCount = count($queryLog);

echo "\nPerformance Results:\n";
echo "Time: " . number_format($duration, 4) . " seconds\n";
echo "Queries: " . $queryCount . "\n";

// Assert redirect to confirm no crash
$response->assertRedirect(route('products.index'));
}
}