- Complete product and inventory management system - Media storage service with proper path conventions - Image handling with dynamic resizing - Database migrations for inventory tracking - Import tools for legacy data migration - Documentation improvements - InventoryItem, InventoryMovement, InventoryAlert models with hashid support - Purchase order tracking on inventory alerts - Inventory dashboard for sellers - Stock level monitoring and notifications - MediaStorageService enforcing consistent path conventions - Image controller with dynamic resizing capabilities - Migration tools for moving images to MinIO - Proper slug-based paths (not IDs or hashids) - ImportProductsFromRemote command - ImportAlohaSales, ImportThunderBudBulk commands - ExploreRemoteDatabase for schema inspection - Legacy data migration utilities - Product variations table - Remote customer mappings - Performance indexes for stats queries - Social media fields for brands - Module flags for businesses - New migrations for inventory, hashids, performance indexes - New services: MediaStorageService - New middleware: EnsureBusinessHasModule, EnsureUserHasCapability - Import commands for legacy data - Inventory models and controllers - Updated views for marketplace and seller areas - Documentation reorganization (archived old docs) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
127 lines
4.3 KiB
PHP
127 lines
4.3 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Product;
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Storage;
|
|
|
|
class MigrateProductImagePaths extends Command
|
|
{
|
|
protected $signature = 'media:migrate-product-images {--dry-run : Show what would be migrated without making changes}';
|
|
|
|
protected $description = 'Migrate product images from old path (products/{id}/) to correct path (brands/{brand}/products/{sku}/images/)';
|
|
|
|
public function handle()
|
|
{
|
|
$dryRun = $this->option('dry-run');
|
|
|
|
if ($dryRun) {
|
|
$this->warn('🔍 DRY RUN MODE - No changes will be made');
|
|
$this->newLine();
|
|
}
|
|
|
|
$this->info('🚀 Starting product image migration...');
|
|
$this->newLine();
|
|
|
|
// Get all products with image_path
|
|
$products = Product::whereNotNull('image_path')
|
|
->with('brand.business')
|
|
->get();
|
|
|
|
$this->info("Found {$products->count()} products with images");
|
|
$this->newLine();
|
|
|
|
$stats = [
|
|
'total' => $products->count(),
|
|
'migrated' => 0,
|
|
'skipped_correct_path' => 0,
|
|
'skipped_missing' => 0,
|
|
'failed' => 0,
|
|
];
|
|
|
|
$progressBar = $this->output->createProgressBar($products->count());
|
|
$progressBar->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
|
|
$progressBar->setMessage('Starting...');
|
|
|
|
foreach ($products as $product) {
|
|
$progressBar->setMessage("Product #{$product->id}: {$product->name}");
|
|
|
|
try {
|
|
// Check if already using correct path pattern
|
|
if (preg_match('#^businesses/[^/]+/brands/[^/]+/products/[^/]+/images/#', $product->image_path)) {
|
|
$stats['skipped_correct_path']++;
|
|
$progressBar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
// Check if old file exists
|
|
if (! Storage::exists($product->image_path)) {
|
|
$stats['skipped_missing']++;
|
|
$progressBar->clear();
|
|
$this->warn(" ⚠️ Product #{$product->id} - Image missing at: {$product->image_path}");
|
|
$progressBar->display();
|
|
$progressBar->advance();
|
|
|
|
continue;
|
|
}
|
|
|
|
// Build new path
|
|
$filename = basename($product->image_path);
|
|
$businessSlug = $product->brand->business->slug ?? 'unknown';
|
|
$brandSlug = $product->brand->slug ?? 'unknown';
|
|
$productSku = $product->sku;
|
|
|
|
$newPath = "businesses/{$businessSlug}/brands/{$brandSlug}/products/{$productSku}/images/{$filename}";
|
|
$oldPath = $product->image_path;
|
|
|
|
if (! $dryRun) {
|
|
// Copy file to new location on MinIO
|
|
$contents = Storage::get($oldPath);
|
|
Storage::put($newPath, $contents);
|
|
|
|
// Update database
|
|
$product->image_path = $newPath;
|
|
$product->save();
|
|
|
|
// Delete old file
|
|
Storage::delete($oldPath);
|
|
}
|
|
|
|
$stats['migrated']++;
|
|
} catch (\Exception $e) {
|
|
$stats['failed']++;
|
|
$progressBar->clear();
|
|
$this->error(" ✗ Failed to migrate product #{$product->id}: {$e->getMessage()}");
|
|
$progressBar->display();
|
|
}
|
|
|
|
$progressBar->advance();
|
|
}
|
|
|
|
$progressBar->finish();
|
|
$this->newLine(2);
|
|
|
|
// Show summary
|
|
$this->info('📊 Migration Summary:');
|
|
$this->table(
|
|
['Metric', 'Count'],
|
|
[
|
|
['Total Products', $stats['total']],
|
|
['✓ Migrated', $stats['migrated']],
|
|
['→ Already Correct Path', $stats['skipped_correct_path']],
|
|
['⊘ Missing Files', $stats['skipped_missing']],
|
|
['✗ Failed', $stats['failed']],
|
|
]
|
|
);
|
|
|
|
if ($dryRun) {
|
|
$this->newLine();
|
|
$this->warn('This was a dry run. Run without --dry-run to actually migrate the images.');
|
|
}
|
|
|
|
return $stats['failed'] > 0 ? 1 : 0;
|
|
}
|
|
}
|