diff --git a/app/Console/Commands/Migrations/MigrateAllFromOldData.php b/app/Console/Commands/Migrations/MigrateAllFromOldData.php new file mode 100644 index 0000000..4e889cc --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateAllFromOldData.php @@ -0,0 +1,167 @@ +option('force') && ! $this->confirm('Are you sure you want to run ALL migrations? This will add data to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('🚀 Starting complete data migration from old database...'); + $this->newLine(); + + $commands = [ + ['command' => 'migrate:outlets', 'name' => 'Outlets', 'options' => ['--recreate' => true]], + ['command' => 'migrate:users', 'name' => 'Users'], + ['command' => 'migrate:employees', 'name' => 'Employees'], + ['command' => 'migrate:categories', 'name' => 'Categories'], + ['command' => 'migrate:products', 'name' => 'Products', 'options' => ['--recreate' => true]], + ['command' => 'migrate:perfumes', 'name' => 'Perfumes', 'options' => ['--recreate' => true]], + ['command' => 'migrate:bottles', 'name' => 'Bottles', 'options' => ['--recreate' => true]], + ]; + + $totalCommands = count($commands); + $completed = 0; + $failed = 0; + + foreach ($commands as $commandConfig) { + $commandName = $commandConfig['command']; + $name = $commandConfig['name']; + $options = $commandConfig['options'] ?? []; + + // Always add force option + $options['--force'] = $this->option('skip-confirm'); + + $this->info("📋 Migrating {$name}..."); + $this->newLine(); + + try { + $exitCode = $this->call($commandName, $options); + + if ($exitCode === 0) { + $this->info("✅ {$name} migration completed successfully!"); + $completed++; + } else { + $this->error("❌ {$name} migration failed!"); + $failed++; + + if (! $this->confirm("Continue with remaining migrations despite {$name} failure?")) { + $this->warn('Migration stopped by user.'); + break; + } + } + } catch (\Exception $e) { + $this->error("❌ {$name} migration error: {$e->getMessage()}"); + $failed++; + + if (! $this->confirm("Continue with remaining migrations despite {$name} error?")) { + $this->warn('Migration stopped by user.'); + break; + } + } + + $this->newLine(2); + } + + $this->newLine(); + $this->info('🎉 Migration process completed!'); + $this->info('📊 Summary:'); + $this->info(" ✅ Completed: {$completed}/{$totalCommands} migrations"); + if ($failed > 0) { + $this->warn(" ❌ Failed: {$failed} migrations"); + } + + if ($completed === $totalCommands) { + $this->info('🎊 All migrations completed successfully!'); + + // Run seeders + $this->runSeeders(); + } else { + $this->warn('⚠️ Some migrations failed. Please check the output above for details.'); + } + + return $failed > 0 ? 1 : 0; + } + + /** + * Run the seeders after successful migration + */ + private function runSeeders() + { + $this->newLine(); + $this->info('🌱 Running seeders...'); + + $seeders = [ + ['class' => UserSeeder::class, 'name' => 'UserSeeder'], + ['class' => FormulaSeeder::class, 'name' => 'FormulaSeeder'], + ]; + + $totalSeeders = count($seeders); + $completedSeeders = 0; + $failedSeeders = 0; + + foreach ($seeders as $seederConfig) { + $seederClass = $seederConfig['class']; + $name = $seederConfig['name']; + + $this->info("📋 Seeding {$name}..."); + + try { + $this->call($seederClass); + + $this->info("✅ {$name} seeded successfully!"); + $completedSeeders++; + } catch (\Exception $e) { + $this->error("❌ {$name} seeding failed: {$e->getMessage()}"); + $failedSeeders++; + + if (! $this->confirm("Continue with remaining seeders despite {$name} failure?")) { + $this->warn('Seeding stopped by user.'); + break; + } + } + + $this->newLine(); + } + + $this->info('🌱 Seeder process completed!'); + $this->info('📊 Seeder Summary:'); + $this->info(" ✅ Completed: {$completedSeeders}/{$totalSeeders} seeders"); + + if ($failedSeeders > 0) { + $this->warn(" ❌ Failed: {$failedSeeders} seeders"); + } + + if ($completedSeeders === $totalSeeders) { + $this->info('🎊 All seeders completed successfully!'); + } else { + $this->warn('⚠️ Some seeders failed. Please check the output above for details.'); + } + } +} diff --git a/app/Console/Commands/Migrations/MigrateBottlesFromOldData.php b/app/Console/Commands/Migrations/MigrateBottlesFromOldData.php new file mode 100644 index 0000000..ed61e8c --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateBottlesFromOldData.php @@ -0,0 +1,158 @@ +option('recreate')) { + if (! $this->confirm('Are you sure you want to DELETE ALL EXISTING BOTTLES and recreate them? This action cannot be undone.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->warn('Deleting all existing bottles...'); + Bottle::query()->delete(); // This will cascade delete outlet relations + $this->info('All bottles deleted.'); + } elseif (! $this->option('force') && ! $this->confirm('Are you sure you want to migrate bottles data? This will add new bottles to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting bottles migration...'); + + try { + $oldBottles = DB::connection('old_database')->table('bottles')->whereNull('deleted_at')->get(); + + $this->info("Found {$oldBottles->count()} bottles to migrate."); + $bar = $this->output->createProgressBar($oldBottles->count()); + + $migrated = 0; + $skipped = 0; + + foreach ($oldBottles as $oldBottle) { + $existingBottle = Bottle::where('name', $oldBottle->name)->first(); + + if (! $this->option('recreate') && $existingBottle) { + $this->warn("Bottle '{$oldBottle->name}' already exists, skipping..."); + $skipped++; + $bar->advance(); + + continue; + } + + // Extract size from name if possible (e.g., "Botol 50ml" -> 50) + $size = $this->extractSizeFromName($oldBottle->name); + + $bottle = Bottle::create([ + 'name' => $oldBottle->name, + 'slug' => Str::slug($oldBottle->name), + 'size' => $size, + 'cost_price' => $oldBottle->buying_price, + 'sale_price' => $oldBottle->selling_price, + 'description' => null, + 'views' => 0, + 'created_at' => $oldBottle->created_at, + 'updated_at' => $oldBottle->updated_at, + ]); + + // Create outlet relations from bottle_stocks + $this->createBottleOutletRelations($bottle->id, $oldBottle->id); + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Bottles migration completed!'); + $this->info("📊 Migrated: {$migrated} bottles"); + $this->info("⏭️ Skipped: {$skipped} bottles (already exist)"); + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } + + private function extractSizeFromName(string $name): int + { + // Try to extract size from name (e.g., "Botol 50ml" -> 50) + preg_match('/(\d+)/', $name, $matches); + + return $matches ? (int) $matches[0] : 100; // Default to 100ml if not found + } + + private function createBottleOutletRelations(int $newBottleId, int $oldBottleId): void + { + try { + // Get bottle stocks from old database + $bottleStocks = DB::connection('old_database') + ->table('bottle_stocks') + ->where('bottle_id', $oldBottleId) + ->get(); + + foreach ($bottleStocks as $stock) { + // Map old store_id to new outlet_id + $outletMapping = $this->getOutletMapping(); + $outletId = $outletMapping[$stock->store_id] ?? null; + + if ($outletId) { + // Create many-to-many relation with stock amount + DB::table('bottle_outlet')->insert([ + 'bottle_id' => $newBottleId, + 'outlet_id' => $outletId, + 'stock' => $stock->amount, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + } catch (\Exception $e) { + $this->warn("Could not create outlet relations for bottle ID {$newBottleId}: {$e->getMessage()}"); + } + } + + private function getOutletMapping(): array + { + // Map old store_id to new outlet_id + // This assumes outlets have been migrated first + $outlets = Outlet::select('id')->orderBy('id')->get(); + + $mapping = []; + foreach ($outlets as $index => $outlet) { + $mapping[$index + 1] = $outlet->id; // Old store_id starts from 1 + } + + return $mapping; + } +} diff --git a/app/Console/Commands/Migrations/MigrateCategoriesFromOldData.php b/app/Console/Commands/Migrations/MigrateCategoriesFromOldData.php new file mode 100644 index 0000000..ae5f4db --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateCategoriesFromOldData.php @@ -0,0 +1,90 @@ +option('force') && ! $this->confirm('Are you sure you want to migrate categories data? This will add new categories to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting categories migration...'); + + try { + $oldCategories = DB::connection('old_database')->table('categories')->whereNull('deleted_at')->get(); + + $this->info("Found {$oldCategories->count()} categories to migrate."); + $bar = $this->output->createProgressBar($oldCategories->count()); + + $migrated = 0; + $skipped = 0; + $sortOrder = 1; + + foreach ($oldCategories as $oldCategory) { + $existingCategory = Category::where('name', $oldCategory->name)->first(); + + if ($existingCategory) { + $this->warn("Category '{$oldCategory->name}' already exists, skipping..."); + $skipped++; + $bar->advance(); + + continue; + } + + Category::create([ + 'name' => $oldCategory->name, + 'slug' => Str::slug($oldCategory->name), + 'description' => null, + 'type' => CategoryType::PERFUME, // Default to perfume type + 'sort_order' => $sortOrder++, + 'created_at' => $oldCategory->created_at, + 'updated_at' => $oldCategory->updated_at, + ]); + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Categories migration completed!'); + $this->info("📊 Migrated: {$migrated} categories"); + $this->info("⏭️ Skipped: {$skipped} categories (already exist)"); + + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } +} diff --git a/app/Console/Commands/Migrations/MigrateEmployeesFromOldData.php b/app/Console/Commands/Migrations/MigrateEmployeesFromOldData.php new file mode 100644 index 0000000..12959dc --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateEmployeesFromOldData.php @@ -0,0 +1,113 @@ +option('force') && ! $this->confirm('Are you sure you want to migrate employees data? This will add new employees to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting employees migration...'); + + try { + $oldBiographical = DB::connection('old_database')->table('biographical')->whereNull('deleted_at')->get(); + + $this->info("Found {$oldBiographical->count()} employees to migrate."); + $bar = $this->output->createProgressBar($oldBiographical->count()); + + $migrated = 0; + $skipped = 0; + + foreach ($oldBiographical as $oldBio) { + $user = User::find($oldBio->user_id); + + if (! $user) { + $this->warn("User ID {$oldBio->user_id} not found, skipping employee {$oldBio->name}..."); + $skipped++; + $bar->advance(); + + continue; + } + + $existingEmployee = Employee::where('user_id', $oldBio->user_id)->first(); + if ($existingEmployee) { + $this->warn("Employee for user ID {$oldBio->user_id} already exists, skipping..."); + $skipped++; + $bar->advance(); + + continue; + } + + // Get user data for additional info + $oldUser = DB::connection('old_database')->table('users')->where('id', $oldBio->user_id)->first(); + + Employee::create([ + 'user_id' => $oldBio->user_id, + 'full_name' => $oldBio->name, + 'code' => $this->generateEmployeeCode($oldBio->user_id), + 'phone_number' => $oldBio->phone_number, + 'base_salary' => $oldUser ? $oldUser->salary : 0, + 'gender' => $oldBio->gender == '1' ? Gender::MALE : Gender::FEMALE, + 'address' => $oldBio->pob, // Using place of birth as address + 'birthdate' => $oldBio->dob, + 'hire_date' => $oldUser ? $oldUser->entry_date : now()->toDateString(), + 'resign_date' => $oldUser && $oldUser->out_date ? $oldUser->out_date : null, + 'status' => EmploymentStatus::PERMANENT, + 'created_at' => $oldBio->created_at, + 'updated_at' => $oldBio->updated_at, + ]); + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Employees migration completed!'); + $this->info("📊 Migrated: {$migrated} employees"); + $this->info("⏭️ Skipped: {$skipped} employees (user not found or already exist)"); + + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } + + private function generateEmployeeCode(int $userId): string + { + return 'EMP'.str_pad($userId, 4, '0', STR_PAD_LEFT); + } +} diff --git a/app/Console/Commands/Migrations/MigrateOutletsFromOldData.php b/app/Console/Commands/Migrations/MigrateOutletsFromOldData.php new file mode 100644 index 0000000..958eb1c --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateOutletsFromOldData.php @@ -0,0 +1,181 @@ +option('recreate')) { + if (! $this->confirm('Are you sure you want to DELETE ALL EXISTING OUTLETS and recreate them? This action cannot be undone.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->warn('Deleting all existing outlets...'); + Outlet::query()->delete(); // This will cascade delete opening hours and facilities + $this->info('All outlets deleted.'); + } elseif (! $this->option('force') && ! $this->confirm('Are you sure you want to migrate outlets data? This will add new outlets to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting outlets migration...'); + + try { + $oldStores = DB::connection('old_database')->table('stores')->whereNull('deleted_at')->get(); + + $this->info("Found {$oldStores->count()} outlets to migrate."); + $bar = $this->output->createProgressBar($oldStores->count()); + + $migrated = 0; + $skipped = 0; + + foreach ($oldStores as $oldStore) { + if (! $this->option('recreate')) { + $existingOutlet = Outlet::where('name', $oldStore->name)->first(); + + if ($existingOutlet) { + // Outlet exists, but ensure opening hours and facilities exist + $this->ensureOpeningHoursExist($existingOutlet->id); + $this->ensureFacilitiesExist($existingOutlet->id); + $this->warn("Outlet '{$oldStore->name}' already exists, ensured opening hours and facilities."); + $skipped++; + $bar->advance(); + + continue; + } + } + + $outlet = Outlet::create([ + 'name' => $oldStore->name, + 'slug' => Str::slug($oldStore->name), + 'phone_number' => '0838 1693 1711', + 'address' => $oldStore->address, + 'landmark' => $oldStore->benchmark, + 'maps_url' => null, + 'status' => $oldStore->closed ? OutletStatus::TERMINATED : OutletStatus::OPERATIONAL, + 'opened_date' => $oldStore->opened, + 'closed_date' => $oldStore->closed, + 'created_at' => $oldStore->created_at, + 'updated_at' => $oldStore->updated_at, + ]); + + // Create opening hours for this outlet (9am-9pm) + $this->createOpeningHours($outlet->id); + + // Create facilities for this outlet + $this->createFacilities($outlet->id); + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Outlets migration completed!'); + $this->info("📊 Migrated: {$migrated} outlets"); + $this->info("⏭️ Skipped: {$skipped} outlets (already exist)"); + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } + + private function ensureOpeningHoursExist(int $outletId): void + { + $existingHours = OpeningHour::where('outlet_id', $outletId)->count(); + + if ($existingHours < 7) { // Should have 7 days + // Delete existing and recreate + OpeningHour::where('outlet_id', $outletId)->delete(); + $this->createOpeningHours($outletId); + $this->info("Recreated opening hours for outlet ID: {$outletId}"); + } + } + + private function ensureFacilitiesExist(int $outletId): void + { + $existingFacilities = Facility::where('outlet_id', $outletId)->count(); + + if ($existingFacilities < 4) { // Should have 4 facilities + // Delete existing and recreate + Facility::where('outlet_id', $outletId)->delete(); + $this->createFacilities($outletId); + $this->info("Recreated facilities for outlet ID: {$outletId}"); + } + } + + private function createOpeningHours(int $outletId): void + { + // Create opening hours from 9am to 9pm for all days + $daysOfWeek = [ + Day::MONDAY, + Day::TUESDAY, + Day::WEDNESDAY, + Day::THURSDAY, + Day::FRIDAY, + Day::SATURDAY, + Day::SUNDAY, + ]; + + foreach ($daysOfWeek as $day) { + OpeningHour::create([ + 'outlet_id' => $outletId, + 'day' => $day, + 'open_time' => '09:00:00', + 'close_time' => '21:00:00', + ]); + } + } + + private function createFacilities(int $outletId): void + { + // Facilities: parkir, sofa, air minum, permen + $facilities = [ + 'Parkir', + 'Sofa', + 'Air Minum', + 'Permen', + ]; + + foreach ($facilities as $facilityName) { + Facility::create([ + 'outlet_id' => $outletId, + 'name' => $facilityName, + ]); + } + } +} diff --git a/app/Console/Commands/Migrations/MigratePerfumesFromOldData.php b/app/Console/Commands/Migrations/MigratePerfumesFromOldData.php new file mode 100644 index 0000000..117486c --- /dev/null +++ b/app/Console/Commands/Migrations/MigratePerfumesFromOldData.php @@ -0,0 +1,166 @@ +option('recreate')) { + if (! $this->confirm('Are you sure you want to DELETE ALL EXISTING PERFUMES and recreate them? This action cannot be undone.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->warn('Deleting all existing perfumes...'); + Perfume::query()->delete(); // This will cascade delete outlet relations + $this->info('All perfumes deleted.'); + } elseif (! $this->option('force') && ! $this->confirm('Are you sure you want to migrate perfumes data? This will add new perfumes to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting perfumes migration...'); + + try { + $oldFragrances = DB::connection('old_database')->table('fragrances')->whereNull('deleted_at')->get(); + + $this->info("Found {$oldFragrances->count()} perfumes to migrate."); + $bar = $this->output->createProgressBar($oldFragrances->count()); + + $migrated = 0; + $skipped = 0; + + foreach ($oldFragrances as $oldFragrance) { + $existingPerfume = Perfume::where('name', $oldFragrance->name)->first(); + + if (! $this->option('recreate') && $existingPerfume) { + $this->warn("Perfume '{$oldFragrance->name}' already exists, skipping..."); + $skipped++; + $bar->advance(); + + continue; + } + + $perfume = Perfume::create([ + 'brand_id' => $oldFragrance->brand_id, + 'name' => $oldFragrance->name, + 'slug' => Str::slug($oldFragrance->name), + 'sku' => $this->generateSKU('PERF', $oldFragrance->id), + 'concentration' => Concentration::EXTRAIT_DE_PERFUME, // Default concentration + 'cost_price' => $oldFragrance->buying_price, + 'sale_price' => $oldFragrance->selling_price, + 'base_notes' => null, + 'middle_notes' => null, + 'top_notes' => null, + 'description' => null, + 'views' => 0, + 'created_at' => $oldFragrance->created_at, + 'updated_at' => $oldFragrance->updated_at, + ]); + + // Attach category if exists + if (isset($oldFragrance->categories_id) && $oldFragrance->categories_id) { + $perfume->categories()->attach($oldFragrance->categories_id); + } + + // Create outlet relations from fragrance_stocks + $this->createPerfumeOutletRelations($perfume->id, $oldFragrance->id); + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Perfumes migration completed!'); + $this->info("📊 Migrated: {$migrated} perfumes"); + $this->info("⏭️ Skipped: {$skipped} perfumes (already exist)"); + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } + + private function createPerfumeOutletRelations(int $newPerfumeId, int $oldFragranceId): void + { + try { + // Get fragrance stocks from old database + $fragranceStocks = DB::connection('old_database') + ->table('fragrance_stocks') + ->where('fragrance_id', $oldFragranceId) + ->get(); + + foreach ($fragranceStocks as $stock) { + // Map old store_id to new outlet_id + $outletMapping = $this->getOutletMapping(); + $outletId = $outletMapping[$stock->store_id] ?? null; + + if ($outletId) { + // Create many-to-many relation with stock amount + DB::table('outlet_perfume')->insert([ + 'outlet_id' => $outletId, + 'perfume_id' => $newPerfumeId, + 'stock' => $stock->amount, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + } catch (\Exception $e) { + $this->warn("Could not create outlet relations for perfume ID {$newPerfumeId}: {$e->getMessage()}"); + } + } + + private function getOutletMapping(): array + { + // Map old store_id to new outlet_id + // This assumes outlets have been migrated first + $outlets = Outlet::select('id')->orderBy('id')->get(); + + $mapping = []; + foreach ($outlets as $index => $outlet) { + $mapping[$index + 1] = $outlet->id; // Old store_id starts from 1 + } + + return $mapping; + } + + private function generateSKU(string $prefix, int $id): string + { + // Use timestamp to ensure uniqueness when recreating + $timestamp = now()->format('His'); + + return $prefix.str_pad($id, 3, '0', STR_PAD_LEFT).$timestamp; + } +} diff --git a/app/Console/Commands/Migrations/MigrateProductsFromOldData.php b/app/Console/Commands/Migrations/MigrateProductsFromOldData.php new file mode 100644 index 0000000..f15a211 --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateProductsFromOldData.php @@ -0,0 +1,155 @@ +option('recreate')) { + if (! $this->confirm('Are you sure you want to DELETE ALL EXISTING PRODUCTS and recreate them? This action cannot be undone.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->warn('Deleting all existing products...'); + Product::query()->delete(); // This will cascade delete outlet relations + $this->info('All products deleted.'); + } elseif (! $this->option('force') && ! $this->confirm('Are you sure you want to migrate products data? This will add new products to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting products migration...'); + + try { + $oldProducts = DB::connection('old_database')->table('products')->whereNull('deleted_at')->get(); + + $this->info("Found {$oldProducts->count()} products to migrate."); + $bar = $this->output->createProgressBar($oldProducts->count()); + + $migrated = 0; + $skipped = 0; + + foreach ($oldProducts as $oldProduct) { + $existingProduct = Product::where('name', $oldProduct->name)->first(); + + if (! $this->option('recreate') && $existingProduct) { + $this->warn("Product '{$oldProduct->name}' already exists, skipping..."); + $skipped++; + $bar->advance(); + + continue; + } + + $product = Product::create([ + 'name' => $oldProduct->name, + 'slug' => Str::slug($oldProduct->name), + 'sku' => $this->generateSKU('PROD', $oldProduct->id), + 'cost_price' => $oldProduct->buying_price, + 'sale_price' => $oldProduct->selling_price, + 'description' => null, + 'views' => 0, + 'created_at' => $oldProduct->created_at, + 'updated_at' => $oldProduct->updated_at, + ]); + + // Create outlet relations from product_stocks + $this->createProductOutletRelations($product->id, $oldProduct->id); + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Products migration completed!'); + $this->info("📊 Migrated: {$migrated} products"); + $this->info("⏭️ Skipped: {$skipped} products (already exist)"); + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } + + private function createProductOutletRelations(int $newProductId, int $oldProductId): void + { + try { + // Get product stocks from old database + $productStocks = DB::connection('old_database') + ->table('product_stocks') + ->where('product_id', $oldProductId) + ->get(); + + foreach ($productStocks as $stock) { + // Map old store_id to new outlet_id + $outletMapping = $this->getOutletMapping(); + $outletId = $outletMapping[$stock->store_id] ?? null; + + if ($outletId) { + // Create many-to-many relation with stock amount + DB::table('outlet_product')->insert([ + 'outlet_id' => $outletId, + 'product_id' => $newProductId, + 'stock' => $stock->amount, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + } catch (\Exception $e) { + $this->warn("Could not create outlet relations for product ID {$newProductId}: {$e->getMessage()}"); + } + } + + private function getOutletMapping(): array + { + // Map old store_id to new outlet_id + // This assumes outlets have been migrated first + $outlets = Outlet::select('id')->orderBy('id')->get(); + + $mapping = []; + foreach ($outlets as $index => $outlet) { + $mapping[$index + 1] = $outlet->id; // Old store_id starts from 1 + } + + return $mapping; + } + + private function generateSKU(string $prefix, int $id): string + { + // Use timestamp to ensure uniqueness when recreating + $timestamp = now()->format('His'); + + return $prefix.str_pad($id, 3, '0', STR_PAD_LEFT).$timestamp; + } +} diff --git a/app/Console/Commands/Migrations/MigrateUsersFromOldData.php b/app/Console/Commands/Migrations/MigrateUsersFromOldData.php new file mode 100644 index 0000000..8a08247 --- /dev/null +++ b/app/Console/Commands/Migrations/MigrateUsersFromOldData.php @@ -0,0 +1,170 @@ +option('force') && ! $this->confirm('Are you sure you want to migrate users data? This will add new users to your database.')) { + $this->info('Migration cancelled.'); + + return; + } + + $this->info('Starting users migration...'); + + // Ensure roles exist + $this->ensureRolesExist(); + + try { + // Filter users yang tidak memiliki end_date (masih aktif) + $oldUsers = DB::connection('old_database') + ->table('users') + ->whereNull('out_date') + ->whereNull('deleted_at') + ->get(); + + $this->info("Found {$oldUsers->count()} active users to migrate."); + $bar = $this->output->createProgressBar($oldUsers->count()); + + $migrated = 0; + $skipped = 0; + + foreach ($oldUsers as $oldUser) { + $existingUser = User::where('email', $oldUser->email)->first(); + + if ($existingUser) { + $this->warn("User with email {$oldUser->email} already exists, skipping..."); + $skipped++; + $bar->advance(); + + continue; + } + + $user = User::create([ + 'email' => $oldUser->email, + 'username' => $this->generateUsername($oldUser->email), + 'password' => Hash::make(config('myconfig.password_default')), + 'status' => UserStatus::ACTIVE, + 'email_verified_at' => now(), + 'created_at' => $oldUser->created_at, + 'updated_at' => $oldUser->updated_at, + ]); + + // Assign role based on role_id + $roleId = $this->mapRoleId($oldUser->role_id); + if ($roleId) { + $user->assignRole($roleId); + } + + // Create outlet relation if store_id exists + if ($oldUser->store_id) { + // Map old store_id to new outlet_id + $outletMapping = $this->getOutletMapping(); + $outletId = $outletMapping[$oldUser->store_id] ?? null; + + if ($outletId) { + // Create many-to-many relation + DB::table('outlet_user')->insert([ + 'outlet_id' => $outletId, + 'user_id' => $user->id, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + } + + $migrated++; + $bar->advance(); + } + + $bar->finish(); + $this->newLine(2); + + $this->info('✅ Users migration completed!'); + $this->info("📊 Migrated: {$migrated} users"); + $this->info("⏭️ Skipped: {$skipped} users (already exist)"); + } catch (\Exception $e) { + $this->error("❌ Migration failed: {$e->getMessage()}"); + + return 1; + } + + return 0; + } + + private function generateUsername(string $email): string + { + $username = explode('@', $email)[0]; + $baseUsername = $username; + $counter = 1; + + while (User::where('username', $username)->exists()) { + $username = $baseUsername.$counter; + $counter++; + } + + return $username; + } + + private function mapRoleId(int $oldRoleId): ?string + { + return match ($oldRoleId) { + 1 => 'Owner', + 2 => 'Leader', + 3 => 'Admin', + default => null, + }; + } + + private function ensureRolesExist(): void + { + $roles = ['Owner', 'Leader', 'Admin']; + + foreach ($roles as $roleName) { + Role::firstOrCreate(['name' => $roleName]); + } + + $this->info('Roles verified/created successfully.'); + } + + private function getOutletMapping(): array + { + // Map old store_id to new outlet_id + // This assumes outlets have been migrated first + $outlets = Outlet::select('id')->orderBy('id')->get(); + + $mapping = []; + foreach ($outlets as $index => $outlet) { + $mapping[$index + 1] = $outlet->id; // Old store_id starts from 1 + } + + return $mapping; + } +} diff --git a/config/database.php b/config/database.php index 53dcae0..85b4c9c 100644 --- a/config/database.php +++ b/config/database.php @@ -63,6 +63,26 @@ ]) : [], ], + 'old_database' => [ + 'driver' => 'mysql', + 'url' => env('SECOND_DB_URL'), + 'host' => env('SECOND_DB_HOST', '127.0.0.1'), + 'port' => env('SECOND_DB_PORT', '3306'), + 'database' => env('SECOND_DB_DATABASE', 'laravel'), + 'username' => env('SECOND_DB_USERNAME', 'root'), + 'password' => env('SECOND_DB_PASSWORD', ''), + 'unix_socket' => env('SECOND_DB_SOCKET', ''), + 'charset' => env('SECOND_DB_CHARSET', 'utf8mb4'), + 'collation' => env('SECOND_DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + 'options' => extension_loaded('pdo_mysql') ? array_filter([ + PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), + ]) : [], + ], + 'mariadb' => [ 'driver' => 'mariadb', 'url' => env('DB_URL'), diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 8d0bf69..4453185 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -14,6 +14,9 @@ public function run(): void WarehouseSeeder::class, TierSeeder::class, ]); + + $this->command->call('migrate:all', ['--force' => true, '--skip-confirm' => true]); + $this->command->newLine(); } elseif (app()->environment(['staging'])) { $this->call([ RolePermissionSeeder::class,