164 lines
7.4 KiB
PHP
164 lines
7.4 KiB
PHP
<?php
|
|
/**
|
|
* Phase 3 Migration: Mobile App Support
|
|
* Run: php scripts/migrate_phase3_mobile.php
|
|
*
|
|
* Adds tables and columns required for:
|
|
* - Mobile OTP Authentication
|
|
* - Device Management
|
|
* - Batch Invoice Upload from Scanner
|
|
* - Processing Queue for AI extraction
|
|
* - Notifications System
|
|
*/
|
|
|
|
require_once __DIR__ . '/../app/bootstrap/init.php';
|
|
|
|
use App\Core\Database;
|
|
|
|
$db = Database::getInstance();
|
|
|
|
echo "═══════════════════════════════════════════\n";
|
|
echo " مُصادَق — Phase 3 Migration\n";
|
|
echo " Mobile App + Batch Processing Support\n";
|
|
echo "═══════════════════════════════════════════\n\n";
|
|
|
|
// --- Fetch Parent Collation dynamically ---
|
|
$stmt = $db->query("SHOW FULL COLUMNS FROM users WHERE Field = 'id'");
|
|
$userCol = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
$charsetCollation = "";
|
|
if ($userCol && !empty($userCol['Collation'])) {
|
|
$collation = $userCol['Collation'];
|
|
list($charset) = explode('_', $collation);
|
|
$charsetCollation = "CHARACTER SET {$charset} COLLATE {$collation}";
|
|
}
|
|
|
|
$migrations = [
|
|
|
|
// ─── 1. User Device Management ─────────────────────────
|
|
'create_user_devices' => "
|
|
CREATE TABLE IF NOT EXISTS user_devices (
|
|
id CHAR(36) {$charsetCollation} PRIMARY KEY DEFAULT (UUID()),
|
|
user_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
device_fingerprint VARCHAR(64) NOT NULL,
|
|
device_name VARCHAR(100) NULL,
|
|
platform ENUM('android','ios','web') NOT NULL DEFAULT 'android',
|
|
app_version VARCHAR(20) NULL,
|
|
push_token TEXT NULL,
|
|
device_secret VARCHAR(128) NULL,
|
|
is_trusted BOOLEAN DEFAULT FALSE,
|
|
last_seen_at DATETIME NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
UNIQUE KEY uq_user_device (user_id, device_fingerprint),
|
|
INDEX idx_device_fingerprint (device_fingerprint)
|
|
)
|
|
",
|
|
|
|
// ─── 2. Users table: Add phone + mobile fields ─────────
|
|
'add_users_phone' => "ALTER TABLE users ADD COLUMN phone VARCHAR(20) NULL AFTER email",
|
|
'add_users_phone_hash' => "ALTER TABLE users ADD COLUMN phone_hash VARCHAR(64) NULL AFTER phone",
|
|
'add_users_pin_hash' => "ALTER TABLE users ADD COLUMN pin_hash VARCHAR(255) NULL AFTER password_hash",
|
|
'add_users_biometric' => "ALTER TABLE users ADD COLUMN biometric_enabled BOOLEAN DEFAULT FALSE AFTER pin_hash",
|
|
'add_users_phone_index' => "CREATE INDEX idx_phone_hash ON users(phone_hash)",
|
|
|
|
// ─── 3. Invoice Batches (Mobile Scanner) ───────────────
|
|
'create_invoice_batches' => "
|
|
CREATE TABLE IF NOT EXISTS invoice_batches (
|
|
id CHAR(36) {$charsetCollation} PRIMARY KEY,
|
|
tenant_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
company_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
uploaded_by CHAR(36) {$charsetCollation} NULL,
|
|
total_images INT NOT NULL DEFAULT 0,
|
|
processed_images INT NOT NULL DEFAULT 0,
|
|
failed_images INT NOT NULL DEFAULT 0,
|
|
status ENUM('uploading','processing','done','partial_fail','failed') DEFAULT 'uploading',
|
|
source ENUM('mobile_scan','web_upload','whatsapp') DEFAULT 'mobile_scan',
|
|
pdf_path VARCHAR(500) NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
completed_at DATETIME NULL,
|
|
INDEX idx_tenant_status (tenant_id, status),
|
|
INDEX idx_company (company_id),
|
|
INDEX idx_uploaded_by (uploaded_by),
|
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL
|
|
)
|
|
",
|
|
|
|
// ─── 4. Invoice Processing Queue ───────────────────────
|
|
'create_processing_queue' => "
|
|
CREATE TABLE IF NOT EXISTS invoice_processing_queue (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
batch_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
invoice_id CHAR(36) {$charsetCollation} NULL,
|
|
tenant_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
company_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
image_path VARCHAR(500) NOT NULL,
|
|
image_order INT NOT NULL DEFAULT 0,
|
|
status ENUM('pending','processing','done','failed') DEFAULT 'pending',
|
|
attempts INT DEFAULT 0,
|
|
max_attempts INT DEFAULT 3,
|
|
error_message TEXT NULL,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
processed_at DATETIME NULL,
|
|
INDEX idx_status_tenant (status, tenant_id),
|
|
INDEX idx_batch (batch_id),
|
|
INDEX idx_pending (status, attempts)
|
|
)
|
|
",
|
|
|
|
// ─── 5. Add batch_id to invoices table ─────────────────
|
|
'add_invoices_batch_id' => "ALTER TABLE invoices ADD COLUMN batch_id CHAR(36) NULL AFTER company_id",
|
|
'add_invoices_batch_index' => "CREATE INDEX idx_batch_id ON invoices(batch_id)",
|
|
|
|
// ─── 6. Notifications Table ────────────────────────────
|
|
'create_notifications' => "
|
|
CREATE TABLE IF NOT EXISTS notifications (
|
|
id CHAR(36) {$charsetCollation} PRIMARY KEY DEFAULT (UUID()),
|
|
tenant_id CHAR(36) {$charsetCollation} NOT NULL,
|
|
user_id CHAR(36) {$charsetCollation} NULL,
|
|
type VARCHAR(50) NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
body TEXT NULL,
|
|
data JSON NULL,
|
|
is_read BOOLEAN DEFAULT FALSE,
|
|
read_at DATETIME NULL,
|
|
push_sent BOOLEAN DEFAULT FALSE,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
INDEX idx_user_unread (user_id, is_read),
|
|
INDEX idx_tenant (tenant_id),
|
|
INDEX idx_type (type),
|
|
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
|
|
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
)
|
|
",
|
|
];
|
|
|
|
$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();
|
|
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";
|
|
echo " Migration Complete!\n";
|
|
echo " ✅ Success: {$success} | ⏭️ Skipped: {$skipped} | ❌ Failed: {$failed}\n";
|
|
echo "═══════════════════════════════════════════\n";
|