Update: 2026-05-08 14:05:50
This commit is contained in:
@@ -259,6 +259,17 @@ foreach ($users as $user) {
|
||||
|
||||
```
|
||||
|
||||
## File: `reset_queue.sql`
|
||||
|
||||
```sql
|
||||
-- Reset failed queue items so the cron worker retries them
|
||||
UPDATE invoice_processing_queue SET status = 'pending', error_message = NULL WHERE status = 'failed';
|
||||
|
||||
-- Verify current queue state
|
||||
SELECT id, batch_id, status, error_message, created_at FROM invoice_processing_queue ORDER BY created_at;
|
||||
|
||||
```
|
||||
|
||||
## File: `schema.sql`
|
||||
|
||||
```sql
|
||||
@@ -714,6 +725,26 @@ foreach ($tables as $table) {
|
||||
|
||||
```
|
||||
|
||||
## File: `create_ai_usage_table.sql`
|
||||
|
||||
```sql
|
||||
-- AI Usage Log — Token tracking for cost analysis
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai_usage_log (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
input_tokens INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
output_tokens INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
total_tokens INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
cost_usd DECIMAL(12, 8) NOT NULL DEFAULT 0,
|
||||
cost_jod DECIMAL(12, 8) NOT NULL DEFAULT 0,
|
||||
model VARCHAR(50) NOT NULL DEFAULT 'gemini-flash-lite',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_created (created_at),
|
||||
INDEX idx_model (model)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
```
|
||||
|
||||
## File: `migrate.php`
|
||||
|
||||
```php
|
||||
@@ -892,6 +923,122 @@ try {
|
||||
|
||||
```
|
||||
|
||||
## File: `phase1_migration.sql`
|
||||
|
||||
```sql
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
-- مُصادَق — Phase 1: AI Usage Tracking + Notifications
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
|
||||
-- AI Usage Log (tracks every AI request)
|
||||
CREATE TABLE IF NOT EXISTS ai_usage_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NULL,
|
||||
company_id CHAR(36) NULL,
|
||||
action_type ENUM('invoice_extraction','voice_transcribe','voice_intent','report_generation','chatbot') NOT NULL,
|
||||
model_name VARCHAR(50) NOT NULL,
|
||||
prompt_tokens INT DEFAULT 0,
|
||||
completion_tokens INT DEFAULT 0,
|
||||
total_tokens INT DEFAULT 0,
|
||||
estimated_cost DECIMAL(10,6) DEFAULT 0,
|
||||
request_metadata JSON NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_tenant_date (tenant_id, created_at),
|
||||
INDEX idx_action (action_type),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Notifications
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NULL,
|
||||
type ENUM('invoice_processed','invoice_rejected','quota_warning','month_end','system','achievement') NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
metadata JSON NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_user_read (user_id, is_read),
|
||||
INDEX idx_tenant (tenant_id),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Referral Codes (Phase 2 prep)
|
||||
CREATE TABLE IF NOT EXISTS referral_codes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
uses_count INT DEFAULT 0,
|
||||
max_uses INT DEFAULT 50,
|
||||
reward_months INT DEFAULT 1,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- Referral Uses (Phase 2 prep)
|
||||
CREATE TABLE IF NOT EXISTS referral_uses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
code_id INT NOT NULL,
|
||||
referred_tenant_id CHAR(36) NOT NULL,
|
||||
reward_applied BOOLEAN DEFAULT FALSE,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (code_id) REFERENCES referral_codes(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- User Achievements (Phase 2 prep)
|
||||
CREATE TABLE IF NOT EXISTS user_achievements (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
achievement_code VARCHAR(50) NOT NULL,
|
||||
points INT NOT NULL DEFAULT 0,
|
||||
earned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY uq_user_achievement (user_id, achievement_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
```
|
||||
|
||||
## File: `update_pricing.sql`
|
||||
|
||||
```sql
|
||||
-- Restore original Musadaq subscription pricing
|
||||
-- Premium pricing justified by AI extraction + JoFotara + mobile app
|
||||
|
||||
UPDATE subscription_plans SET
|
||||
name_ar = 'مجانية', name_en = 'Free',
|
||||
max_companies = 1, max_invoices_month = 15, max_users = 1,
|
||||
price_jod = 0.00, jofotara_enabled = 1
|
||||
WHERE id = 'free';
|
||||
|
||||
UPDATE subscription_plans SET
|
||||
name_ar = 'أساسية', name_en = 'Basic',
|
||||
max_companies = 3, max_invoices_month = 100, max_users = 3,
|
||||
price_jod = 15.00, jofotara_enabled = 1
|
||||
WHERE id = 'basic';
|
||||
|
||||
UPDATE subscription_plans SET
|
||||
name_ar = 'مكتبية', name_en = 'Office',
|
||||
max_companies = 10, max_invoices_month = 500, max_users = 10,
|
||||
price_jod = 45.00, jofotara_enabled = 1
|
||||
WHERE id = 'office';
|
||||
|
||||
UPDATE subscription_plans SET
|
||||
name_ar = 'احترافية', name_en = 'Pro',
|
||||
max_companies = 25, max_invoices_month = 2000, max_users = 25,
|
||||
price_jod = 99.00, jofotara_enabled = 1
|
||||
WHERE id = 'pro';
|
||||
|
||||
UPDATE subscription_plans SET
|
||||
name_ar = 'مؤسسية', name_en = 'Enterprise',
|
||||
max_companies = 999, max_invoices_month = 99999, max_users = 999,
|
||||
price_jod = 249.00, jofotara_enabled = 1
|
||||
WHERE id = 'enterprise';
|
||||
|
||||
```
|
||||
|
||||
## File: `backfill_hashes.php`
|
||||
|
||||
```php
|
||||
@@ -1135,6 +1282,210 @@ echo "════════════════════════
|
||||
|
||||
```
|
||||
|
||||
## File: `deploy_production.sh`
|
||||
|
||||
```sh
|
||||
#!/bin/bash
|
||||
# ─────────────────────────────────────────────────────
|
||||
# Musadaq Production Deployment Script
|
||||
# Run this on the production server after syncing files
|
||||
# ─────────────────────────────────────────────────────
|
||||
|
||||
set -e
|
||||
|
||||
echo "═══════════════════════════════════════════════"
|
||||
echo " مُصادَق — Production Deployment Script"
|
||||
echo "═══════════════════════════════════════════════"
|
||||
|
||||
# 1. Install PHP dependencies
|
||||
echo ""
|
||||
echo "▶ Step 1: Installing Composer dependencies..."
|
||||
cd /home/musadaq/htdocs/musadaq.intaleqapp.com
|
||||
composer install --no-dev --optimize-autoloader
|
||||
|
||||
# 2. Ensure storage directories exist
|
||||
echo ""
|
||||
echo "▶ Step 2: Creating storage directories..."
|
||||
mkdir -p storage/invoices
|
||||
mkdir -p storage/logs
|
||||
mkdir -p storage/exports
|
||||
mkdir -p storage/temp
|
||||
chmod -R 775 storage/
|
||||
|
||||
# 3. Set up the Cron Job for AI Queue Worker
|
||||
echo ""
|
||||
echo "▶ Step 3: Setting up Cron Job for AI Worker..."
|
||||
echo ""
|
||||
echo " Run: crontab -e"
|
||||
echo " Add this line:"
|
||||
echo ""
|
||||
echo " * * * * * /usr/bin/php /home/musadaq/htdocs/musadaq.intaleqapp.com/app/cron/process_batches.php >> /home/musadaq/htdocs/musadaq.intaleqapp.com/storage/logs/cron.log 2>&1"
|
||||
echo ""
|
||||
echo " This runs the AI Queue Worker every minute."
|
||||
echo " The worker has its own lock file to prevent duplicates."
|
||||
echo ""
|
||||
|
||||
# 4. Verify environment variables
|
||||
echo "▶ Step 4: Checking .env configuration..."
|
||||
if [ -f .env ]; then
|
||||
echo " ✅ .env file found"
|
||||
|
||||
# Check critical keys
|
||||
grep -q "GEMINI_API_KEY" .env && echo " ✅ GEMINI_API_KEY set" || echo " ❌ GEMINI_API_KEY missing!"
|
||||
grep -q "DB_HOST" .env && echo " ✅ DB_HOST set" || echo " ❌ DB_HOST missing!"
|
||||
grep -q "ENCRYPTION_KEY" .env && echo " ✅ ENCRYPTION_KEY set" || echo " ❌ ENCRYPTION_KEY missing!"
|
||||
grep -q "JWT_SECRET" .env && echo " ✅ JWT_SECRET set" || echo " ❌ JWT_SECRET missing!"
|
||||
grep -q "FCM_SERVER_KEY\|FIREBASE" .env && echo " ✅ Firebase key set" || echo " ⚠️ Firebase key missing (push notifications won't work)"
|
||||
else
|
||||
echo " ❌ .env file not found! Copy .env.example and configure it."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════════════"
|
||||
echo " ✅ Deployment Complete!"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " 1. Add the Cron Job (shown above)"
|
||||
echo " 2. Test the API: curl https://musadaq.intaleqapp.com/api/v1/auth/login"
|
||||
echo " 3. Monitor logs: tail -f storage/logs/cron.log"
|
||||
echo "═══════════════════════════════════════════════"
|
||||
|
||||
```
|
||||
|
||||
## File: `create_referral_tables.sql`
|
||||
|
||||
```sql
|
||||
-- Referral System Tables
|
||||
|
||||
CREATE TABLE IF NOT EXISTS referral_codes (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
code VARCHAR(20) NOT NULL UNIQUE,
|
||||
is_active TINYINT(1) DEFAULT 1,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_user (user_id),
|
||||
INDEX idx_code (code),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS referrals (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
referrer_id CHAR(36) NOT NULL,
|
||||
referred_id CHAR(36) NULL,
|
||||
referral_code VARCHAR(20) NOT NULL,
|
||||
status ENUM('clicked', 'registered', 'subscribed') DEFAULT 'clicked',
|
||||
reward_claimed TINYINT(1) DEFAULT 0,
|
||||
reward_type VARCHAR(50) NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
converted_at TIMESTAMP NULL,
|
||||
INDEX idx_referrer (referrer_id),
|
||||
INDEX idx_code (referral_code),
|
||||
FOREIGN KEY (referrer_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
```
|
||||
|
||||
## File: `complete_migration.sql`
|
||||
|
||||
```sql
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
-- مُصادَق — Complete Phase 1 Migration (MySQL 8.0 Compatible)
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
|
||||
-- 1. Invoice Line Items (AI extracted data)
|
||||
CREATE TABLE IF NOT EXISTS invoice_lines (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
invoice_id CHAR(36) NOT NULL,
|
||||
line_number INT NOT NULL,
|
||||
description VARCHAR(255) NOT NULL,
|
||||
quantity DECIMAL(10,3) DEFAULT 1,
|
||||
unit_price DECIMAL(15,4) NOT NULL,
|
||||
tax_rate DECIMAL(5,2) DEFAULT 16.00,
|
||||
tax_amount DECIMAL(15,4) DEFAULT 0,
|
||||
discount DECIMAL(15,4) DEFAULT 0,
|
||||
total_amount DECIMAL(15,4) NOT NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_invoice (invoice_id),
|
||||
FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 2. JoFotara Submissions Log
|
||||
CREATE TABLE IF NOT EXISTS jofotara_submissions (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
invoice_id CHAR(36) NOT NULL,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
company_id CHAR(36) NOT NULL,
|
||||
jofotara_uuid VARCHAR(100) NULL,
|
||||
xml_content LONGTEXT NULL,
|
||||
status ENUM('accepted', 'rejected', 'pending') DEFAULT 'pending',
|
||||
qr_code_raw TEXT NULL,
|
||||
response_body JSON NULL,
|
||||
submitted_at DATETIME NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_invoice (invoice_id),
|
||||
INDEX idx_tenant (tenant_id),
|
||||
FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 3. AI Usage Log
|
||||
CREATE TABLE IF NOT EXISTS ai_usage_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NULL,
|
||||
company_id CHAR(36) NULL,
|
||||
action_type ENUM('invoice_extraction','voice_transcribe','voice_intent','report_generation','chatbot') NOT NULL,
|
||||
model_name VARCHAR(50) NOT NULL,
|
||||
prompt_tokens INT DEFAULT 0,
|
||||
completion_tokens INT DEFAULT 0,
|
||||
total_tokens INT DEFAULT 0,
|
||||
estimated_cost DECIMAL(10,6) DEFAULT 0,
|
||||
request_metadata JSON NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_tenant_date (tenant_id, created_at),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- 4. Notifications
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id CHAR(36) PRIMARY KEY DEFAULT (UUID()),
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
user_id CHAR(36) NULL,
|
||||
type ENUM('invoice_processed','invoice_rejected','quota_warning','month_end','system','achievement') NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
is_read BOOLEAN DEFAULT FALSE,
|
||||
metadata JSON NULL,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_user_read (user_id, is_read),
|
||||
FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
-- 5. Safe ALTER TABLE (MySQL 8 compatible — no IF NOT EXISTS)
|
||||
-- Run each block separately. If column already exists,
|
||||
-- MySQL will show "Duplicate column" error — just skip it.
|
||||
-- ════════════════════════════════════════════════════════════
|
||||
|
||||
-- 5a. Companies: JoFotara credentials
|
||||
-- Run these ONE BY ONE. Skip any that say "Duplicate column name"
|
||||
|
||||
ALTER TABLE companies ADD COLUMN jofotara_client_id VARCHAR(255) NULL;
|
||||
ALTER TABLE companies ADD COLUMN jofotara_secret_key VARCHAR(255) NULL;
|
||||
ALTER TABLE companies ADD COLUMN jofotara_status ENUM('active', 'inactive', 'pending') DEFAULT 'inactive';
|
||||
|
||||
-- 5b. Invoices: AI + JoFotara metadata
|
||||
-- Run these ONE BY ONE. Skip any that say "Duplicate column name"
|
||||
|
||||
ALTER TABLE invoices ADD COLUMN invoice_category ENUM('simplified', 'standard') DEFAULT 'simplified';
|
||||
ALTER TABLE invoices ADD COLUMN ubl_type_code VARCHAR(10) DEFAULT '388';
|
||||
ALTER TABLE invoices ADD COLUMN payment_method_code VARCHAR(10) DEFAULT '013';
|
||||
ALTER TABLE invoices ADD COLUMN validation_warnings JSON NULL;
|
||||
ALTER TABLE invoices ADD COLUMN ai_confidence DECIMAL(5,2) DEFAULT 0;
|
||||
ALTER TABLE invoices ADD COLUMN jofotara_uuid VARCHAR(100) NULL;
|
||||
|
||||
```
|
||||
|
||||
## File: `debug_data.php`
|
||||
|
||||
```php
|
||||
@@ -1158,3 +1509,115 @@ print_r($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
|
||||
```
|
||||
|
||||
## File: `create_test_account.php`
|
||||
|
||||
```php
|
||||
<?php
|
||||
/**
|
||||
* Create Test Account for App Reviewers
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../app/bootstrap/init.php';
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Encryption;
|
||||
|
||||
try {
|
||||
$db = Database::getInstance();
|
||||
$db->beginTransaction();
|
||||
|
||||
// 1. Generate UUIDs
|
||||
$tenantId = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
|
||||
$userId = sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff),
|
||||
mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000,
|
||||
mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff)
|
||||
);
|
||||
|
||||
// 2. Test Account Data
|
||||
$tenantName = "مكتب المراجعة التجريبي";
|
||||
$tenantEmail = "reviewer@musadaq.jo";
|
||||
|
||||
$userName = "App Reviewer";
|
||||
$userEmail = "reviewer@musadaq.jo";
|
||||
$userPassword = "Reviewer2026!";
|
||||
|
||||
// 3. Encrypt data
|
||||
$encryptedTenantName = Encryption::encrypt($tenantName);
|
||||
$encryptedTenantEmail = Encryption::encrypt($tenantEmail);
|
||||
|
||||
$encryptedUserName = Encryption::encrypt($userName);
|
||||
$encryptedUserEmail = Encryption::encrypt($userEmail);
|
||||
$emailHash = hash('sha256', strtolower($userEmail));
|
||||
$passwordHash = password_hash($userPassword, PASSWORD_DEFAULT);
|
||||
|
||||
// 4. Delete existing if any (prevent duplicates on re-run)
|
||||
$stmt = $db->prepare("DELETE FROM users WHERE email_hash = ?");
|
||||
$stmt->execute([$emailHash]);
|
||||
|
||||
// 5. Insert Tenant
|
||||
$stmt = $db->prepare("INSERT INTO tenants (id, name, email, status, created_at) VALUES (?, ?, ?, 'active', NOW())");
|
||||
$stmt->execute([
|
||||
$tenantId,
|
||||
$encryptedTenantName,
|
||||
$encryptedTenantEmail
|
||||
]);
|
||||
|
||||
// 6. Insert User (Manager)
|
||||
$stmtUser = $db->prepare("INSERT INTO users (id, tenant_id, name, email, email_hash, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?, ?, 'admin', NOW())");
|
||||
$stmtUser->execute([
|
||||
$userId,
|
||||
$tenantId,
|
||||
$encryptedUserName,
|
||||
$encryptedUserEmail,
|
||||
$emailHash,
|
||||
$passwordHash
|
||||
]);
|
||||
|
||||
// 7. Insert Gamification Profile (Optional but good for testing dashboard)
|
||||
$stmtProfile = $db->prepare("INSERT INTO user_profiles (user_id, points, current_level, rank_title) VALUES (?, 1500, 2, 'مُحاسب مبتدئ') ON DUPLICATE KEY UPDATE points=1500");
|
||||
$stmtProfile->execute([$userId]);
|
||||
|
||||
$db->commit();
|
||||
echo "✅ Test Account Created Successfully!\n";
|
||||
echo "=====================================\n";
|
||||
echo "Email: $userEmail\n";
|
||||
echo "Password: $userPassword\n";
|
||||
echo "=====================================\n";
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$db->rollBack();
|
||||
echo "❌ Error: " . $e->getMessage() . "\n";
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## File: `create_notifications_table.sql`
|
||||
|
||||
```sql
|
||||
-- Notifications Table
|
||||
CREATE TABLE IF NOT EXISTS notifications (
|
||||
id CHAR(36) PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
tenant_id CHAR(36) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
body TEXT,
|
||||
type ENUM('info', 'success', 'warning', 'error') DEFAULT 'info',
|
||||
category VARCHAR(50) DEFAULT 'general',
|
||||
entity_type VARCHAR(50) NULL,
|
||||
entity_id CHAR(36) NULL,
|
||||
is_read TINYINT(1) DEFAULT 0,
|
||||
read_at TIMESTAMP NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_user_read (user_id, is_read),
|
||||
INDEX idx_tenant (tenant_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user