169 lines
7.2 KiB
PHP
169 lines
7.2 KiB
PHP
<?php
|
||
/**
|
||
* Phase 1 Migration: Subscriptions & Quota System
|
||
* Run: php scripts/migrate_phase1.php
|
||
*/
|
||
|
||
require_once __DIR__ . '/../app/bootstrap/init.php';
|
||
|
||
use App\Core\Database;
|
||
|
||
$db = Database::getInstance();
|
||
|
||
echo "═══════════════════════════════════════════\n";
|
||
echo " مُصادَق — Phase 1 Migration\n";
|
||
echo " Subscriptions & Quota System\n";
|
||
echo "═══════════════════════════════════════════\n\n";
|
||
|
||
$migrations = [
|
||
|
||
// 1. Add deleted_at to companies
|
||
'companies_soft_delete' => "ALTER TABLE companies ADD COLUMN deleted_at DATETIME NULL DEFAULT NULL",
|
||
|
||
// 2. Add deleted_at to users
|
||
'users_soft_delete' => "ALTER TABLE users ADD COLUMN deleted_at DATETIME NULL DEFAULT NULL",
|
||
|
||
// 3. Add email_hash to users (if missing)
|
||
'users_email_hash' => "ALTER TABLE users ADD COLUMN email_hash VARCHAR(64) NULL",
|
||
|
||
// 4. Create subscription_plans table
|
||
'subscription_plans_table' => "
|
||
CREATE TABLE IF NOT EXISTS subscription_plans (
|
||
id VARCHAR(20) PRIMARY KEY,
|
||
name_ar VARCHAR(100) NOT NULL,
|
||
name_en VARCHAR(100) NOT NULL,
|
||
max_companies INT NOT NULL DEFAULT 1,
|
||
max_invoices_month INT NOT NULL DEFAULT 30,
|
||
max_users INT NOT NULL DEFAULT 2,
|
||
price_jod DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||
ai_features BOOLEAN DEFAULT FALSE,
|
||
jofotara_enabled BOOLEAN DEFAULT FALSE,
|
||
is_active BOOLEAN DEFAULT TRUE,
|
||
sort_order INT DEFAULT 0,
|
||
features_json JSON NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||
",
|
||
|
||
// 5. Ensure subscriptions table exists with all needed columns
|
||
'subscriptions_table' => "
|
||
CREATE TABLE IF NOT EXISTS subscriptions (
|
||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||
tenant_id CHAR(36) NOT NULL UNIQUE,
|
||
plan_id VARCHAR(20) NOT NULL DEFAULT 'free',
|
||
max_companies INT NOT NULL DEFAULT 1,
|
||
max_invoices_per_month INT NOT NULL DEFAULT 15,
|
||
max_users INT NOT NULL DEFAULT 1,
|
||
price_jod DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||
invoices_used_this_month INT NOT NULL DEFAULT 0,
|
||
status ENUM('active','past_due','cancelled','trial') DEFAULT 'trial',
|
||
current_period_start DATETIME NULL,
|
||
current_period_end DATETIME NULL,
|
||
trial_ends_at DATETIME NULL,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
||
FOREIGN KEY (plan_id) REFERENCES subscription_plans(id)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||
",
|
||
|
||
// 6. Add plan_id column to subscriptions if upgrading from old schema
|
||
'subscriptions_plan_id' => "ALTER TABLE subscriptions ADD COLUMN plan_id VARCHAR(20) NOT NULL DEFAULT 'free'",
|
||
|
||
// 7. Add max_users column to subscriptions if missing
|
||
'subscriptions_max_users' => "ALTER TABLE subscriptions ADD COLUMN max_users INT NOT NULL DEFAULT 1",
|
||
|
||
// 8. Add trial_ends_at to subscriptions if missing
|
||
'subscriptions_trial' => "ALTER TABLE subscriptions ADD COLUMN trial_ends_at DATETIME NULL",
|
||
|
||
// 9. Index on subscriptions status
|
||
'subscriptions_status_idx' => "CREATE INDEX idx_sub_status ON subscriptions(status)",
|
||
];
|
||
|
||
$success = 0;
|
||
$skipped = 0;
|
||
$failed = 0;
|
||
|
||
foreach ($migrations as $name => $sql) {
|
||
try {
|
||
$db->exec($sql);
|
||
echo " ✅ {$name}\n";
|
||
$success++;
|
||
} catch (\PDOException $e) {
|
||
$msg = $e->getMessage();
|
||
// Ignore "duplicate column" (1060), "duplicate key name" (1061), or "already exists" errors
|
||
if (str_contains($msg, 'Duplicate column') || str_contains($msg, 'Duplicate key name') || str_contains($msg, 'already exists')) {
|
||
echo " ⏭️ {$name} (already exists)\n";
|
||
$skipped++;
|
||
} else {
|
||
echo " ❌ {$name}: {$msg}\n";
|
||
$failed++;
|
||
}
|
||
}
|
||
}
|
||
|
||
echo "\n───────────────────────────────────────────\n";
|
||
|
||
// Seed subscription plans
|
||
echo "\n📦 Seeding subscription plans...\n";
|
||
|
||
$plans = [
|
||
['free', 'مجانية', 'Free', 1, 15, 1, 0.00, 0, 0, 10],
|
||
['basic', 'أساسية', 'Basic', 3, 100, 3, 15.00, 1, 0, 20],
|
||
['office', 'مكتبية', 'Office', 10, 500, 10, 45.00, 1, 1, 30],
|
||
['pro', 'احترافية', 'Pro', 25, 2000, 25, 99.00, 1, 1, 40],
|
||
['enterprise', 'مؤسسية', 'Enterprise', 999, 99999, 999, 249.00, 1, 1, 50],
|
||
];
|
||
|
||
$planStmt = $db->prepare("
|
||
INSERT INTO subscription_plans (id, name_ar, name_en, max_companies, max_invoices_month, max_users, price_jod, ai_features, jofotara_enabled, sort_order)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
ON DUPLICATE KEY UPDATE
|
||
name_ar = VALUES(name_ar),
|
||
name_en = VALUES(name_en),
|
||
max_companies = VALUES(max_companies),
|
||
max_invoices_month = VALUES(max_invoices_month),
|
||
max_users = VALUES(max_users),
|
||
price_jod = VALUES(price_jod),
|
||
ai_features = VALUES(ai_features),
|
||
jofotara_enabled = VALUES(jofotara_enabled),
|
||
sort_order = VALUES(sort_order)
|
||
");
|
||
|
||
foreach ($plans as $plan) {
|
||
$planStmt->execute($plan);
|
||
echo " ✅ Plan: {$plan[0]} ({$plan[1]})\n";
|
||
}
|
||
|
||
// Auto-assign 'free' plan to any tenant without a subscription
|
||
echo "\n🔗 Auto-assigning free plan to tenants without subscriptions...\n";
|
||
|
||
$stmt = $db->query("
|
||
SELECT t.id FROM tenants t
|
||
LEFT JOIN subscriptions s ON s.tenant_id = t.id
|
||
WHERE s.id IS NULL
|
||
");
|
||
$orphanTenants = $stmt->fetchAll();
|
||
|
||
if (!empty($orphanTenants)) {
|
||
$insertSub = $db->prepare("
|
||
INSERT INTO subscriptions (tenant_id, plan_id, max_companies, max_invoices_per_month, max_users, price_jod, status, current_period_start, current_period_end, trial_ends_at)
|
||
VALUES (?, 'free', 1, 15, 1, 0.00, 'trial', NOW(), DATE_ADD(NOW(), INTERVAL 30 DAY), DATE_ADD(NOW(), INTERVAL 14 DAY))
|
||
");
|
||
foreach ($orphanTenants as $tenant) {
|
||
try {
|
||
$insertSub->execute([$tenant['id']]);
|
||
echo " ✅ Assigned free plan to tenant: {$tenant['id']}\n";
|
||
} catch (\Exception $e) {
|
||
echo " ⚠️ Tenant {$tenant['id']}: " . $e->getMessage() . "\n";
|
||
}
|
||
}
|
||
} else {
|
||
echo " ℹ️ All tenants already have subscriptions.\n";
|
||
}
|
||
|
||
echo "\n═══════════════════════════════════════════\n";
|
||
echo " Migration Complete!\n";
|
||
echo " ✅ Success: {$success} | ⏭️ Skipped: {$skipped} | ❌ Failed: {$failed}\n";
|
||
echo "═══════════════════════════════════════════\n";
|