add installation module

This commit is contained in:
Flatlogic Bot 2026-02-18 10:32:06 +00:00
parent d7b7397def
commit 675156eee1
9 changed files with 868 additions and 161 deletions

3
.gitignore vendored
View File

@ -1,3 +1,6 @@
node_modules/
*/node_modules/
*/build/
sessions/
backups/
*.lock

View File

@ -79,15 +79,25 @@ body {
color: #94a3b8 !important;
}
.nav-section-title i.bi-chevron-down {
.nav-section-title i.chevron {
transition: transform 0.2s;
font-size: 0.6rem;
}
.nav-section-title.collapsed i.bi-chevron-down {
.nav-section-title.collapsed i.chevron {
transform: rotate(-90deg);
}
.nav-section-title .group-icon {
width: 18px;
margin-right: 10px;
}
[dir="rtl"] .nav-section-title .group-icon {
margin-right: 0;
margin-left: 10px;
}
/* POS Styles */
.pos-container {
display: flex;

48
check_nesting.php Normal file
View File

@ -0,0 +1,48 @@
<?php
$content = file_get_contents('index.php');
$lines = explode("\n", $content);
$stack = [];
$in_php = false;
foreach ($lines as $idx => $line) {
$line_num = $idx + 1;
// Simple check for PHP tags
if (strpos($line, '<?php') !== false) $in_php = true;
if (strpos($line, '?>') !== false) $in_php = false;
// Alternative syntax checks
if (preg_match('/\bif\b\s*\(.*\)\s*:/', $line)) {
$stack[] = ['type' => 'if', 'line' => $line_num];
} elseif (preg_match('/\belseif\b\s*\(.*\)\s*:/', $line)) {
// elseif is part of the current if block, so it doesn't change nesting level
} elseif (preg_match('/\belse\b\s*:/', $line)) {
// else is part of the current if block, so it doesn't change nesting level
} elseif (preg_match('/foreach\s*\(.*\)\s*:/', $line)) {
$stack[] = ['type' => 'foreach', 'line' => $line_num];
}
if (strpos($line, 'endif;') !== false) {
if (empty($stack)) {
echo "Unexpected endif; at line $line_num\n";
} else {
$last = array_pop($stack);
if ($last['type'] !== 'if') {
echo "Mismatched endif; at line $line_num (expected endforeach; for {$last['type']} at line {$last['line']})\n";
}
}
}
if (strpos($line, 'endforeach;') !== false) {
if (empty($stack)) {
echo "Unexpected endforeach; at line $line_num\n";
} else {
$last = array_pop($stack);
if ($last['type'] !== 'foreach') {
echo "Mismatched endforeach; at line $line_num (expected endif; for {$last['type']} at line {$last['line']})\n";
}
}
}
}
foreach ($stack as $unclosed) {
echo "Unclosed {$unclosed['type']} starting at line {$unclosed['line']}\n";
}

37
cron_backup.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* Automated Database Backup Script
* Recommended Cron Job: 0 0 * * * /usr/bin/php /path/to/project/cron_backup.php
*/
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/db/BackupService.php';
// Check if auto-backup is enabled
$stmt = db()->prepare("SELECT `key`, `value` FROM settings WHERE `key` IN ('backup_auto_enabled', 'backup_limit', 'backup_time')");
$stmt->execute();
$settings = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
$enabled = $settings['backup_auto_enabled'] ?? '0';
if ($enabled === '1') {
$scheduledTime = $settings['backup_time'] ?? '00:00';
$currentTime = date('H:i');
if ($currentTime !== $scheduledTime) {
die("[" . date('Y-m-d H:i:s') . "] Not the scheduled time ($scheduledTime). Current time: $currentTime\n");
}
$limit = $settings['backup_limit'] ?? 5;
echo "[" . date('Y-m-d H:i:s') . "] Starting automated backup...\n";
$res = BackupService::createBackup();
if ($res['success']) {
echo "[" . date('Y-m-d H:i:s') . "] Backup created successfully: " . $res['file'] . "\n";
} else {
echo "[" . date('Y-m-d H:i:s') . "] Error creating backup: " . $res['error'] . "\n";
}
} else {
echo "[" . date('Y-m-d H:i:s') . "] Automated backup is disabled in settings.\n";
}

102
db/BackupService.php Normal file
View File

@ -0,0 +1,102 @@
<?php
require_once __DIR__ . '/config.php';
class BackupService {
private static $backupDir = __DIR__ . '/../backups/';
public static function createBackup() {
if (!is_dir(self::$backupDir)) {
mkdir(self::$backupDir, 0775, true);
}
$filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql';
$filePath = self::$backupDir . $filename;
// Use mysqldump for reliable export
$command = sprintf(
'mysqldump --no-tablespaces -h %s -u %s -p%s %s > %s',
escapeshellarg(DB_HOST),
escapeshellarg(DB_USER),
escapeshellarg(DB_PASS),
escapeshellarg(DB_NAME),
escapeshellarg($filePath)
);
exec($command, $output, $returnVar);
if ($returnVar === 0) {
// Get limit from settings
$limit = 5;
try {
$stmt = db()->prepare("SELECT `value` FROM settings WHERE `key` = 'backup_limit'");
$stmt->execute();
$val = $stmt->fetchColumn();
if ($val) $limit = (int)$val;
} catch (Exception $e) {}
self::rotateBackups($limit);
return ['success' => true, 'file' => $filename];
}
return ['success' => false, 'error' => 'Failed to create backup.'];
}
public static function restoreBackup($filename) {
$filePath = self::$backupDir . basename($filename);
if (!file_exists($filePath)) {
return ['success' => false, 'error' => 'Backup file not found.'];
}
$command = sprintf(
'mysql -h %s -u %s -p%s %s < %s',
escapeshellarg(DB_HOST),
escapeshellarg(DB_USER),
escapeshellarg(DB_PASS),
escapeshellarg(DB_NAME),
escapeshellarg($filePath)
);
exec($command, $output, $returnVar);
if ($returnVar === 0) {
return ['success' => true];
}
return ['success' => false, 'error' => 'Failed to restore backup.'];
}
public static function rotateBackups($limit = 5) {
$files = glob(self::$backupDir . 'backup_*.sql');
if (count($files) <= $limit) {
return;
}
// Sort by modification time (oldest first)
usort($files, function($a, $b) {
return filemtime($a) - filemtime($b);
});
$toDelete = count($files) - $limit;
for ($i = 0; $i < $toDelete; $i++) {
unlink($files[$i]);
}
}
public static function getBackups() {
if (!is_dir(self::$backupDir)) return [];
$files = glob(self::$backupDir . 'backup_*.sql');
usort($files, function($a, $b) {
return filemtime($b) - filemtime($a);
});
$result = [];
foreach ($files as $file) {
$result[] = [
'name' => basename($file),
'size' => round(filesize($file) / 1024, 2) . ' KB',
'date' => date('Y-m-d H:i:s', filemtime($file))
];
}
return $result;
}
}

31
debug_395.txt Normal file
View File

@ -0,0 +1,31 @@
// --- User & Role Groups Handlers ---
if (isset($_POST['add_role_group'])) {
$name = $_POST['name'] ?? '';
$permissions = isset($_POST['permissions']) ? json_encode($_POST['permissions']) : '[]';
if ($name) {
try {
$stmt = db()->prepare("INSERT INTO role_groups (name, permissions) VALUES (?, ?)");
$stmt->execute([$name, $permissions]);
$message = "Role Group added successfully!";
} catch (PDOException $e) {
$message = "Error adding role group: " . $e->getMessage();
}
}
}
if (isset($_POST['add_user'])) {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
if ($username && $password) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("INSERT INTO users (username, password, email, group_id) VALUES (?, ?, ?, ?)");
try {
$stmt->execute([$username, $hashed_password, $email, $group_id]);
$message = "User added successfully!";
} catch (PDOException $e) {
if ($e->getCode() == '23000') {
$message = "Error: Username already exists.";
} else {

528
index.php
View File

@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
// Sessions setup
if (!is_dir(__DIR__ . '/sessions')) {
mkdir(__DIR__ . '/sessions', 0777, true);
}
@ -21,6 +23,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
file_put_contents('post_debug.log', date('Y-m-d H:i:s') . " - POST: " . json_encode($_POST) . "\n", FILE_APPEND);
}
require_once 'db/config.php';
require_once 'db/BackupService.php';
require_once 'includes/accounting_helper.php';
// Helper to check permissions
@ -397,12 +400,13 @@ if (isset($_POST['add_hr_department'])) {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
if ($username && $password) {
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
$stmt = db()->prepare("INSERT INTO users (username, password, email, group_id) VALUES (?, ?, ?, ?)");
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id) VALUES (?, ?, ?, ?, ?)");
try {
$stmt->execute([$username, $hashed_password, $email, $group_id]);
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id]);
$message = "User added successfully!";
} catch (PDOException $e) {
if ($e->getCode() == '23000') {
@ -435,11 +439,12 @@ if (isset($_POST['add_hr_department'])) {
$id = (int)$_POST['id'];
$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
$status = $_POST['status'] ?? 'active';
if ($id && $username) {
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, group_id = ?, status = ? WHERE id = ?");
$stmt->execute([$username, $email, $group_id, $status, $id]);
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ? WHERE id = ?");
$stmt->execute([$username, $email, $phone, $group_id, $status, $id]);
if (!empty($_POST['password'])) {
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
@ -458,6 +463,77 @@ if (isset($_POST['add_hr_department'])) {
}
}
if (isset($_POST['update_profile'])) {
$id = $_SESSION['user_id'];
$username = $_POST['username'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
if ($id && $username) {
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ? WHERE id = ?");
$stmt->execute([$username, $email, $phone, $id]);
$_SESSION['username'] = $username;
if (!empty($_POST['password'])) {
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
$stmt = db()->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$hashed_password, $id]);
}
if (isset($_FILES['profile_pic']) && $_FILES['profile_pic']['error'] === 0) {
$ext = pathinfo($_FILES['profile_pic']['name'], PATHINFO_EXTENSION);
$filename = 'uploads/profile_' . $id . '_' . time() . '.' . $ext;
if (!is_dir('uploads')) mkdir('uploads', 0777, true);
if (move_uploaded_file($_FILES['profile_pic']['tmp_name'], $filename)) {
$stmt = db()->prepare("UPDATE users SET profile_pic = ? WHERE id = ?");
$stmt->execute([$filename, $id]);
$_SESSION['profile_pic'] = $filename;
}
}
$message = "Profile updated successfully!";
}
}
// --- Backup Handlers ---
if (isset($_POST['create_backup'])) {
if (can('users_view')) { // Admin check
$res = BackupService::createBackup();
$message = $res['success'] ? "Backup created: " . $res['file'] : "Error: " . $res['error'];
}
}
if (isset($_POST['restore_backup'])) {
if (can('users_view')) {
$filename = $_POST['filename'] ?? '';
$res = BackupService::restoreBackup($filename);
$message = $res['success'] ? "Database restored successfully from $filename!" : "Error: " . $res['error'];
}
}
if (isset($_POST['delete_backup'])) {
if (can('users_view')) {
$filename = basename($_POST['filename'] ?? '');
if (unlink(__DIR__ . '/backups/' . $filename)) {
$message = "Backup deleted successfully.";
} else {
$message = "Error deleting backup.";
}
}
}
if (isset($_POST['save_backup_settings'])) {
if (can('users_view')) {
$limit = (int)($_POST['backup_limit'] ?? 5);
$auto = $_POST['backup_auto_enabled'] ?? '0';
$time = $_POST['backup_time'] ?? '00:00';
$db = db();
$stmt = $db->prepare("INSERT INTO settings (`key`, `value`) VALUES ('backup_limit', ?), ('backup_auto_enabled', ?), ('backup_time', ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)");
$stmt->execute([$limit, $auto, $time]);
$message = "Backup settings saved successfully!";
}
}
// Routing & Data Fetching
$page = $_GET['page'] ?? 'dashboard';
@ -494,6 +570,7 @@ $page_permissions = [
'hr_payroll' => 'hr_view',
'role_groups' => 'users_view',
'users' => 'users_view',
'backups' => 'users_view',
];
if (isset($page_permissions[$page]) && !can($page_permissions[$page])) {
@ -501,7 +578,28 @@ if (isset($page_permissions[$page]) && !can($page_permissions[$page])) {
$message = "Access Denied: You don't have permission to view that module.";
}
$data = [];
$data = [
'payment_methods' => [],
'role_groups' => [],
'users' => [],
'expiry_items' => [],
'low_stock_items' => [],
'items' => [],
'cash_transactions' => [],
'monthly_sales' => [],
'yearly_sales' => [],
'opening_balance' => 0,
'stats' => [
'expired_items' => 0,
'near_expiry_items' => 0,
'low_stock_items_count' => 0,
'total_sales' => 0,
'total_received' => 0,
'total_receivable' => 0,
'total_purchases' => 0,
],
'settings' => [],
];
if ($page === 'export') {
$type = $_GET['type'] ?? 'sales';
@ -832,6 +930,12 @@ switch ($page) {
$data['users'] = db()->query("SELECT u.*, g.name as group_name FROM users u LEFT JOIN role_groups g ON u.group_id = g.id ORDER BY u.username ASC")->fetchAll();
$data['role_groups'] = db()->query("SELECT id, name FROM role_groups ORDER BY name ASC")->fetchAll();
break;
case 'backups':
$data['backups'] = BackupService::getBackups();
$stmt = db()->prepare("SELECT * FROM settings WHERE `key` IN ('backup_limit', 'backup_auto_enabled', 'backup_time')");
$stmt->execute();
$data['backup_settings'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR);
break;
case 'accounting':
$data['journal_entries'] = db()->query("SELECT je.*,
(SELECT SUM(debit) FROM acc_ledger WHERE journal_entry_id = je.id) as total_debit
@ -1050,6 +1154,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
@ -1075,179 +1180,122 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<!-- General Section -->
<?php if (can('dashboard_view')): ?>
<a href="index.php?page=dashboard" class="nav-link <?= !isset($_GET['page']) || $_GET['page'] === 'dashboard' ? 'active' : '' ?>">
<i class="bi bi-speedometer2"></i> <span data-en="Dashboard" data-ar="لوحة القيادة">Dashboard</span>
<i class="fas fa-chart-pie"></i> <span data-en="Dashboard" data-ar="لوحة القيادة">Dashboard</span>
</a>
<?php endif; ?>
<!-- Inventory Section -->
<?php if (can('items_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['items', 'categories', 'units']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#stock-collapse">
<span><i class="fas fa-boxes-stacked group-icon"></i><span data-en="Inventory" data-ar="المخزون">Inventory</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['items', 'categories', 'units']) ? 'show' : '' ?>" id="stock-collapse">
<a href="index.php?page=items" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'items' ? 'active' : '' ?>">
<i class="fas fa-box"></i> <span data-en="Products" data-ar="المنتجات">Products</span>
</a>
<a href="index.php?page=categories" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'categories' ? 'active' : '' ?>">
<i class="fas fa-tags"></i> <span data-en="Categories" data-ar="الفئات">Categories</span>
</a>
<a href="index.php?page=units" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'units' ? 'active' : '' ?>">
<i class="fas fa-ruler-combined"></i> <span data-en="Units" data-ar="الوحدات">Units</span>
</a>
</div>
<?php endif; ?>
<!-- People Section -->
<?php if (can('customers_view') || can('suppliers_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customers', 'suppliers']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#crm-collapse">
<span><i class="fas fa-user-group group-icon"></i><span data-en="People" data-ar="الأشخاص">People</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['customers', 'suppliers']) ? 'show' : '' ?>" id="crm-collapse">
<?php if (can('customers_view')): ?>
<a href="index.php?page=customers" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'customers' ? 'active' : '' ?>">
<i class="fas fa-users"></i> <span data-en="Customers" data-ar="العملاء">Customers</span>
</a>
<?php endif; ?>
<?php if (can('suppliers_view')): ?>
<a href="index.php?page=suppliers" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'suppliers' ? 'active' : '' ?>">
<i class="fas fa-truck-field"></i> <span data-en="Suppliers" data-ar="الموردين">Suppliers</span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Operations Section -->
<?php if (can('pos_view') || can('sales_view') || can('purchases_view') || can('quotations_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['sales', 'purchases']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#ops-collapse">
<span data-en="Operations" data-ar="العمليات">Operations</span>
<i class="bi bi-chevron-down"></i>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['sales', 'purchases', 'pos', 'quotations']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#ops-collapse">
<span><i class="fas fa-gears group-icon"></i><span data-en="Operations" data-ar="العمليات">Operations</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['sales', 'purchases', 'pos', 'quotations']) ? 'show' : '' ?>" id="ops-collapse">
<?php if (can('pos_view')): ?>
<a href="index.php?page=pos" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'pos' ? 'active' : '' ?>">
<i class="bi bi-display"></i> <span data-en="Point of Sale" data-ar="نقطة البيع">Point of Sale</span>
<i class="fas fa-cash-register"></i> <span data-en="Point of Sale" data-ar="نقطة البيع">Point of Sale</span>
</a>
<?php endif; ?>
<?php if (can('sales_view')): ?>
<a href="index.php?page=sales" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'sales' ? 'active' : '' ?>">
<i class="bi bi-cart"></i> <span data-en="Sales Tax Invoices" data-ar="فواتير المبيعات الضريبية">Sales Tax Invoices</span>
<i class="fas fa-file-invoice-dollar"></i> <span data-en="Sales Tax Invoices" data-ar="فواتير المبيعات الضريبية">Sales Tax Invoices</span>
</a>
<a href="index.php?page=sales_returns" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'sales_returns' ? 'active' : '' ?>">
<i class="bi bi-arrow-return-left"></i> <span data-en="Sales Returns" data-ar="مرتجع المبيعات">Sales Returns</span>
<i class="fas fa-reply"></i> <span data-en="Sales Returns" data-ar="مرتجع المبيعات">Sales Returns</span>
</a>
<?php endif; ?>
<?php if (can('purchases_view')): ?>
<a href="index.php?page=purchases" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'purchases' ? 'active' : '' ?>">
<i class="bi bi-bag"></i> <span data-en="Purchase Tax Invoices" data-ar="فواتير المشتريات الضريبية">Purchase Tax Invoices</span>
<i class="fas fa-cart-shopping"></i> <span data-en="Purchase Tax Invoices" data-ar="فواتير المشتريات الضريبية">Purchase Tax Invoices</span>
</a>
<a href="index.php?page=purchase_returns" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'purchase_returns' ? 'active' : '' ?>">
<i class="bi bi-arrow-return-right"></i> <span data-en="Purchase Returns" data-ar="مرتجع المشتريات">Purchase Returns</span>
<i class="fas fa-share"></i> <span data-en="Purchase Returns" data-ar="مرتجع المشتريات">Purchase Returns</span>
</a>
<?php endif; ?>
<?php if (can('quotations_view')): ?>
<a href="index.php?page=quotations" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'quotations' ? 'active' : '' ?>">
<i class="bi bi-file-earmark-text"></i> <span data-en="Quotations" data-ar="العروض">Quotations</span>
<i class="fas fa-file-lines"></i> <span data-en="Quotations" data-ar="العروض">Quotations</span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Accounting Section -->
<?php if (can('accounting_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['accounting', 'expense_report']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#accounting-collapse">
<span data-en="Accounting" data-ar="المحاسبة">Accounting</span>
<i class="bi bi-chevron-down"></i>
</div>
<div class="collapse <?= in_array($page, ['accounting', 'expense_report']) ? 'show' : '' ?>" id="accounting-collapse">
<a href="index.php?page=accounting" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'accounting' ? 'active' : '' ?>">
<i class="bi bi-calculator"></i> <span data-en="Journal & Ledger" data-ar="اليومية والأستاذ">Journal & Ledger</span>
</a>
<a href="index.php?page=accounting&view=trial_balance" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'active' : '' ?>">
<i class="bi bi-list-columns-reverse"></i> <span data-en="Trial Balance" data-ar="ميزان المراجعة">Trial Balance</span>
</a>
<a href="index.php?page=accounting&view=profit_loss" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'profit_loss' ? 'active' : '' ?>">
<i class="bi bi-graph-up-arrow"></i> <span data-en="Profit & Loss" data-ar="الأرباح والخسائر">Profit & Loss</span>
</a>
<a href="index.php?page=accounting&view=balance_sheet" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'balance_sheet' ? 'active' : '' ?>">
<i class="bi bi-journal-text"></i> <span data-en="Balance Sheet" data-ar="الميزانية العمومية">Balance Sheet</span>
</a>
<a href="index.php?page=accounting&view=vat_report" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'vat_report' ? 'active' : '' ?>">
<i class="bi bi-percent"></i> <span data-en="VAT Report" data-ar="تقرير الضريبة">VAT Report</span>
</a>
</div>
<?php endif; ?>
<!-- Expenses Section -->
<?php if (can('accounting_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['expense_categories', 'expenses']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#expenses-collapse">
<span data-en="Expenses" data-ar="المصروفات">Expenses</span>
<i class="bi bi-chevron-down"></i>
<span><i class="fas fa-wallet group-icon"></i><span data-en="Expenses" data-ar="المصروفات">Expenses</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['expense_categories', 'expenses']) ? 'show' : '' ?>" id="expenses-collapse">
<a href="index.php?page=expense_categories" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'expense_categories' ? 'active' : '' ?>">
<i class="bi bi-tags"></i> <span data-en="Categories" data-ar="الفئات">Categories</span>
<i class="fas fa-layer-group"></i> <span data-en="Categories" data-ar="الفئات">Categories</span>
</a>
<a href="index.php?page=expenses" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'expenses' ? 'active' : '' ?>">
<i class="bi bi-receipt"></i> <span data-en="All Expenses" data-ar="كل المصروفات">All Expenses</span>
<i class="fas fa-file-invoice"></i> <span data-en="All Expenses" data-ar="كل المصروفات">All Expenses</span>
</a>
</div>
<?php endif; ?>
<!-- Inventory Section -->
<?php if (can('items_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['items', 'categories', 'units']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#stock-collapse">
<span data-en="Inventory" data-ar="المخزون">Inventory</span>
<i class="bi bi-chevron-down"></i>
<!-- Accounting Section -->
<?php if (can('accounting_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['accounting', 'expense_report']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#accounting-collapse">
<span><i class="fas fa-calculator group-icon"></i><span data-en="Accounting" data-ar="المحاسبة">Accounting</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['items', 'categories', 'units']) ? 'show' : '' ?>" id="stock-collapse">
<a href="index.php?page=items" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'items' ? 'active' : '' ?>">
<i class="bi bi-box"></i> <span data-en="Products" data-ar="المنتجات">Products</span>
<div class="collapse <?= in_array($page, ['accounting', 'expense_report']) ? 'show' : '' ?>" id="accounting-collapse">
<a href="index.php?page=accounting" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'accounting' ? 'active' : '' ?>">
<i class="fas fa-book-open"></i> <span data-en="Journal & Ledger" data-ar="اليومية والأستاذ">Journal & Ledger</span>
</a>
<a href="index.php?page=categories" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'categories' ? 'active' : '' ?>">
<i class="bi bi-grid"></i> <span data-en="Categories" data-ar="الفئات">Categories</span>
<a href="index.php?page=accounting&view=trial_balance" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'active' : '' ?>">
<i class="fas fa-scale-balanced"></i> <span data-en="Trial Balance" data-ar="ميزان المراجعة">Trial Balance</span>
</a>
<a href="index.php?page=units" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'units' ? 'active' : '' ?>">
<i class="bi bi-rulers"></i> <span data-en="Units" data-ar="الوحدات">Units</span>
<a href="index.php?page=accounting&view=profit_loss" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'profit_loss' ? 'active' : '' ?>">
<i class="fas fa-chart-column"></i> <span data-en="Profit & Loss" data-ar="الأرباح والخسائر">Profit & Loss</span>
</a>
</div>
<?php endif; ?>
<!-- CRM Section -->
<?php if (can('customers_view') || can('suppliers_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customers', 'suppliers']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#crm-collapse">
<span data-en="People" data-ar="الأشخاص">People</span>
<i class="bi bi-chevron-down"></i>
</div>
<div class="collapse <?= in_array($page, ['customers', 'suppliers']) ? 'show' : '' ?>" id="crm-collapse">
<?php if (can('customers_view')): ?>
<a href="index.php?page=customers" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'customers' ? 'active' : '' ?>">
<i class="bi bi-people"></i> <span data-en="Customers" data-ar="العملاء">Customers</span>
<a href="index.php?page=accounting&view=balance_sheet" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'balance_sheet' ? 'active' : '' ?>">
<i class="fas fa-file-contract"></i> <span data-en="Balance Sheet" data-ar="الميزانية العمومية">Balance Sheet</span>
</a>
<?php endif; ?>
<?php if (can('suppliers_view')): ?>
<a href="index.php?page=suppliers" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'suppliers' ? 'active' : '' ?>">
<i class="bi bi-truck"></i> <span data-en="Suppliers" data-ar="الموردين">Suppliers</span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Reports Section -->
<?php if (can('sales_view') || can('purchases_view') || can('items_view') || can('customers_view') || can('suppliers_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customer_statement', 'supplier_statement', 'cashflow_report', 'expiry_report', 'low_stock_report']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#reports-collapse">
<span data-en="Reports" data-ar="التقارير">Reports</span>
<i class="bi bi-chevron-down"></i>
</div>
<div class="collapse <?= in_array($page, ['customer_statement', 'supplier_statement', 'cashflow_report', 'expiry_report', 'low_stock_report', 'loyalty_history']) ? 'show' : '' ?>" id="reports-collapse">
<?php if (can('customers_view')): ?>
<a href="index.php?page=customer_statement" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'customer_statement' ? 'active' : '' ?>">
<i class="bi bi-file-earmark-person"></i> <span data-en="Customer Statement" data-ar="كشف حساب عميل">Customer Statement</span>
</a>
<?php endif; ?>
<?php if (can('suppliers_view')): ?>
<a href="index.php?page=supplier_statement" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'supplier_statement' ? 'active' : '' ?>">
<i class="bi bi-file-earmark-medical"></i> <span data-en="Supplier Statement" data-ar="كشف حساب مورد">Supplier Statement</span>
</a>
<?php endif; ?>
<?php if (can('accounting_view')): ?>
<a href="index.php?page=cashflow_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'cashflow_report' ? 'active' : '' ?>">
<i class="bi bi-cash-stack"></i> <span data-en="Cashflow Statement" data-ar="قائمة التدفقات النقدية">Cashflow Statement</span>
</a>
<?php endif; ?>
<?php if (can('items_view')): ?>
<a href="index.php?page=expiry_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'expiry_report' ? 'active' : '' ?>">
<i class="bi bi-calendar-x"></i> <span data-en="Expiry Report" data-ar="تقرير الانتهاء">Expiry Report</span>
</a>
<a href="index.php?page=low_stock_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'low_stock_report' ? 'active' : '' ?>">
<i class="bi bi-graph-down-arrow"></i> <span data-en="Low Stock Report" data-ar="تقرير نواقص المخزون">Low Stock Report</span>
</a>
<?php endif; ?>
<?php if (can('customers_view')): ?>
<a href="index.php?page=loyalty_history" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'loyalty_history' ? 'active' : '' ?>">
<i class="bi bi-star"></i> <span data-en="Loyalty History" data-ar="سجل الولاء">Loyalty History</span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Configuration Section -->
<?php if (can('settings_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['payment_methods', 'settings']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#config-collapse">
<span data-en="Configuration" data-ar="الإعدادات">Configuration</span>
<i class="bi bi-chevron-down"></i>
</div>
<div class="collapse <?= in_array($page, ['payment_methods', 'settings', 'devices']) ? 'show' : '' ?>" id="config-collapse">
<a href="index.php?page=payment_methods" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'payment_methods' ? 'active' : '' ?>">
<i class="bi bi-credit-card"></i> <span data-en="Payment Methods" data-ar="طرق الدفع">Payment Methods</span>
</a>
<a href="index.php?page=devices" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'devices' ? 'active' : '' ?>">
<i class="bi bi-fingerprint"></i> <span data-en="Biometric Devices" data-ar="أجهزة البصمة">Biometric Devices</span>
</a>
<a href="index.php?page=settings" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'settings' ? 'active' : '' ?>">
<i class="bi bi-building"></i> <span data-en="Company Profile" data-ar="ملف الشركة">Company Profile</span>
<a href="index.php?page=accounting&view=vat_report" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'vat_report' ? 'active' : '' ?>">
<i class="fas fa-receipt"></i> <span data-en="VAT Report" data-ar="تقرير الضريبة">VAT Report</span>
</a>
</div>
<?php endif; ?>
@ -1255,37 +1303,97 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<!-- HR Section -->
<?php if (can('hr_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['hr_employees', 'hr_departments', 'hr_attendance', 'hr_payroll']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#hr-collapse">
<span data-en="HR" data-ar="الموارد البشرية">HR</span>
<i class="bi bi-chevron-down"></i>
<span><i class="fas fa-user-tie group-icon"></i><span data-en="HR" data-ar="الموارد البشرية">HR</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['hr_employees', 'hr_departments', 'hr_attendance', 'hr_payroll']) ? 'show' : '' ?>" id="hr-collapse">
<a href="index.php?page=hr_departments" class="nav-link <?= $page === 'hr_departments' ? 'active' : '' ?>">
<i class="bi bi-building"></i> <span data-en="Departments" data-ar="الأقسام">Departments</span>
<i class="fas fa-building-user"></i> <span data-en="Departments" data-ar="الأقسام">Departments</span>
</a>
<a href="index.php?page=hr_employees" class="nav-link <?= $page === 'hr_employees' ? 'active' : '' ?>">
<i class="bi bi-person-badge"></i> <span data-en="Employees" data-ar="الموظفون">Employees</span>
<i class="fas fa-user-badge"></i> <span data-en="Employees" data-ar="الموظفون">Employees</span>
</a>
<a href="index.php?page=hr_attendance" class="nav-link <?= $page === 'hr_attendance' ? 'active' : '' ?>">
<i class="bi bi-calendar-check"></i> <span data-en="Attendance" data-ar="الحضور">Attendance</span>
<i class="fas fa-user-check"></i> <span data-en="Attendance" data-ar="الحضور">Attendance</span>
</a>
<a href="index.php?page=hr_payroll" class="nav-link <?= $page === 'hr_payroll' ? 'active' : '' ?>">
<i class="bi bi-cash-coin"></i> <span data-en="Payroll" data-ar="الرواتب">Payroll</span>
<i class="fas fa-sack-dollar"></i> <span data-en="Payroll" data-ar="الرواتب">Payroll</span>
</a>
</div>
<?php endif; ?>
<!-- Administration Section -->
<!-- Reports Section -->
<?php if (can('sales_view') || can('purchases_view') || can('items_view') || can('customers_view') || can('suppliers_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customer_statement', 'supplier_statement', 'cashflow_report', 'expiry_report', 'low_stock_report']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#reports-collapse">
<span><i class="fas fa-chart-line group-icon"></i><span data-en="Reports" data-ar="التقارير">Reports</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['customer_statement', 'supplier_statement', 'cashflow_report', 'expiry_report', 'low_stock_report', 'loyalty_history']) ? 'show' : '' ?>" id="reports-collapse">
<?php if (can('customers_view')): ?>
<a href="index.php?page=customer_statement" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'customer_statement' ? 'active' : '' ?>">
<i class="fas fa-file-invoice"></i> <span data-en="Customer Statement" data-ar="كشف حساب عميل">Customer Statement</span>
</a>
<?php endif; ?>
<?php if (can('suppliers_view')): ?>
<a href="index.php?page=supplier_statement" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'supplier_statement' ? 'active' : '' ?>">
<i class="fas fa-file-lines"></i> <span data-en="Supplier Statement" data-ar="كشف حساب مورد">Supplier Statement</span>
</a>
<?php endif; ?>
<?php if (can('accounting_view')): ?>
<a href="index.php?page=cashflow_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'cashflow_report' ? 'active' : '' ?>">
<i class="fas fa-money-bill-transfer"></i> <span data-en="Cashflow Statement" data-ar="قائمة التدفقات النقدية">Cashflow Statement</span>
</a>
<?php endif; ?>
<?php if (can('items_view')): ?>
<a href="index.php?page=expiry_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'expiry_report' ? 'active' : '' ?>">
<i class="fas fa-calendar-xmark"></i> <span data-en="Expiry Report" data-ar="تقرير الانتهاء">Expiry Report</span>
</a>
<a href="index.php?page=low_stock_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'low_stock_report' ? 'active' : '' ?>">
<i class="fas fa-arrow-trend-down"></i> <span data-en="Low Stock Report" data-ar="تقرير نواقص المخزون">Low Stock Report</span>
</a>
<?php endif; ?>
<?php if (can('customers_view')): ?>
<a href="index.php?page=loyalty_history" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'loyalty_history' ? 'active' : '' ?>">
<i class="fas fa-award"></i> <span data-en="Loyalty History" data-ar="سجل الولاء">Loyalty History</span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Configurations Section -->
<?php if (can('settings_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['payment_methods', 'settings', 'devices']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#config-collapse">
<span><i class="fas fa-sliders group-icon"></i><span data-en="Configurations" data-ar="الإعدادات">Configurations</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['payment_methods', 'settings', 'devices']) ? 'show' : '' ?>" id="config-collapse">
<a href="index.php?page=payment_methods" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'payment_methods' ? 'active' : '' ?>">
<i class="fas fa-credit-card"></i> <span data-en="Payment Methods" data-ar="طرق الدفع">Payment Methods</span>
</a>
<a href="index.php?page=devices" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'devices' ? 'active' : '' ?>">
<i class="fas fa-id-card"></i> <span data-en="Biometric Devices" data-ar="أجهزة البصمة">Biometric Devices</span>
</a>
<a href="index.php?page=settings" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'settings' ? 'active' : '' ?>">
<i class="fas fa-building-gear"></i> <span data-en="Company Profile" data-ar="ملف الشركة">Company Profile</span>
</a>
</div>
<?php endif; ?>
<!-- Administrations Section -->
<?php if (can('users_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['role_groups', 'users']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#admin-collapse">
<span data-en="Administration" data-ar="الإدارة">Administration</span>
<i class="bi bi-chevron-down"></i>
<span><i class="fas fa-user-gear group-icon"></i><span data-en="Administrations" data-ar="الإدارة">Administrations</span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['role_groups', 'users']) ? 'show' : '' ?>" id="admin-collapse">
<a href="index.php?page=role_groups" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
<i class="bi bi-shield-lock"></i> <span data-en="Role Groups" data-ar="مجموعات الأدوار">Role Groups</span>
<i class="fas fa-user-shield"></i> <span data-en="Role Groups" data-ar="مجموعات الأدوار">Role Groups</span>
</a>
<a href="index.php?page=users" class="nav-link <?= $page === 'users' ? 'active' : '' ?>">
<i class="bi bi-people"></i> <span data-en="Users" data-ar="المستخدمين">Users</span>
<i class="fas fa-users-gear"></i> <span data-en="Users" data-ar="المستخدمين">Users</span>
</a>
<a href="index.php?page=backups" class="nav-link <?= $page === 'backups' ? 'active' : '' ?>">
<i class="fas fa-database"></i> <span data-en="Backups" data-ar="نسخ احتياطي">Backups</span>
</a>
</div>
<?php endif; ?>
@ -1327,6 +1435,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
'cashflow_report' => ['en' => 'Cashflow Statement', 'ar' => 'قائمة التدفقات النقدية'],
'loyalty_history' => ['en' => 'Loyalty History', 'ar' => 'سجل الولاء'],
'users' => ['en' => 'User Management', 'ar' => 'إدارة المستخدمين'],
'backups' => ['en' => 'Database Backups', 'ar' => 'نسخ قاعدة البيانات'],
'role_groups' => ['en' => 'Role Groups', 'ar' => 'مجموعات الأدوار'],
];
$currTitle = $titles[$page] ?? $titles['dashboard'];
@ -1342,18 +1451,9 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<div class="fw-bold small">
<a href="index.php?page=my_profile" class="text-dark text-decoration-none">
<?= htmlspecialchars((string)($_SESSION['username'] ?? 'User')) ?>
</div>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars((string)($_SESSION['user_role_name'] ?? '')) ?></div>
</a>
</div>
</div>
<div class="ms-auto d-none d-lg-block">
<?php if (!empty($_SESSION['profile_pic'])): ?>
<div class="rounded-circle overflow-hidden border border-2 border-primary border-opacity-10" style="width: 38px; height: 38px;">
<img src="<?= htmlspecialchars((string)$_SESSION['profile_pic']) ?>?v=<?= time() ?>" alt="Profile" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<?php if (isset($_SESSION['user_role_name']) && strcasecmp($_SESSION['username'] ?? '', $_SESSION['user_role_name']) !== 0 && $_SESSION['user_role_name'] !== 'Administrator'): ?>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars($_SESSION['user_role_name']) ?></div>
<?php endif; ?>
<div class="text-muted" style="font-size: 0.7rem;"><?= htmlspecialchars((string)($_SESSION['user_role_name'] ?? '')) ?></div>
</div>
<div class="dropdown">
<a href="index.php?page=my_profile" class="btn btn-light rounded-circle p-0 overflow-hidden shadow-sm d-inline-block position-relative" style="width: 40px; height: 40px;" title="Edit Profile">
@ -1907,7 +2007,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<?php endif; ?>
</div>
</td>
<td><?= $item['expiry_date'] ?: '---' ?></td>
<td><?= !empty($item['expiry_date']) ? htmlspecialchars((string)$item['expiry_date']) : '---' ?></td>
<td><?= number_format((float)$item['vat_rate'], 3) ?>%</td>
<td>
<div class="btn-group btn-group-sm">
@ -2104,8 +2204,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<?php endif; ?>
<?php foreach ($data['expiry_items'] as $item): ?>
<?php
$is_expired = strtotime($item['expiry_date']) <= strtotime(date('Y-m-d'));
$is_near = !$is_expired && strtotime($item['expiry_date']) <= strtotime(date('Y-m-d', strtotime('+30 days')));
$expiry_date = $item['expiry_date'] ?? '';
$expiry_ts = $expiry_date !== '' ? strtotime($expiry_date) : null;
$is_expired = $expiry_ts ? $expiry_ts <= strtotime(date('Y-m-d')) : false;
$is_near = $expiry_ts ? (!$is_expired && $expiry_ts <= strtotime(date('Y-m-d', strtotime('+30 days')))) : false;
?>
<tr class="<?= $is_expired ? 'table-danger' : ($is_near ? 'table-warning' : '') ?>">
<td><?= htmlspecialchars($item['sku']) ?></td>
@ -2115,7 +2217,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</td>
<td><?= htmlspecialchars($item['cat_en'] ?? '---') ?></td>
<td><?= number_format((float)$item['stock_quantity'], 3) ?></td>
<td><?= htmlspecialchars($item['expiry_date']) ?></td>
<td><?= $expiry_date !== '' ? htmlspecialchars((string)$expiry_date) : '---' ?></td>
<td>
<?php if ($is_expired): ?>
<span class="badge bg-danger" data-en="Expired" data-ar="منتهي">Expired</span>
@ -5021,6 +5123,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<label class="form-label fw-semibold" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
<input type="email" name="email" class="form-control rounded-3" value="<?= htmlspecialchars($data['user']['email'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold" data-en="Phone" data-ar="الهاتف">Phone</label>
<input type="text" name="phone" class="form-control rounded-3" value="<?= htmlspecialchars($data['user']['phone'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold" data-en="New Password" data-ar="كلمة مرور جديدة">New Password</label>
<input type="password" name="password" class="form-control rounded-3" placeholder="Leave blank to keep current">
@ -5239,6 +5345,99 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
</div>
</div>
<?php elseif ($page === 'backups'): ?>
<div class="row g-4">
<div class="col-md-4">
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-white py-3 border-0">
<h5 class="card-title mb-0" data-en="Backup Settings" data-ar="إعدادات النسخ الاحتياطي">Backup Settings</h5>
</div>
<div class="card-body">
<form method="POST">
<div class="mb-3">
<label class="form-label small fw-semibold" data-en="Keep Last N Backups" data-ar="الاحتفاظ بآخر N نسخ">Keep Last N Backups</label>
<input type="number" name="backup_limit" class="form-control" value="<?= htmlspecialchars($data['backup_settings']['backup_limit'] ?? '5') ?>" min="1" max="50">
</div>
<div class="mb-3">
<label class="form-label small fw-semibold" data-en="Auto Backup Time" data-ar="وقت النسخ التلقائي">Auto Backup Time</label>
<input type="time" name="backup_time" class="form-control" value="<?= htmlspecialchars($data['backup_settings']['backup_time'] ?? '00:00') ?>">
</div>
<div class="mb-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" name="backup_auto_enabled" value="1" id="autoBackupSwitch" <?= ($data['backup_settings']['backup_auto_enabled'] ?? '0') === '1' ? 'checked' : '' ?>>
<label class="form-check-label small fw-semibold" for="autoBackupSwitch" data-en="Enable Automated Backups" data-ar="تفعيل النسخ الاحتياطي التلقائي">Enable Automated Backups</label>
</div>
<small class="text-muted" data-en="Requires a cron job running cron_backup.php every minute to respect the scheduled time." data-ar="يتطلب تعيين مهمة مجدولة (cron) لتشغيل cron_backup.php كل دقيقة للالتزام بالوقت المحدد.">Requires a cron job running cron_backup.php every minute to respect the scheduled time.</small>
</div>
<button type="submit" name="save_backup_settings" class="btn btn-primary w-100" data-en="Save Settings" data-ar="حفظ الإعدادات">Save Settings</button>
</form>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body text-center py-4">
<div class="bg-primary bg-opacity-10 rounded-circle d-inline-flex align-items-center justify-content-center mb-3" style="width: 60px; height: 60px;">
<i class="bi bi-cloud-upload text-primary fs-3"></i>
</div>
<h5 data-en="Manual Backup" data-ar="نسخ يدوي">Manual Backup</h5>
<p class="text-muted small" data-en="Create a database backup immediately." data-ar="إنشاء نسخة احتياطية من قاعدة البيانات فوراً.">Create a database backup immediately.</p>
<form method="POST">
<button type="submit" name="create_backup" class="btn btn-outline-primary" data-en="Backup Now" data-ar="نسخ الآن">Backup Now</button>
</form>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card border-0 shadow-sm">
<div class="card-header bg-white py-3 border-0 d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0" data-en="Available Backups" data-ar="النسخ المتاحة">Available Backups</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4" data-en="Filename" data-ar="اسم الملف">Filename</th>
<th data-en="Size" data-ar="الحجم">Size</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th class="text-end pe-4" data-en="Actions" data-ar="الإجراءات">Actions</th>
</tr>
</thead>
<tbody>
<?php if (empty($data['backups'])): ?>
<tr>
<td colspan="4" class="text-center py-4 text-muted" data-en="No backups found." data-ar="لا توجد نسخ احتياطية.">No backups found.</td>
</tr>
<?php else: ?>
<?php foreach ($data['backups'] as $b): ?>
<tr>
<td class="ps-4 fw-medium"><?= htmlspecialchars($b['name']) ?></td>
<td><?= htmlspecialchars($b['size']) ?></td>
<td><?= htmlspecialchars($b['date']) ?></td>
<td class="text-end pe-4">
<div class="btn-group">
<form method="POST" class="d-inline" onsubmit="return confirm('Restore this backup? Current data will be overwritten!');">
<input type="hidden" name="filename" value="<?= htmlspecialchars($b['name']) ?>">
<button type="submit" name="restore_backup" class="btn btn-sm btn-outline-success" title="Restore" data-en="Restore" data-ar="استعادة"><i class="bi bi-arrow-counterclockwise"></i></button>
</form>
<form method="POST" class="d-inline" onsubmit="return confirm('Permanently delete this backup?');">
<input type="hidden" name="filename" value="<?= htmlspecialchars($b['name']) ?>">
<button type="submit" name="delete_backup" class="btn btn-sm btn-outline-danger ms-1" title="Delete" data-en="Delete" data-ar="حذف"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<?php elseif ($page === 'users'): ?>
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center border-0">
@ -5258,7 +5457,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<tr>
<th class="ps-4" data-en="User Info" data-ar="معلومات المستخدم">User Info</th>
<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
<th data-en="Email" data-ar="البريد">Email</th>
<th data-en="Contact" data-ar="الاتصال">Contact</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end pe-4">Actions</th>
</tr>
@ -5286,7 +5485,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<?= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
</span>
</td>
<td><span class="text-muted small"><?= htmlspecialchars((string)($u['email'] ?? '')) ?></span></td>
<td>
<div class="text-dark small mb-1"><i class="bi bi-envelope me-1"></i> <?= htmlspecialchars((string)($u['email'] ?? '')) ?></div>
<div class="text-muted small"><i class="bi bi-phone me-1"></i> <?= htmlspecialchars((string)($u['phone'] ?? '-')) ?></div>
</td>
<td>
<?php if ($u['status'] === 'active'): ?>
<span class="badge rounded-pill bg-success bg-opacity-10 text-success px-3">Active</span>
@ -5334,6 +5536,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<label class="form-label fw-semibold" data-en="Email Address" data-ar="البريد الإلكتروني">Email Address</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars((string)($u['email'] ?? '')) ?>">
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-en="Phone Number" data-ar="رقم الهاتف">Phone Number</label>
<input type="text" name="phone" class="form-control" value="<?= htmlspecialchars((string)($u['phone'] ?? '')) ?>">
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-en="Assign Role Group" data-ar="تعيين مجموعة الأدوار">Assign Role Group</label>
<select name="group_id" class="form-select">
@ -5521,6 +5727,10 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
<label class="form-label" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
<input type="email" name="email" class="form-control" autocomplete="email">
</div>
<div class="mb-3">
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
<input type="text" name="phone" class="form-control" autocomplete="tel">
</div>
<div class="mb-3">
<label class="form-label" data-en="Password" data-ar="كلمة المرور">Password</label>
<input type="password" name="password" class="form-control" required autocomplete="new-password">

258
installation/index.php Normal file
View File

@ -0,0 +1,258 @@
<?php
session_start();
$lockFile = __DIR__ . '/../installed.lock';
if (file_exists($lockFile)) {
die("Installation already completed. Please remove 'installed.lock' if you want to re-install.");
}
$step = isset($_GET['step']) ? (int)$_GET['step'] : 1;
$error = '';
$success = '';
// Step 1: Requirements
if ($step === 1) {
$requirements = [
'php_version' => [
'name' => 'PHP Version (>= 8.0)',
'status' => version_compare(PHP_VERSION, '8.0.0', '>='),
'current' => PHP_VERSION
],
'pdo_mysql' => [
'name' => 'PDO MySQL Extension',
'status' => extension_loaded('pdo_mysql'),
'current' => extension_loaded('pdo_mysql') ? 'Loaded' : 'Missing'
],
'config_writable' => [
'name' => 'db/config.php Writable',
'status' => is_writable(__DIR__ . '/../db/config.php'),
'current' => is_writable(__DIR__ . '/../db/config.php') ? 'Yes' : 'No'
],
'root_writable' => [
'name' => 'Root Directory Writable',
'status' => is_writable(__DIR__ . '/..'),
'current' => is_writable(__DIR__ . '/..') ? 'Yes' : 'No'
]
];
$allOk = true;
foreach ($requirements as $req) {
if (!$req['status']) $allOk = false;
}
}
// Step 2: Database
if ($step === 2 && $_SERVER['REQUEST_METHOD'] === 'POST') {
$host = $_POST['db_host'] ?? '';
$name = $_POST['db_name'] ?? '';
$user = $_POST['db_user'] ?? '';
$pass = $_POST['db_pass'] ?? '';
try {
$pdo = new PDO("mysql:host=$host;dbname=$name", $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
// Save to config.php
$configContent = "<?php\ndefine('DB_HOST', '$host');\ndefine('DB_NAME', '$name');\ndefine('DB_USER', '$user');\ndefine('DB_PASS', '$pass');\n\nfunction db() {\n static \$pdo;\n if (!\$pdo) {\n \$pdo = new PDO('mysql:host='.DB_HOST.';dbname='.DB_NAME.';charset=utf8mb4', DB_USER, DB_PASS, [\n PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,\n PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,\n ]);\n }\n return \$pdo;\n}\n";
file_put_contents(__DIR__ . '/../db/config.php', $configContent);
header("Location: index.php?step=3");
exit;
} catch (PDOException $e) {
$error = "Database Connection Failed: " . $e->getMessage();
}
}
// Step 3: Super Admin
if ($step === 3 && $_SERVER['REQUEST_METHOD'] === 'POST') {
require_once __DIR__ . '/../db/config.php';
$adminUser = $_POST['admin_user'] ?? '';
$adminPass = $_POST['admin_pass'] ?? '';
$adminEmail = $_POST['admin_email'] ?? '';
try {
$pdo = db();
// Create tables if they don't exist
$pdo->exec("CREATE TABLE IF NOT EXISTS role_groups (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
permissions TEXT
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
group_id INT,
status ENUM('active', 'inactive') DEFAULT 'active',
profile_pic VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)");
// Insert Default Admin Role if missing
$stmt = $pdo->prepare("SELECT id FROM role_groups WHERE name = 'Administrator'");
$stmt->execute();
$role = $stmt->fetch();
if (!$role) {
$pdo->exec("INSERT INTO role_groups (name, permissions) VALUES ('Administrator', 'all')");
$roleId = $pdo->lastInsertId();
} else {
$roleId = $role['id'];
}
// Insert Admin User
$hashedPass = password_hash($adminPass, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("INSERT INTO users (username, password, email, group_id, status) VALUES (?, ?, ?, ?, 'active')");
$stmt->execute([$adminUser, $hashedPass, $adminEmail, $roleId]);
header("Location: index.php?step=4");
exit;
} catch (Exception $e) {
$error = "Setup Failed: " . $e->getMessage();
}
}
// Step 4: Finish
if ($step === 4) {
file_put_contents($lockFile, date('Y-m-d H:i:s'));
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Installation - Step <?= $step ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
<style>
body { background: #f0f2f5; font-family: 'Inter', sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; }
.install-container { width: 100%; max-width: 600px; padding: 20px; }
.card { border: none; border-radius: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.08); overflow: hidden; }
.card-header { background: linear-gradient(135deg, #6e8efb, #a777e3); color: white; border: none; padding: 30px; text-align: center; }
.step-indicator { display: flex; justify-content: space-between; margin-bottom: 30px; position: relative; }
.step-indicator::before { content: ''; position: absolute; top: 15px; left: 0; right: 0; height: 2px; background: #e0e0e0; z-index: 1; }
.step-dot { width: 32px; height: 32px; background: #fff; border: 2px solid #e0e0e0; border-radius: 50%; display: flex; align-items: center; justify-content: center; z-index: 2; font-weight: bold; font-size: 14px; color: #999; }
.step-dot.active { border-color: #6e8efb; color: #6e8efb; background: #fff; box-shadow: 0 0 10px rgba(110, 142, 251, 0.3); }
.step-dot.completed { background: #6e8efb; border-color: #6e8efb; color: #fff; }
.btn-primary { background: linear-gradient(135deg, #6e8efb, #a777e3); border: none; border-radius: 12px; padding: 12px 24px; font-weight: 600; }
.form-control { border-radius: 12px; padding: 12px; border: 1px solid #dee2e6; }
</style>
</head>
<body>
<div class="install-container">
<div class="card">
<div class="card-header">
<h3 class="fw-bold mb-0">System Installation</h3>
<p class="mb-0 opacity-75">Configure your application in minutes</p>
</div>
<div class="card-body p-4 p-md-5">
<div class="step-indicator">
<div class="step-dot <?= $step >= 1 ? ($step > 1 ? 'completed' : 'active') : '' ?>"><?= $step > 1 ? '<i class="bi bi-check"></i>' : '1' ?></div>
<div class="step-dot <?= $step >= 2 ? ($step > 2 ? 'completed' : 'active') : '' ?>"><?= $step > 2 ? '<i class="bi bi-check"></i>' : '2' ?></div>
<div class="step-dot <?= $step >= 3 ? ($step > 3 ? 'completed' : 'active') : '' ?>"><?= $step > 3 ? '<i class="bi bi-check"></i>' : '3' ?></div>
<div class="step-dot <?= $step >= 4 ? 'completed' : '' ?>">4</div>
</div>
<?php if ($error): ?>
<div class="alert alert-danger d-flex align-items-center mb-4">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<div><?= $error ?></div>
</div>
<?php endif; ?>
<?php if ($step === 1): ?>
<h5 class="fw-bold mb-4">Step 1: Check Requirements</h5>
<div class="list-group list-group-flush mb-4">
<?php foreach ($requirements as $req): ?>
<div class="list-group-item d-flex justify-content-between align-items-center py-3">
<div>
<h6 class="mb-0 fw-semibold"><?= $req['name'] ?></h6>
<small class="text-muted">Current: <?= $req['current'] ?></small>
</div>
<?php if ($req['status']): ?>
<span class="badge bg-success-subtle text-success rounded-pill px-3">Passed</span>
<?php else: ?>
<span class="badge bg-danger-subtle text-danger rounded-pill px-3">Failed</span>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<?php if ($allOk): ?>
<div class="d-grid">
<a href="index.php?step=2" class="btn btn-primary">Next: Database Setup</a>
</div>
<?php else: ?>
<div class="alert alert-warning small">Please fix the issues above to continue.</div>
<div class="d-grid">
<button onclick="window.location.reload()" class="btn btn-secondary">Retry Check</button>
</div>
<?php endif; ?>
<?php elseif ($step === 2): ?>
<h5 class="fw-bold mb-4">Step 2: Database Configuration</h5>
<form method="POST">
<div class="mb-3">
<label class="form-label small fw-bold">Database Host</label>
<input type="text" name="db_host" class="form-control" value="127.0.0.1" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">Database Name</label>
<input type="text" name="db_name" class="form-control" placeholder="e.g. admin_db" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">Database User</label>
<input type="text" name="db_user" class="form-control" placeholder="e.g. root" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">Database Password</label>
<input type="password" name="db_pass" class="form-control" placeholder="Enter password">
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Verify & Save Config</button>
</div>
</form>
<?php elseif ($step === 3): ?>
<h5 class="fw-bold mb-4">Step 3: Super Admin Setup</h5>
<form method="POST">
<div class="mb-3">
<label class="form-label small fw-bold">Admin Username</label>
<input type="text" name="admin_user" class="form-control" placeholder="e.g. admin" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">Admin Email</label>
<input type="email" name="admin_email" class="form-control" placeholder="admin@example.com" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold">Admin Password</label>
<input type="password" name="admin_pass" class="form-control" placeholder="Minimum 6 characters" required minlength="6">
</div>
<div class="d-grid mt-4">
<button type="submit" class="btn btn-primary">Finish Installation</button>
</div>
</form>
<?php elseif ($step === 4): ?>
<div class="text-center py-4">
<div class="display-1 text-success mb-4">
<i class="bi bi-check-circle-fill"></i>
</div>
<h4 class="fw-bold">Ready to Launch!</h4>
<p class="text-muted">Installation completed successfully. Your system is now secure and ready to use.</p>
<div class="alert alert-info small mt-4">
<i class="bi bi-info-circle me-2"></i>
For security, the <code>installed.lock</code> file has been created.
</div>
<div class="d-grid mt-5">
<a href="../index.php" class="btn btn-primary">Go to Dashboard</a>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,8 @@
2026-02-18 09:15:41 - POST: {"username":"admin","password":"admin","login":""}
2026-02-18 09:30:37 - POST: {"username":"moosa","email":"aalabry@gmail.com","password":"123456","group_id":"1","add_user":""}
2026-02-18 09:32:14 - POST: {"name":"Cashier","permissions":["pos_view","pos_add","items_view","items_add"],"add_role_group":""}
2026-02-18 09:33:29 - POST: {"name":"Admin","permissions":["pos_view","pos_add","pos_edit","quotations_view","quotations_add","quotations_edit","customers_view","customers_add","customers_edit","suppliers_view","suppliers_add","suppliers_edit","sales_view","sales_add","sales_edit","purchases_view","purchases_add","purchases_edit","hr_view","hr_add","hr_edit","hr_delete","users_view"],"add_role_group":""}
2026-02-18 09:34:03 - POST: {"name":"Accountant","permissions":["accounting_view","accounting_add","accounting_edit"],"add_role_group":""}
2026-02-18 10:10:35 - POST: {"name":"Accountant","permissions":["accounting_view","accounting_add","accounting_edit"],"add_role_group":""}
2026-02-18 10:10:56 - POST: {"id":"7","delete_role_group":""}
2026-02-18 10:12:05 - POST: {"create_backup":""}