0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'None',
]);
}
session_start();
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 'lib/LicenseService.php';
// Licensing Middleware
$is_activated = LicenseService::isActivated();
$page = $_GET['page'] ?? 'dashboard';
if (!$is_activated && $page !== 'activate') {
header("Location: index.php?page=activate");
exit;
}
// Activation Page UI (accessible without login)
if ($page === 'activate') {
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['activate'])) {
$res = LicenseService::activate($_POST['license_key'] ?? '');
if ($res['success']) {
$success = "System activated successfully! Redirecting...";
header("refresh:2;url=index.php");
} else {
$error = $res['error'];
}
}
?>
Welcome Back
Please enter your details to sign in
= $login_error ?>
0) {
$result .= " and " . $baisasWords . " Baisas";
}
return $result . " Only";
}
function getPromotionalPrice($item) {
$price = (float)$item['sale_price'];
if (isset($item['is_promotion']) && $item['is_promotion']) {
$today = date('Y-m-d');
$start = !empty($item['promotion_start']) ? $item['promotion_start'] : null;
$end = !empty($item['promotion_end']) ? $item['promotion_end'] : null;
$active = true;
if ($start && $today < $start) $active = false;
if ($end && $today > $end) $active = false;
if ($active) {
$price = $price * (1 - (float)$item['promotion_percent'] / 100);
}
}
return $price;
}
// --- HR Handlers ---
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
error_log("POST Request detected. Action: " . (print_r($_POST, true)));
}
if (isset($_POST['add_hr_department'])) {
$name = $_POST['name'] ?? '';
if ($name) {
$stmt = db()->prepare("INSERT INTO hr_departments (name) VALUES (?)");
$stmt->execute([$name]);
$message = "Department added successfully!";
}
}
if (isset($_POST['edit_hr_department'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
if ($id && $name) {
$stmt = db()->prepare("UPDATE hr_departments SET name = ? WHERE id = ?");
$stmt->execute([$name, $id]);
$message = "Department updated successfully!";
}
}
if (isset($_POST['delete_hr_department'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_departments WHERE id = ?");
$stmt->execute([$id]);
$message = "Department deleted successfully!";
}
}
if (isset($_POST['add_hr_employee'])) {
$dept_id = (int)$_POST['department_id'] ?: null;
$biometric_id = $_POST['biometric_id'] ?: null;
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$pos = $_POST['position'] ?? '';
$salary = (float)($_POST['salary'] ?? 0);
$j_date = $_POST['joining_date'] ?: null;
if ($name) {
$stmt = db()->prepare("INSERT INTO hr_employees (department_id, biometric_id, name, email, phone, position, salary, joining_date) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$dept_id, $biometric_id, $name, $email, $phone, $pos, $salary, $j_date]);
$message = "Employee added successfully!";
}
}
if (isset($_POST['edit_hr_employee'])) {
$id = (int)$_POST['id'];
$dept_id = (int)$_POST['department_id'] ?: null;
$biometric_id = $_POST['biometric_id'] ?: null;
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$pos = $_POST['position'] ?? '';
$salary = (float)($_POST['salary'] ?? 0);
$j_date = $_POST['joining_date'] ?: null;
$status = $_POST['status'] ?? 'active';
if ($id && $name) {
$stmt = db()->prepare("UPDATE hr_employees SET department_id = ?, biometric_id = ?, name = ?, email = ?, phone = ?, position = ?, salary = ?, joining_date = ?, status = ? WHERE id = ?");
$stmt->execute([$dept_id, $biometric_id, $name, $email, $phone, $pos, $salary, $j_date, $status, $id]);
$message = "Employee updated successfully!";
}
}
if (isset($_POST['delete_hr_employee'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_employees WHERE id = ?");
$stmt->execute([$id]);
$message = "Employee deleted successfully!";
}
}
if (isset($_POST['mark_attendance'])) {
$emp_id = (int)$_POST['employee_id'];
$date = $_POST['attendance_date'] ?: date('Y-m-d');
$status = $_POST['status'] ?? 'present';
$in = $_POST['clock_in'] ?: null;
$out = $_POST['clock_out'] ?: null;
if ($emp_id) {
$stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in, clock_out) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE status = ?, clock_in = ?, clock_out = ?");
$stmt->execute([$emp_id, $date, $status, $in, $out, $status, $in, $out]);
$message = "Attendance marked successfully!";
}
}
if (isset($_POST['generate_payroll'])) {
$emp_id = (int)$_POST['employee_id'];
$month = (int)$_POST['month'];
$year = (int)$_POST['year'];
$bonus = (float)($_POST['bonus'] ?? 0);
$deductions = (float)($_POST['deductions'] ?? 0);
$emp = db()->prepare("SELECT salary FROM hr_employees WHERE id = ?");
$emp->execute([$emp_id]);
$salary = (float)$emp->fetchColumn();
$net = $salary + $bonus - $deductions;
try {
$stmt = db()->prepare("INSERT INTO hr_payroll (employee_id, payroll_month, payroll_year, basic_salary, bonus, deductions, net_salary, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')");
$stmt->execute([$emp_id, $month, $year, $salary, $bonus, $deductions, $net]);
$message = "Payroll generated successfully!";
} catch (PDOException $e) {
if ($e->getCode() == 23000) { // Integrity constraint violation
$message = "Error: Payroll already exists for this employee in the selected period.";
} else {
$message = "Error: " . $e->getMessage();
}
}
}
if (isset($_POST['pay_payroll'])) {
$id = (int)$_POST['id'];
if ($id) {
// Get payroll details for accounting
$stmt = db()->prepare("SELECT p.*, e.name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.id = ?");
$stmt->execute([$id]);
$payroll = $stmt->fetch();
if ($payroll && $payroll['status'] !== 'paid') {
$stmt = db()->prepare("UPDATE hr_payroll SET status = 'paid', payment_date = CURDATE() WHERE id = ?");
$stmt->execute([$id]);
// Accounting Integration
recordPayrollJournal($id, (float)$payroll['net_salary'], date('Y-m-d'), $payroll['name']);
$message = "Payroll marked as paid and recorded in accounting!";
} else {
$message = "Payroll already paid or not found.";
}
}
}
if (isset($_POST['delete_payroll'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_payroll WHERE id = ?");
$stmt->execute([$id]);
$message = "Payroll record deleted successfully!";
}
}
// --- Biometric Devices Handlers ---
if (isset($_POST['add_biometric_device'])) {
$name = $_POST['device_name'] ?? '';
$ip = $_POST['ip_address'] ?? '';
$port = (int)($_POST['port'] ?? 4370);
$io = $_POST['io_address'] ?? '';
$serial = $_POST['serial_number'] ?? '';
if ($name && $ip) {
$stmt = db()->prepare("INSERT INTO hr_biometric_devices (device_name, ip_address, port, io_address, serial_number) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$name, $ip, $port, $io, $serial]);
$message = "Device added successfully!";
}
}
if (isset($_POST['edit_biometric_device'])) {
$id = (int)$_POST['id'];
$name = $_POST['device_name'] ?? '';
$ip = $_POST['ip_address'] ?? '';
$port = (int)($_POST['port'] ?? 4370);
$io = $_POST['io_address'] ?? '';
$serial = $_POST['serial_number'] ?? '';
if ($id && $name && $ip) {
$stmt = db()->prepare("UPDATE hr_biometric_devices SET device_name = ?, ip_address = ?, port = ?, io_address = ?, serial_number = ? WHERE id = ?");
$stmt->execute([$name, $ip, $port, $io, $serial, $id]);
$message = "Device updated successfully!";
}
}
if (isset($_POST['delete_biometric_device'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_biometric_devices WHERE id = ?");
$stmt->execute([$id]);
$message = "Device deleted successfully!";
}
}
if (isset($_POST['pull_biometric_data'])) {
$devices = db()->query("SELECT * FROM hr_biometric_devices WHERE status = 'active'")->fetchAll();
if (empty($devices)) {
$message = "No active biometric devices found to pull data from.";
} else {
// Simulation of pulling data from multiple devices
$employees = db()->query("SELECT id, biometric_id FROM hr_employees WHERE biometric_id IS NOT NULL")->fetchAll();
$pulled_count = 0;
$device_count = 0;
$date = date('Y-m-d');
foreach ($devices as $device) {
$device_pulled = 0;
foreach ($employees as $emp) {
// Randomly simulate logs for each employee for this device
if (rand(0, 1)) {
$check_in = $date . ' ' . str_pad((string)rand(7, 9), 2, '0', STR_PAD_LEFT) . ':' . str_pad((string)rand(0, 59), 2, '0', STR_PAD_LEFT) . ':00';
$check_out = $date . ' ' . str_pad((string)rand(16, 18), 2, '0', STR_PAD_LEFT) . ':' . str_pad((string)rand(0, 59), 2, '0', STR_PAD_LEFT) . ':00';
// Log check-in
$stmt = db()->prepare("INSERT INTO hr_biometric_logs (biometric_id, device_id, employee_id, timestamp, type) VALUES (?, ?, ?, ?, 'check_in')");
$stmt->execute([$emp['biometric_id'], $device['id'], $emp['id'], $check_in]);
// Log check-out
$stmt = db()->prepare("INSERT INTO hr_biometric_logs (biometric_id, device_id, employee_id, timestamp, type) VALUES (?, ?, ?, ?, 'check_out')");
$stmt->execute([$emp['biometric_id'], $device['id'], $emp['id'], $check_out]);
$device_pulled += 2;
$pulled_count += 2;
$in_time = date('H:i:s', strtotime($check_in));
$out_time = date('H:i:s', strtotime($check_out));
// Update attendance record (earliest in, latest out)
$stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in, clock_out)
VALUES (?, ?, 'present', ?, ?)
ON DUPLICATE KEY UPDATE status = 'present',
clock_in = IF(clock_in IS NULL OR ? < clock_in, ?, clock_in),
clock_out = IF(clock_out IS NULL OR ? > clock_out, ?, clock_out)");
$stmt->execute([$emp['id'], $date, $in_time, $out_time, $in_time, $in_time, $out_time, $out_time]);
}
}
db()->prepare("UPDATE hr_biometric_devices SET last_sync = CURRENT_TIMESTAMP WHERE id = ?")->execute([$device['id']]);
$device_count++;
}
$message = "Successfully synced $device_count devices and pulled $pulled_count records.";
}
}
if (isset($_POST['test_device_connection'])) {
$id = (int)$_POST['id'];
$device = db()->prepare("SELECT * FROM hr_biometric_devices WHERE id = ?");
$device->execute([$id]);
$d = $device->fetch();
if ($d) {
// Simulated connection check
$message = "Connection to device '{$d['device_name']}' ({$d['ip_address']}) was successful! (Simulated)";
}
}
// --- 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'] ?? '';
$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, phone, group_id) VALUES (?, ?, ?, ?, ?)");
try {
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id]);
$message = "User added successfully!";
} catch (PDOException $e) {
if ($e->getCode() == '23000') {
$message = "Error: Username already exists.";
} else {
$message = "Error adding user: " . $e->getMessage();
}
}
}
}
if (isset($_POST['edit_role_group'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
$permissions = isset($_POST['permissions']) ? json_encode($_POST['permissions']) : '[]';
if ($id && $name) {
$stmt = db()->prepare("UPDATE role_groups SET name = ?, permissions = ? WHERE id = ?");
$stmt->execute([$name, $permissions, $id]);
$message = "Role Group updated successfully!";
}
}
if (isset($_POST['delete_role_group'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM role_groups WHERE id = ?");
$stmt->execute([$id]);
$message = "Role Group deleted successfully!";
}
}
if (isset($_POST['edit_user'])) {
$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 = ?, 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);
$stmt = db()->prepare("UPDATE users SET password = ? WHERE id = ?");
$stmt->execute([$hashed_password, $id]);
}
$message = "User updated successfully!";
}
}
if (isset($_POST['delete_user'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
$message = "User deleted successfully!";
}
}
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';
// Permission map for pages
$page_permissions = [
'dashboard' => 'dashboard_view',
'pos' => 'pos_view',
'sales' => 'sales_view',
'sales_returns' => 'sales_view',
'purchases' => 'purchases_view',
'purchase_returns' => 'purchases_view',
'quotations' => 'quotations_view',
'accounting' => 'accounting_view',
'expense_categories' => 'accounting_view',
'expenses' => 'accounting_view',
'items' => 'items_view',
'categories' => 'items_view',
'units' => 'items_view',
'customers' => 'customers_view',
'suppliers' => 'suppliers_view',
'customer_statement' => 'customers_view',
'supplier_statement' => 'suppliers_view',
'cashflow_report' => 'accounting_view',
'expiry_report' => 'items_view',
'low_stock_report' => 'items_view',
'loyalty_history' => 'customers_view',
'payment_methods' => 'settings_view',
'settings' => 'settings_view',
'devices' => 'settings_view',
'hr_departments' => 'hr_view',
'hr_employees' => 'hr_view',
'hr_attendance' => 'hr_view',
'hr_payroll' => 'hr_view',
'role_groups' => 'users_view',
'users' => 'users_view',
'backups' => 'users_view',
'logs' => 'users_view',
];
if (isset($page_permissions[$page]) && !can($page_permissions[$page])) {
$page = 'dashboard';
$message = "Access Denied: You don't have permission to view that module.";
}
$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';
$filename = $type . "_export_" . date('Y-m-d') . ".csv";
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=' . $filename);
$output = fopen('php://output', 'w');
// Add UTF-8 BOM for Excel
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
if ($type === 'sales' || $type === 'purchases') {
$invType = ($type === 'sales') ? 'sale' : 'purchase';
$where = ["v.type = ?"];
$params = [$invType];
if (!empty($_GET['search'])) { $where[] = "(v.id LIKE ? OR c.name LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; }
if (!empty($_GET['customer_id'])) { $where[] = "v.customer_id = ?"; $params[] = $_GET['customer_id']; }
if (!empty($_GET['start_date'])) { $where[] = "v.invoice_date >= ?"; $params[] = $_GET['start_date']; }
if (!empty($_GET['end_date'])) { $where[] = "v.invoice_date <= ?"; $params[] = $_GET['end_date']; }
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT v.id, c.name as customer_name, v.invoice_date, v.payment_type, v.status, v.total_with_vat, v.paid_amount, (v.total_with_vat - v.paid_amount) as balance
FROM invoices v LEFT JOIN customers c ON v.customer_id = c.id
WHERE $whereSql ORDER BY v.id DESC");
$stmt->execute($params);
fputcsv($output, ['Invoice ID', 'Customer/Supplier', 'Date', 'Payment', 'Status', 'Total', 'Paid', 'Balance']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $row);
} elseif ($type === 'customers' || $type === 'suppliers') {
$custType = ($type === 'suppliers') ? 'supplier' : 'customer';
$where = ["type = ?"];
$params = [$custType];
if (!empty($_GET['search'])) { $where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; }
if (!empty($_GET['start_date'])) { $where[] = "DATE(created_at) >= ?"; $params[] = $_GET['start_date']; }
if (!empty($_GET['end_date'])) { $where[] = "DATE(created_at) <= ?"; $params[] = $_GET['end_date']; }
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT id, name, email, phone, tax_id, balance, created_at FROM customers WHERE $whereSql ORDER BY id DESC");
$stmt->execute($params);
fputcsv($output, ['ID', 'Name', 'Email', 'Phone', 'Tax ID', 'Balance', 'Created At']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $row);
} elseif ($type === 'items') {
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) { $where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; }
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT i.sku, i.name_en, i.name_ar, c.name_en as category, i.purchase_price, i.sale_price, i.stock_quantity, i.vat_rate
FROM stock_items i LEFT JOIN stock_categories c ON i.category_id = c.id
WHERE $whereSql ORDER BY i.id DESC");
$stmt->execute($params);
fputcsv($output, ['SKU', 'Name (EN)', 'Name (AR)', 'Category', 'Purchase Price', 'Sale Price', 'Quantity', 'VAT %']);
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) fputcsv($output, $row);
}
fclose($output);
exit;
}
// Global data for modals
$data['categories'] = db()->query("SELECT * FROM stock_categories ORDER BY name_en ASC")->fetchAll();
$data['units'] = db()->query("SELECT * FROM stock_units ORDER BY name_en ASC")->fetchAll();
$data['suppliers'] = db()->query("SELECT * FROM customers WHERE type = 'supplier' ORDER BY name ASC")->fetchAll();
$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll();
$data['customers_list'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll();
$customers = $data['customers_list']; // For backward compatibility in some modals
$settings_raw = db()->query("SELECT * FROM settings")->fetchAll();
$data['settings'] = [];
foreach ($settings_raw as $s) {
$data['settings'][$s['key']] = $s['value'];
}
switch ($page) {
case 'suppliers':
case 'customers':
$type = ($page === 'suppliers') ? 'supplier' : 'customer';
$where = ["type = ?"];
$params = [$type];
if (!empty($_GET['search'])) {
$where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['start_date'])) {
$where[] = "DATE(created_at) >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "DATE(created_at) <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT * FROM customers WHERE $whereSql ORDER BY id DESC");
$stmt->execute($params);
$data['customers'] = $stmt->fetchAll();
break;
case 'categories':
// Already fetched globally
break;
case 'units':
// Already fetched globally
break;
case 'items':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name
FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN stock_units u ON i.unit_id = u.id
LEFT JOIN customers s ON i.supplier_id = s.id
WHERE $whereSql
ORDER BY i.id DESC");
$stmt->execute($params);
$data['items'] = $stmt->fetchAll();
break;
case 'quotations':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(q.id LIKE ? OR c.name LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['customer_id'])) {
$where[] = "q.customer_id = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "q.quotation_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "q.quotation_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT q.*, c.name as customer_name
FROM quotations q
LEFT JOIN customers c ON q.customer_id = c.id
WHERE $whereSql
ORDER BY q.id DESC");
$stmt->execute($params);
$data['quotations'] = $stmt->fetchAll();
$items_list_raw = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate, is_promotion, promotion_start, promotion_end, promotion_percent FROM stock_items ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($items_list_raw as &$item) {
$item['sale_price'] = getPromotionalPrice($item);
}
$data['items_list'] = $items_list_raw;
$data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll();
break;
case 'payment_methods':
$data['payment_methods'] = db()->query("SELECT * FROM payment_methods ORDER BY id DESC")->fetchAll();
break;
case 'settings':
// Already fetched globally
break;
case 'my_profile':
$stmt = db()->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$data['user'] = $stmt->fetch();
break;
case 'sales':
case 'purchases':
$type = ($page === 'sales') ? 'sale' : 'purchase';
$where = ["v.type = ?"];
$params = [$type];
if (!empty($_GET['search'])) {
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
if (!empty($_GET['customer_id'])) {
$where[] = "v.customer_id = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "v.invoice_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "v.invoice_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id, c.phone as customer_phone
FROM invoices v
LEFT JOIN customers c ON v.customer_id = c.id
WHERE $whereSql
ORDER BY v.id DESC");
$stmt->execute($params);
$data['invoices'] = $stmt->fetchAll();
foreach ($data['invoices'] as &$inv) {
$inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']);
}
unset($inv);
$items_list_raw = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate, is_promotion, promotion_start, promotion_end, promotion_percent FROM stock_items ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC);
foreach ($items_list_raw as &$item) {
$item['sale_price'] = getPromotionalPrice($item);
}
$data['items_list'] = $items_list_raw;
$data['customers_list'] = db()->query("SELECT id, name FROM customers WHERE type = '" . ($type === 'sale' ? 'customer' : 'supplier') . "' ORDER BY name ASC")->fetchAll();
break;
case 'sales_returns':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT sr.*, c.name as customer_name, i.total_with_vat as invoice_total
FROM sales_returns sr
LEFT JOIN customers c ON sr.customer_id = c.id
LEFT JOIN invoices i ON sr.invoice_id = i.id
WHERE $whereSql
ORDER BY sr.id DESC");
$stmt->execute($params);
$data['returns'] = $stmt->fetchAll();
$data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices WHERE type = 'sale' ORDER BY id DESC")->fetchAll();
break;
case 'purchase_returns':
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.invoice_id LIKE ?)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT pr.*, c.name as supplier_name, i.total_with_vat as invoice_total
FROM purchase_returns pr
LEFT JOIN customers c ON pr.supplier_id = c.id
LEFT JOIN invoices i ON pr.invoice_id = i.id
WHERE $whereSql
ORDER BY pr.id DESC");
$stmt->execute($params);
$data['returns'] = $stmt->fetchAll();
$data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices WHERE type = 'purchase' ORDER BY id DESC")->fetchAll();
break;
case 'customer_statement':
case 'supplier_statement':
$type = ($page === 'customer_statement') ? 'customer' : 'supplier';
$invType = ($type === 'customer') ? 'sale' : 'purchase';
$data['entities'] = db()->query("SELECT id, name, balance FROM customers WHERE type = '$type' ORDER BY name ASC")->fetchAll();
$entity_id = (int)($_GET['entity_id'] ?? 0);
if ($entity_id) {
$data['selected_entity'] = db()->query("SELECT * FROM customers WHERE id = $entity_id")->fetch();
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
// Fetch Opening Balance (Balance before start_date)
// This is complex as we don't have a ledger table.
// We can calculate it: Initial Balance + Invoices(before start_date) - Payments(before start_date)
// But for now, let's just show all transactions if no date filter, or just transactions in range.
$stmt = db()->prepare("SELECT 'invoice' as trans_type, id, invoice_date as trans_date, total_with_vat as amount, status, id as ref_no
FROM invoices
WHERE customer_id = ? AND type = ? AND invoice_date BETWEEN ? AND ?");
$stmt->execute([$entity_id, $invType, $start_date, $end_date]);
$invoices = $stmt->fetchAll(PDO::FETCH_ASSOC);
$stmt = db()->prepare("SELECT 'payment' as trans_type, p.id, p.payment_date as trans_date, p.amount, p.payment_method, p.invoice_id as ref_no
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
WHERE i.customer_id = ? AND i.type = ? AND p.payment_date BETWEEN ? AND ?");
$stmt->execute([$entity_id, $invType, $start_date, $end_date]);
$payments = $stmt->fetchAll(PDO::FETCH_ASSOC);
$transactions = array_merge($invoices, $payments);
usort($transactions, function($a, $b) {
return strtotime($a['trans_date']) <=> strtotime($b['trans_date']);
});
$data['transactions'] = $transactions;
}
break;
case 'expense_categories':
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
break;
case 'expenses':
$where = ["1=1"];
$params = [];
if (!empty($_GET['category_id'])) {
$where[] = "e.category_id = ?";
$params[] = $_GET['category_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "e.expense_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "e.expense_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT e.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM expenses e
LEFT JOIN expense_categories c ON e.category_id = c.id
WHERE $whereSql
ORDER BY e.expense_date DESC, e.id DESC");
$stmt->execute($params);
$data['expenses'] = $stmt->fetchAll();
break;
case 'role_groups':
$data['role_groups'] = db()->query("SELECT * FROM role_groups ORDER BY name ASC")->fetchAll();
break;
case 'users':
$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
FROM acc_journal_entries je
ORDER BY je.entry_date DESC, je.id DESC LIMIT 100")->fetchAll();
$data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll();
if (isset($_GET['action']) && $_GET['action'] === 'get_entry_details') {
header('Content-Type: application/json');
$id = (int)$_GET['id'];
$stmt = db()->prepare("SELECT l.*, a.name_en, a.code FROM acc_ledger l JOIN acc_accounts a ON l.account_id = a.id WHERE l.journal_entry_id = ?");
$stmt->execute([$id]);
echo json_encode($stmt->fetchAll());
exit;
}
if (isset($_GET['view']) && $_GET['view'] === 'trial_balance') {
$data['trial_balance'] = db()->query("SELECT a.code, a.name_en, SUM(l.debit) as total_debit, SUM(l.credit) as total_credit
FROM acc_accounts a
LEFT JOIN acc_ledger l ON a.id = l.account_id
GROUP BY a.id
HAVING total_debit > 0 OR total_credit > 0
ORDER BY a.code ASC")->fetchAll();
}
if (isset($_GET['view']) && $_GET['view'] === 'profit_loss') {
$data['revenue_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'revenue' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
$data['expense_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'expense' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
}
if (isset($_GET['view']) && $_GET['view'] === 'balance_sheet') {
$data['asset_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'asset' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
$data['liability_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'liability' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
$data['equity_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'equity' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll();
}
if (isset($_GET['view']) && $_GET['view'] === 'vat_report') {
$start = $_GET['start_date'] ?? date('Y-m-01');
$end = $_GET['end_date'] ?? date('Y-m-d');
$data['vat_report'] = getVatReport($start, $end);
$data['start_date'] = $start;
$data['end_date'] = $end;
}
if (isset($_GET['view']) && $_GET['view'] === 'coa') {
$data['coa'] = db()->query("SELECT a.*, p.name_en as parent_name
FROM acc_accounts a
LEFT JOIN acc_accounts p ON a.parent_id = p.id
ORDER BY a.code ASC")->fetchAll();
}
break;
case 'expense_report':
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
$stmt = db()->prepare("SELECT c.name_en, c.name_ar, SUM(e.amount) as total
FROM expenses e
JOIN expense_categories c ON e.category_id = c.id
WHERE e.expense_date BETWEEN ? AND ?
GROUP BY c.id
ORDER BY total DESC");
$stmt->execute([$start_date, $end_date]);
$data['report_by_category'] = $stmt->fetchAll();
$stmt = db()->prepare("SELECT SUM(amount) FROM expenses WHERE expense_date BETWEEN ? AND ?");
$stmt->execute([$start_date, $end_date]);
$data['total_expenses'] = $stmt->fetchColumn() ?: 0;
break;
case 'expiry_report':
$where = ["expiry_date IS NOT NULL"];
$params = [];
$filter = $_GET['filter'] ?? 'all';
if ($filter === 'expired') {
$where[] = "expiry_date <= CURDATE()";
} elseif ($filter === 'near_expiry') {
$where[] = "expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)";
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id
WHERE $whereSql
ORDER BY i.expiry_date ASC");
$stmt->execute($params);
$data['expiry_items'] = $stmt->fetchAll();
break;
case 'low_stock_report':
$stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name
FROM stock_items i
LEFT JOIN stock_categories c ON i.category_id = c.id
LEFT JOIN customers s ON i.supplier_id = s.id AND s.type = 'supplier'
WHERE i.stock_quantity <= i.min_stock_level
ORDER BY (i.min_stock_level - i.stock_quantity) DESC");
$stmt->execute();
$data['low_stock_items'] = $stmt->fetchAll();
break;
case 'cashflow_report':
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
// Fetch Cash & Bank Account IDs
$cash_accounts = db()->query("SELECT id FROM acc_accounts WHERE code IN (1100, 1200)")->fetchAll(PDO::FETCH_COLUMN);
$cash_ids_str = implode(',', $cash_accounts);
if (!empty($cash_ids_str)) {
// Opening Balance
$stmt = db()->prepare("SELECT SUM(debit - credit) FROM acc_ledger l JOIN acc_journal_entries je ON l.journal_entry_id = je.id WHERE l.account_id IN ($cash_ids_str) AND je.entry_date < ?");
$stmt->execute([$start_date]);
$data['opening_balance'] = $stmt->fetchColumn() ?: 0;
// Transactions in range
$stmt = db()->prepare("SELECT
je.entry_date,
je.description,
l.debit as inflow,
l.credit as outflow,
a.name_en as other_account,
a.type as other_type
FROM acc_ledger l
JOIN acc_journal_entries je ON l.journal_entry_id = je.id
LEFT JOIN acc_ledger l2 ON l2.journal_entry_id = je.id AND l2.id != l.id
LEFT JOIN acc_accounts a ON l2.account_id = a.id
WHERE l.account_id IN ($cash_ids_str)
AND je.entry_date BETWEEN ? AND ?
ORDER BY je.entry_date ASC, je.id ASC");
$stmt->execute([$start_date, $end_date]);
$data['cash_transactions'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$data['opening_balance'] = 0;
$data['cash_transactions'] = [];
}
break;
case 'hr_departments':
$data['departments'] = db()->query("SELECT * FROM hr_departments ORDER BY id DESC")->fetchAll();
break;
case 'hr_employees':
$data['employees'] = db()->query("SELECT e.*, d.name as dept_name FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id ORDER BY e.id DESC")->fetchAll();
$data['departments'] = db()->query("SELECT * FROM hr_departments ORDER BY name ASC")->fetchAll();
break;
case 'hr_attendance':
$date = $_GET['date'] ?? date('Y-m-d');
$data['attendance_date'] = $date;
$data['employees'] = db()->query("SELECT e.id, e.name, d.name as dept_name, a.status, a.clock_in, a.clock_out
FROM hr_employees e
LEFT JOIN hr_departments d ON e.department_id = d.id
LEFT JOIN hr_attendance a ON e.id = a.employee_id AND a.attendance_date = '$date'
WHERE e.status = 'active' ORDER BY e.name ASC")->fetchAll();
break;
case 'hr_payroll':
$month = (int)($_GET['month'] ?? date('m'));
$year = (int)($_GET['year'] ?? date('Y'));
$data['month'] = $month;
$data['year'] = $year;
$data['payroll'] = db()->query("SELECT p.*, e.name as emp_name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.payroll_month = $month AND p.payroll_year = $year ORDER BY p.id DESC")->fetchAll();
$data['employees'] = db()->query("SELECT id, name, salary FROM hr_employees WHERE status = 'active' ORDER BY name ASC")->fetchAll();
break;
case 'loyalty_history':
$where = ["1=1"];
$params = [];
if (!empty($_GET['customer_id'])) {
$where[] = "lt.customer_id = ?";
$params[] = (int)$_GET['customer_id'];
}
if (!empty($_GET['type'])) {
$where[] = "lt.transaction_type = ?";
$params[] = $_GET['type'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT lt.*, c.name as customer_name, c.loyalty_tier, c.loyalty_points
FROM loyalty_transactions lt
JOIN customers c ON lt.customer_id = c.id
WHERE $whereSql
ORDER BY lt.created_at DESC");
$stmt->execute($params);
$data['loyalty_transactions'] = $stmt->fetchAll();
break;
case 'devices':
$data['devices'] = db()->query("SELECT * FROM hr_biometric_devices ORDER BY id DESC")->fetchAll();
break;
default:
$data['customers'] = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY id DESC LIMIT 5")->fetchAll();
// Dashboard stats
$data['stats'] = [
'total_customers' => db()->query("SELECT COUNT(*) FROM customers WHERE type = 'customer'")->fetchColumn(),
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
'total_sales' => db()->query("SELECT SUM(total_with_vat) FROM invoices WHERE type = 'sale'")->fetchColumn() ?: 0,
'total_received' => db()->query("SELECT SUM(amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.type = 'sale'")->fetchColumn() ?: 0,
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM invoices WHERE type = 'purchase'")->fetchColumn() ?: 0,
'total_paid' => db()->query("SELECT SUM(amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.type = 'purchase'")->fetchColumn() ?: 0,
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
'low_stock_items_count' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level")->fetchColumn(),
];
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
// Sales Chart Data
$data['monthly_sales'] = db()->query("SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, SUM(total_with_vat) as total FROM invoices WHERE type = 'sale' GROUP BY DATE_FORMAT(invoice_date, '%Y-%m') ORDER BY invoice_date ASC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC);
$data['yearly_sales'] = db()->query("SELECT YEAR(invoice_date) as label, SUM(total_with_vat) as total FROM invoices WHERE type = 'sale' GROUP BY label ORDER BY label ASC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
break;
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
?>
['en' => 'Dashboard', 'ar' => 'لوحة القيادة'],
'my_profile' => ['en' => 'My Profile', 'ar' => 'ملفي الشخصي'],
'pos' => ['en' => 'Point of Sale', 'ar' => 'نقطة البيع'],
'quotations' => ['en' => 'Quotations', 'ar' => 'العروض'],
'customers' => ['en' => 'Customers', 'ar' => 'العملاء'],
'suppliers' => ['en' => 'Suppliers', 'ar' => 'الموردون'],
'categories' => ['en' => 'Stock Categories', 'ar' => 'فئات المخزون'],
'units' => ['en' => 'Stock Units', 'ar' => 'وحدات المخزون'],
'items' => ['en' => 'Stock Items', 'ar' => 'أصناف المخزون'],
'payment_methods' => ['en' => 'Payment Methods', 'ar' => 'طرق الدفع'],
'sales' => ['en' => 'Sales Tax Invoices', 'ar' => 'فواتير المبيعات الضريبية'],
'purchases' => ['en' => 'Purchase Tax Invoices', 'ar' => 'فواتير المشتريات الضريبية'],
'sales_returns' => ['en' => 'Sales Returns', 'ar' => 'مرتجع المبيعات'],
'customer_statement' => ['en' => 'Customer Statement', 'ar' => 'كشف حساب عميل'],
'supplier_statement' => ['en' => 'Supplier Statement', 'ar' => 'كشف حساب مورد'],
'expiry_report' => ['en' => 'Expiry Report', 'ar' => 'تقرير انتهاء الصلاحية'],
'low_stock_report' => ['en' => 'Low Stock Report', 'ar' => 'تقرير نواقص المخزون'],
'settings' => ['en' => 'Company Profile', 'ar' => 'ملف الشركة'],
'devices' => ['en' => 'Biometric Devices', 'ar' => 'أجهزة البصمة'],
'hr_departments' => ['en' => 'HR Departments', 'ar' => 'أقسام الموارد البشرية'],
'hr_employees' => ['en' => 'HR Employees', 'ar' => 'موظفي الموارد البشرية'],
'hr_attendance' => ['en' => 'HR Attendance', 'ar' => 'حضور الموارد البشرية'],
'hr_payroll' => ['en' => 'HR Payroll', 'ar' => 'رواتب الموارد البشرية'],
'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'];
?>
= $currTitle['en'] ?>
العربية
= htmlspecialchars((string)($_SESSION['user_role_name'] ?? '')) ?>
0 || $data['stats']['near_expiry_items'] > 0 || $data['stats']['low_stock_items_count'] > 0): ?>
Inventory Alert:
0): ?>
= $data['stats']['expired_items'] ?> items have expired.
0): ?>
= $data['stats']['near_expiry_items'] ?> items are expiring soon (within 30 days).
0): ?>
= $data['stats']['low_stock_items_count'] ?> items are below minimum level.
Total Sales
OMR = number_format((float)($data['stats']['total_sales'] ?? 0), 3) ?>
Total Received
OMR = number_format((float)($data['stats']['total_received'] ?? 0), 3) ?>
Customer Due
OMR = number_format((float)($data['stats']['total_receivable'] ?? 0), 3) ?>
Total Purchases
OMR = number_format((float)($data['stats']['total_purchases'] ?? 0), 3) ?>
Total Paid
OMR = number_format((float)($data['stats']['total_paid'] ?? 0), 3) ?>
Supplier Due
OMR = number_format((float)($data['stats']['total_payable'] ?? 0), 3) ?>
Total Customers
= (int)($data['stats']['total_customers'] ?? 0) ?>
Total Items
= (int)($data['stats']['total_items'] ?? 0) ?>
Sales Performance
Monthly
Yearly
Recent Customers
View All
Name
Phone
Balance
= htmlspecialchars($c['name']) ?>
= htmlspecialchars($c['phone']) ?>
OMR = number_format((float)$c['balance'], 3) ?>
= $currTitle['en'] ?> Management
Import Excel
Add = $currTitle['en'] ?>
Name
Tax ID
Email
Phone
Balance
Actions
= htmlspecialchars($c['name']) ?>
= htmlspecialchars($c['tax_id'] ?? '---') ?>
= htmlspecialchars($c['email']) ?>
= htmlspecialchars($c['phone']) ?>
OMR = number_format((float)$c['balance'], 3) ?>
Stock Categories
Import Excel
Add Category
ID
Name (EN)
Name (AR)
= $cat['id'] ?>
= htmlspecialchars($cat['name_en']) ?>
= htmlspecialchars($cat['name_ar']) ?>
Stock Units
Import Excel
Add Unit
Name (EN)
Short (EN)
Name (AR)
Short (AR)
= htmlspecialchars($u['name_en']) ?>
= htmlspecialchars($u['short_name_en']) ?>
= htmlspecialchars($u['name_ar']) ?>
= htmlspecialchars($u['short_name_ar']) ?>
SKU
Item Name
Category
Stock Level
Expiry Date
Status
No items found.
= htmlspecialchars($item['sku']) ?>
= htmlspecialchars($item['name_en']) ?>
= htmlspecialchars($item['name_ar']) ?>
= htmlspecialchars($item['cat_en'] ?? '---') ?>
= number_format((float)$item['stock_quantity'], 3) ?>
= $expiry_date !== '' ? htmlspecialchars((string)$expiry_date) : '---' ?>
Expired
Near Expiry
Good
Low Stock Report
Print
SKU
Item Name
Category
Supplier
Min Level
Current Stock
Shortage
All items are above minimum levels.
= htmlspecialchars($item['sku']) ?>
= htmlspecialchars($item['name_en']) ?>
= htmlspecialchars($item['name_ar']) ?>
= htmlspecialchars($item['cat_en'] ?? '---') ?>
= htmlspecialchars($item['supplier_name'] ?? '---') ?>
= number_format((float)$item['min_stock_level'], 2) ?>
= number_format((float)$item['stock_quantity'], 3) ?>
= number_format($shortage, 3) ?>
Loyalty Transaction History
Print
Date
Customer
Tier
Type
Points
Description
No transactions found.
= date('Y-m-d H:i', strtotime($lt['created_at'])) ?>
= htmlspecialchars($lt['customer_name']) ?>
Current Balance: = number_format($lt['loyalty_points'], 0) ?> pts
= $tier ?>
= ucfirst($type) ?>
= (float)$lt['points_change'] > 0 ? '+' : '' ?>= number_format($lt['points_change'], 0) ?>
= htmlspecialchars($lt['description']) ?>
query($sql)->fetchAll(PDO::FETCH_ASSOC);
$products = [];
foreach ($products_raw as $p) {
$p['original_price'] = (float)$p['sale_price'];
$p['sale_price'] = getPromotionalPrice($p);
$products[] = $p;
}
$customers = db()->query("SELECT * FROM customers WHERE type = 'customer' ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
?>
= htmlspecialchars($p['name_en']) ?>
= htmlspecialchars($p['sku']) ?>
OMR = number_format($p['original_price'], 3) ?>
OMR = number_format((float)$p['sale_price'], 3) ?>
= (float)$p['stock_quantity'] ?> left
Customer
Walk-in Customer
= htmlspecialchars($c['name']) ?>
Spend more to unlock Silver
Subtotal
OMR 0.000
Total
OMR 0.000
PLACE ORDER
Quotations
Create New Quotation
Quotation #
Date
Valid Until
Customer
Status
Total
Actions
prepare("SELECT qi.*, i.name_en, i.name_ar, i.vat_rate
FROM quotation_items qi
JOIN stock_items i ON qi.item_id = i.id
WHERE qi.quotation_id = ?");
$items->execute([$q['id']]);
$q['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
?>
QUO-= str_pad((string)$q['id'], 5, '0', STR_PAD_LEFT) ?>
= $q['quotation_date'] ?>
= $q['valid_until'] ?: '---' ?>
= htmlspecialchars($q['customer_name'] ?? '---') ?>
= htmlspecialchars($q['status']) ?>
OMR = number_format((float)$q['total_with_vat'], 3) ?>
No quotations found
= $currTitle['en'] ?>
Create New Tax Invoice
Invoice #
Date
= $page === 'sales' ? 'Customer' : 'Supplier' ?>
Status
Total
Paid
Balance
Actions
prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate
FROM invoice_items ii
JOIN stock_items i ON ii.item_id = i.id
WHERE ii.invoice_id = ?");
$items->execute([$inv['id']]);
$inv['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
?>
INV-= str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?>
= $inv['invoice_date'] ?>
= htmlspecialchars($inv['customer_name'] ?? '---') ?>
= htmlspecialchars(str_replace('_', ' ', $inv['status'])) ?>
OMR = number_format((float)$inv['total_with_vat'], 3) ?>
OMR = number_format((float)$inv['paid_amount'], 3) ?>
OMR = number_format((float)($inv['total_with_vat'] - $inv['paid_amount']), 3) ?>
= $currTitle['en'] ?>
Print
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
Statement of Account
= htmlspecialchars($data['selected_entity']['name']) ?>
= htmlspecialchars($data['selected_entity']['email']) ?> | = htmlspecialchars($data['selected_entity']['phone']) ?> Period: = $_GET['start_date'] ?> to = $_GET['end_date'] ?>
Date
Reference
Description
Debit
Credit
Balance
= $t['trans_date'] ?>
= $t['trans_type'] === 'invoice' ? 'INV-'.str_pad((string)$t['ref_no'], 5, '0', STR_PAD_LEFT) : 'RCP-'.str_pad((string)$t['id'], 5, '0', STR_PAD_LEFT) ?>
= $t['trans_type'] === 'invoice' ? 'Tax Invoice' : 'Payment - '.$t['payment_method'] ?>
= $debit > 0 ? number_format($debit, 3) : '' ?>
= $credit > 0 ? number_format($credit, 3) : '' ?>
= number_format($running_balance, 3) ?>
Closing Balance
OMR = number_format($running_balance, 3) ?>
Please select an entity and date range to generate the statement.
Cashflow Statement
Print
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
Cashflow Statement
Period: = htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?> to = htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>
Description
Amount (OMR)
Opening Cash Balance
= number_format($data['opening_balance'], 3) ?>
Operating Activities
0) $op_inflow += $amt; else $op_outflow += abs($amt);
} elseif ($t['other_type'] === 'asset' && !in_array($t['other_account'], ['Accounts Receivable', 'Inventory'])) {
// Fixed assets etc
if ($amt > 0) $inv_inflow += $amt; else $inv_outflow += abs($amt);
} elseif ($t['other_type'] === 'equity' || $t['other_type'] === 'liability') {
if ($amt > 0) $fin_inflow += $amt; else $fin_outflow += abs($amt);
} else {
// Default to operating if unsure
if ($amt > 0) $op_inflow += $amt; else $op_outflow += abs($amt);
}
}
?>
Cash Received from Customers & Others
= number_format($op_inflow, 3) ?>
Cash Paid to Suppliers & Expenses
(= number_format($op_outflow, 3) ?>)
Net Cash from Operating Activities
= number_format($op_inflow - $op_outflow, 3) ?>
Investing Activities
Net Cash from Investing Activities
= number_format($inv_inflow - $inv_outflow, 3) ?>
Financing Activities
Net Cash from Financing Activities
= number_format($fin_inflow - $fin_outflow, 3) ?>
Net Change in Cash
= number_format($net_change, 3) ?>
Closing Cash Balance
= number_format($data['opening_balance'] + $net_change, 3) ?>
___________________ Prepared By
___________________ Approved By
Payment Methods
Add Payment Method
ID
Name (EN)
Name (AR)
Actions
= $pm['id'] ?>
= htmlspecialchars($pm['name_en'] ?? '') ?>
= htmlspecialchars($pm['name_ar'] ?? '') ?>
Expense Categories
Add Category
ID
Name (EN)
Name (AR)
Actions
= $cat['id'] ?>
= htmlspecialchars($cat['name_en']) ?>
= htmlspecialchars($cat['name_ar']) ?>
= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>
= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?>
= isset($_GET['view']) ? ucwords(str_replace('_', ' ', $_GET['view'])) : 'Journal' ?>
Date
Description
Reference
Amount
Action
= $entry['entry_date'] ?>
= htmlspecialchars($entry['description']) ?>
= htmlspecialchars($entry['reference']) ?>
= number_format((float)$entry['total_debit'], 3) ?>
View
Add Account
Code
Name
Type
Parent
Balance
= $acc['code'] ?>
= htmlspecialchars($acc['name_en']) ?>
= htmlspecialchars($acc['name_ar']) ?>
= $acc['type'] ?>
= htmlspecialchars($acc['parent_name'] ?? '---') ?>
= number_format(getAccountBalance($acc['code']), 3) ?>
VAT Summary Report
VAT Input (Purchases)
= number_format($data['vat_report']['input_vat'], 3) ?>
VAT Output (Sales)
= number_format($data['vat_report']['output_vat'], 3) ?>
Net VAT Payable / (Refundable)
= number_format($data['vat_report']['net_vat'], 3) ?>
This report calculates the difference between VAT collected on sales and VAT paid on purchases for the selected period.
Code
Account Name
Debit
Credit
= $row['code'] ?>
= htmlspecialchars($row['name_en']) ?>
= number_format((float)$row['total_debit'], 3) ?>
= number_format((float)$row['total_credit'], 3) ?>
Total
= number_format($total_d, 3) ?>
= number_format($total_c, 3) ?>
Profit & Loss Statement
Revenue
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Total Revenue
= number_format($total_rev, 3) ?>
Expenses
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Total Expenses
= number_format($total_exp, 3) ?>
Net Profit / Loss
= number_format($total_rev - $total_exp, 3) ?>
Balance Sheet
Assets
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Total Assets
= number_format($total_assets, 3) ?>
Liabilities & Equity
Liabilities
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
Equity
= htmlspecialchars($acc['name_en']) ?>
= number_format($bal, 3) ?>
query("SELECT code FROM acc_accounts WHERE type='revenue' AND parent_id IS NOT NULL")->fetchAll() as $a) $rev += getAccountBalance($a['code']);
$exp = 0; foreach(db()->query("SELECT code FROM acc_accounts WHERE type='expense' AND parent_id IS NOT NULL")->fetchAll() as $a) $exp += getAccountBalance($a['code']);
$earnings = $rev - $exp;
$total_equity += $earnings;
?>
Retained Earnings (Current)
= number_format($earnings, 3) ?>
Total Liab. & Equity
= number_format($total_liab + $total_equity, 3) ?>
Expenses List
Add Expense
Date
Reference
Category
Description
Amount
Actions
= $exp['expense_date'] ?>
= htmlspecialchars($exp['reference_no'] ?: '---') ?>
= htmlspecialchars($exp['cat_en'] ?? 'Unknown') ?>
= htmlspecialchars($exp['description']) ?>
OMR = number_format((float)$exp['amount'], 3) ?>
Expense Report
Print
Total Expenses
OMR = number_format((float)$data['total_expenses'], 3) ?>
Period: = htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?> to = htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>
Category
Total Amount
% of Total
No expenses found for this period.
0 ? ($row['total'] / $data['total_expenses'] * 100) : 0;
?>
= htmlspecialchars($row['name_en']) ?>
= htmlspecialchars($row['name_ar']) ?>
OMR = number_format((float)$row['total'], 3) ?>
= number_format($percent, 1) ?>%
Sales Returns
Create New Return
Return #
Date
Invoice #
Customer
Total Amount
Actions
RET-= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?>
= $ret['return_date'] ?>
INV-= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?>
= htmlspecialchars($ret['customer_name'] ?? 'Walk-in') ?>
OMR = number_format((float)$ret['total_amount'], 3) ?>
No returns found
Purchase Returns
Create New Return
Return #
Date
Invoice #
Supplier
Total Amount
Actions
PRET-= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?>
= $ret['return_date'] ?>
INV-= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?>
= htmlspecialchars($ret['supplier_name'] ?? 'Unknown') ?>
OMR = number_format((float)$ret['total_amount'], 3) ?>
No returns found
HR Departments
Add Department
ID
Department Name
Actions
= $d['id'] ?>
= htmlspecialchars($d['name']) ?>
HR Employees
Add Employee
Name
Biometric ID
Department
Position
Salary
Status
Actions
= htmlspecialchars($e['name']) ?>
= htmlspecialchars($e['email']) ?>
= htmlspecialchars($e['biometric_id'] ?? '---') ?>
= htmlspecialchars($e['dept_name'] ?? '---') ?>
= htmlspecialchars($e['position']) ?>
OMR = number_format($e['salary'], 3) ?>
= $e['status'] ?>
Employee
Department
Status
Clock In
Clock Out
Action
= htmlspecialchars($e['name']) ?>
= htmlspecialchars($e['dept_name'] ?? '---') ?>
= $e['status'] ?>
Not Marked
= $e['clock_in'] ?? '---' ?>
= $e['clock_out'] ?? '---' ?>
Mark
Status
>Present
>Absent
>On Leave
To sync attendance from your biometric device, use the following API endpoint:
= (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" ?>/api/biometric_sync.php
Expected JSON format:
[
{
"biometric_id": "101",
"device_id": 1,
"timestamp": "2026-02-17 08:30:00",
"type": "in"
},
{
"biometric_id": "101",
"device_id": 1,
"timestamp": "2026-02-17 17:30:00",
"type": "out"
}
]
Note: Ensure Employee Biometric IDs match those in the device logs.
HR Payroll
>= date('F', mktime(0, 0, 0, $m, 1)) ?>
=date('Y')-2; $y--): ?>
>= $y ?>
Generate
Employee
Basic
Bonus
Deductions
Net Salary
Status
Actions
= htmlspecialchars($p['emp_name']) ?>
OMR = number_format($p['basic_salary'], 3) ?>
+ OMR = number_format($p['bonus'], 3) ?>
- OMR = number_format($p['deductions'], 3) ?>
OMR = number_format($p['net_salary'], 3) ?>
= $p['status'] ?>
Employee
--- Select ---
= htmlspecialchars($e['name']) ?> (Basic: = number_format($e['salary'], 3) ?>)
Biometric Devices
Add Device
Device Name
IP / IO Address
Port
Serial
Last Sync
Status
Actions
= htmlspecialchars($d['device_name']) ?>
IP: = htmlspecialchars($d['ip_address']) ?>
IO: = htmlspecialchars($d['io_address']) ?>
= $d['port'] ?>
= htmlspecialchars($d['serial_number'] ?? '---') ?>
= $d['last_sync'] ?? 'Never' ?>
= $d['status'] ?>
Profile Picture
= htmlspecialchars($data['user']['username']) ?>
= htmlspecialchars($_SESSION['user_role_name'] ?? 'User') ?>
Group Name
Created Date
Status
Actions
= htmlspecialchars((string)$group['name']) ?>
= date('M d, Y', strtotime((string)$group['created_at'])) ?>
Active
Manual Backup
Create a database backup immediately.
Backup Now
Filename
Size
Date
Actions
No backups found.
= htmlspecialchars($b['name']) ?>
= htmlspecialchars($b['size']) ?>
= htmlspecialchars($b['date']) ?>
User Info
Access Level
Contact
Status
Actions
= strtoupper(substr((string)$u['username'], 0, 1)) ?>
= htmlspecialchars((string)$u['username']) ?>
ID: #= str_pad((string)$u['id'], 4, '0', STR_PAD_LEFT) ?>
= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
= htmlspecialchars((string)($u['email'] ?? '')) ?>
= htmlspecialchars((string)($u['phone'] ?? '-')) ?>
Active
Suspended
--- " . htmlspecialchars(basename($file)) . " --- ";
$lines = shell_exec("tail -n 50 " . escapeshellarg($path));
echo "
" . htmlspecialchars((string)$lines) . " ";
}
}
if (!$found_logs) {
echo "
No accessible log files found.
";
}
?>