سعر الدولار اليومي
هذا هو الرقم الوحيد الذي يتغير، أما أسعار المعدات فتظل ثابتة بالدولار.
$type, 'message' => $message]; } function pullFlash(): ?array { if (!isset($_SESSION['flash'])) { return null; } $flash = $_SESSION['flash']; unset($_SESSION['flash']); return $flash; } function csrfToken(): string { if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } return (string) $_SESSION['csrf_token']; } function verifyCsrf(): void { $token = (string) ($_POST['csrf_token'] ?? ''); if ($token === '' || !hash_equals((string) ($_SESSION['csrf_token'] ?? ''), $token)) { throw new RuntimeException('انتهت الجلسة. أعد تحميل الصفحة ثم جرّب مرة ثانية.'); } } function fetchAllRows(PDO $pdo, string $sql, array $params = []): array { $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } function fetchRow(PDO $pdo, string $sql, array $params = []): ?array { $stmt = $pdo->prepare($sql); $stmt->execute($params); $row = $stmt->fetch(); return $row ?: null; } function fetchValue(PDO $pdo, string $sql, array $params = []) { $stmt = $pdo->prepare($sql); $stmt->execute($params); return $stmt->fetchColumn(); } function ensureSchema(PDO $pdo): void { $pdo->exec( "CREATE TABLE IF NOT EXISTS app_users ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, username VARCHAR(100) NOT NULL UNIQUE, full_name VARCHAR(150) NOT NULL, password_hash VARCHAR(255) NOT NULL, role VARCHAR(50) NOT NULL DEFAULT 'admin', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); $pdo->exec( "CREATE TABLE IF NOT EXISTS app_settings ( setting_key VARCHAR(100) PRIMARY KEY, setting_value VARCHAR(255) NOT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); $pdo->exec( "CREATE TABLE IF NOT EXISTS equipment ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(180) NOT NULL, quantity INT NOT NULL DEFAULT 0, price_usd DECIMAL(12,2) NOT NULL DEFAULT 0, notes TEXT DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); $pdo->exec( "CREATE TABLE IF NOT EXISTS invoices ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_number VARCHAR(50) NOT NULL UNIQUE, customer_name VARCHAR(180) DEFAULT NULL, exchange_rate DECIMAL(12,2) NOT NULL, total_usd DECIMAL(12,2) NOT NULL, total_syp BIGINT NOT NULL, notes TEXT DEFAULT NULL, created_by VARCHAR(120) DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); $pdo->exec( "CREATE TABLE IF NOT EXISTS invoice_items ( id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, invoice_id INT UNSIGNED NOT NULL, equipment_id INT UNSIGNED NOT NULL, item_name VARCHAR(180) NOT NULL, quantity INT NOT NULL, unit_price_usd DECIMAL(12,2) NOT NULL, line_total_usd DECIMAL(12,2) NOT NULL, line_total_syp BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_invoice_items_invoice FOREIGN KEY (invoice_id) REFERENCES invoices(id) ON DELETE CASCADE, CONSTRAINT fk_invoice_items_equipment FOREIGN KEY (equipment_id) REFERENCES equipment(id) ON DELETE RESTRICT ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci" ); } function seedDefaults(PDO $pdo): void { $defaultPasswordHash = password_hash('admin123', PASSWORD_DEFAULT); $adminUser = fetchRow($pdo, 'SELECT * FROM app_users WHERE username = :username LIMIT 1', [':username' => 'admin']); if (!$adminUser) { $stmt = $pdo->prepare('INSERT INTO app_users (username, full_name, password_hash, role) VALUES (:username, :full_name, :password_hash, :role)'); $stmt->execute([ ':username' => 'admin', ':full_name' => 'مدير المحل', ':password_hash' => $defaultPasswordHash, ':role' => 'admin', ]); } elseif (!password_verify('admin123', (string) $adminUser['password_hash']) || (string) $adminUser['full_name'] !== 'مدير المحل' || (string) $adminUser['role'] !== 'admin') { $stmt = $pdo->prepare('UPDATE app_users SET full_name = :full_name, password_hash = :password_hash, role = :role WHERE id = :id'); $stmt->execute([ ':full_name' => 'مدير المحل', ':password_hash' => $defaultPasswordHash, ':role' => 'admin', ':id' => (int) $adminUser['id'], ]); } $settingCount = (int) fetchValue($pdo, 'SELECT COUNT(*) FROM app_settings WHERE setting_key = :key', [':key' => 'exchange_rate']); if ($settingCount === 0) { $stmt = $pdo->prepare('INSERT INTO app_settings (setting_key, setting_value) VALUES (:key, :value)'); $stmt->execute([ ':key' => 'exchange_rate', ':value' => '15000', ]); } $equipmentCount = (int) fetchValue($pdo, 'SELECT COUNT(*) FROM equipment'); if ($equipmentCount === 0) { $stmt = $pdo->prepare('INSERT INTO equipment (name, quantity, price_usd, notes) VALUES (:name, :quantity, :price_usd, :notes)'); $items = [ ['طابعة فواتير', 5, 120, 'طابعة حرارية 80 مم'], ['قارئ باركود', 8, 75, 'مناسب لنقاط البيع'], ['مولدة صغيرة', 3, 250, 'للأحمال الخفيفة'], ]; foreach ($items as [$name, $quantity, $priceUsd, $notes]) { $stmt->execute([ ':name' => $name, ':quantity' => $quantity, ':price_usd' => $priceUsd, ':notes' => $notes, ]); } } } function getSetting(PDO $pdo, string $key, string $default = ''): string { $value = fetchValue($pdo, 'SELECT setting_value FROM app_settings WHERE setting_key = :key LIMIT 1', [':key' => $key]); return $value !== false ? (string) $value : $default; } function setSetting(PDO $pdo, string $key, string $value): void { $stmt = $pdo->prepare( 'INSERT INTO app_settings (setting_key, setting_value) VALUES (:key, :value) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)' ); $stmt->execute([ ':key' => $key, ':value' => $value, ]); } function currentUser(): ?array { return isset($_SESSION['user']) && is_array($_SESSION['user']) ? $_SESSION['user'] : null; } function isLoggedIn(): bool { return currentUser() !== null; } function loginUser(array $user): void { session_regenerate_id(true); $_SESSION['user'] = [ 'id' => (int) $user['id'], 'username' => (string) $user['username'], 'full_name' => (string) $user['full_name'], 'role' => (string) $user['role'], ]; } function logoutUser(): void { $_SESSION = []; if (ini_get('session.use_cookies')) { $params = session_get_cookie_params(); setcookie(session_name(), '', time() - 42000, $params['path'], $params['domain'], (bool) $params['secure'], (bool) $params['httponly']); } session_destroy(); } function currentTab(): string { $allowed = ['equipment', 'invoices', 'reports']; $tab = (string) ($_GET['tab'] ?? 'equipment'); return in_array($tab, $allowed, true) ? $tab : 'equipment'; } function formatUsd(float $value): string { return '$' . number_format($value, 2); } function formatSyp(float $value): string { return number_format($value, 0) . ' ل.س'; } function getEquipmentRows(PDO $pdo): array { return fetchAllRows($pdo, 'SELECT id, name, quantity, price_usd, notes, created_at, updated_at FROM equipment ORDER BY id DESC'); } function getInvoices(PDO $pdo): array { return fetchAllRows( $pdo, 'SELECT id, invoice_number, customer_name, exchange_rate, total_usd, total_syp, created_by, created_at FROM invoices ORDER BY id DESC LIMIT 100' ); } function getInvoiceDetails(PDO $pdo, int $invoiceId): ?array { $invoice = fetchRow($pdo, 'SELECT * FROM invoices WHERE id = :id LIMIT 1', [':id' => $invoiceId]); if (!$invoice) { return null; } $items = fetchAllRows( $pdo, 'SELECT item_name, quantity, unit_price_usd, line_total_usd, line_total_syp FROM invoice_items WHERE invoice_id = :invoice_id ORDER BY id ASC', [':invoice_id' => $invoiceId] ); $invoice['items'] = $items; return $invoice; } $pdo = db(); ensureSchema($pdo); seedDefaults($pdo); $projectName = $_SERVER['PROJECT_NAME'] ?? 'إدارة محل بسيطة'; $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'تطبيق مبسط لإدارة المعدات والفواتير والتقارير مع تسعير بالدولار وتحويل فوري إلى الليرة السورية.'; $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? ''; $assetCssVersion = (string) (@filemtime(__DIR__ . '/assets/css/custom.css') ?: time()); $assetJsVersion = (string) (@filemtime(__DIR__ . '/assets/js/main.js') ?: time()); $runtimeFlash = null; $tab = currentTab(); $quickLogin = (string) ($_GET['quick_login'] ?? ''); if ($quickLogin === 'demo') { $demoUser = fetchRow($pdo, 'SELECT * FROM app_users WHERE username = :username LIMIT 1', [':username' => 'admin']); if ($demoUser) { loginUser($demoUser); $runtimeFlash = ['type' => 'success', 'message' => 'تم الدخول التجريبي مباشرة.']; $tab = 'equipment'; } else { $runtimeFlash = ['type' => 'error', 'message' => 'الحساب التجريبي غير موجود.']; } } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = ''; try { verifyCsrf(); $action = (string) ($_POST['action'] ?? ''); if ($action === 'logout') { logoutUser(); redirectTo('index.php'); } if ($action === 'login') { $username = mb_strtolower(trim((string) ($_POST['username'] ?? ''))); $password = trim((string) ($_POST['password'] ?? '')); if ($username === '' || $password === '') { throw new RuntimeException('أدخل اسم المستخدم وكلمة المرور.'); } $user = fetchRow($pdo, 'SELECT * FROM app_users WHERE username = :username LIMIT 1', [':username' => $username]); if (!$user || !password_verify($password, (string) $user['password_hash'])) { throw new RuntimeException('بيانات الدخول غير صحيحة. استخدم بالضبط: admin / admin123'); } loginUser($user); $runtimeFlash = ['type' => 'success', 'message' => 'تم تسجيل الدخول بنجاح.']; $tab = 'equipment'; } if (!isLoggedIn()) { throw new RuntimeException('يرجى تسجيل الدخول أولاً.'); } if ($action === 'save_rate') { $rate = (float) ($_POST['exchange_rate'] ?? 0); if ($rate <= 0) { throw new RuntimeException('أدخل قيمة صحيحة لسعر الدولار اليومي.'); } setSetting($pdo, 'exchange_rate', (string) $rate); setFlash('success', 'تم تحديث سعر الدولار اليومي.'); redirectTo('index.php?tab=equipment'); } if ($action === 'add_equipment') { $name = trim((string) ($_POST['name'] ?? '')); $quantity = (int) ($_POST['quantity'] ?? 0); $priceUsd = (float) ($_POST['price_usd'] ?? 0); $notes = trim((string) ($_POST['notes'] ?? '')); if ($name === '') { throw new RuntimeException('أدخل اسم المعدة.'); } if ($quantity < 0) { throw new RuntimeException('الكمية يجب أن تكون صفر أو أكثر.'); } if ($priceUsd <= 0) { throw new RuntimeException('سعر الدولار يجب أن يكون أكبر من الصفر.'); } $stmt = $pdo->prepare('INSERT INTO equipment (name, quantity, price_usd, notes) VALUES (:name, :quantity, :price_usd, :notes)'); $stmt->execute([ ':name' => $name, ':quantity' => $quantity, ':price_usd' => $priceUsd, ':notes' => $notes !== '' ? $notes : null, ]); setFlash('success', 'تمت إضافة المعدة بنجاح.'); redirectTo('index.php?tab=equipment'); } if ($action === 'create_invoice') { $equipmentId = (int) ($_POST['equipment_id'] ?? 0); $quantity = (int) ($_POST['invoice_quantity'] ?? 0); $customerName = trim((string) ($_POST['customer_name'] ?? '')); $notes = trim((string) ($_POST['invoice_notes'] ?? '')); $exchangeRate = (float) getSetting($pdo, 'exchange_rate', '0'); if ($equipmentId <= 0) { throw new RuntimeException('اختر المعدة أولاً.'); } if ($quantity <= 0) { throw new RuntimeException('أدخل كمية صحيحة للفاتورة.'); } if ($exchangeRate <= 0) { throw new RuntimeException('أدخل سعر الدولار اليومي قبل إنشاء أي فاتورة.'); } $pdo->beginTransaction(); try { $equipment = fetchRow($pdo, 'SELECT id, name, quantity, price_usd FROM equipment WHERE id = :id FOR UPDATE', [':id' => $equipmentId]); if (!$equipment) { throw new RuntimeException('المعدة غير موجودة.'); } if ((int) $equipment['quantity'] < $quantity) { throw new RuntimeException('الكمية المطلوبة أكبر من المخزون المتاح.'); } $lineTotalUsd = round((float) $equipment['price_usd'] * $quantity, 2); $lineTotalSyp = (int) round($lineTotalUsd * $exchangeRate); $invoiceStmt = $pdo->prepare( 'INSERT INTO invoices (invoice_number, customer_name, exchange_rate, total_usd, total_syp, notes, created_by) VALUES (:invoice_number, :customer_name, :exchange_rate, :total_usd, :total_syp, :notes, :created_by)' ); $tempNumber = 'TMP-' . bin2hex(random_bytes(4)); $invoiceStmt->execute([ ':invoice_number' => $tempNumber, ':customer_name' => $customerName !== '' ? $customerName : null, ':exchange_rate' => $exchangeRate, ':total_usd' => $lineTotalUsd, ':total_syp' => $lineTotalSyp, ':notes' => $notes !== '' ? $notes : null, ':created_by' => currentUser()['full_name'] ?? 'admin', ]); $invoiceId = (int) $pdo->lastInsertId(); $invoiceNumber = 'INV-' . gmdate('Ymd') . '-' . str_pad((string) $invoiceId, 4, '0', STR_PAD_LEFT); $updateNumberStmt = $pdo->prepare('UPDATE invoices SET invoice_number = :invoice_number WHERE id = :id'); $updateNumberStmt->execute([ ':invoice_number' => $invoiceNumber, ':id' => $invoiceId, ]); $itemStmt = $pdo->prepare( 'INSERT INTO invoice_items (invoice_id, equipment_id, item_name, quantity, unit_price_usd, line_total_usd, line_total_syp) VALUES (:invoice_id, :equipment_id, :item_name, :quantity, :unit_price_usd, :line_total_usd, :line_total_syp)' ); $itemStmt->execute([ ':invoice_id' => $invoiceId, ':equipment_id' => $equipmentId, ':item_name' => $equipment['name'], ':quantity' => $quantity, ':unit_price_usd' => $equipment['price_usd'], ':line_total_usd' => $lineTotalUsd, ':line_total_syp' => $lineTotalSyp, ]); $stockStmt = $pdo->prepare('UPDATE equipment SET quantity = quantity - :qty WHERE id = :id'); $stockStmt->execute([ ':qty' => $quantity, ':id' => $equipmentId, ]); $pdo->commit(); setFlash('success', 'تم إنشاء الفاتورة وتحديث المخزون تلقائياً.'); redirectTo('index.php?tab=invoices&invoice_id=' . $invoiceId); } catch (Throwable $exception) { $pdo->rollBack(); throw $exception; } } } catch (Throwable $exception) { if ($action === 'login') { $runtimeFlash = ['type' => 'error', 'message' => $exception->getMessage()]; $tab = 'equipment'; } else { setFlash('error', $exception->getMessage()); $tab = currentTab(); redirectTo('index.php?tab=' . $tab); } } } $flash = $runtimeFlash ?? pullFlash(); $csrfToken = csrfToken(); $exchangeRate = (float) getSetting($pdo, 'exchange_rate', '15000'); $equipmentRows = getEquipmentRows($pdo); $invoiceRows = getInvoices($pdo); $selectedInvoiceId = (int) ($_GET['invoice_id'] ?? ($invoiceRows[0]['id'] ?? 0)); $selectedInvoice = $selectedInvoiceId > 0 ? getInvoiceDetails($pdo, $selectedInvoiceId) : null; $tab = currentTab(); $user = currentUser(); $totalEquipmentTypes = (int) fetchValue($pdo, 'SELECT COUNT(*) FROM equipment'); $totalStockUnits = (int) fetchValue($pdo, 'SELECT COALESCE(SUM(quantity), 0) FROM equipment'); $totalInvoices = (int) fetchValue($pdo, 'SELECT COUNT(*) FROM invoices'); $totalSalesSyp = (float) fetchValue($pdo, 'SELECT COALESCE(SUM(total_syp), 0) FROM invoices'); $todaySalesSyp = (float) fetchValue($pdo, 'SELECT COALESCE(SUM(total_syp), 0) FROM invoices WHERE DATE(created_at) = UTC_DATE()'); $todayInvoices = (int) fetchValue($pdo, 'SELECT COUNT(*) FROM invoices WHERE DATE(created_at) = UTC_DATE()'); ?>
Store Admin
= isLoggedIn() ? 'واجهة بسيطة: معدات + فواتير + تقارير، مع تسعير ثابت بالدولار وتحويل يومي إلى الليرة السورية.' : 'ادخل بحساب واحد بسيط ثم افتح 3 أقسام فقط داخل التطبيق.' ?>
أبسط نسخة ممكنة: شاشة دخول واحدة ثم 3 أقسام فقط داخل التطبيق.
بيانات الدخول الأكيدة: admin / admin123
دخول مباشر للتطبيقمرحبا، = e($user['full_name'] ?? 'مدير المحل') ?>
كل الأسعار الأساسية بالدولار، والتحويل إلى الليرة السورية يتم حسب سعر اليوم فقط.
هذا هو الرقم الوحيد الذي يتغير، أما أسعار المعدات فتظل ثابتة بالدولار.
أدخل السعر بالدولار، والتطبيق سيحسب الليرة السورية عند البيع فقط.
= e((string) $totalEquipmentTypes) ?> نوع / = e((string) $totalStockUnits) ?> قطعة في المخزون
| المعدة | الكمية | السعر بالدولار | السعر بالليرة | ملاحظات |
|---|---|---|---|---|
| لا توجد معدات حتى الآن. | ||||
| = e($item['name']) ?> | = e((string) $item['quantity']) ?> | = e(formatUsd((float) $item['price_usd'])) ?> | = e(formatSyp((float) $item['price_usd'] * $exchangeRate)) ?> | = e($item['notes'] ?? '—') ?> |
اختر معدة واحدة، أدخل الكمية، وسيتم خصمها من المخزون مباشرة.
اختر فاتورة من الجدول أو أنشئ واحدة جديدة لتظهر هنا.
= e($selectedInvoice['notes'] ?: 'بدون ملاحظات') ?>
آخر 100 فاتورة مع إمكانية تصدير القائمة كاملة كملف PDF.
| رقم الفاتورة | العميل | الإجمالي بالدولار | الإجمالي بالليرة | التاريخ |
|---|---|---|---|---|
| لا توجد فواتير حتى الآن. | ||||
| = e($invoice['invoice_number']) ?> | = e($invoice['customer_name'] ?: 'عميل مباشر') ?> | = e(formatUsd((float) $invoice['total_usd'])) ?> | = e(formatSyp((float) $invoice['total_syp'])) ?> | = e((string) $invoice['created_at']) ?> |
قراءة مباشرة لوضع المحل اليوم.
أحدث الحركات المالية داخل التطبيق.
= e($invoice['customer_name'] ?: 'عميل مباشر') ?>