Update: 2026-05-03 23:08:56

This commit is contained in:
Hamza-Ayed
2026-05-03 23:08:56 +03:00
parent 87809ac893
commit bef134ea77
6 changed files with 140 additions and 6 deletions

View File

@@ -0,0 +1,40 @@
<?php
/**
* Create Tenant Endpoint (Super Admin Only)
*/
use App\Core\Database;
use App\Core\Validator;
use App\Middleware\AuthMiddleware;
$decoded = AuthMiddleware::check();
if ($decoded['role'] !== 'super_admin') {
json_error('Unauthorized', 403);
}
$data = input();
$errors = Validator::validate($data, [
'name' => 'required',
'email' => 'required|email'
]);
if ($errors) {
json_error('Validation Failed', 422, $errors);
}
$db = Database::getInstance();
try {
$stmt = $db->prepare("INSERT INTO tenants (name, email, phone, status, created_at) VALUES (?, ?, ?, 'active', NOW())");
$stmt->execute([
$data['name'],
$data['email'],
$data['phone'] ?? null
]);
json_success(null, 'تم إنشاء المكتب بنجاح');
} catch (\Exception $e) {
json_error('حدث خطأ أثناء حفظ البيانات', 500);
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* Tenants List Endpoint (Super Admin Only)
*/
use App\Core\Database;
use App\Middleware\AuthMiddleware;
$decoded = AuthMiddleware::check();
if ($decoded['role'] !== 'super_admin') {
json_error('Unauthorized', 403);
}
$db = Database::getInstance();
$stmt = $db->query("SELECT id, name, email, phone, status, created_at FROM tenants ORDER BY created_at DESC");
$tenants = $stmt->fetchAll();
json_success($tenants);

View File

@@ -25,6 +25,8 @@ $routes = [
'v1/companies/create' => ['POST', 'companies/create.php'], 'v1/companies/create' => ['POST', 'companies/create.php'],
'v1/invoices/upload' => ['POST', 'invoices/upload.php'], 'v1/invoices/upload' => ['POST', 'invoices/upload.php'],
'v1/dashboard/stats' => ['GET', 'dashboard/stats.php'], 'v1/dashboard/stats' => ['GET', 'dashboard/stats.php'],
'v1/tenants' => ['GET', 'tenants/index.php'],
'v1/tenants/create' => ['POST', 'tenants/create.php'],
]; ];
if (isset($routes[$route])) { if (isset($routes[$route])) {

View File

@@ -30,7 +30,8 @@
</div> </div>
<nav class="flex-1 px-4 space-y-2"> <nav class="flex-1 px-4 space-y-2">
<a href="#" @click="page='dashboard'" class="block p-3 rounded hover:bg-gray-800" :class="page==='dashboard'?'bg-emerald-900/20 text-emerald-500':''">📊 لوحة التحكم</a> <a href="#" @click="page='dashboard'" class="block p-3 rounded hover:bg-gray-800" :class="page==='dashboard'?'bg-emerald-900/20 text-emerald-500':''">📊 لوحة التحكم</a>
<a href="#" @click="page='companies'" class="block p-3 rounded hover:bg-gray-800" :class="page==='companies'?'bg-emerald-900/20 text-emerald-500':''">🏢 الشركات</a> <a x-show="user?.role === 'super_admin'" href="#" @click="page='tenants'" class="block p-3 rounded hover:bg-gray-800" :class="page==='tenants'?'bg-emerald-900/20 text-emerald-500':''">🏢 المكاتب</a>
<a x-show="user?.role !== 'accountant' && user?.role !== 'employee'" href="#" @click="page='companies'" class="block p-3 rounded hover:bg-gray-800" :class="page==='companies'?'bg-emerald-900/20 text-emerald-500':''">🏢 الشركات</a>
<a x-show="user?.role === 'super_admin' || user?.role === 'admin'" href="#" @click="page='users'" class="block p-3 rounded hover:bg-gray-800" :class="page==='users'?'bg-emerald-900/20 text-emerald-500':''">👥 المستخدمون</a> <a x-show="user?.role === 'super_admin' || user?.role === 'admin'" href="#" @click="page='users'" class="block p-3 rounded hover:bg-gray-800" :class="page==='users'?'bg-emerald-900/20 text-emerald-500':''">👥 المستخدمون</a>
</nav> </nav>
<div class="p-6 border-t border-gray-800"> <div class="p-6 border-t border-gray-800">
@@ -43,6 +44,7 @@
<header class="mb-10 flex justify-between items-center"> <header class="mb-10 flex justify-between items-center">
<h2 class="text-2xl font-bold" x-text="title()"></h2> <h2 class="text-2xl font-bold" x-text="title()"></h2>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<button x-show="page==='tenants' && user?.role === 'super_admin'" @click="showAddTenantModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-2 rounded text-sm font-bold transition"> إضافة مكتب</button>
<button x-show="page==='users' && (user?.role === 'super_admin' || user?.role === 'admin')" @click="showAddModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-2 rounded text-sm font-bold transition"> إضافة مستخدم</button> <button x-show="page==='users' && (user?.role === 'super_admin' || user?.role === 'admin')" @click="showAddModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-2 rounded text-sm font-bold transition"> إضافة مستخدم</button>
<button x-show="page==='companies' && (user?.role === 'super_admin' || user?.role === 'admin')" @click="showAddCompanyModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-2 rounded text-sm font-bold transition"> إضافة شركة</button> <button x-show="page==='companies' && (user?.role === 'super_admin' || user?.role === 'admin')" @click="showAddCompanyModal = true" class="bg-emerald-600 hover:bg-emerald-500 px-4 py-2 rounded text-sm font-bold transition"> إضافة شركة</button>
<div class="text-sm text-gray-500" x-text="user?.name"></div> <div class="text-sm text-gray-500" x-text="user?.name"></div>
@@ -159,6 +161,31 @@
</div> </div>
</div> </div>
<!-- Add Tenant Modal -->
<div x-show="showAddTenantModal" class="fixed inset-0 bg-black/80 flex items-center justify-center p-4 z-50" style="display: none;">
<div class="bg-gray-900 border border-gray-800 rounded-lg p-6 w-full max-w-md shadow-2xl" @click.outside="showAddTenantModal = false">
<h3 class="text-xl font-bold mb-6 text-emerald-500">مكتب جديد</h3>
<form @submit.prevent="createTenant" class="space-y-4">
<div>
<label class="block text-xs text-gray-500 uppercase mb-1">اسم المكتب</label>
<input type="text" x-model="newTenant.name" required class="w-full bg-gray-950 border border-gray-800 p-3 rounded outline-none focus:border-emerald-500">
</div>
<div>
<label class="block text-xs text-gray-500 uppercase mb-1">البريد الإلكتروني</label>
<input type="email" x-model="newTenant.email" required class="w-full bg-gray-950 border border-gray-800 p-3 rounded outline-none focus:border-emerald-500">
</div>
<div>
<label class="block text-xs text-gray-500 uppercase mb-1">الهاتف</label>
<input type="text" x-model="newTenant.phone" class="w-full bg-gray-950 border border-gray-800 p-3 rounded outline-none focus:border-emerald-500">
</div>
<div class="flex gap-3 pt-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 p-3 rounded font-bold transition">حفظ</button>
<button type="button" @click="showAddTenantModal = false" class="flex-1 bg-gray-800 hover:bg-gray-700 p-3 rounded transition">إلغاء</button>
</div>
</form>
</div>
</div>
<!-- Add Company Modal --> <!-- Add Company Modal -->
<div x-show="showAddCompanyModal" class="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50" x-cloak> <div x-show="showAddCompanyModal" class="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50" x-cloak>
<div class="bg-surface border border-gray-800 w-full max-w-md p-8 rounded-lg shadow-2xl" @click.away="showAddCompanyModal = false"> <div class="bg-surface border border-gray-800 w-full max-w-md p-8 rounded-lg shadow-2xl" @click.away="showAddCompanyModal = false">

45
scripts/fix_data.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
/**
* Data Reset & Fix Script
* Clears corrupted company/assignment data but keeps Users and Tenants.
*/
require_once __DIR__ . '/../app/bootstrap/init.php';
use App\Core\Database;
$db = Database::getInstance();
echo "--- Starting Data Reset ---\n";
try {
$db->beginTransaction();
// 1. Clear corrupted data tables
$db->exec("SET FOREIGN_KEY_CHECKS = 0");
$db->exec("TRUNCATE TABLE user_company_assignments");
$db->exec("TRUNCATE TABLE invoices");
$db->exec("TRUNCATE TABLE companies");
$db->exec("SET FOREIGN_KEY_CHECKS = 1");
echo "[OK] Cleared companies, invoices, and assignments.\n";
// 2. Ensure Super Admin does not have a tenant_id (if your schema allows NULL, else set to empty string)
// Actually, schema.sql says tenant_id CHAR(36) NOT NULL.
// This is a flaw in schema.sql for Super Admins. We will leave users alone for now.
// 3. Fix the admin's tenant_id to match the first available tenant
$stmt = $db->query("SELECT id FROM tenants LIMIT 1");
$tenantId = $stmt->fetchColumn();
if ($tenantId) {
$db->exec("UPDATE users SET tenant_id = '$tenantId' WHERE role != 'super_admin'");
echo "[OK] Linked all non-super-admin users to Tenant ID: $tenantId\n";
}
$db->commit();
echo "--- Reset Complete ---\n";
} catch (\Exception $e) {
$db->rollBack();
echo "[ERROR] Reset failed: " . $e->getMessage() . "\n";
}

View File

@@ -59,10 +59,10 @@ echo "User ID {$user['id']} migrated successfully.\n";
// 4. Create user_company_assignments table // 4. Create user_company_assignments table
try { try {
$db->exec("CREATE TABLE IF NOT EXISTS user_company_assignments ( $db->exec("CREATE TABLE IF NOT EXISTS user_company_assignments (
id CHAR(36) NOT NULL DEFAULT (UUID()), id INT AUTO_INCREMENT,
user_id CHAR(36) NOT NULL, user_id VARCHAR(100) NOT NULL,
company_id CHAR(36) NOT NULL, company_id VARCHAR(100) NOT NULL,
assigned_by CHAR(36) NOT NULL, assigned_by VARCHAR(100) NOT NULL,
assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, assigned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
is_active TINYINT(1) NOT NULL DEFAULT 1, is_active TINYINT(1) NOT NULL DEFAULT 1,
PRIMARY KEY (id), PRIMARY KEY (id),
@@ -78,7 +78,7 @@ try {
// 5. Update invoices table to include uploaded_by // 5. Update invoices table to include uploaded_by
try { try {
$db->exec("ALTER TABLE invoices ADD COLUMN uploaded_by CHAR(36) NULL AFTER status"); $db->exec("ALTER TABLE invoices ADD COLUMN uploaded_by VARCHAR(100) NULL AFTER status");
$db->exec("ALTER TABLE invoices ADD CONSTRAINT fk_inv_uploader FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL"); $db->exec("ALTER TABLE invoices ADD CONSTRAINT fk_inv_uploader FOREIGN KEY (uploaded_by) REFERENCES users(id) ON DELETE SET NULL");
echo "[OK] Updated invoices table with uploaded_by tracker.\n"; echo "[OK] Updated invoices table with uploaded_by tracker.\n";
} catch (\Exception $e) { } catch (\Exception $e) {