Files
hub/app/Console/Commands/MigrateFlagsToSuites.php
kelly 3905f86d6a feat: V1 Release - Complete platform with all premium modules
Major Features:
- CRM Lite: Pipeline, tasks, accounts, calendar, inbox
- AI Copilot: Multi-provider support, brand voice, content rules
- Marketing: Campaigns, templates, channels, broadcasts
- Intelligence: Buyer analytics, market intelligence dashboard
- Orchestrator: Sales & marketing automation with AI
- Compliance: License tracking (minimal shell)
- Conversations: Buyer-seller messaging with email/SMS routing

Infrastructure:
- Suites & Plans system for feature gating
- 60+ new migrations
- Module middleware for access control
- Database seeders for production sync
- Enhanced product management (varieties, inventory modes)

Documentation:
- V1 scope, launch checklist, QA scripts
- Module current state audit
- Feature matrix (standard vs premium)
2025-12-01 09:48:40 -07:00

217 lines
7.1 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Console\Commands;
use App\Models\Business;
use App\Models\Suite;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
/**
* Migrates the legacy has_* flags on businesses to the new suites pivot table.
*
* This command maps the old feature flags (has_analytics, has_manufacturing, etc.)
* to suite assignments in the business_suite pivot table.
*/
class MigrateFlagsToSuites extends Command
{
protected $signature = 'migrate:flags-to-suites
{--dry-run : Run without making changes}
{--force : Force migration without confirmation}';
protected $description = 'Migrate legacy has_* flags to business_suite pivot table';
/**
* Map feature flags to suite keys.
*
* Some feature flags map to specific suites, others are deprecated or
* will be handled by the new granular permissions system.
*/
private const FLAG_TO_SUITE_MAP = [
// Feature flags that map directly to suites
'has_manufacturing' => 'manufacturing',
'has_processing' => 'processing',
'has_marketing' => 'marketing',
'has_compliance' => 'compliance',
'has_inventory' => 'inventory',
'has_accounting' => 'finance', // accounting maps to finance suite
// Feature flags that are part of the Sales suite
'has_analytics' => 'sales',
'has_crm' => 'sales',
'has_assemblies' => 'inventory', // assemblies is part of inventory
'has_conversations' => 'inbox', // conversations maps to inbox suite
'has_buyer_intelligence' => 'sales',
// Legacy suite flags (already named as suites)
'has_sales_suite' => 'sales',
'has_processing_suite' => 'processing',
'has_manufacturing_suite' => 'manufacturing',
'has_delivery_suite' => 'distribution',
'has_management_suite' => 'management',
'has_enterprise_suite' => 'enterprise',
];
public function handle(): int
{
$isDryRun = $this->option('dry-run');
$force = $this->option('force');
$this->info('Starting has_* flags to suites migration...');
if ($isDryRun) {
$this->warn('DRY RUN MODE - No changes will be made');
}
if (! $force && ! $isDryRun) {
if (! $this->confirm('This will migrate has_* flags to the business_suite pivot table. Continue?')) {
$this->info('Migration cancelled.');
return self::SUCCESS;
}
}
// Ensure suites exist
$suites = Suite::all()->keyBy('key');
if ($suites->isEmpty()) {
$this->error('No suites found. Please run: php artisan db:seed --class=SuitesSeeder');
return self::FAILURE;
}
$this->info('Found '.$suites->count().' suites in database');
// Get all businesses with any has_* flags enabled
$businesses = Business::query()
->where(function ($query) {
foreach (array_keys(self::FLAG_TO_SUITE_MAP) as $flag) {
$query->orWhere($flag, true);
}
})
->get();
$this->info("Found {$businesses->count()} businesses with enabled flags");
$this->newLine();
$stats = [
'total_businesses' => 0,
'total_suite_assignments' => 0,
'skipped_existing' => 0,
'errors' => 0,
];
$progressBar = $this->output->createProgressBar($businesses->count());
$progressBar->start();
foreach ($businesses as $business) {
$stats['total_businesses']++;
try {
$suitesToAssign = $this->determineSuitesForBusiness($business, $suites);
if (empty($suitesToAssign)) {
$progressBar->advance();
continue;
}
foreach ($suitesToAssign as $suiteKey) {
$suite = $suites->get($suiteKey);
if (! $suite) {
$this->newLine();
$this->warn(" Suite '{$suiteKey}' not found for business {$business->name}");
continue;
}
// Check if already assigned
$existingAssignment = DB::table('business_suite')
->where('business_id', $business->id)
->where('suite_id', $suite->id)
->exists();
if ($existingAssignment) {
$stats['skipped_existing']++;
continue;
}
if (! $isDryRun) {
DB::table('business_suite')->insert([
'business_id' => $business->id,
'suite_id' => $suite->id,
'created_at' => now(),
'updated_at' => now(),
]);
}
$stats['total_suite_assignments']++;
}
} catch (\Exception $e) {
$stats['errors']++;
$this->newLine();
$this->error(" Error processing {$business->name}: {$e->getMessage()}");
}
$progressBar->advance();
}
$progressBar->finish();
$this->newLine(2);
// Display stats
$this->table(
['Metric', 'Count'],
[
['Total Businesses Processed', $stats['total_businesses']],
['Suite Assignments Created', $isDryRun ? "{$stats['total_suite_assignments']} (would create)" : $stats['total_suite_assignments']],
['Skipped (Already Assigned)', $stats['skipped_existing']],
['Errors', $stats['errors']],
]
);
$this->info('Migration completed!');
if (! $isDryRun) {
$this->newLine();
$this->info('Note: The has_* flags remain on the businesses for backwards compatibility.');
$this->info('They can be deprecated once all code uses the suites system.');
}
return self::SUCCESS;
}
/**
* Determine which suites a business should have based on its flags.
*/
private function determineSuitesForBusiness(Business $business, $suites): array
{
$assignedSuites = [];
foreach (self::FLAG_TO_SUITE_MAP as $flag => $suiteKey) {
// Check if the business has this flag enabled
if ($business->getAttribute($flag)) {
// Don't duplicate suite assignments
if (! in_array($suiteKey, $assignedSuites)) {
$assignedSuites[] = $suiteKey;
}
}
}
// Enterprise suite gets all suites
if (in_array('enterprise', $assignedSuites)) {
// Add all non-internal suites
foreach ($suites as $suite) {
if (! $suite->is_internal && ! in_array($suite->key, $assignedSuites)) {
$assignedSuites[] = $suite->key;
}
}
}
return $assignedSuites;
}
}