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)
217 lines
7.1 KiB
PHP
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;
|
|
}
|
|
}
|