38471-vm/index.php
2026-03-01 18:32:06 +00:00

15603 lines
923 KiB
PHP

<?php
declare(strict_types=1);
// Sessions setup
$sessions_dir = __DIR__ . '/sessions';
if (!is_dir($sessions_dir)) {
@mkdir($sessions_dir, 0777, true);
}
if (is_writable($sessions_dir)) {
session_save_path($sessions_dir);
}
// Check for required extensions
$required_extensions = ['pdo', 'pdo_mysql', 'curl', 'json'];
$missing_extensions = [];
foreach ($required_extensions as $ext) {
if (!extension_loaded($ext)) {
$missing_extensions[] = $ext;
}
}
if (!empty($missing_extensions)) {
die("Error: The following PHP extensions are required but missing: " . implode(', ', $missing_extensions) . ". Please contact your hosting provider to enable them.");
}
// Enhanced session security and iframe compatibility
if ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')) {
session_set_cookie_params([
'lifetime' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'None',
]);
}
session_start();
if (isset($_GET['action']) && $_GET['action'] === 'switch_outlet') {
$target = (int)$_GET['id'];
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
$assigned_outlets = isset($_SESSION['assigned_outlets']) ? explode(',', $_SESSION['assigned_outlets']) : [];
if ($is_admin || in_array($target, $assigned_outlets)) {
$_SESSION['outlet_id'] = ($target === 0 && $is_admin) ? null : $target;
}
header("Location: " . ($_SERVER['HTTP_REFERER'] ?? 'index.php'));
exit;
}
if (isset($_GET['action']) && $_GET['action'] === 'download_items_template') {
header('Content-Type: text/csv; charset=utf-8');
header('Content-Disposition: attachment; filename=items_import_template.csv');
$output = fopen('php://output', 'w');
// Add BOM for Excel UTF-8 compatibility
fprintf($output, chr(0xEF).chr(0xBB).chr(0xBF));
fputcsv($output, ['SKU', 'English Name', 'Arabic Name', 'Sale Price', 'Cost Price']);
fclose($output);
exit;
}
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 'includes/DatabaseInstaller.php';
// Auto-install database if not installed
if (!DatabaseInstaller::isInstalled()) {
try {
DatabaseInstaller::install();
} catch (Exception $e) {
die("Installation Error: " . $e->getMessage());
}
}
require_once 'lib/LicenseService.php';
require_once 'includes/lang.php';
// Language Setup
if (isset($_GET['lang'])) {
$_SESSION['lang'] = in_array($_GET['lang'], ['en', 'ar']) ? $_GET['lang'] : 'ar';
}
if (!isset($_SESSION['lang'])) {
$_SESSION['lang'] = 'ar'; // Default to Arabic as requested
}
$lang = $_SESSION['lang'];
$dir = ($lang === 'ar') ? 'rtl' : 'ltr';
// Licensing Middleware
try {
$is_activated = LicenseService::isActivated();
$trial_days = LicenseService::getTrialRemainingDays();
$can_access = LicenseService::canAccess();
} catch (PDOException $e) {
die("Database Connection Error: " . $e->getMessage() . "<br><br>Please check your <b>db/config.php</b> settings.");
} catch (Exception $e) {
die("Application Error: " . $e->getMessage());
}
$page = $_GET['page'] ?? 'dashboard';
if (!$can_access && $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'];
}
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['start_trial'])) {
// We need a way to call startTrial() which is private in LicenseService
// I'll make it public or use a public wrapper
$res = LicenseService::initTrial();
if ($res['success']) {
$success = "Trial period started! Redirecting...";
header("refresh:2;url=index.php");
} else {
$error = $res['error'];
}
}
?>
<!DOCTYPE html>
<html lang="<?= $lang ?>" dir="<?= $dir ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= __('activate_product') ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap<?= $dir === 'rtl' ? '.rtl' : '' ?>.min.css" rel="stylesheet">
<style>
body { background: #f4f7f6; height: 100vh; display: flex; align-items: center; justify-content: center; font-family: 'Inter', sans-serif; }
.activation-card { background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 10px 25px rgba(0,0,0,0.05); width: 100%; max-width: 450px; }
.fingerprint { background: #eee; padding: 0.5rem; border-radius: 0.5rem; font-family: monospace; font-size: 0.8rem; word-break: break-all; }
[dir="rtl"] { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
</style>
</head>
<body>
<div class="activation-card">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="fw-bold mb-0"><?= __('activate_product') ?></h3>
<div class="dropdown">
<button class="btn btn-sm btn-outline-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown">
<?= $lang === 'ar' ? 'العربية' : 'English' ?>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-menu" href="?page=activate&lang=en">English</a></li>
<li><a class="dropdown-menu" href="?page=activate&lang=ar">العربية</a></li>
</ul>
</div>
</div>
<p class="text-muted small mb-4"><?= $lang === 'ar' ? 'يرجى إدخال مفتاح التسلسل للمتابعة.' : 'Please enter your serial key to continue using the application.' ?></p>
<?php if ($error): ?>
<div class="alert alert-danger small"><?= $error ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success small"><?= $success ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3">
<label class="form-label small fw-bold"><?= __('serial_key') ?></label>
<input type="text" name="license_key" class="form-control" placeholder="FLAT-XXXX-XXXX-XXXX" required>
</div>
<div class="mb-3">
<label class="form-label small fw-bold text-muted"><?= $lang === 'ar' ? 'بصمة السيرفر' : 'Server Fingerprint' ?></label>
<div class="fingerprint text-muted"><?= LicenseService::getFingerprint() ?></div>
</div>
<button type="submit" name="activate" class="btn btn-primary w-100 py-2 mb-3"><?= __('activate_now') ?></button>
</form>
<?php if (!$is_activated && $trial_days <= 0): ?>
<div class="text-center">
<hr>
<p class="text-muted small"><?= $lang === 'ar' ? 'أو ابدأ الفترة التجريبية (15 يوم)' : 'Or start your 15-day trial period' ?></p>
<form method="POST">
<button type="submit" name="start_trial" class="btn btn-outline-secondary w-100 py-2"><?= $lang === 'ar' ? 'بدء الفترة التجريبية' : 'Start Free Trial' ?></button>
</form>
</div>
<?php endif; ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php
exit;
}
require_once 'db/BackupService.php';
require_once 'includes/accounting_helper.php';
// Helper to check permissions
function can(string $permission): bool {
if (!isset($_SESSION['user_id'])) return false;
if (($_SESSION['user_role_name'] ?? '') === 'Administrator') return true;
$user_perms = $_SESSION['user_permissions'] ?? [];
if ($user_perms === 'all') return true;
if (is_array($user_perms)) {
return in_array('all', $user_perms) || in_array($permission, $user_perms);
}
$perms = json_decode((string)$user_perms, true);
return is_array($perms) && (in_array('all', $perms) || in_array($permission, $perms));
}
function getPurchaseAlerts() {
if (!can('dashboard_view')) return [];
$db = db();
$stmt = $db->query("SELECT p.id, p.due_date, p.total_with_vat, s.name as supplier_name
FROM purchases p
LEFT JOIN suppliers s ON p.supplier_id = s.id
WHERE p.status != 'paid'
AND p.due_date IS NOT NULL
AND p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)
ORDER BY p.due_date ASC");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
// Missing helper functions
function getLoyaltyMultiplier($tier) {
switch (strtolower((string)$tier)) {
case 'gold': return 2.0;
case 'silver': return 1.5;
default: return 1.0;
}
}
function numberToWords($num) {
$num = (int)$num;
if ($num == 0) return "zero";
$ones = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"];
$tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"];
if ($num < 20) return $ones[$num];
if ($num < 100) return $tens[(int)($num / 10)] . ($num % 10 ? "-" . $ones[$num % 10] : "");
if ($num < 1000) return $ones[(int)($num / 100)] . " hundred" . ($num % 100 ? " and " . numberToWords($num % 100) : "");
if ($num < 1000000) return numberToWords((int)($num / 1000)) . " thousand" . ($num % 1000 ? " " . numberToWords($num % 1000) : "");
return (string)$num;
}
function numberToWordsArabic($num) {
$num = (int)$num;
if ($num == 0) return "صفر";
$ones = ["", "واحد", "اثنان", "ثلاثة", "أربعة", "خمسة", "ستة", "سبعة", "ثمانية", "تسعة", "عشرة", "أحد عشر", "اثنا عشر", "ثلاثة عشر", "أربعة عشر", "خمسة عشر", "ستة عشر", "سبعة عشر", "ثمانية عشر", "تسعة عشر"];
$tens = ["", "", "عشرون", "ثلاثون", "أربعون", "خمسون", "ستون", "سبعون", "ثمانون", "تسعون"];
$hundreds = ["", "مائة", "مائتان", "ثلاثمائة", "أربعمائة", "خمسمائة", "ستمائة", "سبعمائة", "ثمانمائة", "تسعمائة"];
if ($num < 20) return $ones[$num];
if ($num < 100) return ($num % 10 ? $ones[$num % 10] . " و " : "") . $tens[(int)($num / 10)];
if ($num < 1000) return $hundreds[(int)($num / 100)] . ($num % 100 ? " و " . numberToWordsArabic($num % 100) : "");
if ($num < 1000000) {
$thousands = (int)($num / 1000);
$rem = $num % 1000;
$tStr = "ألف";
if ($thousands == 1) $tStr = "ألف";
else if ($thousands == 2) $tStr = "ألفين";
else if ($thousands >= 3 && $thousands <= 10) $tStr = numberToWordsArabic($thousands) . " آلاف";
else $tStr = numberToWordsArabic($thousands) . " ألف";
return $tStr . ($rem ? " و " . numberToWordsArabic($rem) : "");
}
return (string)$num;
}
function renderPagination($currentPage, $totalPages) {
$query = $_GET;
unset($query['p']);
$url = 'index.php?' . http_build_query($query) . '&p=';
$limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 20;
$limitHtml = "
<div class='d-flex justify-content-end align-items-center mb-2'>
<label class='me-2 small text-muted' data-en='Rows per page:' data-ar='الصفوف لكل صفحة:'>Rows per page:</label>
<select class='form-select form-select-sm w-auto' onchange='window.location.href=this.value'>
<option value='index.php?" . http_build_query(array_merge($_GET, ['limit' => 5, 'p' => 1])) . "' " . ($limit == 5 ? 'selected' : '') . ">5</option>
<option value='index.php?" . http_build_query(array_merge($_GET, ['limit' => 20, 'p' => 1])) . "' " . ($limit == 20 ? 'selected' : '') . ">20</option>
<option value='index.php?" . http_build_query(array_merge($_GET, ['limit' => 40, 'p' => 1])) . "' " . ($limit == 40 ? 'selected' : '') . ">40</option>
<option value='index.php?" . http_build_query(array_merge($_GET, ['limit' => 100, 'p' => 1])) . "' " . ($limit == 100 ? 'selected' : '') . ">100</option>
<option value='index.php?" . http_build_query(array_merge($_GET, ['limit' => 200, 'p' => 1])) . "' " . ($limit == 200 ? 'selected' : '') . ">200</option>
<option value='index.php?" . http_build_query(array_merge($_GET, ['limit' => 500, 'p' => 1])) . "' " . ($limit == 500 ? 'selected' : '') . ">500</option>
</select>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
let scriptTag = document.currentScript;
if(scriptTag) {
let container = scriptTag.parentElement;
let grid = container.previousElementSibling;
if (grid && grid.classList.contains('table-responsive')) {
grid.parentNode.insertBefore(container.querySelector('.d-flex'), grid);
}
}
});
</script>
";
if ($totalPages <= 1) return $limitHtml;
$html = '<nav aria-label="Page navigation" class="mt-4"><ul class="pagination justify-content-center">';
// Previous
$disabled = ($currentPage <= 1) ? 'disabled' : '';
$html .= '<li class="page-item ' . $disabled . '"><a class="page-link" href="' . $url . ($currentPage - 1) . '"><i class="bi bi-chevron-left"></i></a></li>';
// Pages
$start = max(1, $currentPage - 2);
$end = min($totalPages, $currentPage + 2);
if ($start > 1) {
$html .= '<li class="page-item"><a class="page-link" href="' . $url . '1">1</a></li>';
if ($start > 2) $html .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
}
for ($i = $start; $i <= $end; $i++) {
$active = ($i == $currentPage) ? 'active' : '';
$html .= '<li class="page-item ' . $active . '"><a class="page-link" href="' . $url . $i . '">' . $i . '</a></li>';
}
if ($end < $totalPages) {
if ($end < $totalPages - 1) $html .= '<li class="page-item disabled"><span class="page-link">...</span></li>';
$html .= '<li class="page-item"><a class="page-link" href="' . $url . $totalPages . '">' . $totalPages . '</a></li>';
}
// Next
$disabled = ($currentPage >= $totalPages) ? 'disabled' : '';
$html .= '<li class="page-item ' . $disabled . '"><a class="page-link" href="' . $url . ($currentPage + 1) . '"><i class="bi bi-chevron-right"></i></a></li>';
$html .= '</ul></nav>';
return $html;
}
// Login Logic
$login_error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
$user = $_POST['username'] ?? '';
$pass = $_POST['password'] ?? '';
$stmt = db()->prepare("SELECT u.*, g.name as role_name FROM users u LEFT JOIN role_groups g ON u.group_id = g.id WHERE u.username = ? AND u.status = 'active'");
$stmt->execute([$user]);
$u = $stmt->fetch();
if ($u && password_verify($pass, $u['password'])) {
$_SESSION['user_id'] = $u['id'];
$_SESSION['username'] = $u['username'];
$_SESSION['user_role_name'] = $u['role_name'];
$_SESSION['outlet_id'] = $u['outlet_id'];
$_SESSION['assigned_outlets'] = $u['assigned_outlets'];
// Fetch permissions from the new role_permissions table
$permStmt = db()->prepare("SELECT permission FROM role_permissions WHERE role_id = ?");
$permStmt->execute([$u['group_id']]);
$permissions = $permStmt->fetchAll(PDO::FETCH_COLUMN);
$_SESSION['user_permissions'] = $permissions;
$_SESSION['profile_pic'] = $u['profile_pic'];
$_SESSION['theme'] = $u['theme'] ?? 'default';
header("Location: index.php");
exit;
} else {
$login_error = "Invalid username or password";
// Debugging
$reason = (!$u) ? "User not found or inactive" : "Password mismatch";
file_put_contents('login_debug.log', date('Y-m-d H:i:s') . " - Failed login for '$user'. Reason: $reason\n", FILE_APPEND);
}
}
// Logout
if (isset($_GET['action']) && $_GET['action'] === 'logout') {
session_destroy();
header("Location: index.php");
exit;
}
// --- POS AJAX Handlers ---
if (isset($_GET['action']) || isset($_POST['action'])) {
$action = $_GET['action'] ?? $_POST['action'] ?? '';
if ($action === 'validate_discount') {
header('Content-Type: application/json');
$code = $_GET['code'] ?? '';
$stmt = db()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND (expiry_date IS NULL OR expiry_date >= CURDATE())");
$stmt->execute([$code]);
$discount = $stmt->fetch(PDO::FETCH_ASSOC);
if ($discount) {
echo json_encode(['success' => true, 'discount' => $discount]);
} else {
echo json_encode(['success' => false, 'error' => 'Invalid or expired discount code']);
}
exit;
}
if ($action === 'search_items') {
header('Content-Type: application/json');
$q = $_GET['q'] ?? '';
$searchTerm = "%$q%";
// Optimization: Fetch items with promotional prices calculated
$stmt = db()->prepare("SELECT * FROM stock_items WHERE name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ? LIMIT 50");
$stmt->execute([$searchTerm, $searchTerm, $searchTerm]);
$items = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($items as &$item) {
$item['original_price'] = (float)$item['sale_price'];
$item['sale_price'] = getPromotionalPrice($item);
}
echo json_encode($items);
exit;
}
if ($action === 'get_payments') {
header('Content-Type: application/json');
$invoice_id = (int)$_GET['invoice_id'];
$stmt = db()->prepare("SELECT * FROM payments WHERE invoice_id = ? ORDER BY payment_date DESC");
$stmt->execute([$invoice_id]);
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if ($action === 'get_payment_details') {
header('Content-Type: application/json');
$payment_id = (int)$_GET['payment_id'];
$stmt = db()->prepare("SELECT p.*, i.customer_id, i.id as inv_id, c.name as customer_name,
o.name as outlet_name, o.address as outlet_address, o.phone as outlet_phone,
o.ctr_number as outlet_ctr_number, o.vat_number as outlet_vat_number, o.logo as outlet_logo
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
LEFT JOIN customers c ON i.customer_id = c.id
LEFT JOIN outlets o ON i.outlet_id = o.id
WHERE p.id = ?");
$stmt->execute([$payment_id]);
echo json_encode($stmt->fetch(PDO::FETCH_ASSOC));
exit;
}
if ($action === 'get_held_carts') {
header('Content-Type: application/json');
$stmt = db()->query("SELECT h.*, c.name as customer_name FROM pos_held_carts h LEFT JOIN customers c ON h.customer_id = c.id ORDER BY h.created_at DESC");
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if ($action === 'hold_pos_cart') {
header('Content-Type: application/json');
$name = $_POST['cart_name'] ?? 'Untitled Cart';
$items = $_POST['items'] ?? '[]';
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
$stmt = db()->prepare("INSERT INTO pos_held_carts (cart_name, items_json, customer_id) VALUES (?, ?, ?)");
$stmt->execute([$name, $items, $customer_id]);
echo json_encode(['success' => true]);
exit;
}
if ($action === 'delete_held_cart') {
header('Content-Type: application/json');
$id = (int)$_POST['id'];
$stmt = db()->prepare("DELETE FROM pos_held_carts WHERE id = ?");
$stmt->execute([$id]);
echo json_encode(['success' => true]);
exit;
}
if ($action === 'save_pos_transaction') {
header('Content-Type: application/json');
$db = db();
try {
$db->beginTransaction();
$customer_id = !empty($_POST['customer_id']) ? (int)$_POST['customer_id'] : null;
$payments = json_decode($_POST['payments'] ?? '[]', true);
$items = json_decode($_POST['items'] ?? '[]', true);
$total_amount = (float)($_POST['total_amount'] ?? 0);
$tax_amount = (float)($_POST['tax_amount'] ?? 0);
$discount_code_id = !empty($_POST['discount_code_id']) ? (int)$_POST['discount_code_id'] : null;
$discount_amount = (float)($_POST['discount_amount'] ?? 0);
$loyalty_redeemed = (float)($_POST['loyalty_redeemed'] ?? 0);
$net_amount = $total_amount - $discount_amount - $loyalty_redeemed;
$transaction_no = 'POS-' . time() . rand(10, 99);
$session_id = $_SESSION['register_session_id'] ?? null;
if (!$session_id) {
// Fallback: try to find an open session for this user
$check_session = $db->prepare("SELECT id FROM register_sessions WHERE user_id = ? AND status = 'open' LIMIT 1");
$check_session->execute([$_SESSION['user_id']]);
$session_id = $check_session->fetchColumn() ?: null;
if ($session_id) {
$_SESSION['register_session_id'] = $session_id;
}
}
// Insert into unified Invoice table
$items_for_journal = [];
foreach ($items as $item) {
$items_for_journal[] = ['id' => $item['id'], 'qty' => $item['qty']];
}
$stmt = $db->prepare("INSERT INTO invoices (outlet_id, transaction_no, customer_id, invoice_date, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, status, register_session_id, is_pos, discount_amount, loyalty_points_redeemed, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'paid', ?, 1, ?, ?, ?)");
$stmt->execute([(int)($_SESSION["outlet_id"] ?? 1), $transaction_no, $customer_id, date('Y-m-d'), 'pos', $total_amount, $tax_amount, $net_amount, $net_amount, $session_id, $discount_amount, $loyalty_redeemed, $_SESSION['user_id']]);
$transaction_id = (int)$db->lastInsertId();
// Insert Items & Update Stock
$stmtItem = $db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)");
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
foreach ($items as $item) {
$sub = (float)$item['price'] * (float)$item['qty'];
$va = (float)($item['vat_amount'] ?? 0);
$stmtItem->execute([$transaction_id, $item['id'], $item['qty'], $item['price'], $va, $sub]);
$stmtStock->execute([$item['qty'], $item['id']]);
}
// Insert Payments
require_once 'includes/accounting_helper.php';
$stmtPay = $db->prepare("INSERT INTO payments (invoice_id, amount, payment_date, payment_method, notes) VALUES (?, ?, ?, ?, ?)");
foreach ($payments as $p) {
$stmtPay->execute([$transaction_id, $p['amount'], date('Y-m-d'), $p['method'], 'POS Transaction']);
$payment_id = $db->lastInsertId();
recordPaymentReceivedJournal((int)$payment_id, $p['amount'], date('Y-m-d'), $p['method']);
}
// Update Loyalty Points if customer exists
if ($customer_id) {
// Earn points
$points_earned = floor($net_amount);
$stmtPoints = $db->prepare("UPDATE customers SET loyalty_points = loyalty_points - ? + ? WHERE id = ?");
$stmtPoints->execute([$loyalty_redeemed * 100, $points_earned, $customer_id]);
// Record transactions
if ($points_earned > 0) {
$db->prepare("INSERT INTO loyalty_transactions (customer_id, transaction_id, points_change, transaction_type, description) VALUES (?, ?, ?, 'earned', ?)")
->execute([$customer_id, $transaction_id, $points_earned, "Earned from POS order #$transaction_no"]);
}
if ($loyalty_redeemed > 0) {
$db->prepare("INSERT INTO loyalty_transactions (customer_id, transaction_id, points_change, transaction_type, description) VALUES (?, ?, ?, 'redeemed', ?)")
->execute([$customer_id, $transaction_id, -$loyalty_redeemed * 100, "Redeemed for POS order #$transaction_no"]);
}
// Update invoice with points earned
$db->prepare("UPDATE invoices SET loyalty_points_earned = ? WHERE id = ?")->execute([$points_earned, $transaction_id]);
}
// Record Sale Journal for POS
recordSaleJournal($transaction_id, $net_amount, date('Y-m-d'), $items_for_journal, $tax_amount);
$db->commit();
$outlet = [];
if (!empty($_SESSION['outlet_id'])) {
$stmtO = $db->prepare("SELECT name, phone, address, ctr_number, vat_number, logo FROM outlets WHERE id = ?");
$stmtO->execute([$_SESSION['outlet_id']]);
$outlet = $stmtO->fetch(PDO::FETCH_ASSOC);
}
echo json_encode([
'success' => true,
'invoice_id' => $transaction_id,
'transaction_no' => $transaction_no,
'outlet_name' => $outlet['name'] ?? null,
'outlet_phone' => $outlet['phone'] ?? null,
'outlet_address' => $outlet['address'] ?? null,
'outlet_ctr_number' => $outlet['ctr_number'] ?? null,
'outlet_vat_number' => $outlet['vat_number'] ?? null,
'outlet_logo' => $outlet['logo'] ?? null
]);
} catch (Exception $e) {
$db->rollBack();
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
exit;
}
if ($action === 'save_theme') {
header('Content-Type: application/json');
if (!isset($_SESSION['user_id'])) {
echo json_encode(['success' => false, 'error' => 'Not authenticated']);
exit;
}
$theme = $_POST['theme'] ?? 'default';
$allowed = ['default', 'dark', 'ocean', 'forest', 'sunset', 'nord', 'dracula', 'citrus'];
if (!in_array($theme, $allowed)) $theme = 'default';
$stmt = db()->prepare("UPDATE users SET theme = ? WHERE id = ?");
$stmt->execute([$theme, $_SESSION['user_id']]);
$_SESSION['theme'] = $theme;
echo json_encode(['success' => true]);
exit;
}
if ($action === 'get_invoice_items') {
header('Content-Type: application/json');
$invoice_id = (int)$_GET['invoice_id'];
$type = $_GET['type'] ?? 'sale';
if ($type === 'purchase') {
$stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.sku
FROM purchase_items pi
JOIN stock_items i ON pi.item_id = i.id
WHERE pi.purchase_id = ?");
} else {
$stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.sku
FROM invoice_items ii
JOIN stock_items i ON ii.item_id = i.id
WHERE ii.invoice_id = ?");
}
$stmt->execute([$invoice_id]);
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if ($action === 'get_return_details') {
header('Content-Type: application/json');
$return_id = (int)$_GET['return_id'];
$type = $_GET['type'] ?? 'sale';
if ($type === 'purchase') {
$stmt = db()->prepare("SELECT pr.*, c.name as party_name FROM purchase_returns pr LEFT JOIN suppliers c ON pr.supplier_id = c.id WHERE pr.id = ?");
$stmt->execute([$return_id]);
$return = $stmt->fetch(PDO::FETCH_ASSOC);
if ($return) {
$stmtItems = db()->prepare("SELECT pri.*, i.name_en, i.name_ar, i.sku FROM purchase_return_items pri JOIN stock_items i ON pri.item_id = i.id WHERE pri.return_id = ?");
$stmtItems->execute([$return_id]);
$return['items'] = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
}
} else {
$stmt = db()->prepare("SELECT sr.*, c.name as party_name FROM sales_returns sr LEFT JOIN customers c ON sr.customer_id = c.id WHERE sr.id = ?");
$stmt->execute([$return_id]);
$return = $stmt->fetch(PDO::FETCH_ASSOC);
if ($return) {
$stmtItems = db()->prepare("SELECT sri.*, i.name_en, i.name_ar, i.sku FROM sales_return_items sri JOIN stock_items i ON sri.item_id = i.id WHERE sri.return_id = ?");
$stmtItems->execute([$return_id]);
$return['items'] = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
}
}
echo json_encode($return);
exit;
}
if ($action === 'translate') {
header('Content-Type: application/json');
require_once __DIR__ . '/ai/LocalAIApi.php';
$text = $_POST['text'] ?? '';
$target = $_POST['target'] ?? 'ar';
if (empty($text)) {
echo json_encode(['success' => false, 'error' => 'No text provided']);
exit;
}
$prompt = "Translate the following product name to " . ($target === 'ar' ? 'Arabic' : 'English') . ". Return ONLY the translation, no extra text or explanations.\n\nText: " . $text;
$resp = LocalAIApi::createResponse([
'input' => [
['role' => 'system', 'content' => 'You are a translation assistant. You translate product names between English and Arabic.'],
['role' => 'user', 'content' => $prompt],
],
]);
if (!empty($resp['success'])) {
$translated = trim(LocalAIApi::extractText($resp));
echo json_encode(['success' => true, 'translated' => $translated]);
} else {
error_log("Translation failed for text '$text': " . json_encode($resp));
echo json_encode(['success' => false, 'error' => $resp['error'] ?? 'Translation failed']);
}
exit;
}
}
// Redirect to login if not authenticated
if (!isset($_SESSION['user_id'])) {
?>
<!DOCTYPE html>
<html lang="<?= $lang ?>" dir="<?= $dir ?>">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= __('sign_in') ?> - Admin Panel</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap<?= $dir === 'rtl' ? '.rtl' : '' ?>.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: #f8f9fa; height: 100vh; display: flex; align-items: center; justify-content: center; font-family: 'Inter', sans-serif; }
.login-card { border: none; border-radius: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.05); width: 100%; max-width: 400px; padding: 40px; background: #fff; }
.btn-primary { border-radius: 12px; padding: 12px; font-weight: 600; background: linear-gradient(135deg, #6e8efb, #a777e3); border: none; }
.form-control { border-radius: 12px; padding: 12px; border: 1px solid #eee; }
.form-control:focus { box-shadow: 0 0 0 4px rgba(110, 142, 251, 0.1); border-color: #6e8efb; }
[dir="rtl"] { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
</style>
</head>
<body>
<div class="login-card text-center">
<div class="d-flex justify-content-end mb-3">
<a href="?lang=<?= $lang === 'ar' ? 'en' : 'ar' ?>" class="btn btn-sm btn-light border">
<?= $lang === 'ar' ? 'English' : 'العربية' ?>
</a>
</div>
<div class="mb-4">
<div class="d-inline-flex align-items-center justify-content-center bg-primary bg-opacity-10 rounded-circle mb-3" style="width: 60px; height: 60px;">
<i class="bi bi-shield-lock text-primary fs-3"></i>
</div>
<h4 class="fw-bold"><?= __('welcome_back') ?></h4>
<p class="text-muted small"><?= $lang === 'ar' ? 'يرجى إدخال تفاصيلك لتسجيل الدخول' : 'Please enter your details to sign in' ?></p>
</div>
<?php if ($login_error): ?>
<div class="alert alert-danger small py-2 mb-4"><?= $login_error ?></div>
<?php endif; ?>
<form method="POST">
<div class="mb-3 text-start">
<label class="form-label small fw-semibold"><?= __('username') ?></label>
<input type="text" name="username" class="form-control" placeholder="admin" required autofocus>
</div>
<div class="mb-4 text-start">
<label class="form-label small fw-semibold"><?= __('password') ?></label>
<input type="password" name="password" class="form-control" placeholder="••••••••" required>
</div>
<button type="submit" name="login" class="btn btn-primary w-100 mb-3"><?= __('sign_in') ?></button>
<div class="text-muted small"><?= $lang === 'ar' ? 'المستخدم الافتراضي: admin / admin' : 'Default user: admin / admin' ?></div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
<?php
exit;
}
// Handle POST Requests
$message = $_SESSION['message'] ?? '';
unset($_SESSION['message']);
function redirectWithMessage($msg, $url = null) {
if (!$url) {
$url = $_SERVER['REQUEST_URI'];
}
$_SESSION['message'] = $msg;
header("Location: $url");
exit;
}
// Fetch theme if not in session but user is logged in
if (isset($_SESSION['user_id']) && !isset($_SESSION['theme'])) {
$stmt = db()->prepare("SELECT theme FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
$_SESSION['theme'] = $stmt->fetchColumn() ?: 'default';
}
function numberToWordsOMR($number) {
$number = number_format((float)$number, 3, '.', '');
list($rials, $baisas) = explode('.', $number);
$rialsWordsEn = numberToWords((int)$rials);
$baisasWordsEn = numberToWords((int)$baisas);
$enResult = $rialsWordsEn . " Omani Rials";
if ((int)$baisas > 0) {
$enResult .= " and " . $baisasWordsEn . " Baisas";
}
$enResult .= " Only";
$rialsWordsAr = numberToWordsArabic((int)$rials);
$baisasWordsAr = numberToWordsArabic((int)$baisas);
$arResult = $rialsWordsAr . " ريال عماني";
if ((int)$baisas > 0) {
$arResult .= " و " . $baisasWordsAr . " بيسة";
}
$arResult .= " فقط";
return $enResult . " / " . $arResult;
}
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;
}
// --- Inventory & Core Handlers ---
if (isset($_POST['add_item'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$category_id = (int)$_POST['category_id'] ?: null;
$unit_id = (int)$_POST['unit_id'] ?: null;
$supplier_id = (int)$_POST['supplier_id'] ?: null;
$sku = $_POST['sku'] ?? '';
$sale_price = (float)($_POST['sale_price'] ?? 0);
$purchase_price = (float)($_POST['purchase_price'] ?? 0);
$stock_quantity = (float)($_POST['stock_quantity'] ?? 0);
$min_stock_level = (float)($_POST['min_stock_level'] ?? 0);
$vat_rate = (float)($_POST['vat_rate'] ?? 0);
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$is_promotion = isset($_POST['is_promotion']) ? 1 : 0;
$promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null;
$promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null;
$promotion_percent = (float)($_POST['promotion_percent'] ?? 0);
$image_path = null;
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'uploads/item_' . time() . '.' . $ext;
if (!is_dir('uploads')) mkdir('uploads', 0777, true);
if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) $image_path = $filename;
}
$stmt = db()->prepare("INSERT INTO stock_items (name_en, name_ar, category_id, unit_id, supplier_id, sku, sale_price, purchase_price, stock_quantity, min_stock_level, image_path, vat_rate, expiry_date, is_promotion, promotion_start, promotion_end, promotion_percent) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $image_path, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent]);
redirectWithMessage("Item added successfully!");
}
if (isset($_POST['edit_item'])) {
$id = (int)$_POST['id'];
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
$category_id = (int)$_POST['category_id'] ?: null;
$unit_id = (int)$_POST['unit_id'] ?: null;
$supplier_id = (int)$_POST['supplier_id'] ?: null;
$sku = $_POST['sku'] ?? '';
$sale_price = (float)($_POST['sale_price'] ?? 0);
$purchase_price = (float)($_POST['purchase_price'] ?? 0);
$stock_quantity = (float)($_POST['stock_quantity'] ?? 0);
$min_stock_level = (float)($_POST['min_stock_level'] ?? 0);
$vat_rate = (float)($_POST['vat_rate'] ?? 0);
$expiry_date = !empty($_POST['expiry_date']) ? $_POST['expiry_date'] : null;
$is_promotion = isset($_POST['is_promotion']) ? 1 : 0;
$promotion_start = !empty($_POST['promotion_start']) ? $_POST['promotion_start'] : null;
$promotion_end = !empty($_POST['promotion_end']) ? $_POST['promotion_end'] : null;
$promotion_percent = (float)($_POST['promotion_percent'] ?? 0);
$stmt = db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, category_id = ?, unit_id = ?, supplier_id = ?, sku = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, min_stock_level = ?, vat_rate = ?, expiry_date = ?, is_promotion = ?, promotion_start = ?, promotion_end = ?, promotion_percent = ? WHERE id = ?");
$stmt->execute([$name_en, $name_ar, $category_id, $unit_id, $supplier_id, $sku, $sale_price, $purchase_price, $stock_quantity, $min_stock_level, $vat_rate, $expiry_date, $is_promotion, $promotion_start, $promotion_end, $promotion_percent, $id]);
if (isset($_FILES['image']) && $_FILES['image']['error'] === 0) {
$ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
$filename = 'uploads/item_' . $id . '_' . time() . '.' . $ext;
if (move_uploaded_file($_FILES['image']['tmp_name'], $filename)) db()->prepare("UPDATE stock_items SET image_path = ? WHERE id = ?")->execute([$filename, $id]);
}
redirectWithMessage("Item updated successfully!");
}
if (isset($_POST['delete_item'])) {
db()->prepare("DELETE FROM stock_items WHERE id = ?")->execute([(int)$_POST['id']]);
redirectWithMessage("Item deleted successfully!");
}
if (isset($_POST['cancel_all_promotions'])) {
db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1");
redirectWithMessage("All active promotions have been cancelled.");
}
// Auto-expire finished promotions
db()->query("UPDATE stock_items SET is_promotion = 0 WHERE is_promotion = 1 AND promotion_end IS NOT NULL AND promotion_end < '" . date('Y-m-d') . "'");
if (isset($_POST['add_category'])) {
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']);
redirectWithMessage("Category added!");
}
if (isset($_POST['add_unit'])) {
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '']);
redirectWithMessage("Unit added!");
}
if (isset($_POST['edit_category'])) {
db()->prepare("UPDATE stock_categories SET name_en = ?, name_ar = ? WHERE id = ?")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', (int)$_POST['id']]);
redirectWithMessage("Category updated!");
}
if (isset($_POST['delete_category'])) {
db()->prepare("DELETE FROM stock_categories WHERE id = ?")->execute([(int)$_POST['id']]);
redirectWithMessage("Category deleted!");
}
if (isset($_POST['edit_unit'])) {
db()->prepare("UPDATE stock_units SET name_en = ?, name_ar = ?, short_name_en = ?, short_name_ar = ? WHERE id = ?")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $_POST['short_en'] ?? '', $_POST['short_ar'] ?? '', (int)$_POST['id']]);
redirectWithMessage("Unit updated!");
}
if (isset($_POST['delete_unit'])) {
db()->prepare("DELETE FROM stock_units WHERE id = ?")->execute([(int)$_POST['id']]);
redirectWithMessage("Unit deleted!");
}
if (isset($_POST['add_customer'])) {
$table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers';
$sql = "INSERT INTO $table (name, email, phone, tax_id, balance" . ($table === 'customers' ? ", loyalty_points" : "") . ") VALUES (?, ?, ?, ?, ?" . ($table === 'customers' ? ", 0" : "") . ")";
db()->prepare($sql)->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0)]);
redirectWithMessage("Entity added!");
}
if (isset($_POST['edit_customer'])) {
$table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers';
db()->prepare("UPDATE $table SET name = ?, email = ?, phone = ?, tax_id = ?, balance = ? WHERE id = ?")->execute([$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? '', $_POST['tax_id'] ?? '', (float)($_POST['balance'] ?? 0), (int)$_POST['id']]);
redirectWithMessage("Entity updated!");
}
if (isset($_POST['delete_customer'])) {
$table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers';
db()->prepare("DELETE FROM $table WHERE id = ?")->execute([(int)$_POST['id']]);
redirectWithMessage("Entity deleted!");
}
// Invoices
if (isset($_POST['add_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$type = $_POST['type'] ?? 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$cust_id = (int)$_POST['customer_id'];
$inv_date = $_POST['invoice_date'] ?: date('Y-m-d');
$due_date = $_POST['due_date'] ?: null;
$status = $_POST['status'] ?? 'pending';
$pay_type = $_POST['payment_type'] ?? 'cash';
$items = $_POST['item_ids'] ?? [];
if (empty($items)) {
throw new Exception("Please add at least one item.");
}
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$paid = (float)($_POST['paid_amount'] ?? 0);
if ($status === 'paid') $paid = $total_with_vat;
$stmt = $db->prepare("INSERT INTO $table ($cust_supplier_col, invoice_date, due_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$cust_id, $inv_date, $due_date, $status, $pay_type, $total_subtotal, $total_vat, $total_with_vat, $paid]);
$inv_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO $item_table ($fk_col, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item_id, $qty, $price, $vatAmount, $subtotal]);
// Update stock
$change = ($type === 'sale') ? -$qty : $qty;
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]);
$items_for_journal[] = ['id' => $item_id, 'qty' => $qty];
}
// Accounting
if ($type === 'sale') {
recordSaleJournal($inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat);
} else {
// For purchases, you might have recordPurchaseJournal, but let's check if it exists
if (function_exists('recordPurchaseJournal')) {
recordPurchaseJournal($inv_id, $total_with_vat, $inv_date, $items_for_journal, $total_vat);
}
}
$db->commit();
$msg = ($type === 'purchase' ? "Purchase" : "Invoice") . " #$inv_id created!";
redirectWithMessage($msg, "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales'));
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST["add_quotation"])) {
$db = db();
try {
$db->beginTransaction();
$cust_id = (int)$_POST["customer_id"];
$quot_date = $_POST["quotation_date"] ?: date("Y-m-d");
$valid_until = $_POST["valid_until"] ?: null;
$status = $_POST["status"] ?? "pending";
$items = $_POST["item_ids"] ?? [];
$qtys = $_POST["quantities"] ?? [];
$prices = $_POST["prices"] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$stmt = $db->prepare("INSERT INTO quotations (customer_id, quotation_date, valid_until, status, total_amount, vat_amount, total_with_vat) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$cust_id, $quot_date, $valid_until, $status, $total_subtotal, $total_vat, $total_with_vat]);
$quot_id = $db->lastInsertId();
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)")->execute([$quot_id, $item_id, $qty, $price, $subtotal]);
}
$db->commit();
$msg = "Quotation #$quot_id created!";
redirectWithMessage($msg, "index.php?page=quotations");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['edit_quotation'])) {
$db = db();
try {
$db->beginTransaction();
$quot_id = (int)$_POST['quotation_id'];
$cust_id = (int)$_POST['customer_id'];
$quot_date = $_POST['quotation_date'];
$valid_until = $_POST['valid_until'] ?: null;
$status = $_POST['status'] ?? 'pending';
$items = $_POST['item_ids'] ?? [];
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$stmt = $db->prepare("UPDATE quotations SET customer_id = ?, quotation_date = ?, valid_until = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ? WHERE id = ?");
$stmt->execute([$cust_id, $quot_date, $valid_until, $status, $total_subtotal, $total_vat, $total_with_vat, $quot_id]);
// Delete old items
$db->prepare("DELETE FROM quotation_items WHERE quotation_id = ?")->execute([$quot_id]);
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO quotation_items (quotation_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)")->execute([$quot_id, $item_id, $qty, $price, $subtotal]);
}
$db->commit();
$msg = "Quotation #$quot_id updated!";
redirectWithMessage($msg, "index.php?page=quotations");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['delete_quotation'])) {
$id = (int)$_POST['id'];
db()->prepare("DELETE FROM quotations WHERE id = ?")->execute([$id]);
redirectWithMessage("Quotation deleted!", "index.php?page=quotations");
}
if (isset($_POST['add_lpo'])) {
$db = db();
try {
$db->beginTransaction();
$supp_id = (int)$_POST['supplier_id'];
$lpo_date = $_POST['lpo_date'] ?: date('Y-m-d');
$delivery_date = $_POST['delivery_date'] ?: null;
$status = 'pending';
$terms = $_POST['terms_conditions'] ?? '';
$items = $_POST['item_ids'] ?? [];
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$stmt = $db->prepare("INSERT INTO lpos (outlet_id, supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?)");
$stmt->execute([(int)($_SESSION["outlet_id"] ?? 1), $supp_id, $lpo_date, $delivery_date, $total_subtotal, $total_vat, $total_with_vat, $terms]);
$lpo_id = $db->lastInsertId();
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]);
}
$db->commit();
redirectWithMessage("LPO #$lpo_id created!", "index.php?page=lpos");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['edit_lpo'])) {
$db = db();
try {
$db->beginTransaction();
$lpo_id = (int)$_POST['lpo_id'];
$supp_id = (int)$_POST['supplier_id'];
$lpo_date = $_POST['lpo_date'];
$delivery_date = $_POST['delivery_date'] ?: null;
$status = $_POST['status'] ?? 'pending';
$terms = $_POST['terms_conditions'] ?? '';
$items = $_POST['item_ids'] ?? [];
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$stmt = $db->prepare("UPDATE lpos SET supplier_id = ?, lpo_date = ?, delivery_date = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, terms_conditions = ? WHERE id = ?");
$stmt->execute([$supp_id, $lpo_date, $delivery_date, $status, $total_subtotal, $total_vat, $total_with_vat, $terms, $lpo_id]);
$db->prepare("DELETE FROM lpo_items WHERE lpo_id = ?")->execute([$lpo_id]);
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]);
}
$db->commit();
redirectWithMessage("LPO #$lpo_id updated!", "index.php?page=lpos");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['delete_lpo'])) {
$id = (int)$_POST['id'];
db()->prepare("DELETE FROM lpos WHERE id = ?")->execute([$id]);
redirectWithMessage("LPO deleted!", "index.php?page=lpos");
}
if (isset($_POST['convert_to_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$quot_id = (int)$_POST['quotation_id'];
$stmt = $db->prepare("SELECT * FROM quotations WHERE id = ?");
$stmt->execute([$quot_id]);
$quot = $stmt->fetch();
if (!$quot) throw new Exception("Quotation not found.");
if ($quot['status'] === 'converted') throw new Exception("Quotation already converted.");
$stmtItems = $db->prepare("SELECT * FROM quotation_items WHERE quotation_id = ?");
$stmtItems->execute([$quot_id]);
$qItems = $stmtItems->fetchAll();
// Create Invoice
$inv_date = date('Y-m-d');
$stmtInv = $db->prepare("INSERT INTO invoices (outlet_id, customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
$stmtInv->execute([(int)($_SESSION["outlet_id"] ?? 1), $quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat']]);
$inv_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($qItems as $item) {
$db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_price']]);
// Update stock
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
}
// Update Quotation status
$db->prepare("UPDATE quotations SET status = 'converted' WHERE id = ?")->execute([$quot_id]);
// Accounting
recordSaleJournal($inv_id, $quot['total_with_vat'], $inv_date, $items_for_journal, $quot['vat_amount']);
$db->commit();
redirectWithMessage("Quotation converted to Invoice #$inv_id successfully!", "index.php?page=sales");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['convert_lpo_to_purchase'])) {
$db = db();
try {
$db->beginTransaction();
$lpo_id = (int)$_POST['lpo_id'];
$stmt = $db->prepare("SELECT * FROM lpos WHERE id = ?");
$stmt->execute([$lpo_id]);
$lpo = $stmt->fetch();
if (!$lpo) throw new Exception("LPO not found.");
if ($lpo['status'] === 'converted') throw new Exception("LPO already converted.");
$stmtItems = $db->prepare("SELECT * FROM lpo_items WHERE lpo_id = ?");
$stmtItems->execute([$lpo_id]);
$lItems = $stmtItems->fetchAll();
// Create Purchase Invoice
$pur_date = date('Y-m-d');
$stmtPur = $db->prepare("INSERT INTO purchases (outlet_id, supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
$stmtPur->execute([(int)($_SESSION["outlet_id"] ?? 1), $lpo['supplier_id'], $pur_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat']]);
$pur_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($lItems as $item) {
$db->prepare("INSERT INTO purchase_items (purchase_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$pur_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_amount']]);
// Update stock
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
}
// Update LPO status
$db->prepare("UPDATE lpos SET status = 'converted' WHERE id = ?")->execute([$lpo_id]);
$db->commit();
redirectWithMessage("LPO converted to Purchase Invoice #$pur_id successfully!", "index.php?page=purchases");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['record_payment'])) {
$id = (int)$_POST['invoice_id'];
$amount = (float)$_POST['amount'];
$date = $_POST['payment_date'] ?: date('Y-m-d');
$method = $_POST['payment_method'] ?? 'Cash';
$type = ($page === 'purchases') ? 'purchase' : 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$payment_table = ($type === 'purchase') ? 'purchase_payments' : 'payments';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$db = db();
$db->prepare("INSERT INTO $payment_table ($fk_col, amount, payment_date, payment_method, notes) VALUES (?, ?, ?, ?, ?)")->execute([$id, $amount, $date, $method, $_POST['notes'] ?? '']);
$pay_id = $db->lastInsertId();
$db->prepare("UPDATE $table SET paid_amount = paid_amount + ?, status = IF(paid_amount + ? >= total_with_vat, 'paid', 'partially_paid') WHERE id = ?")->execute([$amount, $amount, $id]);
if ($type === 'sale') recordPaymentReceivedJournal((int)$pay_id, $amount, $date, $method);
else recordPaymentMadeJournal((int)$pay_id, $amount, $date, $method);
$_SESSION['trigger_receipt_modal'] = true;
$_SESSION['show_receipt_id'] = $pay_id;
redirectWithMessage("Payment recorded!", "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales'));
}
if (isset($_POST['add_expense'])) {
$amt = (float)$_POST['amount'];
$date = $_POST['expense_date'] ?: date('Y-m-d');
$desc = $_POST['description'] ?? '';
db()->prepare("INSERT INTO expenses (outlet_id, category_id, amount, expense_date, reference_no, description) VALUES (?, ?, ?, ?, ?, ?)")->execute([(int)($_SESSION["outlet_id"] ?? 1), (int)$_POST['category_id'], $amt, $date, $_POST['reference_no'] ?? '', $desc]);
recordExpenseJournal(db()->lastInsertId(), $amt, $date, $desc);
redirectWithMessage("Expense recorded!", "index.php?page=expenses");
}
if (isset($_POST['import_customers'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
}
} else {
// CSV fallback
$handle = fopen($tmpPath, "r");
fgetcsv($handle); // skip header
while (($data = fgetcsv($handle)) !== FALSE) {
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Customers imported! $count processed." . ($errors ? " ($errors skipped)" : ""), "index.php?page=customers");
}
}
if (isset($_POST['import_suppliers'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO suppliers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
}
} else {
// CSV fallback
$handle = fopen($tmpPath, "r");
fgetcsv($handle); // skip header
while (($data = fgetcsv($handle)) !== FALSE) {
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO suppliers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Suppliers imported! $count processed." . ($errors ? " ($errors skipped)" : ""), "index.php?page=suppliers");
}
}
if (isset($_POST['import_categories'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")
->execute([$name_en, $name_ar]);
$count++;
}
}
} else {
$handle = fopen($tmpPath, "r");
fgetcsv($handle); // skip header
while (($data = fgetcsv($handle)) !== FALSE) {
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")
->execute([$name_en, $name_ar]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Categories imported! $count processed.", "index.php?page=stock_categories");
}
}
if (isset($_POST['import_units'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
$short_name_en = trim($data[2] ?? '');
$short_name_ar = trim($data[3] ?? '');
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")
->execute([$name_en, $name_ar, $short_name_en, $short_name_ar]);
$count++;
}
}
} else {
$handle = fopen($tmpPath, "r");
fgetcsv($handle);
while (($data = fgetcsv($handle)) !== FALSE) {
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
$short_name_en = trim($data[2] ?? '');
$short_name_ar = trim($data[3] ?? '');
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")
->execute([$name_en, $name_ar, $short_name_en, $short_name_ar]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Units imported! $count processed.", "index.php?page=stock_units");
}
}
if (isset($_POST['import_items'])) {
error_log("Import items triggered. POST: " . print_r($_POST, true));
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
error_log("File uploaded to: $tmpPath");
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
if ($firstBytes === "PK\x03\x04") {
// EXCEL IMPORT VIA SimpleXLSX
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
$rows = $xlsx->rows();
$rowNum = 0;
$count = 0;
$errors = 0;
foreach ($rows as $data) {
$rowNum++;
if ($rowNum === 1) continue; // Skip header
$sku = trim($data[0] ?? '');
$name_en = trim($data[1] ?? '');
$name_ar = trim($data[2] ?? '');
$sale_price = (float)($data[3] ?? 0);
$purchase_price = (float)($data[4] ?? 0);
$qty = (float)($data[5] ?? 0);
$vat_rate = (float)($data[6] ?? 0);
if (!$sku && !$name_en) { $errors++; continue; }
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?");
$check->execute([$sku]);
if ($check->fetch()) {
db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, vat_rate = ? WHERE sku = ?")
->execute([$name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate, $sku]);
} else {
db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?)")
->execute([$sku, $name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate]);
}
$count++;
}
redirectWithMessage("Excel Import completed! $count items processed." . ($errors > 0 ? " ($errors rows skipped.)" : ""), "index.php?page=items");
} else {
$message = "Error parsing Excel file: " . \Shuchkin\SimpleXLSX::parseError();
}
} else {
// Check for BOM and skip it
if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") {
$handle = fopen($tmpPath, "r");
fseek($handle, 3);
} else {
$handle = fopen($tmpPath, "r");
}
$firstLine = fgets($handle);
rewind($handle);
if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") fseek($handle, 3);
error_log("First line of CSV: " . $firstLine);
$seps = [",", ";", "\t", "|"];
$sep = ",";
$maxCount = 0;
foreach ($seps as $s) {
$count = substr_count($firstLine, $s);
if ($count > $maxCount) {
$maxCount = $count;
$sep = $s;
}
}
error_log("Detected separator: $sep");
$count = 0;
$errors = 0;
$rowNum = 0;
while (($data = fgetcsv($handle, 10000, $sep)) !== FALSE) {
$rowNum++;
if ($rowNum === 1) continue; // Skip header
if (count($data) < 4) { $errors++; continue; }
$sku = trim($data[0]);
$name_en = trim($data[1]);
$name_ar = trim($data[2]);
$price = (float)($data[3] ?? 0);
$qty = (float)($data[4] ?? 0);
$vat_rate = (float)($data[5] ?? 0);
if (!$sku || !$name_en) { $errors++; continue; }
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?");
$check->execute([$sku]);
if ($check->fetch()) {
db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, stock_quantity = ?, vat_rate = ? WHERE sku = ?")
->execute([$name_en, $name_ar, $price, $qty, $vat_rate, $sku]);
} else {
db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?)")
->execute([$sku, $name_en, $name_ar, $price, $qty, $vat_rate]);
}
$count++;
}
fclose($handle);
redirectWithMessage("Import completed! $count items processed." . ($errors > 0 ? " ($errors rows skipped due to data errors.)" : ""), "index.php?page=items");
}
} else {
$errCode = $_FILES['excel_file']['error'] ?? 'no file';
$message = "Error: File upload failed. (Error code: $errCode)";
}
}
if (isset($_POST['add_expense_category'])) {
$name_en = $_POST['name_en'] ?? '';
$name_ar = $_POST['name_ar'] ?? '';
db()->prepare("INSERT INTO expense_categories (name_en, name_ar) VALUES (?, ?)")->execute([$name_en, $name_ar]);
redirectWithMessage("Expense category added!", "index.php?page=expense_categories");
}
if (isset($_POST['add_payment_method'])) {
$name = $_POST['name'] ?? '';
db()->prepare("INSERT INTO payment_methods (name) VALUES (?)")->execute([$name]);
redirectWithMessage("Payment method added!", "index.php?page=payment_methods");
}
if (isset($_POST['delete_invoice'])) {
$id = (int)$_POST['id'];
$type = ($page === 'purchases') ? 'purchase' : 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
db()->prepare("DELETE FROM $table WHERE id = ?")->execute([$id]);
db()->prepare("DELETE FROM $item_table WHERE $fk_col = ?")->execute([$id]);
redirectWithMessage(($type === 'purchase' ? "Purchase" : "Invoice") . " deleted!", "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales'));
}
if (isset($_POST['delete_quotation'])) {
$id = (int)$_POST['id'];
db()->prepare("DELETE FROM quotations WHERE id = ?")->execute([$id]);
$message = "Quotation deleted!";
}
if (isset($_POST['add_lpo'])) {
$db = db();
try {
$db->beginTransaction();
$supp_id = (int)$_POST['supplier_id'];
$lpo_date = $_POST['lpo_date'] ?: date('Y-m-d');
$delivery_date = $_POST['delivery_date'] ?: null;
$terms = $_POST['terms_conditions'] ?? '';
$items = $_POST['item_ids'] ?? [];
if (empty($items)) {
throw new Exception("Please add at least one item.");
}
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$stmt = $db->prepare("INSERT INTO lpos (outlet_id, supplier_id, lpo_date, delivery_date, status, total_amount, vat_amount, total_with_vat, terms_conditions) VALUES (?, ?, ?, ?, 'pending', ?, ?, ?, ?)");
$stmt->execute([(int)($_SESSION["outlet_id"] ?? 1), $supp_id, $lpo_date, $delivery_date, $total_subtotal, $total_vat, $total_with_vat, $terms]);
$lpo_id = $db->lastInsertId();
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]);
}
$db->commit();
redirectWithMessage("LPO #$lpo_id created!", "index.php?page=lpos");
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['edit_lpo'])) {
$db = db();
try {
$db->beginTransaction();
$lpo_id = (int)$_POST['lpo_id'];
$supp_id = (int)$_POST['supplier_id'];
$lpo_date = $_POST['lpo_date'];
$delivery_date = $_POST['delivery_date'] ?: null;
$status = $_POST['status'] ?? 'pending';
$terms = $_POST['terms_conditions'] ?? '';
$items = $_POST['item_ids'] ?? [];
if (empty($items)) {
throw new Exception("Please add at least one item.");
}
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$stmt = $db->prepare("UPDATE lpos SET supplier_id = ?, lpo_date = ?, delivery_date = ?, status = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, terms_conditions = ? WHERE id = ?");
$stmt->execute([$supp_id, $lpo_date, $delivery_date, $status, $total_subtotal, $total_vat, $total_with_vat, $terms, $lpo_id]);
$db->prepare("DELETE FROM lpo_items WHERE lpo_id = ?")->execute([$lpo_id]);
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO lpo_items (lpo_id, item_id, quantity, unit_price, vat_percentage, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)")->execute([$lpo_id, $item_id, $qty, $price, $vatRate, $vatAmount, $subtotal]);
}
$db->commit();
$message = "LPO #$lpo_id updated!";
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['delete_lpo'])) {
$id = (int)$_POST['id'];
db()->prepare("DELETE FROM lpos WHERE id = ?")->execute([$id]);
$message = "LPO deleted!";
}
if (isset($_POST['convert_to_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$quot_id = (int)$_POST['quotation_id'];
$stmt = $db->prepare("SELECT * FROM quotations WHERE id = ?");
$stmt->execute([$quot_id]);
$quot = $stmt->fetch();
if (!$quot) throw new Exception("Quotation not found.");
if ($quot['status'] === 'converted') throw new Exception("Quotation already converted.");
$stmtItems = $db->prepare("SELECT * FROM quotation_items WHERE quotation_id = ?");
$stmtItems->execute([$quot_id]);
$qItems = $stmtItems->fetchAll();
// Create Invoice
$inv_date = date('Y-m-d');
$stmtInv = $db->prepare("INSERT INTO invoices (outlet_id, customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
$stmtInv->execute([(int)($_SESSION["outlet_id"] ?? 1), $quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat']]);
$inv_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($qItems as $item) {
$db->prepare("INSERT INTO invoice_items (invoice_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$inv_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_price']]);
// Update stock
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
}
// Update Quotation status
$db->prepare("UPDATE quotations SET status = 'converted' WHERE id = ?")->execute([$quot_id]);
// Accounting
recordSaleJournal($inv_id, $quot['total_with_vat'], $inv_date, $items_for_journal, $quot['vat_amount']);
$db->commit();
$message = "Quotation converted to Invoice #$inv_id successfully!";
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['convert_lpo_to_purchase'])) {
$db = db();
try {
$db->beginTransaction();
$lpo_id = (int)$_POST['lpo_id'];
$stmt = $db->prepare("SELECT * FROM lpos WHERE id = ?");
$stmt->execute([$lpo_id]);
$lpo = $stmt->fetch();
if (!$lpo) throw new Exception("LPO not found.");
if ($lpo['status'] === 'converted') throw new Exception("LPO already converted.");
$stmtItems = $db->prepare("SELECT * FROM lpo_items WHERE lpo_id = ?");
$stmtItems->execute([$lpo_id]);
$lItems = $stmtItems->fetchAll();
// Create Purchase Invoice
$inv_date = date('Y-m-d');
$stmtPur = $db->prepare("INSERT INTO purchases (outlet_id, supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount) VALUES (?, ?, ?, 'unpaid', 'credit', ?, ?, ?, 0)");
$stmtPur->execute([(int)($_SESSION["outlet_id"] ?? 1), $lpo['supplier_id'], $inv_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat']]);
$pur_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($lItems as $item) {
$db->prepare("INSERT INTO purchase_items (purchase_id, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$pur_id, $item['item_id'], $item['quantity'], $item['unit_price'], $item['vat_amount'], $item['total_amount']]);
// Update stock
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$item['quantity'], $item['item_id']]);
$items_for_journal[] = ['id' => $item['item_id'], 'qty' => $item['quantity']];
}
// Update LPO status
$db->prepare("UPDATE lpos SET status = 'converted' WHERE id = ?")->execute([$lpo_id]);
// Accounting (if exists)
if (function_exists('recordPurchaseJournal')) {
recordPurchaseJournal($pur_id, $lpo['total_with_vat'], $inv_date, $items_for_journal, $lpo['vat_amount']);
}
$db->commit();
$message = "LPO converted to Purchase Invoice #$pur_id successfully!";
header("Location: index.php?page=purchases");
exit;
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
if (isset($_POST['record_payment'])) {
$id = (int)$_POST['invoice_id'];
$amount = (float)$_POST['amount'];
$date = $_POST['payment_date'] ?: date('Y-m-d');
$method = $_POST['payment_method'] ?? 'Cash';
$type = ($page === 'purchases') ? 'purchase' : 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$payment_table = ($type === 'purchase') ? 'purchase_payments' : 'payments';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$db = db();
$db->prepare("INSERT INTO $payment_table ($fk_col, amount, payment_date, payment_method, notes) VALUES (?, ?, ?, ?, ?)")->execute([$id, $amount, $date, $method, $_POST['notes'] ?? '']);
$pay_id = $db->lastInsertId();
$db->prepare("UPDATE $table SET paid_amount = paid_amount + ?, status = IF(paid_amount + ? >= total_with_vat, 'paid', 'partially_paid') WHERE id = ?")->execute([$amount, $amount, $id]);
if ($type === 'sale') recordPaymentReceivedJournal((int)$pay_id, $amount, $date, $method);
else recordPaymentMadeJournal((int)$pay_id, $amount, $date, $method);
$message = "Payment recorded!";
$_SESSION['trigger_receipt_modal'] = true; $_SESSION['show_receipt_id'] = $pay_id;
}
if (isset($_POST['add_expense'])) {
$amt = (float)$_POST['amount'];
$date = $_POST['expense_date'] ?: date('Y-m-d');
$desc = $_POST['description'] ?? '';
db()->prepare("INSERT INTO expenses (outlet_id, category_id, amount, expense_date, reference_no, description) VALUES (?, ?, ?, ?, ?, ?)")->execute([(int)($_SESSION["outlet_id"] ?? 1), (int)$_POST['category_id'], $amt, $date, $_POST['reference_no'] ?? '', $desc]);
recordExpenseJournal(db()->lastInsertId(), $amt, $date, $desc);
$message = "Expense recorded!";
}
if (isset($_POST['import_customers'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
}
} else {
// CSV fallback
$handle = fopen($tmpPath, "r");
fgetcsv($handle); // skip header
while (($data = fgetcsv($handle)) !== FALSE) {
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO customers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Customers imported! $count processed." . ($errors ? " ($errors skipped)" : ""), "index.php?page=customers");
}
}
if (isset($_POST['import_suppliers'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO suppliers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
}
} else {
// CSV fallback
$handle = fopen($tmpPath, "r");
fgetcsv($handle); // skip header
while (($data = fgetcsv($handle)) !== FALSE) {
$name = trim($data[0] ?? '');
if (!$name) { $errors++; continue; }
$email = trim($data[1] ?? '');
$phone = trim($data[2] ?? '');
$tax_id = trim($data[3] ?? '');
$balance = (float)($data[4] ?? 0);
db()->prepare("INSERT INTO suppliers (name, email, phone, tax_id, balance) VALUES (?, ?, ?, ?, ?)")
->execute([$name, $email, $phone, $tax_id, $balance]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Suppliers imported! $count processed." . ($errors ? " ($errors skipped)" : ""), "index.php?page=suppliers");
}
}
if (isset($_POST['import_categories'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")
->execute([$name_en, $name_ar]);
$count++;
}
}
} else {
$handle = fopen($tmpPath, "r");
fgetcsv($handle); // skip header
while (($data = fgetcsv($handle)) !== FALSE) {
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
db()->prepare("INSERT INTO stock_categories (name_en, name_ar) VALUES (?, ?)")
->execute([$name_en, $name_ar]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Categories imported! $count processed.", "index.php?page=stock_categories");
}
}
if (isset($_POST['import_units'])) {
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
$count = 0; $errors = 0;
if ($firstBytes === "PK\x03\x04") {
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
foreach ($xlsx->rows() as $i => $data) {
if ($i === 0) continue;
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
$short_name_en = trim($data[2] ?? '');
$short_name_ar = trim($data[3] ?? '');
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")
->execute([$name_en, $name_ar, $short_name_en, $short_name_ar]);
$count++;
}
}
} else {
$handle = fopen($tmpPath, "r");
fgetcsv($handle);
while (($data = fgetcsv($handle)) !== FALSE) {
$name_en = trim($data[0] ?? '');
if (!$name_en) { $errors++; continue; }
$name_ar = trim($data[1] ?? '');
$short_name_en = trim($data[2] ?? '');
$short_name_ar = trim($data[3] ?? '');
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?)")
->execute([$name_en, $name_ar, $short_name_en, $short_name_ar]);
$count++;
}
fclose($handle);
}
redirectWithMessage("Units imported! $count processed.", "index.php?page=stock_units");
}
}
if (isset($_POST['import_items'])) {
error_log("Import items triggered. POST: " . print_r($_POST, true));
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
error_log("File uploaded to: $tmpPath");
$firstBytes = file_get_contents($tmpPath, false, null, 0, 4);
if ($firstBytes === "PK\x03\x04") {
// EXCEL IMPORT VIA SimpleXLSX
require_once __DIR__ . '/includes/SimpleXLSX.php';
if ($xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath)) {
$rows = $xlsx->rows();
$rowNum = 0;
$count = 0;
$errors = 0;
foreach ($rows as $data) {
$rowNum++;
if ($rowNum === 1) continue; // Skip header
$sku = trim($data[0] ?? '');
$name_en = trim($data[1] ?? '');
$name_ar = trim($data[2] ?? '');
$sale_price = (float)($data[3] ?? 0);
$purchase_price = (float)($data[4] ?? 0);
$qty = (float)($data[5] ?? 0);
$vat_rate = (float)($data[6] ?? 0);
if (!$sku && !$name_en) { $errors++; continue; }
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ?");
$check->execute([$sku]);
if ($check->fetch()) {
db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, stock_quantity = ?, vat_rate = ? WHERE sku = ?")
->execute([$name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate, $sku]);
} else {
db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?)")
->execute([$sku, $name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate]);
}
$count++;
}
redirectWithMessage("Excel Import completed! $count items processed." . ($errors > 0 ? " ($errors rows skipped.)" : ""), "index.php?page=items");
} else {
$message = "Error parsing Excel file: " . \Shuchkin\SimpleXLSX::parseError();
}
} else {
// Check for BOM and skip it
if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") {
$handle = fopen($tmpPath, "r");
fseek($handle, 3);
} else {
$handle = fopen($tmpPath, "r");
}
$firstLine = fgets($handle);
rewind($handle);
if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") fseek($handle, 3);
error_log("First line of CSV: " . $firstLine);
$seps = [",", ";", "\t", "|"];
$sep = ",";
$maxCount = 0;
foreach ($seps as $s) {
$count = substr_count($firstLine, $s);
if ($count > $maxCount) {
$maxCount = $count;
$sep = $s;
}
}
error_log("Detected separator: '$sep' (count: $maxCount)");
// Try to detect if first line is header
$firstRow = fgetcsv($handle, 0, $sep);
$isHeader = true;
// If the first row's 4th or 5th columns are numeric, it's probably NOT a header
if (isset($firstRow[3]) && is_numeric(str_replace(',', '', trim($firstRow[3])))) $isHeader = false;
if (isset($firstRow[4]) && is_numeric(str_replace(',', '', trim($firstRow[4])))) $isHeader = false;
if (!$isHeader) {
rewind($handle);
if (substr($firstBytes, 0, 3) === "\xEF\xBB\xBF") fseek($handle, 3);
error_log("No header detected, starting from first row.");
} else {
error_log("Header detected and skipped: " . print_r($firstRow, true));
}
$count = 0; $errors = 0;
while (($data = fgetcsv($handle, 0, $sep)) !== FALSE) {
if (count($data) < 2) continue; // Skip empty or single-column rows that aren't SKUs
if (empty($data[0]) && empty($data[1]) && empty($data[2])) continue;
try {
foreach ($data as &$val) {
if ($val === null) continue;
if (!mb_check_encoding($val, 'UTF-8')) {
$val = mb_convert_encoding($val, 'UTF-8', 'Windows-1252');
}
$val = mb_convert_encoding($val, 'UTF-8', 'UTF-8');
$val = trim($val);
}
// Map: 0:sku, 1:name_en, 2:name_ar, 3:sale_price, 4:cost_price
$sku = substr(trim($data[0] ?? ''), 0, 100);
$name_en = $data[1] ?? '';
$name_ar = $data[2] ?? '';
$sale_price = str_replace(',', '', $data[3] ?? 0);
$purchase_price = str_replace(',', '', $data[4] ?? 0);
if (empty($sku) && empty($name_en)) continue;
$stmt = db()->prepare("INSERT INTO stock_items (sku, name_en, name_ar, sale_price, purchase_price)
VALUES (?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE name_en=VALUES(name_en), name_ar=VALUES(name_ar),
sale_price=VALUES(sale_price), purchase_price=VALUES(purchase_price)");
$stmt->execute([$sku, $name_en, $name_ar, (float)$sale_price, (float)$purchase_price]);
$count++;
} catch (Throwable $e) {
file_put_contents('import_debug.log', date('Y-m-d H:i:s') . " - Row error: " . $e->getMessage() . " Data: " . json_encode($data) . "\n", FILE_APPEND);
error_log("Import row error: " . $e->getMessage() . " Data: " . print_r($data, true));
$errors++;
}
}
fclose($handle);
$message = "Import completed! $count items processed." . ($errors > 0 ? " ($errors rows skipped due to data errors.)" : "");
error_log("Import finished. Processed: $count, Errors: $errors");
}
} else {
$errCode = $_FILES['excel_file']['error'] ?? 'no file';
error_log("File upload failed or missing. Error code: $errCode");
$message = "Error: File upload failed. (Error code: $errCode)";
}
}
if (isset($_POST['add_expense_category'])) {
db()->prepare("INSERT INTO expense_categories (name_en, name_ar) VALUES (?, ?)")->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '']);
$message = "Expense category added!";
}
if (isset($_POST['add_payment_method'])) {
db()->prepare("INSERT INTO payment_methods (name, type) VALUES (?, ?)")->execute([$_POST['name'] ?? '', $_POST['type'] ?? 'Cash']);
$message = "Payment method added!";
}
if (isset($_POST['delete_invoice'])) {
$id = (int)$_POST['id'];
$type = ($page === 'purchases') ? 'purchase' : 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
db()->prepare("DELETE FROM $table WHERE id = ?")->execute([$id]);
db()->prepare("DELETE FROM $item_table WHERE $fk_col = ?")->execute([$id]);
$message = ($type === 'purchase' ? "Purchase" : "Invoice") . " deleted!";
}
if (isset($_POST['edit_invoice'])) {
$db = db();
try {
$db->beginTransaction();
$id = (int)$_POST['invoice_id'];
$type = ($page === 'purchases') ? 'purchase' : 'sale';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$item_table = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$fk_col = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$cust_id = (int)$_POST['customer_id'];
$date = $_POST['invoice_date'] ?: date('Y-m-d');
$due_date = $_POST['due_date'] ?: null;
$status = $_POST['status'] ?? 'pending';
$pay_type = $_POST['payment_type'] ?? 'cash';
$items = $_POST['item_ids'] ?? [];
$qtys = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
$total_subtotal = 0;
$total_vat = 0;
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$total_subtotal += $subtotal;
$total_vat += $vatAmount;
}
$total_with_vat = $total_subtotal + $total_vat;
$paid = (float)($_POST['paid_amount'] ?? 0);
if ($status === 'paid') $paid = $total_with_vat;
$db->prepare("UPDATE $table SET $cust_supplier_col = ?, invoice_date = ?, due_date = ?, status = ?, payment_type = ?, total_amount = ?, vat_amount = ?, total_with_vat = ?, paid_amount = ? WHERE id = ?")
->execute([$cust_id, $date, $due_date, $status, $pay_type, $total_subtotal, $total_vat, $total_with_vat, $paid, $id]);
// Revert stock for old items
$stmtOld = $db->prepare("SELECT item_id, quantity FROM $item_table WHERE $fk_col = ?");
$stmtOld->execute([$id]);
$oldItems = $stmtOld->fetchAll();
foreach ($oldItems as $old) {
$change = ($type === 'sale') ? (float)$old['quantity'] : -(float)$old['quantity'];
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $old['item_id']]);
}
// Delete old items
$db->prepare("DELETE FROM $item_table WHERE $fk_col = ?")->execute([$id]);
// Insert new items and update stock
foreach ($items as $i => $item_id) {
if (!$item_id) continue;
$qty = (float)$qtys[$i];
$price = (float)$prices[$i];
$subtotal = $qty * $price;
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([$item_id]);
$vatRate = (float)$stmtVat->fetchColumn();
$vatAmount = $subtotal * ($vatRate / 100);
$db->prepare("INSERT INTO $item_table ($fk_col, item_id, quantity, unit_price, vat_amount, total_price) VALUES (?, ?, ?, ?, ?, ?)")->execute([$id, $item_id, $qty, $price, $vatAmount, $subtotal]);
$change = ($type === 'sale') ? -$qty : $qty;
$db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?")->execute([$change, $item_id]);
}
$db->commit();
$msg = ($type === 'purchase' ? "Purchase" : "Invoice") . " updated successfully!";
redirectWithMessage($msg, "index.php?page=" . ($type === 'purchase' ? 'purchases' : 'sales'));
} catch (Exception $e) { $db->rollBack(); $message = "Error: " . $e->getMessage(); }
}
// --- 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]);
redirectWithMessage("Department updated successfully!", "index.php?page=hr_departments");
}
}
if (isset($_POST['delete_hr_department'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_departments WHERE id = ?");
$stmt->execute([$id]);
redirectWithMessage("Department deleted successfully!", "index.php?page=hr_departments");
}
}
if (isset($_POST['add_hr_employee'])) {
$dept_id = (int)$_POST['department_id'];
$biometric_id = $_POST['biometric_id'] ?? '';
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$pos = $_POST['position'] ?? '';
$salary = (float)$_POST['salary'];
$j_date = $_POST['joining_date'] ?: date('Y-m-d');
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]);
redirectWithMessage("Employee added successfully!", "index.php?page=hr_employees");
}
}
if (isset($_POST['edit_hr_employee'])) {
$id = (int)$_POST['id'];
$dept_id = (int)$_POST['department_id'];
$biometric_id = $_POST['biometric_id'] ?? '';
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$phone = $_POST['phone'] ?? '';
$pos = $_POST['position'] ?? '';
$salary = (float)$_POST['salary'];
$j_date = $_POST['joining_date'];
$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]);
redirectWithMessage("Employee updated successfully!", "index.php?page=hr_employees");
}
}
if (isset($_POST['delete_hr_employee'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM hr_employees WHERE id = ?");
$stmt->execute([$id]);
redirectWithMessage("Employee deleted successfully!", "index.php?page=hr_employees");
}
}
if (isset($_POST['mark_hr_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) {
$check = db()->prepare("SELECT id FROM hr_attendance WHERE employee_id = ? AND attendance_date = ?");
$check->execute([$emp_id, $date]);
if ($check->fetch()) {
$stmt = db()->prepare("UPDATE hr_attendance SET status = ?, clock_in = ?, clock_out = ? WHERE employee_id = ? AND attendance_date = ?");
$stmt->execute([$status, $in, $out, $emp_id, $date]);
} else {
$stmt = db()->prepare("INSERT INTO hr_attendance (employee_id, attendance_date, status, clock_in, clock_out) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$emp_id, $date, $status, $in, $out]);
}
redirectWithMessage("Attendance marked successfully!", "index.php?page=hr_attendance&date=$date");
}
}
if (isset($_POST['generate_payroll'])) {
$emp_id = (int)$_POST['employee_id'];
$month = (int)$_POST['month'];
$year = (int)$_POST['year'];
$bonus = (float)$_POST['bonus'];
$deduct = (float)$_POST['deductions'];
$notes = $_POST['notes'] ?? '';
$db = db();
try {
$db->beginTransaction();
$emp = $db->query("SELECT salary FROM hr_employees WHERE id = $emp_id")->fetch();
if (!$emp) throw new Exception("Employee not found.");
$basic = (float)$emp['salary'];
$net = $basic + $bonus - $deduct;
$check = $db->prepare("SELECT id FROM hr_payroll WHERE employee_id = ? AND payroll_month = ? AND payroll_year = ?");
$check->execute([$emp_id, $month, $year]);
if ($check->fetch()) {
throw new Exception("Payroll already exists for this employee in the selected period.");
}
$stmt = $db->prepare("INSERT INTO hr_payroll (employee_id, payroll_month, payroll_year, basic_salary, bonus, deductions, net_salary, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$emp_id, $month, $year, $basic, $bonus, $deduct, $net, $notes]);
$db->commit();
redirectWithMessage("Payroll generated successfully!", "index.php?page=hr_payroll&month=$month&year=$year");
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
if (isset($_POST['pay_payroll'])) {
$id = (int)$_POST['id'];
$db = db();
try {
$db->beginTransaction();
$stmt = $db->prepare("SELECT * FROM hr_payroll WHERE id = ? AND status = 'unpaid'");
$stmt->execute([$id]);
$p = $stmt->fetch();
if ($p) {
$db->prepare("UPDATE hr_payroll SET status = 'paid', payment_date = CURDATE() WHERE id = ?")->execute([$id]);
// Accounting
recordExpenseJournalForPayroll($id, (float)$p['net_salary'], date('Y-m-d'));
$db->commit();
redirectWithMessage("Payroll marked as paid and recorded in accounting!", "index.php?page=hr_payroll&month={$p['payroll_month']}&year={$p['payroll_year']}");
} else {
throw new Exception("Payroll already paid or not found.");
}
} catch (Exception $e) {
$db->rollBack();
$message = "Error: " . $e->getMessage();
}
}
if (isset($_POST['delete_payroll'])) {
$id = (int)$_POST['id'];
$stmt = db()->prepare("SELECT payroll_month, payroll_year FROM hr_payroll WHERE id = ?");
$stmt->execute([$id]);
$p = $stmt->fetch();
if ($p) {
db()->prepare("DELETE FROM hr_payroll WHERE id = ?")->execute([$id]);
redirectWithMessage("Payroll record deleted successfully!", "index.php?page=hr_payroll&month={$p['payroll_month']}&year={$p['payroll_year']}");
}
}
if (isset($_POST['add_sales_return'])) {
$invoice_id = (int)$_POST['invoice_id'];
$return_date = $_POST['return_date'] ?: date('Y-m-d');
$notes = $_POST['notes'] ?? '';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($invoice_id && !empty($item_ids)) {
$db = db();
try {
$db->beginTransaction();
// Get customer_id from invoice
$stmtInv = $db->prepare("SELECT customer_id FROM invoices WHERE id = ?");
$stmtInv->execute([$invoice_id]);
$customer_id = $stmtInv->fetchColumn();
$total_return = 0;
foreach ($quantities as $i => $qty) {
$total_return += (float)$qty * (float)$prices[$i];
}
// Insert Sales Return
$stmt = $db->prepare("INSERT INTO sales_returns (invoice_id, customer_id, return_date, total_amount, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $customer_id, $return_date, $total_return, $notes]);
$return_id = $db->lastInsertId();
// Insert Return Items and Update Stock
$stmtItem = $db->prepare("INSERT INTO sales_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity + ? WHERE id = ?");
foreach ($item_ids as $i => $item_id) {
$qty = (float)$quantities[$i];
if ($qty > 0) {
$price = (float)$prices[$i];
$line_total = $qty * $price;
$stmtItem->execute([$return_id, $item_id, $qty, $price, $line_total]);
$stmtStock->execute([$qty, $item_id]);
}
}
$db->commit();
redirectWithMessage("Sales Return processed successfully!");
} catch (Exception $e) {
$db->rollBack();
$message = "Error processing return: " . $e->getMessage();
}
}
}
if (isset($_POST['add_purchase_return'])) {
$invoice_id = (int)$_POST['invoice_id'];
$return_date = $_POST['return_date'] ?: date('Y-m-d');
$notes = $_POST['notes'] ?? '';
$item_ids = $_POST['item_ids'] ?? [];
$quantities = $_POST['quantities'] ?? [];
$prices = $_POST['prices'] ?? [];
if ($invoice_id && !empty($item_ids)) {
$db = db();
try {
$db->beginTransaction();
// Get supplier_id from purchase
$stmtInv = $db->prepare("SELECT supplier_id FROM purchases WHERE id = ?");
$stmtInv->execute([$invoice_id]);
$supplier_id = $stmtInv->fetchColumn();
$total_return = 0;
foreach ($quantities as $i => $qty) {
$total_return += (float)$qty * (float)$prices[$i];
}
// Insert Purchase Return
$stmt = $db->prepare("INSERT INTO purchase_returns (invoice_id, supplier_id, return_date, total_amount, notes) VALUES (?, ?, ?, ?, ?)");
$stmt->execute([$invoice_id, $supplier_id, $return_date, $total_return, $notes]);
$return_id = $db->lastInsertId();
// Insert Return Items and Update Stock
$stmtItem = $db->prepare("INSERT INTO purchase_return_items (return_id, item_id, quantity, unit_price, total_price) VALUES (?, ?, ?, ?, ?)");
$stmtStock = $db->prepare("UPDATE stock_items SET stock_quantity = stock_quantity - ? WHERE id = ?");
foreach ($item_ids as $i => $item_id) {
$qty = (float)$quantities[$i];
if ($qty > 0) {
$price = (float)$prices[$i];
$line_total = $qty * $price;
$stmtItem->execute([$return_id, $item_id, $qty, $price, $line_total]);
$stmtStock->execute([$qty, $item_id]);
}
}
$db->commit();
redirectWithMessage("Purchase Return processed successfully!");
} catch (Exception $e) {
$db->rollBack();
$message = "Error processing return: " . $e->getMessage();
}
}
}
// --- 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']) ? $_POST['permissions'] : [];
if ($name) {
try {
$db = db();
$db->beginTransaction();
$stmt = $db->prepare("INSERT INTO role_groups (name) VALUES (?)");
$stmt->execute([$name]);
$role_id = $db->lastInsertId();
if (!empty($permissions)) {
$stmtPerm = $db->prepare("INSERT INTO role_permissions (role_id, permission) VALUES (?, ?)");
foreach ($permissions as $p) {
$stmtPerm->execute([$role_id, $p]);
}
}
$db->commit();
$message = "Role Group added successfully!";
} catch (PDOException $e) {
if ($db->inTransaction()) $db->rollBack();
$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);
$outlet_ids = !empty($_POST['outlet_ids']) && is_array($_POST['outlet_ids']) ? array_filter($_POST['outlet_ids'], function($v) { return $v !== ''; }) : [];
$outlet_id = !empty($outlet_ids) ? (int)reset($outlet_ids) : null;
$assigned_outlets = !empty($outlet_ids) ? implode(',', array_map('intval', $outlet_ids)) : null;
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id, outlet_id, assigned_outlets) VALUES (?, ?, ?, ?, ?, ?, ?)");
try {
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id, $outlet_id, $assigned_outlets]);
$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']) ? $_POST['permissions'] : [];
if ($id && $name) {
try {
$db = db();
$db->beginTransaction();
$stmt = $db->prepare("UPDATE role_groups SET name = ? WHERE id = ?");
$stmt->execute([$name, $id]);
// Refresh permissions
$stmtDel = $db->prepare("DELETE FROM role_permissions WHERE role_id = ?");
$stmtDel->execute([$id]);
if (!empty($permissions)) {
$stmtPerm = $db->prepare("INSERT INTO role_permissions (role_id, permission) VALUES (?, ?)");
foreach ($permissions as $p) {
$stmtPerm->execute([$id, $p]);
}
}
$db->commit();
$message = "Role Group updated successfully!";
} catch (PDOException $e) {
if ($db->inTransaction()) $db->rollBack();
$message = "Error updating role group: " . $e->getMessage();
}
}
}
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';
$outlet_ids = !empty($_POST['outlet_ids']) && is_array($_POST['outlet_ids']) ? array_filter($_POST['outlet_ids'], function($v) { return $v !== ''; }) : [];
$outlet_id = !empty($outlet_ids) ? (int)reset($outlet_ids) : null;
$assigned_outlets = !empty($outlet_ids) ? implode(',', array_map('intval', $outlet_ids)) : null;
if ($id && $username) {
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ?, outlet_id = ?, assigned_outlets = ? WHERE id = ?");
$stmt->execute([$username, $email, $phone, $group_id, $status, $outlet_id, $assigned_outlets, $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!";
}
}
// --- POS Devices Handlers ---
if (isset($_POST['add_pos_device'])) {
$name = $_POST['device_name'] ?? '';
$type = $_POST['device_type'] ?? 'scale';
$conn = $_POST['connection_type'] ?? 'usb';
$ip = $_POST['ip_address'] ?? '';
$port = $_POST['port'] ? (int)$_POST['port'] : null;
$baud = $_POST['baud_rate'] ? (int)$_POST['baud_rate'] : null;
if ($name) {
$stmt = db()->prepare("INSERT INTO pos_devices (device_name, device_type, connection_type, ip_address, port, baud_rate) VALUES (?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $type, $conn, $ip, $port, $baud]);
redirectWithMessage("Device added successfully!", "index.php?page=scale_devices");
}
}
if (isset($_POST['edit_pos_device'])) {
$id = (int)$_POST['id'];
$name = $_POST['device_name'] ?? '';
$type = $_POST['device_type'] ?? 'scale';
$conn = $_POST['connection_type'] ?? 'usb';
$ip = $_POST['ip_address'] ?? '';
$port = $_POST['port'] ? (int)$_POST['port'] : null;
$baud = $_POST['baud_rate'] ? (int)$_POST['baud_rate'] : null;
$status = $_POST['status'] ?? 'active';
if ($id && $name) {
$stmt = db()->prepare("UPDATE pos_devices SET device_name = ?, device_type = ?, connection_type = ?, ip_address = ?, port = ?, baud_rate = ?, status = ? WHERE id = ?");
$stmt->execute([$name, $type, $conn, $ip, $port, $baud, $status, $id]);
redirectWithMessage("Device updated successfully!", "index.php?page=scale_devices");
}
}
if (isset($_POST['delete_pos_device'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM pos_devices WHERE id = ?");
$stmt->execute([$id]);
redirectWithMessage("Device deleted successfully!", "index.php?page=scale_devices");
}
}
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;
}
}
redirectWithMessage("Profile updated successfully!", "index.php?page=my_profile");
}
}
if (isset($_POST['update_settings'])) {
if (can('settings_view')) {
$db = db();
if (isset($_POST['settings']) && is_array($_POST['settings'])) {
foreach ($_POST['settings'] as $key => $value) {
$stmt = $db->prepare("INSERT INTO settings (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?");
$stmt->execute([$key, $value, $value]);
}
}
// Handle file uploads
$files = ['company_logo', 'favicon', 'manager_signature', 'display_slide_1', 'display_slide_2', 'display_slide_3'];
foreach ($files as $file_key) {
if (isset($_FILES[$file_key]) && $_FILES[$file_key]['error'] === 0) {
$ext = pathinfo($_FILES[$file_key]['name'], PATHINFO_EXTENSION);
$filename = 'uploads/' . $file_key . '_' . time() . '.' . $ext;
if (!is_dir('uploads')) mkdir('uploads', 0777, true);
if (move_uploaded_file($_FILES[$file_key]['tmp_name'], $filename)) {
$stmt = $db->prepare("INSERT INTO settings (`key`, `value`) VALUES (?, ?) ON DUPLICATE KEY UPDATE `value` = ?");
$stmt->execute([$file_key, $filename, $filename]);
}
}
}
redirectWithMessage("Settings updated successfully!", "index.php?page=settings");
}
}
// --- 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);
redirectWithMessage($res['success'] ? "Database restored successfully from $filename!" : "Error: " . $res['error'], "index.php?page=backups");
}
}
if (isset($_POST['delete_backup'])) {
if (can('users_view')) {
$filename = basename($_POST['filename'] ?? '');
if (unlink(__DIR__ . '/backups/' . $filename)) {
redirectWithMessage("Backup deleted successfully.", "index.php?page=backups");
} else {
redirectWithMessage("Error deleting backup.", "index.php?page=backups");
}
}
}
if (isset($_POST['save_backup_settings'])) {
$limit = (int)($_POST['backup_limit'] ?? 5);
$auto = $_POST['backup_auto_enabled'] ?? '0';
$time = $_POST['backup_time'] ?? '00:00';
$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]);
redirectWithMessage("Backup settings updated.", "index.php?page=backups");
}
if (isset($_GET['download_backup'])) {
$filename = basename($_GET['download_backup']);
$filepath = __DIR__ . '/backups/' . $filename;
if (file_exists($filepath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit;
}
}
// --- Outlets Handlers ---
if (isset($_POST['add_outlet']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
$name = $_POST['name'] ?? '';
$phone = $_POST['phone'] ?? '';
$address = $_POST['address'] ?? '';
$ctr_number = $_POST['ctr_number'] ?? '';
$vat_number = $_POST['vat_number'] ?? '';
$status = $_POST['status'] ?? 'active';
$logo = null;
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
$ext = strtolower(pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
if (in_array($ext, $allowed)) {
$filename = 'outlet_logo_' . time() . '_' . rand(100, 999) . '.' . $ext;
$dest = __DIR__ . '/assets/images/' . $filename;
if (!is_dir(__DIR__ . '/assets/images/')) {
mkdir(__DIR__ . '/assets/images/', 0777, true);
}
if (move_uploaded_file($_FILES['logo']['tmp_name'], $dest)) {
$logo = 'assets/images/' . $filename;
}
}
}
if ($name) {
$stmt = db()->prepare("INSERT INTO outlets (name, phone, address, ctr_number, vat_number, logo, status) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([$name, $phone, $address, $ctr_number, $vat_number, $logo, $status]);
redirectWithMessage("Outlet added successfully!", "index.php?page=outlets");
}
}
if (isset($_POST['edit_outlet']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
$phone = $_POST['phone'] ?? '';
$address = $_POST['address'] ?? '';
$ctr_number = $_POST['ctr_number'] ?? '';
$vat_number = $_POST['vat_number'] ?? '';
$status = $_POST['status'] ?? 'active';
$logo_query_part = "";
$params = [$name, $phone, $address, $ctr_number, $vat_number, $status, $id];
if (isset($_FILES['logo']) && $_FILES['logo']['error'] === UPLOAD_ERR_OK) {
$ext = strtolower(pathinfo($_FILES['logo']['name'], PATHINFO_EXTENSION));
$allowed = ['jpg', 'jpeg', 'png', 'gif'];
if (in_array($ext, $allowed)) {
$filename = 'outlet_logo_' . time() . '_' . rand(100, 999) . '.' . $ext;
$dest = __DIR__ . '/assets/images/' . $filename;
if (!is_dir(__DIR__ . '/assets/images/')) {
mkdir(__DIR__ . '/assets/images/', 0777, true);
}
if (move_uploaded_file($_FILES['logo']['tmp_name'], $dest)) {
$logo = 'assets/images/' . $filename;
$logo_query_part = ", logo=?";
// Insert logo before ID in params
array_splice($params, 6, 0, [$logo]);
}
}
}
if ($name) {
$stmt = db()->prepare("UPDATE outlets SET name=?, phone=?, address=?, ctr_number=?, vat_number=?, status=? $logo_query_part WHERE id=?");
$stmt->execute($params);
redirectWithMessage("Outlet updated successfully!", "index.php?page=outlets");
}
}
if (isset($_POST['delete_outlet']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
$id = (int)$_POST['id'];
$stmt = db()->prepare("DELETE FROM outlets WHERE id=?");
$stmt->execute([$id]);
redirectWithMessage("Outlet deleted successfully!", "index.php?page=outlets");
}
// --- Data Synchronization Handler ---
if (isset($_POST['perform_data_sync']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
$source_id = (int)$_POST['source_outlet_id'];
$target_id = (int)$_POST['target_outlet_id'];
if ($source_id === $target_id) {
redirectWithMessage("Source and target outlet must be different.", "index.php?page=data_sync");
}
$db = db();
$db->beginTransaction();
try {
// 1. Sync & Map Categories
$cat_map = [];
if (isset($_POST['sync_categories'])) {
$source_cats = $db->prepare("SELECT * FROM _stock_categories WHERE outlet_id = ?");
$source_cats->execute([$source_id]);
foreach ($source_cats->fetchAll() as $cat) {
$stmt = $db->prepare("INSERT INTO _stock_categories (outlet_id, name_en, name_ar) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE name_ar = VALUES(name_ar)");
$stmt->execute([$target_id, $cat['name_en'], $cat['name_ar']]);
}
}
// Build category map based on Name EN (unique within outlet)
$s_cats = $db->prepare("SELECT id, name_en FROM _stock_categories WHERE outlet_id = ?");
$s_cats->execute([$source_id]);
$source_cats_by_name = $s_cats->fetchAll(PDO::FETCH_KEY_PAIR);
$t_cats = $db->prepare("SELECT id, name_en FROM _stock_categories WHERE outlet_id = ?");
$t_cats->execute([$target_id]);
$target_cats_by_name = array_flip($t_cats->fetchAll(PDO::FETCH_KEY_PAIR));
foreach ($source_cats_by_name as $sid => $name) {
if (isset($target_cats_by_name[$name])) {
$cat_map[$sid] = $target_cats_by_name[$name];
}
}
// 2. Sync & Map Units
$unit_map = [];
if (isset($_POST['sync_units'])) {
$source_units = $db->prepare("SELECT * FROM _stock_units WHERE outlet_id = ?");
$source_units->execute([$source_id]);
foreach ($source_units->fetchAll() as $unit) {
$stmt = $db->prepare("INSERT INTO _stock_units (outlet_id, name_en, name_ar, short_name_en, short_name_ar) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE name_ar = VALUES(name_ar), short_name_en = VALUES(short_name_en), short_name_ar = VALUES(short_name_ar)");
$stmt->execute([$target_id, $unit['name_en'], $unit['name_ar'], $unit['short_name_en'], $unit['short_name_ar']]);
}
}
$s_units = $db->prepare("SELECT id, name_en FROM _stock_units WHERE outlet_id = ?");
$s_units->execute([$source_id]);
$source_units_by_name = $s_units->fetchAll(PDO::FETCH_KEY_PAIR);
$t_units = $db->prepare("SELECT id, name_en FROM _stock_units WHERE outlet_id = ?");
$t_units->execute([$target_id]);
$target_units_by_name = array_flip($t_units->fetchAll(PDO::FETCH_KEY_PAIR));
foreach ($source_units_by_name as $sid => $name) {
if (isset($target_units_by_name[$name])) {
$unit_map[$sid] = $target_units_by_name[$name];
}
}
// 3. Sync Items
if (isset($_POST['sync_items'])) {
$source_items = $db->prepare("SELECT * FROM _stock_items WHERE outlet_id = ?");
$source_items->execute([$source_id]);
foreach ($source_items->fetchAll() as $item) {
// Check if item exists in target by SKU
$check_stmt = $db->prepare("SELECT id FROM _stock_items WHERE outlet_id = ? AND sku = ?");
$check_stmt->execute([$target_id, $item['sku']]);
$target_item_id = $check_stmt->fetchColumn();
$t_cat_id = $cat_map[$item['category_id']] ?? null;
$t_unit_id = $unit_map[$item['unit_id']] ?? null;
if ($target_item_id) {
// Update existing: Keep stock_quantity
$stmt = $db->prepare("UPDATE _stock_items SET
category_id = ?, unit_id = ?,
name_en = ?, name_ar = ?, purchase_price = ?, sale_price = ?,
min_stock_level = ?, expiry_date = ?, image_path = ?,
vat_rate = ?, is_promotion = ?, promotion_start = ?,
promotion_end = ?, promotion_percent = ?
WHERE id = ?");
$stmt->execute([
$t_cat_id, $t_unit_id,
$item['name_en'], $item['name_ar'], $item['purchase_price'], $item['sale_price'],
$item['min_stock_level'], $item['expiry_date'], $item['image_path'],
$item['vat_rate'], $item['is_promotion'], $item['promotion_start'],
$item['promotion_end'], $item['promotion_percent'], $target_item_id
]);
} else {
// Insert new: Set stock_quantity to 0
$stmt = $db->prepare("INSERT INTO _stock_items
(outlet_id, category_id, unit_id, supplier_id, name_en, name_ar, sku,
purchase_price, sale_price, stock_quantity, min_stock_level,
expiry_date, image_path, vat_rate, is_promotion, promotion_start,
promotion_end, promotion_percent)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
$target_id, $t_cat_id, $t_unit_id, $item['supplier_id'],
$item['name_en'], $item['name_ar'], $item['sku'],
$item['purchase_price'], $item['sale_price'],
$item['min_stock_level'], $item['expiry_date'], $item['image_path'],
$item['vat_rate'], $item['is_promotion'], $item['promotion_start'],
$item['promotion_end'], $item['promotion_percent']
]);
}
}
}
$db->commit();
redirectWithMessage("Data synchronization completed successfully!", "index.php?page=data_sync");
} catch (Exception $e) {
$db->rollBack();
redirectWithMessage("Error: " . $e->getMessage(), "index.php?page=data_sync");
}
}
// --- Cash Register & Session Handlers ---
if (isset($_POST['add_cash_register'])) {
$name = $_POST['name'] ?? '';
if ($name) {
// Check license limit
$allowed = LicenseService::getAllowedActivations();
$stmt = db()->query("SELECT COUNT(*) FROM cash_registers");
$current_count = (int)$stmt->fetchColumn();
if ($current_count >= $allowed) {
$message = "Error: Activation Limit Reached. Your license only allows $allowed register(s).";
} else {
$stmt = db()->prepare("INSERT INTO cash_registers (name) VALUES (?)");
$stmt->execute([$name]);
redirectWithMessage("Cash Register added successfully!", "index.php?page=cash_registers");
}
}
}
if (isset($_POST['edit_cash_register'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
$status = $_POST['status'] ?? 'active';
if ($id && $name) {
$stmt = db()->prepare("UPDATE cash_registers SET name = ?, status = ? WHERE id = ?");
$stmt->execute([$name, $status, $id]);
redirectWithMessage("Cash Register updated successfully!", "index.php?page=cash_registers");
}
}
if (isset($_POST['delete_cash_register'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM cash_registers WHERE id = ?");
$stmt->execute([$id]);
redirectWithMessage("Cash Register deleted successfully!", "index.php?page=cash_registers");
}
}
if (isset($_POST['open_register'])) {
$register_id = (int)$_POST['register_id'];
$user_id = $_SESSION['user_id'];
$opening_balance = (float)$_POST['opening_balance'];
// Check if user already has an open session
$check = db()->prepare("SELECT id FROM register_sessions WHERE user_id = ? AND status = 'open'");
$check->execute([$user_id]);
if ($check->fetch()) {
$message = "Error: You already have an open register session.";
} else {
$stmt = db()->prepare("INSERT INTO register_sessions (register_id, user_id, opening_balance, status) VALUES (?, ?, ?, 'open')");
$stmt->execute([$register_id, $user_id, $opening_balance]);
$_SESSION['register_session_id'] = db()->lastInsertId();
redirectWithMessage("Register opened successfully!", "index.php?page=pos");
}
}
if (isset($_POST['close_register'])) {
$session_id = $_SESSION['register_session_id'] ?? null;
$closing_balance = (float)$_POST['closing_balance'];
$notes = $_POST['notes'] ?? '';
if ($session_id) {
$stmt = db()->prepare("UPDATE register_sessions SET closing_balance = ?, closed_at = NOW(), status = 'closed', notes = ? WHERE id = ?");
$stmt->execute([$closing_balance, $notes, $session_id]);
unset($_SESSION['register_session_id']);
redirectWithMessage("Register closed successfully!", "index.php?page=pos");
}
}
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'])) {
$limit = (int)($_POST['backup_limit'] ?? 5);
$auto = $_POST['backup_auto_enabled'] ?? '0';
$time = $_POST['backup_time'] ?? '00:00';
$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 updated.";
}
if (isset($_GET['download_backup'])) {
$filename = basename($_GET['download_backup']);
$filepath = __DIR__ . '/backups/' . $filename;
if (file_exists($filepath)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filepath));
readfile($filepath);
exit;
}
}
// --- Cash Register & Session Handlers ---
if (isset($_POST['add_cash_register'])) {
$name = $_POST['name'] ?? '';
if ($name) {
// Check license limit
$allowed = LicenseService::getAllowedActivations();
$stmt = db()->query("SELECT COUNT(*) FROM cash_registers");
$current_count = (int)$stmt->fetchColumn();
if ($current_count >= $allowed) {
$message = "Error: Activation Limit Reached. Your license only allows $allowed register(s).";
} else {
$stmt = db()->prepare("INSERT INTO cash_registers (name) VALUES (?)");
$stmt->execute([$name]);
$message = "Cash Register added successfully!";
}
}
}
if (isset($_POST['edit_cash_register'])) {
$id = (int)$_POST['id'];
$name = $_POST['name'] ?? '';
$status = $_POST['status'] ?? 'active';
if ($id && $name) {
$stmt = db()->prepare("UPDATE cash_registers SET name = ?, status = ? WHERE id = ?");
$stmt->execute([$name, $status, $id]);
$message = "Cash Register updated successfully!";
}
}
if (isset($_POST['delete_cash_register'])) {
$id = (int)$_POST['id'];
if ($id) {
$stmt = db()->prepare("DELETE FROM cash_registers WHERE id = ?");
$stmt->execute([$id]);
$message = "Cash Register deleted successfully!";
}
}
if (isset($_POST['open_register'])) {
$register_id = (int)$_POST['register_id'];
$user_id = $_SESSION['user_id'];
$opening_balance = (float)$_POST['opening_balance'];
// Check if user already has an open session
$check = db()->prepare("SELECT id FROM register_sessions WHERE user_id = ? AND status = 'open'");
$check->execute([$user_id]);
if ($check->fetch()) {
$message = "Error: You already have an open register session.";
} else {
$stmt = db()->prepare("INSERT INTO register_sessions (register_id, user_id, opening_balance, status) VALUES (?, ?, ?, 'open')");
$stmt->execute([$register_id, $user_id, $opening_balance]);
$_SESSION['register_session_id'] = db()->lastInsertId();
$message = "Register opened successfully!";
header("Location: index.php?page=pos");
exit;
}
}
if (isset($_POST['close_register'])) {
$session_id = (int)$_POST['session_id'];
$cash_in_hand = (float)$_POST['cash_in_hand'];
$notes = $_POST['notes'] ?? '';
// Calculate expected closing balance
// Opening + Sum of POS Transactions (Cash) - Any cash outflows (if any)
$session = db()->prepare("SELECT opening_balance FROM register_sessions WHERE id = ?");
$session->execute([$session_id]);
$opening = (float)$session->fetchColumn();
$sales = db()->prepare("SELECT SUM(p.amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1 AND p.payment_method = 'cash'");
$sales->execute([$session_id]);
$cash_sales = (float)$sales->fetchColumn();
$expected = $opening + $cash_sales;
$stmt = db()->prepare("UPDATE register_sessions SET closing_balance = ?, cash_in_hand = ?, closed_at = CURRENT_TIMESTAMP, status = 'closed', notes = ? WHERE id = ?");
$stmt->execute([$expected, $cash_in_hand, $notes, $session_id]);
unset($_SESSION['register_session_id']);
$message = "Register closed successfully!";
header("Location: index.php?page=dashboard");
exit;
}
// Routing & Data Fetching
$page = $_GET['page'] ?? 'dashboard';
// Permission map for pages
$page_permissions = [
'pos' => 'pos_view',
'sales' => 'sales_view',
'sales_returns' => 'sales_returns_view',
'purchases' => 'purchases_view',
'purchase_returns' => 'purchase_returns_view',
'quotations' => 'quotations_view',
'lpos' => 'lpos_view',
'accounting' => 'accounting_view',
'expense_categories' => 'expense_categories_view',
'expenses' => 'expenses_view',
'expense_report' => 'expenses_view',
'items' => 'items_view',
'categories' => 'categories_view',
'units' => 'units_view',
'customers' => 'customers_view',
'suppliers' => 'suppliers_view',
'customer_statement' => 'customer_statement_view',
'supplier_statement' => 'supplier_statement_view',
'cashflow_report' => 'cashflow_report_view',
'expiry_report' => 'expiry_report_view',
'low_stock_report' => 'low_stock_report_view',
'loyalty_history' => 'loyalty_history_view',
'payment_methods' => 'payment_methods_view',
'settings' => 'settings_view',
'devices' => 'devices_view',
'hr_departments' => 'hr_departments_view',
'hr_employees' => 'hr_employees_view',
'hr_attendance' => 'hr_attendance_view',
'hr_payroll' => 'hr_payroll_view',
'role_groups' => 'role_groups_view',
'users' => 'users_view',
'scale_devices' => 'scale_devices_view',
'customer_display_settings' => 'customer_display_settings_view',
'backups' => 'backups_view',
'logs' => 'logs_view',
'cash_registers' => 'cash_registers_view',
'register_sessions' => 'register_sessions_view',
];
if (isset($page_permissions[$page]) && !can($page_permissions[$page])) {
$page = 'dashboard';
$message = "Access Denied: You don't have permission to view that module.";
}
$currTitle = ['en' => 'Dashboard', 'ar' => 'لوحة القيادة'];
$titles = [
'dashboard' => ['en' => 'Dashboard', 'ar' => 'لوحة القيادة'],
'pos' => ['en' => 'Point of Sale', 'ar' => 'نقطة البيع'],
'sales' => ['en' => 'Sales', 'ar' => 'المبيعات'],
'sales_returns' => ['en' => 'Sales Returns', 'ar' => 'مرتجعات المبيعات'],
'purchases' => ['en' => 'Purchases', 'ar' => 'المشتريات'],
'purchase_returns' => ['en' => 'Purchase Returns', 'ar' => 'مرتجعات المشتريات'],
'quotations' => ['en' => 'Quotations', 'ar' => 'عروض الأسعار'],
'lpos' => ['en' => 'LPOs', 'ar' => 'أوامر الشراء'],
'accounting' => ['en' => 'Accounting', 'ar' => 'المحاسبة'],
'expense_categories' => ['en' => 'Expense Categories', 'ar' => 'فئات المصروفات'],
'expenses' => ['en' => 'Expenses', 'ar' => 'المصروفات'],
'expense_report' => ['en' => 'Expense Report', 'ar' => 'تقرير المصروفات'],
'items' => ['en' => 'Items', 'ar' => 'الأصناف'],
'categories' => ['en' => 'Categories', 'ar' => 'الفئات'],
'units' => ['en' => 'Units', 'ar' => 'الوحدات'],
'customers' => ['en' => 'Customers', 'ar' => 'العملاء'],
'suppliers' => ['en' => 'Suppliers', 'ar' => 'الموردين'],
'customer_statement' => ['en' => 'Customer Statement', 'ar' => 'كشف حساب عميل'],
'supplier_statement' => ['en' => 'Supplier Statement', 'ar' => 'كشف حساب مورد'],
'cashflow_report' => ['en' => 'Cashflow Report', 'ar' => 'تقرير التدفق النقدي'],
'expiry_report' => ['en' => 'Expiry Report', 'ar' => 'تقرير الصلاحية'],
'low_stock_report' => ['en' => 'Low Stock Report', 'ar' => 'تقرير المخزون المنخفض'],
'loyalty_history' => ['en' => 'Loyalty History', 'ar' => 'سجل الولاء'],
'payment_methods' => ['en' => 'Payment Methods', 'ar' => 'طرق الدفع'],
'settings' => ['en' => 'Settings', 'ar' => 'الإعدادات'],
'devices' => ['en' => 'Biometric Devices', 'ar' => 'أجهزة البصمة'],
'hr_departments' => ['en' => 'Departments', 'ar' => 'الأقسام'],
'hr_employees' => ['en' => 'Employees', 'ar' => 'الموظفين'],
'hr_attendance' => ['en' => 'Attendance', 'ar' => 'الحضور'],
'hr_payroll' => ['en' => 'Payroll', 'ar' => 'الرواتب'],
'role_groups' => ['en' => 'Roles & Permissions', 'ar' => 'الأدوار والصلاحيات'],
'users' => ['en' => 'Users', 'ar' => 'المستخدمين'],
'cash_registers' => ['en' => 'Cash Registers', 'ar' => 'صناديق الكاشير'],
'register_sessions' => ['en' => 'Register Sessions', 'ar' => 'جلسات الصناديق']
];
if (isset($titles[$page])) {
$currTitle = $titles[$page];
}
$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' => [],
];
$permission_groups = [
'General' => ['dashboard' => __('dashboard')],
'Inventory' => [
'items' => __('items'),
'categories' => __('categories'),
'units' => __('units')
],
'Customers' => [
'customers' => __('customers')
],
'Suppliers' => [
'suppliers' => __('suppliers')
],
'POS' => [
'pos' => __('pos')
],
'Sales' => [
'sales' => __('sales'),
'sales_returns' => __('sales_returns'),
'quotations' => __('quotations')
],
'Purchases' => [
'purchases' => __('purchases'),
'lpos' => __('lpos'),
'purchase_returns' => __('purchase_returns')
],
'Expenses' => [
'expense_categories' => __('expense_categories'),
'expenses' => __('expenses')
],
'Accounting' => [
'accounting' => __('accounting'),
'trial_balance' => __('trial_balance'),
'profit_loss' => __('profit_loss'),
'balance_sheet' => __('balance_sheet'),
'vat_report' => __('vat_report')
],
'HR' => [
'hr_departments' => __('departments'),
'hr_employees' => __('employees'),
'hr_attendance' => __('attendance'),
'hr_payroll' => __('payroll')
],
'Reports' => [
'customer_statement' => __('customer_statement'),
'supplier_statement' => __('supplier_statement'),
'expense_report' => __('expense_report'),
'cashflow_report' => __('cashflow_report'),
'expiry_report' => __('expiry_report'),
'low_stock_report' => __('low_stock_report'),
'loyalty_history' => __('loyalty_history')
],
'Settings' => [
'payment_methods' => __('payment_methods'),
'devices' => __('devices'),
'settings' => __('settings')
],
'Administration' => [
'role_groups' => __('role_groups'),
'users' => __('users'),
'cash_registers' => __('cash_registers'),
'register_sessions' => __('register_sessions'),
'logs' => 'System Logs'
]
];
if ($page === 'export') {
$type = $_GET['type'] ?? 'sales';
$format = $_GET['format'] ?? 'csv';
$filename = $type . "_export_" . date('Y-m-d') . ($format === 'excel' ? ".xls" : ".csv");
if ($format === 'excel') {
header('Content-Type: application/vnd.ms-excel; charset=utf-8');
header('Content-Disposition: attachment; filename=' . $filename);
echo "<table border='1'>";
} else {
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));
}
$headers = [];
$rows = [];
if ($type === 'sales' || $type === 'purchases') {
$table = ($type === 'sales') ? 'invoices' : 'purchases';
$cust_table = ($type === 'sales') ? 'customers' : 'suppliers';
$cust_col = ($type === 'sales') ? 'customer_id' : 'supplier_id';
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
} else {
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
if (!empty($_GET['customer_id'])) { $where[] = "v.$cust_col = ?"; $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 $table v LEFT JOIN $cust_table c ON v.$cust_col = c.id
WHERE $whereSql ORDER BY v.id DESC");
$stmt->execute($params);
$headers = ['Invoice ID', 'Customer/Supplier', 'Date', 'Payment', 'Status', 'Total', 'Paid', 'Balance'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'customers' || $type === 'suppliers') {
$table = ($type === 'suppliers') ? 'suppliers' : 'customers';
$where = ["1=1"];
$params = [];
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 $table WHERE $whereSql ORDER BY id DESC");
$stmt->execute($params);
$headers = ['ID', 'Name', 'Email', 'Phone', 'Tax ID', 'Balance', 'Created At'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $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);
$headers = ['SKU', 'Name (EN)', 'Name (AR)', 'Category', 'Purchase Price', 'Sale Price', 'Quantity', 'VAT %'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'expenses') {
$where = ["1=1"];
$params = [];
$stmt = db()->prepare("SELECT e.id, c.name_en as category, e.amount, e.expense_date, e.reference_no, e.description
FROM expenses e JOIN expense_categories c ON e.category_id = c.id
ORDER BY e.expense_date DESC");
$stmt->execute();
$headers = ['ID', 'Category', 'Amount', 'Date', 'Reference', 'Description'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'quotations') {
$stmt = db()->prepare("SELECT q.id, c.name as customer_name, q.quotation_date, q.total_with_vat, q.status
FROM quotations q JOIN customers c ON q.customer_id = c.id
ORDER BY q.id DESC");
$stmt->execute();
$headers = ['Quotation #', 'Customer', 'Date', 'Total', 'Status'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'lpos') {
$stmt = db()->prepare("SELECT q.id, s.name as supplier_name, q.lpo_date, q.total_with_vat, q.status
FROM lpos q JOIN suppliers s ON q.supplier_id = s.id
ORDER BY q.id DESC");
$stmt->execute();
$headers = ['LPO #', 'Supplier', 'Date', 'Total', 'Status'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'categories') {
$stmt = db()->query("SELECT id, name_en, name_ar FROM stock_categories ORDER BY id DESC");
$headers = ['ID', 'Name (EN)', 'Name (AR)'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'units') {
$stmt = db()->query("SELECT id, name_en, name_ar, short_name_en, short_name_ar FROM stock_units ORDER BY id DESC");
$headers = ['ID', 'Name (EN)', 'Name (AR)', 'Short (EN)', 'Short (AR)'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'sales_returns') {
$stmt = db()->query("SELECT sr.id, sr.invoice_id, c.name as customer, sr.return_date, sr.total_amount FROM sales_returns sr LEFT JOIN customers c ON sr.customer_id = c.id ORDER BY sr.id DESC");
$headers = ['Return ID', 'Invoice ID', 'Customer', 'Date', 'Amount'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'purchase_returns') {
$stmt = db()->query("SELECT pr.id, pr.purchase_id, s.name as supplier, pr.return_date, pr.total_amount FROM purchase_returns pr LEFT JOIN suppliers s ON pr.supplier_id = s.id ORDER BY pr.id DESC");
$headers = ['Return ID', 'Purchase ID', 'Supplier', 'Date', 'Amount'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'hr_employees') {
$stmt = db()->query("SELECT e.id, e.name, d.name as department, e.position, e.salary, e.status FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id ORDER BY e.id DESC");
$headers = ['ID', 'Name', 'Department', 'Position', 'Salary', 'Status'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'hr_departments') {
$stmt = db()->query("SELECT id, name FROM hr_departments ORDER BY id DESC");
$headers = ['ID', 'Name'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'hr_attendance') {
$stmt = db()->query("SELECT a.attendance_date, e.name, a.status, a.clock_in, a.clock_out FROM hr_attendance a JOIN hr_employees e ON a.employee_id = e.id ORDER BY a.attendance_date DESC, e.name ASC");
$headers = ['Date', 'Employee', 'Status', 'In', 'Out'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'hr_payroll') {
$stmt = db()->query("SELECT p.payroll_month, p.payroll_year, e.name, p.basic_salary, p.bonus, p.deductions, p.net_salary, p.status FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id ORDER BY p.payroll_year DESC, p.payroll_month DESC");
$headers = ['Month', 'Year', 'Employee', 'Salary', 'Bonus', 'Deductions', 'Net', 'Status'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'users') {
$stmt = db()->query("SELECT u.id, u.username, u.email, g.name as role, u.status FROM users u LEFT JOIN role_groups g ON u.group_id = g.id ORDER BY u.id DESC");
$headers = ['ID', 'Username', 'Email', 'Role', 'Status'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
}
if ($format === 'excel') {
echo "<tr>";
foreach ($headers as $h) echo "<th>" . htmlspecialchars($h) . "</th>";
echo "</tr>";
foreach ($rows as $row) {
echo "<tr>";
foreach ($row as $val) echo "<td>" . htmlspecialchars((string)$val) . "</td>";
echo "</tr>";
}
echo "</table>";
} else {
fputcsv($output, $headers);
foreach ($rows as $row) 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 suppliers 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 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'];
}
$limit = isset($_GET["limit"]) ? max(5, (int)$_GET["limit"]) : 20;
$page_num = isset($_GET["p"]) ? (int)$_GET["p"] : 1;
if ($page_num < 1) $page_num = 1;
$offset = ($page_num - 1) * $limit;
switch ($page) {
case 'outlets':
$stmt = db()->prepare("SELECT * FROM outlets ORDER BY id DESC");
$stmt->execute();
$data['outlets'] = $stmt->fetchAll();
break;
case 'data_sync':
$stmt = db()->prepare("SELECT * FROM outlets ORDER BY name ASC");
$stmt->execute();
$data['outlets'] = $stmt->fetchAll();
break;
case 'suppliers':
$where = ["1=1"];
$params = [];
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);
$countStmt = db()->prepare("SELECT COUNT(*) FROM suppliers WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$stmt = db()->prepare("SELECT * FROM suppliers WHERE $whereSql ORDER BY id DESC LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['customers'] = $stmt->fetchAll(); // Keep 'customers' key for template compatibility if needed, or update template
break;
case 'customers':
$where = ["1=1"];
$params = [];
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);
$countStmt = db()->prepare("SELECT COUNT(*) FROM customers WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$stmt = db()->prepare("SELECT * FROM customers WHERE $whereSql ORDER BY id DESC LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['customers'] = $stmt->fetchAll();
break;
case 'categories':
// Already fetched globally
break;
case 'units':
// Already fetched globally
break;
case 'items':
file_put_contents('debug.log', date('Y-m-d H:i:s') . " - Items case hit\n", FILE_APPEND);
$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);
$countStmt = db()->prepare("SELECT COUNT(*) 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 suppliers s ON i.supplier_id = s.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$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 suppliers s ON i.supplier_id = s.id
WHERE $whereSql
ORDER BY i.id DESC LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['items'] = $stmt->fetchAll();
break;
case 'quotations':
$where = ["1=1"];
if (isset($_SESSION['outlet_id'])) {
$where[] = "q.outlet_id = " . (int)$_SESSION['outlet_id'];
}
$params = [];
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(q.id LIKE ? OR c.name LIKE ? OR q.id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
} else {
$where[] = "(q.id LIKE ? OR c.name LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
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);
$countStmt = db()->prepare("SELECT COUNT(*) FROM quotations q JOIN customers c ON q.customer_id = c.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$stmt = db()->prepare("SELECT q.*, c.name as customer_name, o.name as outlet_name, o.address as outlet_address, o.phone as outlet_phone, o.ctr_number as outlet_ctr_number, o.vat_number as outlet_vat_number, o.logo as outlet_logo
FROM quotations q
JOIN customers c ON q.customer_id = c.id
LEFT JOIN outlets o ON q.outlet_id = o.id
WHERE $whereSql
ORDER BY q.id DESC
LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['quotations'] = $stmt->fetchAll();
break;
case 'lpos':
$where = ["1=1"];
if (isset($_SESSION['outlet_id'])) {
$where[] = "q.outlet_id = " . (int)$_SESSION['outlet_id'];
}
$params = [];
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(q.id LIKE ? OR s.name LIKE ? OR q.id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
} else {
$where[] = "(q.id LIKE ? OR s.name LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
if (!empty($_GET['supplier_id'])) {
$where[] = "q.supplier_id = ?";
$params[] = $_GET['supplier_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "q.lpo_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "q.lpo_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$countStmt = db()->prepare("SELECT COUNT(*) FROM lpos q JOIN suppliers s ON q.supplier_id = s.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$stmt = db()->prepare("SELECT q.*, s.name as supplier_name, o.name as outlet_name, o.address as outlet_address, o.phone as outlet_phone
FROM lpos q
JOIN suppliers s ON q.supplier_id = s.id
LEFT JOIN outlets o ON q.outlet_id = o.id
WHERE $whereSql
ORDER BY q.id DESC
LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['lpos'] = $stmt->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';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers';
$where = ["1=1"];
if (isset($_SESSION['outlet_id'])) {
$where[] = "v.outlet_id = " . (int)$_SESSION['outlet_id'];
}
$params = [];
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
} else {
$where[] = "(v.id LIKE ? OR c.name LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
if (!empty($_GET['customer_id'])) {
$where[] = "v.$cust_supplier_col = ?";
$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);
$countStmt = db()->prepare("SELECT COUNT(*) FROM $table v LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id, c.phone as customer_phone, o.name as outlet_name, o.phone as outlet_phone, o.address as outlet_address
FROM $table v
LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id
LEFT JOIN outlets o ON v.outlet_id = o.id
WHERE $whereSql
ORDER BY v.id DESC LIMIT $limit OFFSET $offset");
$stmt->execute($params);
$data['invoices'] = $stmt->fetchAll();
foreach ($data['invoices'] as &$inv) {
$inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']);
if ($type === 'sale') {
$item_stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?");
$item_stmt->execute([$inv['id']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$item_stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?");
$item_stmt->execute([$inv['id']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
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 $cust_supplier_table ORDER BY name ASC")->fetchAll();
if ($type === 'sale') {
$data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices ORDER BY id DESC")->fetchAll();
} else {
$data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM purchases ORDER BY id DESC")->fetchAll();
}
break;
case 'sales_returns':
$where = ["1=1"];
if (isset($_SESSION['outlet_id'])) {
$where[] = "sr.outlet_id = " . (int)$_SESSION['outlet_id'];
}
$params = [];
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ? OR sr.id = ? OR sr.invoice_id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
$params[] = $clean_id;
} else {
$where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
$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 ORDER BY id DESC")->fetchAll();
break;
case 'purchase_returns':
$where = ["1=1"];
if (isset($_SESSION['outlet_id'])) {
$where[] = "pr.outlet_id = " . (int)$_SESSION['outlet_id'];
}
$params = [];
if (!empty($_GET['search'])) {
$s = $_GET['search'];
$clean_id = preg_replace('/[^0-9]/', '', $s);
if ($clean_id !== '') {
$where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.purchase_id LIKE ? OR pr.id = ? OR pr.purchase_id = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
$params[] = $clean_id;
} else {
$where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.purchase_id LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
$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 suppliers c ON pr.supplier_id = c.id
LEFT JOIN purchases i ON pr.purchase_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 purchases ORDER BY id DESC")->fetchAll();
break;
case 'customer_statement':
case 'supplier_statement':
$isCustomer = ($page === 'customer_statement');
$entityTable = $isCustomer ? 'customers' : 'suppliers';
$invoiceTable = $isCustomer ? 'invoices' : 'purchases';
$paymentTable = $isCustomer ? 'payments' : 'purchase_payments';
$fkColumn = $isCustomer ? 'customer_id' : 'supplier_id';
$invFkColumn = $isCustomer ? 'invoice_id' : 'purchase_id';
$data['entities'] = db()->query("SELECT id, name, balance FROM $entityTable ORDER BY name ASC")->fetchAll();
$entity_id = (int)($_GET['entity_id'] ?? 0);
if ($entity_id) {
$data['selected_entity'] = db()->query("SELECT * FROM $entityTable WHERE id = $entity_id")->fetch();
$start_date = $_GET['start_date'] ?? date('Y-m-01');
$end_date = $_GET['end_date'] ?? date('Y-m-d');
$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 $invoiceTable
WHERE $fkColumn = ? AND invoice_date BETWEEN ? AND ?");
$stmt->execute([$entity_id, $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.$invFkColumn as ref_no
FROM $paymentTable p
JOIN $invoiceTable i ON p.$invFkColumn = i.id
WHERE i.$fkColumn = ? AND p.payment_date BETWEEN ? AND ?");
$stmt->execute([$entity_id, $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"];
if (isset($_SESSION['outlet_id'])) {
$where[] = "e.outlet_id = " . (int)$_SESSION['outlet_id'];
}
$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();
$data['outlets'] = db()->query("SELECT id, name FROM outlets 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');
$category_id = $_GET['category_id'] ?? '';
$where = "WHERE e.expense_date BETWEEN ? AND ?";
$params = [$start_date, $end_date];
if ($category_id !== '') {
$where .= " AND e.category_id = ?";
$params[] = $category_id;
}
$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
GROUP BY c.id
ORDER BY total DESC");
$stmt->execute($params);
$data['report_by_category'] = $stmt->fetchAll();
$stmt = db()->prepare("SELECT SUM(amount) FROM expenses e $where");
$stmt->execute($params);
$data['total_expenses'] = $stmt->fetchColumn() ?: 0;
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
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 suppliers s ON i.supplier_id = s.id
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;
case 'scale_devices':
$data['scale_devices'] = db()->query("SELECT * FROM pos_devices ORDER BY id DESC")->fetchAll();
break;
case 'cash_registers':
$data['cash_registers'] = db()->query("SELECT * FROM cash_registers ORDER BY id DESC")->fetchAll();
break;
case 'register_sessions':
$where = ["1=1"];
$params = [];
// Filter by user if provided and user has permission
if (isset($_GET['user_id']) && !empty($_GET['user_id'])) {
if (can('users_view')) {
$where[] = "s.user_id = ?";
$params[] = $_GET['user_id'];
}
}
if (!can('users_view')) {
$where[] = "s.user_id = ?";
$params[] = $_SESSION['user_id'];
}
// Filter by date range
if (isset($_GET['date_from']) && !empty($_GET['date_from'])) {
$where[] = "s.opened_at >= ?";
$params[] = $_GET['date_from'] . ' 00:00:00';
}
if (isset($_GET['date_to']) && !empty($_GET['date_to'])) {
$where[] = "s.opened_at <= ?";
$params[] = $_GET['date_to'] . ' 23:59:59';
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT s.*, r.name as register_name, u.username
FROM register_sessions s
LEFT JOIN cash_registers r ON s.register_id = r.id
LEFT JOIN users u ON s.user_id = u.id
WHERE $whereSql
ORDER BY s.id DESC");
$stmt->execute($params);
$data['sessions'] = $stmt->fetchAll();
$data['cash_registers'] = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll();
$data['users'] = db()->query("SELECT id, username FROM users ORDER BY username ASC")->fetchAll();
break;
default:
if (can('dashboard_view')) {
$out_w = isset($_SESSION['outlet_id']) ? "WHERE outlet_id = " . (int)$_SESSION['outlet_id'] : "WHERE 1=1";
$out_and = isset($_SESSION['outlet_id']) ? "AND outlet_id = " . (int)$_SESSION['outlet_id'] : "";
$data['customers'] = db()->query("SELECT * FROM customers $out_w ORDER BY id DESC LIMIT 5")->fetchAll();
$data['stats'] = [
'total_customers' => db()->query("SELECT COUNT(*) FROM customers $out_w")->fetchColumn(),
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items $out_w")->fetchColumn(),
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices $out_w")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = 'completed' $out_and")->fetchColumn() ?: 0),
'total_received' => (db()->query("SELECT SUM(amount) FROM payments $out_w")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments WHERE 1=1 $out_and")->fetchColumn() ?: 0),
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases $out_w")->fetchColumn() ?: 0,
'total_paid' => db()->query("SELECT SUM(amount) FROM purchase_payments $out_w")->fetchColumn() ?: 0,
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE() $out_and")->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) $out_and")->fetchColumn(),
'low_stock_items_count' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level $out_and")->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 (Invoices + POS)
$data['monthly_sales'] = db()->query("
SELECT label, SUM(tot) as total FROM (
SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, total_with_vat as tot, DATE_FORMAT(invoice_date, '%Y-%m') as sort_col FROM invoices $out_w
UNION ALL
SELECT DATE_FORMAT(created_at, '%M %Y') as label, net_amount as tot, DATE_FORMAT(created_at, '%Y-%m') as sort_col FROM pos_transactions WHERE status = 'completed' $out_and
) t
GROUP BY label, sort_col
ORDER BY sort_col ASC LIMIT 12
")->fetchAll(PDO::FETCH_ASSOC);
$data['yearly_sales'] = db()->query("
SELECT label, SUM(tot) as total FROM (
SELECT YEAR(invoice_date) as label, total_with_vat as tot FROM invoices $out_w
UNION ALL
SELECT YEAR(created_at) as label, net_amount as tot FROM pos_transactions WHERE status = 'completed' $out_and
) t
GROUP BY label
ORDER BY label ASC LIMIT 5
")->fetchAll(PDO::FETCH_ASSOC);
// Cash Flow Data (Income vs Expense - last 6 months)
$data['cash_flow'] = db()->query("
SELECT m.sort_col, m.label,
(
SELECT COALESCE(SUM(amount), 0) FROM payments WHERE DATE_FORMAT(payment_date, '%Y-%m') = m.sort_col $out_and
) + (
SELECT COALESCE(SUM(amount), 0) FROM pos_payments WHERE DATE_FORMAT(created_at, '%Y-%m') = m.sort_col $out_and
) as income,
(
SELECT COALESCE(SUM(amount), 0) FROM expenses WHERE DATE_FORMAT(expense_date, '%Y-%m') = m.sort_col $out_and
) + (
SELECT COALESCE(SUM(amount), 0) FROM purchase_payments WHERE DATE_FORMAT(payment_date, '%Y-%m') = m.sort_col $out_and
) + (
SELECT COALESCE(SUM(net_salary), 0) FROM hr_payroll WHERE DATE_FORMAT(payment_date, '%Y-%m') = m.sort_col $out_and
) as expense
FROM (
SELECT DISTINCT DATE_FORMAT(dt, '%Y-%m') as sort_col, DATE_FORMAT(dt, '%M %Y') as label
FROM (
SELECT payment_date as dt FROM payments $out_w
UNION SELECT created_at as dt FROM pos_payments $out_w
UNION SELECT expense_date as dt FROM expenses $out_w
UNION SELECT payment_date as dt FROM purchase_payments $out_w
) dates
) m
ORDER BY m.sort_col DESC LIMIT 6
")->fetchAll(PDO::FETCH_ASSOC);
$data['cash_flow'] = array_reverse($data['cash_flow']); // Chronological order
}
break;
}
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
?>
<!doctype html>
<html lang="<?= $lang ?>" dir="<?= $dir ?>">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><?= __('accounting') ?> - Admin Panel</title>
<meta name="description" content="<?= htmlspecialchars($projectDescription) ?>" />
<?php if (!empty($data['settings']['favicon'])): ?>
<link rel="icon" href="<?= htmlspecialchars($data['settings']['favicon']) ?>?v=<?= time() ?>">
<?php endif; ?>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap<?= $dir === 'rtl' ? '.rtl' : '' ?>.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>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
<script src="assets/js/main.js?v=<?= time() ?>"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
<style>
/* Force RTL Sidebar Position */
[dir="rtl"] .sidebar {
right: 0 !important;
left: auto !important;
}
[dir="rtl"] .main-content {
margin-left: 0 !important;
margin-right: 210px !important;
}
@media (max-width: 1199.98px) {
[dir="rtl"] .sidebar {
right: -210px !important;
left: auto !important;
}
[dir="rtl"] .sidebar.show {
right: 0 !important;
left: auto !important;
}
[dir="rtl"] .main-content {
margin-right: 0 !important;
margin-left: 0 !important;
}
.pos-container {
flex-direction: column !important;
height: auto !important;
}
.pos-cart {
width: 100% !important;
height: auto !important;
position: sticky;
bottom: 0;
z-index: 1001;
}
.pos-products {
height: auto !important;
max-height: 500px;
}
}
/* General Responsive Helpers */
@media (max-width: 767.98px) {
.table:not(.table-borderless):not(.table-sm) {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.card {
padding: 1rem !important;
}
.topbar {
padding: 0.75rem 1rem !important;
margin: -1rem -1rem 1rem -1rem !important;
}
.main-content {
padding: 1rem !important;
}
.h4, h4 {
font-size: 1.1rem;
}
.btn-sm-square {
width: 32px;
height: 32px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.topbar {
flex-wrap: wrap;
gap: 10px;
position: sticky;
top: 0;
z-index: 990;
}
.topbar h4 {
width: 100%;
order: 2;
font-size: 1.1rem;
margin-top: 5px !important;
}
.topbar > div:first-child {
order: 1;
}
.topbar > div:last-child {
order: 3;
width: 100%;
justify-content: space-between;
border-top: 1px solid var(--border);
padding-top: 5px;
}
.topbar .btn span {
display: none;
}
}
@media print {
.sidebar, .topbar, .d-print-none, .no-print, .btn-group, .btn, .badge i { display: none !important; }
.main-content { margin: 0 !important; padding: 0 !important; background: white !important; width: 100% !important; }
.card { border: none !important; box-shadow: none !important; padding: 0 !important; margin: 0 !important; }
.table { border-collapse: collapse !important; width: 100% !important; margin-top: 20px !important; }
.table th, .table td { border: 1px solid #000 !important; padding: 8px !important; font-size: 11px !important; color: #000 !important; }
.table thead th { background-color: #eee !important; color: #000 !important; font-weight: bold !important; text-transform: uppercase; }
.print-only { display: block !important; }
.text-success, .text-danger, .text-primary, .text-warning { color: #000 !important; }
.badge { border: none !important; padding: 0 !important; color: #000 !important; background: transparent !important; font-weight: normal !important; }
body { font-size: 12px !important; color: #000 !important; background: #fff !important; }
@page { margin: 1cm; }
}
/* Thermal Receipt Layout */
.thermal-receipt {
width: 300px;
margin: 0 auto;
background: #fff;
color: #000;
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
line-height: 1.4;
padding: 10px;
}
.thermal-receipt .center {
text-align: center;
}
.thermal-receipt .separator {
border-bottom: 1px dashed #000;
margin: 8px 0;
}
.thermal-receipt table {
width: 100%;
border-collapse: collapse;
}
.thermal-receipt table th, .thermal-receipt table td {
padding: 4px 2px;
text-align: left;
vertical-align: top;
}
.thermal-receipt table th {
border-bottom: 1px dashed #000;
font-weight: bold;
}
.thermal-receipt .total-row {
font-weight: bold;
font-size: 14px;
border-top: 1px dashed #000;
border-bottom: 1px dashed #000;
padding: 5px 0;
margin: 5px 0;
}
.thermal-receipt.rtl {
direction: rtl;
text-align: right;
}
.thermal-receipt.rtl table th, .thermal-receipt.rtl table td {
text-align: right;
}
@media print {
body.printing-receipt {
background: none;
}
body.printing-receipt .thermal-receipt-print {
width: 100% !important;
max-width: 300px; /* typically 80mm thermal paper width */
margin: 0;
padding: 0;
}
body.printing-receipt #posPrintArea {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
}
.print-only { display: none; }
[dir="rtl"] { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
</style>
</head>
<body class="theme-<?= htmlspecialchars($_SESSION['theme'] ?? 'default') ?>">
<?php if (!$is_activated && $trial_days > 0): ?>
<div class="alert alert-warning text-center mb-0 rounded-0 d-print-none py-2" style="position: sticky; top: 0; z-index: 2000; font-size: 0.85rem;">
<i class="bi bi-info-circle-fill me-2"></i>
<?= $lang === 'ar' ? "نسخة تجريبية: متبقي $trial_days يوم" : "Trial Version: $trial_days days remaining" ?>.
<a href="index.php?page=activate" class="alert-link ms-2 fw-bold"><?= __('activate_now') ?></a>
</div>
<?php endif; ?>
<div class="sidebar">
<div class="sidebar-header">
<div class="text-primary fw-bold">Accounting</div>
<div class="text-muted small" style="font-size: 0.7rem;">System v1.2.5</div>
</div>
<nav class="mt-4">
<!-- General Section -->
<a href="index.php?page=dashboard" class="nav-link <?= !isset($_GET['page']) || $_GET['page'] === 'dashboard' ? 'active' : '' ?>">
<i class="fas fa-chart-pie"></i> <span><?= __('dashboard') ?></span>
</a>
<!-- POS Section -->
<?php if (can('pos_view')): ?>
<a href="index.php?page=pos" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'pos' ? 'active' : '' ?>">
<i class="fas fa-cash-register"></i> <span><?= __('pos') ?></span>
</a>
<?php endif; ?>
<!-- Inventory Section -->
<?php if (can('items_view') || can('categories_view') || can('units_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><?= __('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">
<?php if (can('items_view')): ?>
<a href="index.php?page=items" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'items' ? 'active' : '' ?>">
<i class="fas fa-box"></i> <span><?= __('items') ?></span>
</a>
<?php endif; ?>
<?php if (can('categories_view')): ?>
<a href="index.php?page=categories" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'categories' ? 'active' : '' ?>">
<i class="fas fa-tags"></i> <span><?= __('categories') ?></span>
</a>
<?php endif; ?>
<?php if (can('units_view')): ?>
<a href="index.php?page=units" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'units' ? 'active' : '' ?>">
<i class="fas fa-ruler-combined"></i> <span><?= __('units') ?></span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Customers Section -->
<?php if (can('customers_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['customers']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#customers-collapse">
<span><i class="fas fa-users group-icon"></i><span><?= __('customers') ?></span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['customers']) ? 'show' : '' ?>" id="customers-collapse">
<a href="index.php?page=customers" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'customers' ? 'active' : '' ?>">
<i class="fas fa-users"></i> <span><?= __('customers') ?></span>
</a>
</div>
<?php endif; ?>
<!-- Suppliers Section -->
<?php if (can('suppliers_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['suppliers']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#suppliers-collapse">
<span><i class="fas fa-truck-field group-icon"></i><span><?= __('suppliers') ?></span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['suppliers']) ? 'show' : '' ?>" id="suppliers-collapse">
<a href="index.php?page=suppliers" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'suppliers' ? 'active' : '' ?>">
<i class="fas fa-truck-field"></i> <span><?= __('suppliers') ?></span>
</a>
</div>
<?php endif; ?>
<!-- Sales Section -->
<?php if (can('sales_view') || can('sales_returns_view') || can('quotations_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['sales', 'sales_returns', 'quotations']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#sales-collapse">
<span><i class="fas fa-file-invoice-dollar group-icon"></i><span><?= __('sales') ?></span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['sales', 'sales_returns', 'quotations']) ? 'show' : '' ?>" id="sales-collapse">
<?php if (can('sales_view')): ?>
<a href="index.php?page=sales" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'sales' ? 'active' : '' ?>">
<i class="fas fa-file-invoice-dollar"></i> <span><?= __('sales') ?></span>
</a>
<?php endif; ?>
<?php if (can('sales_returns_view')): ?>
<a href="index.php?page=sales_returns" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'sales_returns' ? 'active' : '' ?>">
<i class="fas fa-reply"></i> <span><?= __('sales_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="fas fa-file-lines"></i> <span><?= __('quotations') ?></span>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
<!-- Purchases Section -->
<?php if (can('purchases_view') || can('lpos_view') || can('purchase_returns_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['purchases', 'lpos', 'purchase_returns']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#purchases-collapse">
<span><i class="fas fa-cart-shopping group-icon"></i><span><?= __('purchases') ?></span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['purchases', 'lpos', 'purchase_returns']) ? 'show' : '' ?>" id="purchases-collapse">
<?php if (can('purchases_view')): ?>
<a href="index.php?page=purchases" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'purchases' ? 'active' : '' ?>">
<i class="fas fa-cart-shopping"></i> <span><?= __('purchases') ?></span>
</a>
<?php endif; ?>
<?php if (can('lpos_view')): ?>
<a href="index.php?page=lpos" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'lpos' ? 'active' : '' ?>">
<i class="fas fa-file-contract"></i> <span><?= __('lpos') ?></span>
</a>
<?php endif; ?>
<?php if (can('purchase_returns_view')): ?>
<a href="index.php?page=purchase_returns" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'purchase_returns' ? 'active' : '' ?>">
<i class="fas fa-share"></i> <span><?= __('purchase_returns') ?></span>
</a>
<?php endif; ?>
</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><i class="fas fa-wallet group-icon"></i><span><?= __('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="fas fa-layer-group"></i> <span><?= __('expense_categories') ?></span>
</a>
<a href="index.php?page=expenses" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'expenses' ? 'active' : '' ?>">
<i class="fas fa-file-invoice"></i> <span><?= __('expenses') ?></span>
</a>
</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><i class="fas fa-calculator group-icon"></i><span><?= __('accounting') ?></span></span>
<i class="fas fa-chevron-down chevron"></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="fas fa-book-open"></i> <span><?= __('accounting') ?></span>
</a>
<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><?= __('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="fas fa-chart-column"></i> <span><?= __('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="fas fa-file-contract"></i> <span><?= __('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="fas fa-receipt"></i> <span><?= __('vat_report') ?></span>
</a>
</div>
<?php endif; ?>
<!-- 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><i class="fas fa-user-tie group-icon"></i><span><?= __('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="fas fa-building-user"></i> <span><?= __('departments') ?></span>
</a>
<a href="index.php?page=hr_employees" class="nav-link <?= $page === 'hr_employees' ? 'active' : '' ?>">
<i class="fas fa-user-badge"></i> <span><?= __('employees') ?></span>
</a>
<a href="index.php?page=hr_attendance" class="nav-link <?= $page === 'hr_attendance' ? 'active' : '' ?>">
<i class="fas fa-user-check"></i> <span><?= __('attendance') ?></span>
</a>
<a href="index.php?page=hr_payroll" class="nav-link <?= $page === 'hr_payroll' ? 'active' : '' ?>">
<i class="fas fa-sack-dollar"></i> <span><?= __('payroll') ?></span>
</a>
</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', 'expense_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><?= __('reports') ?></span></span>
</div>
<div class="collapse <?= in_array($page, ['customer_statement', 'supplier_statement', 'cashflow_report', 'expense_report', 'expiry_report', 'low_stock_report', 'loyalty_history']) ? 'show' : '' ?>" id="reports-collapse">
<?php if (can('expenses_view')): ?>
<a href="index.php?page=expense_report" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'expense_report' ? 'active' : '' ?>">
<i class="fas fa-file-invoice-dollar"></i> <span><?= __('expense_report') ?></span>
</a>
<?php endif; ?>
<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><?= __('customer_statement') ?></span>
</a>
<?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><?= __('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><?= __('cashflow_report') ?></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><?= __('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><?= __('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><?= __('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><?= __('settings') ?></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><?= __('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><?= __('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><?= __('settings') ?></span>
</a>
</div>
<?php endif; ?>
<!-- Administrations Section -->
<?php if (can('users_view') || ($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['role_groups', 'users', 'cash_registers', 'register_sessions', 'scale_devices', 'backups', 'customer_display_settings', 'outlets']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#admin-collapse">
<span><i class="fas fa-user-gear group-icon"></i><span><?= __('admin') ?></span></span>
<i class="fas fa-chevron-down chevron"></i>
</div>
<div class="collapse <?= in_array($page, ['role_groups', 'users', 'cash_registers', 'register_sessions', 'scale_devices', 'backups', 'customer_display_settings', 'outlets']) ? 'show' : '' ?>" id="admin-collapse">
<?php if (($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
<a href="index.php?page=outlets" class="nav-link <?= $page === 'outlets' ? 'active' : '' ?>">
<i class="fas fa-shop"></i> <span>Outlets</span>
</a>
<a href="index.php?page=data_sync" class="nav-link <?= $page === 'data_sync' ? 'active' : '' ?>">
<i class="fas fa-sync"></i> <span>Data Sync</span>
</a>
<?php endif; ?>
<a href="index.php?page=role_groups" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
<i class="fas fa-user-shield"></i> <span><?= __('role_groups') ?></span>
</a>
<a href="index.php?page=users" class="nav-link <?= $page === 'users' ? 'active' : '' ?>">
<i class="fas fa-users-gear"></i> <span><?= __('users') ?></span>
</a>
<a href="index.php?page=cash_registers" class="nav-link <?= $page === 'cash_registers' ? 'active' : '' ?>">
<i class="fas fa-cash-register"></i> <span><?= __('cash_registers') ?></span>
</a>
<a href="index.php?page=register_sessions" class="nav-link <?= $page === 'register_sessions' ? 'active' : '' ?>">
<i class="fas fa-clock-rotate-left"></i> <span><?= __('register_sessions') ?></span>
</a>
<a href="index.php?page=scale_devices" class="nav-link <?= $page === 'scale_devices' ? 'active' : '' ?>">
<i class="fas fa-microchip"></i> <span><?= __('scale_devices') ?></span>
</a>
<a href="index.php?page=customer_display_settings" class="nav-link <?= $page === 'customer_display_settings' ? 'active' : '' ?>">
<i class="fas fa-desktop"></i> <span><?= __('customer_display') ?></span>
</a>
<a href="index.php?page=backups" class="nav-link <?= $page === 'backups' ? 'active' : '' ?>">
<i class="fas fa-database"></i> <span><?= __('backups') ?></span>
</a>
</div>
<?php endif; ?>
<!-- Tools Section -->
<a href="price_checker.php" target="_blank" class="nav-link">
<i class="fas fa-barcode"></i> <span>Price Checker</span>
</a>
<!-- Help Section -->
<a href="index.php?page=help" class="nav-link <?= $page === 'help' ? 'active' : '' ?>">
<i class="fas fa-question-circle"></i> <span><?= __('help') ?></span>
</a>
<!-- Version & Logs -->
<div class="mt-5 px-4 pb-4 border-top pt-4 sidebar-footer">
<div class="text-muted small fw-bold mb-1">Accounting System</div>
<div class="d-flex align-items-center justify-content-between text-muted small mb-2">
<span><i class="bi bi-info-circle me-1"></i> v1.2.5</span>
</div>
<a href="?page=logs" class="text-decoration-none text-muted small d-block">
<i class="bi bi-journal-text me-1"></i> <span>System Logs</span>
</a>
</div>
</nav>
</div>
<div class="main-content">
<header class="topbar">
<!-- DEBUG: page=<?= $page ?> can_access=<?= $can_access ? 'yes' : 'no' ?> is_activated=<?= $is_activated ? 'yes' : 'no' ?> trial_days=<?= $trial_days ?> -->
<div class="d-flex align-items-center">
<button id="sidebarToggle" class="btn btn-link text-dark p-0 me-3 d-xl-none">
<i class="bi bi-list fs-3"></i>
</button>
<h4 class="m-0"><?= __($page) ?></h4>
</div>
<div class="d-flex align-items-center">
<?php
$purchaseAlerts = getPurchaseAlerts();
if (!empty($purchaseAlerts)):
?>
<div class="dropdown me-3">
<button class="btn btn-outline-danger btn-sm position-relative" type="button" data-bs-toggle="dropdown">
<i class="fas fa-bell"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<?= count($purchaseAlerts) ?>
</span>
</button>
<div class="dropdown-menu dropdown-menu-end shadow border-0 p-0" style="width: 300px;">
<div class="p-3 border-bottom bg-light">
<h6 class="m-0 fw-bold"><?= $lang === 'ar' ? 'تنبيهات المدفوعات' : 'Payment Alerts' ?></h6>
</div>
<div class="list-group list-group-flush" style="max-height: 300px; overflow-y: auto;">
<?php foreach ($purchaseAlerts as $alert):
$isOverdue = strtotime($alert['due_date']) < time();
?>
<a href="index.php?page=purchases&search=<?= $alert['id'] ?>" class="list-group-item list-group-item-action p-3">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="badge <?= $isOverdue ? 'bg-danger' : 'bg-warning text-dark' ?>">
<?= $isOverdue ? ($lang === 'ar' ? 'متأخر' : 'Overdue') : ($lang === 'ar' ? 'مستحق قريباً' : 'Due Soon') ?>
</span>
<small class="text-muted"><?= htmlspecialchars($alert['due_date']) ?></small>
</div>
<div class="fw-bold small"><?= htmlspecialchars($alert['supplier_name']) ?></div>
<div class="text-primary small">OMR <?= number_format($alert['total_with_vat'], 3) ?></div>
</a>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
$assigned_outlets_str = $_SESSION['assigned_outlets'] ?? '';
$assigned_outlets_arr = array_filter(explode(',', $assigned_outlets_str));
if ($is_admin || count($assigned_outlets_arr) > 1):
if ($is_admin) {
$outlets = db()->query("SELECT * FROM outlets WHERE status = 'active'")->fetchAll(PDO::FETCH_ASSOC);
} else {
$in_clause = implode(',', array_map('intval', $assigned_outlets_arr));
$outlets = db()->query("SELECT * FROM outlets WHERE status = 'active' AND id IN ($in_clause)")->fetchAll(PDO::FETCH_ASSOC);
}
$cur_out = $_SESSION['outlet_id'] ?? 0;
$cur_name = $is_admin ? 'All Outlets' : (count($outlets) > 0 ? $outlets[0]['name'] : 'Select Outlet');
foreach ($outlets as $o) { if ($o['id'] == $cur_out) $cur_name = $o['name']; }
?>
<div class="dropdown me-3">
<button class="btn btn-outline-primary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-shop"></i> <span class="d-none d-md-inline"><?= htmlspecialchars($cur_name) ?></span>
</button>
<ul class="dropdown-menu shadow-sm border-0">
<?php if ($is_admin): ?>
<li><a class="dropdown-item <?= $cur_out == 0 ? 'active' : '' ?>" href="index.php?action=switch_outlet&id=0">All Outlets</a></li>
<li><hr class="dropdown-divider"></li>
<?php endif; ?>
<?php foreach ($outlets as $o): ?>
<li><a class="dropdown-item <?= $cur_out == $o['id'] ? 'active' : '' ?>" href="index.php?action=switch_outlet&id=<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></a></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<div class="dropdown me-3">
<button class="btn btn-outline-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-palette"></i> <span><?= $lang === 'ar' ? 'المظهر' : 'Theme' ?></span>
</button>
<ul class="dropdown-menu shadow-sm border-0">
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="default"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#0f172a;"></span> <?= $lang === 'ar' ? 'الافتراضي' : 'Default' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="dark"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#1e293b;"></span> <?= $lang === 'ar' ? 'ليلي' : 'Midnight' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="ocean"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#083344;"></span> <?= $lang === 'ar' ? 'محيط' : 'Ocean' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="forest"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#064e3b;"></span> <?= $lang === 'ar' ? 'غابة' : 'Forest' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="sunset"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#451a03;"></span> <?= $lang === 'ar' ? 'غروب' : 'Sunset' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="nord"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#88c0d0;"></span> <?= $lang === 'ar' ? 'نورد' : 'Nord' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="dracula"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#bd93f9;"></span> <?= $lang === 'ar' ? 'دراكولا' : 'Dracula' ?></a></li>
<li><a class="dropdown-item d-flex align-items-center theme-select" href="#" data-theme="citrus"><span class="rounded-circle me-2" style="width:12px; height:12px; background:#84cc16;"></span> <?= $lang === 'ar' ? 'حمضيات' : 'Citrus' ?></a></li>
</ul>
</div>
<a href="?lang=<?= $lang === 'ar' ? 'en' : 'ar' ?>" class="btn btn-outline-secondary btn-sm me-3">
<i class="bi bi-translate"></i> <span><?= $lang === 'ar' ? 'English' : 'العربية' ?></span>
</a>
<div class="me-3 d-none d-md-block text-end">
<div class="fw-bold small">
<a href="index.php?page=my_profile" class="text-dark text-decoration-none">
<?= htmlspecialchars((string)($_SESSION['username'] ?? 'User')) ?>
</a>
</div>
<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') ?>">
<?php if (!empty($_SESSION['profile_pic'])): ?>
<img src="<?= htmlspecialchars($_SESSION['profile_pic']) ?>?v=<?= time() ?>" alt="Profile" style="width: 100%; height: 100%; object-fit: cover;">
<?php else: ?>
<i class="bi bi-person fs-5" style="line-height: 40px;"></i>
<?php endif; ?>
<span class="position-absolute bottom-0 end-0 bg-primary rounded-circle text-white d-flex align-items-center justify-content-center" style="width: 15px; height: 15px; font-size: 0.6rem;">
<i class="bi bi-pencil-fill"></i>
</span>
</a>
<button class="btn btn-link text-dark p-0 ms-1" type="button" data-bs-toggle="dropdown">
<i class="bi bi-chevron-down small"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end border-0 shadow-sm rounded-3 mt-2">
<li><a class="dropdown-item py-2" href="index.php?page=my_profile"><i class="bi bi-person-badge me-2"></i> <?= $lang === 'ar' ? 'ملفي الشخصي' : 'My Profile' ?></a></li>
<li><a class="dropdown-item py-2" href="index.php?page=settings"><i class="bi bi-gear me-2"></i> <?= $lang === 'ar' ? 'إعدادات الشركة' : 'Company Settings' ?></a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item py-2 text-danger" href="index.php?action=logout"><i class="bi bi-box-arrow-right me-2"></i> <?= __('logout') ?></a></li>
</ul>
</div>
</div>
</header>
<script>
const companySettings = <?= json_encode($data['settings']) ?>;
</script>
<?php if ($message): ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
let msg = <?= json_encode($message) ?>;
let type = 'success';
let title = 'Success';
if (msg.toLowerCase().includes('error') || msg.toLowerCase().includes('failed')) {
type = 'error';
title = 'Error';
}
Swal.fire({
icon: type,
title: title,
text: msg,
timer: 3000,
timerProgressBar: true,
showConfirmButton: false
});
});
</script>
<?php endif; ?>
<?php if ($page === 'dashboard'): ?>
<?php if (can('dashboard_view')): ?>
<?php
$purchaseAlertsCount = count(getPurchaseAlerts());
if ($data['stats']['expired_items'] > 0 || $data['stats']['near_expiry_items'] > 0 || $data['stats']['low_stock_items_count'] > 0 || $purchaseAlertsCount > 0): ?>
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-warning border-0 shadow-sm d-flex align-items-center mb-0">
<i class="bi bi-exclamation-triangle-fill h4 mb-0 me-3 text-warning"></i>
<div class="flex-grow-1">
<span data-en="Administrative Alerts:" data-ar="تنبيهات إدارية:"><strong>Administrative Alerts:</strong></span>
<?php if ($data['stats']['expired_items'] > 0): ?>
<span class="ms-2" data-en="<?= $data['stats']['expired_items'] ?> items have expired." data-ar="هنالك <?= $data['stats']['expired_items'] ?> صنف منتهي الصلاحية."><?= $data['stats']['expired_items'] ?> items have expired.</span>
<?php endif; ?>
<?php if ($data['stats']['near_expiry_items'] > 0): ?>
<span class="ms-2" data-en="<?= $data['stats']['near_expiry_items'] ?> items are expiring soon." data-ar="هنالك <?= $data['stats']['near_expiry_items'] ?> صنف ستنتهي صلاحيتها قريباً."><?= $data['stats']['near_expiry_items'] ?> items are expiring soon.</span>
<?php endif; ?>
<?php if ($data['stats']['low_stock_items_count'] > 0): ?>
<span class="ms-2" data-en="<?= $data['stats']['low_stock_items_count'] ?> items are below minimum level." data-ar="هنالك <?= $data['stats']['low_stock_items_count'] ?> صنف تحت الحد الأدنى للمخزون."><?= $data['stats']['low_stock_items_count'] ?> items are below minimum level.</span>
<?php endif; ?>
<?php if ($purchaseAlertsCount > 0): ?>
<span class="ms-2 text-danger fw-bold" data-en="<?= $purchaseAlertsCount ?> purchase invoices are due or overdue." data-ar="هنالك <?= $purchaseAlertsCount ?> فاتورة مشتريات مستحقة أو متأخرة."><?= $purchaseAlertsCount ?> purchase invoices are due or overdue.</span>
<?php endif; ?>
</div>
<div class="d-flex gap-2">
<?php if ($purchaseAlertsCount > 0): ?>
<a href="index.php?page=purchases" class="btn btn-primary btn-sm" data-en="View Purchases" data-ar="عرض المشتريات">View Purchases</a>
<?php endif; ?>
<a href="index.php?page=expiry_report" class="btn btn-warning btn-sm" data-en="Expiry Report" data-ar="تقرير الانتهاء">Expiry Report</a>
<a href="index.php?page=low_stock_report" class="btn btn-danger btn-sm" data-en="Low Stock Report" data-ar="تقرير النواقص">Low Stock Report</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card p-3 border-start border-primary border-4 h-100">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('total_sales') ?></div>
<div class="h4 m-0">OMR <?= number_format((float)($data['stats']['total_sales'] ?? 0), 3) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-cart h2 text-primary op-50"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 border-start border-warning border-4 h-100">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('total_received') ?></div>
<div class="h4 m-0">OMR <?= number_format((float)($data['stats']['total_received'] ?? 0), 3) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-cash-stack h2 text-warning op-50"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 border-start border-danger border-4 h-100">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('customer_due') ?></div>
<div class="h4 m-0">OMR <?= number_format((float)($data['stats']['total_receivable'] ?? 0), 3) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-person-exclamation h2 text-danger op-50"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 border-start border-dark border-4 h-100 bg-light">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('total_purchases') ?></div>
<div class="h4 m-0">OMR <?= number_format((float)($data['stats']['total_purchases'] ?? 0), 3) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-bag h2 text-dark op-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-md-3">
<div class="card p-3 border-start border-info border-4 h-100 bg-light">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('total_paid') ?></div>
<div class="h4 m-0">OMR <?= number_format((float)($data['stats']['total_paid'] ?? 0), 3) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-wallet2 h2 text-info op-50"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 border-start border-secondary border-4 h-100 bg-light">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('supplier_due') ?></div>
<div class="h4 m-0">OMR <?= number_format((float)($data['stats']['total_payable'] ?? 0), 3) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-truck h2 text-secondary op-50"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 border-start border-success border-4 h-100">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small"><?= __('total_customers') ?></div>
<div class="h4 m-0"><?= (int)($data['stats']['total_customers'] ?? 0) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-people h2 text-success op-50"></i>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div class="card p-3 border-start border-info border-4 h-100">
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<div class="text-muted small" data-en="Total Items" data-ar="إجمالي الأصناف">Total Items</div>
<div class="h4 m-0"><?= (int)($data['stats']['total_items'] ?? 0) ?></div>
</div>
<div class="ms-3">
<i class="bi bi-box-seam h2 text-info op-50"></i>
</div>
</div>
</div>
</div>
</div>
<div class="row g-4 mb-4">
<div class="col-md-7">
<div class="card p-4 h-100 shadow-sm border-0" style="border-radius: 12px;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0 fw-bold text-dark" data-en="Sales Trend" data-ar="اتجاه المبيعات">Sales Trend</h5>
<div class="btn-group btn-group-sm shadow-sm">
<button type="button" class="btn btn-outline-primary active" id="btnMonthly" data-en="Monthly" data-ar="شهري">Monthly</button>
<button type="button" class="btn btn-outline-primary" id="btnYearly" data-en="Yearly" data-ar="سنوي">Yearly</button>
</div>
</div>
<div style="height: 300px; position: relative;">
<canvas id="salesChart"></canvas>
</div>
</div>
</div>
<div class="col-md-5">
<div class="card p-4 h-100 shadow-sm border-0" style="border-radius: 12px;">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0 fw-bold text-dark" data-en="Cash Flow Overview" data-ar="نظرة عامة على التدفق النقدي">Cash Flow Overview</h5>
<span class="badge bg-light text-muted border" data-en="Last 6 Months" data-ar="آخر 6 أشهر">Last 6 Months</span>
</div>
<div style="height: 300px; position: relative;">
<canvas id="cashFlowChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Recent Customers" data-ar="العملاء الحاليين">Recent Customers</h5>
<a href="index.php?page=customers" class="btn btn-outline-primary btn-sm">
<span data-en="View All" data-ar="عرض الكل">View All</span>
</a>
</div>
<div class="table-responsive">
<table class="table table-hover table-bordered align-middle">
<thead>
<tr>
<th data-en="Name" data-ar="الاسم">Name</th>
<th data-en="Phone" data-ar="الهاتف">Phone</th>
<th data-en="Balance" data-ar="الرصيد" class="text-end">Balance</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['customers'] as $c): ?>
<tr>
<td><?= htmlspecialchars((string)($c['name'] ?? '')) ?></td>
<td><?= htmlspecialchars((string)($c['phone'] ?? '')) ?></td>
<td class="text-end" data-en="OMR <?= number_format((float)$c['balance'], 3) ?>" data-ar="<?= number_format((float)$c['balance'], 3) ?> ر.ع."><?= $lang === 'ar' ? number_format((float)$c['balance'], 3) . ' ر.ع.' : 'OMR ' . number_format((float)$c['balance'], 3) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card p-4">
<h5 class="mb-4" data-en="Quick Links" data-ar="روابط سريعة">Quick Links</h5>
<div class="list-group list-group-flush">
<button class="list-group-item list-group-item-action border-0 px-0" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-person-plus text-primary"></i> <span data-en="Add Customer" data-ar="إضافة عميل">Add Customer</span>
</button>
<button class="list-group-item list-group-item-action border-0 px-0" data-bs-toggle="modal" data-bs-target="#addItemModal">
<i class="bi bi-box-seam text-success"></i> <span data-en="Add Item" data-ar="إضافة صنف">Add Item</span>
</button>
<button class="list-group-item list-group-item-action border-0 px-0" data-bs-toggle="modal" data-bs-target="#importItemsModal">
<i class="bi bi-file-earmark-excel text-success"></i> <span data-en="Import Items" data-ar="استيراد أصناف">Import Items</span>
</button>
<a href="index.php?page=sales" class="list-group-item list-group-item-action border-0 px-0">
<i class="bi bi-cart text-primary"></i> <span data-en="Sales Tax Invoices" data-ar="فواتير المبيعات الضريبية">Sales Tax Invoices</span>
</a>
<a href="index.php?page=purchases" class="list-group-item list-group-item-action border-0 px-0">
<i class="bi bi-bag text-warning"></i> <span data-en="Purchase Tax Invoices" data-ar="فواتير المشتريات الضريبية">Purchase Tax Invoices</span>
</a>
</div>
</div>
</div>
</div>
<?php else: ?>
<div class="d-flex flex-column justify-content-center align-items-center h-100" style="min-height: 70vh;">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>" alt="Company Logo" class="mb-4" style="max-height: 200px; max-width: 350px; object-fit: contain;">
<?php endif; ?>
<h1 class="display-4 fw-bold text-muted text-center mt-3"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Company Name') ?></h1>
</div>
<?php endif; ?>
<?php elseif ($page === 'outlets' && ($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-header bg-white border-bottom-0 pt-4 pb-0 px-4 d-flex justify-content-between align-items-center">
<h5 class="fw-bold mb-0"><i class="bi bi-shop text-primary me-2"></i> Manage Outlets</h5>
<button class="btn btn-primary rounded-pill px-3 py-2" data-bs-toggle="modal" data-bs-target="#addOutletModal">
<i class="bi bi-plus-lg me-1"></i> Add Outlet
</button>
</div>
<div class="card-body p-4">
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Logo</th>
<th>Name</th>
<th>Details</th>
<th>Address</th>
<th>Status</th>
<th class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['outlets'] as $o): ?>
<tr>
<td>#<?= $o['id'] ?></td>
<td>
<?php if ($o['logo']): ?>
<img src="<?= htmlspecialchars($o['logo']) ?>" alt="Logo" class="rounded" style="max-height: 40px; max-width: 80px; object-fit: contain;">
<?php else: ?>
<span class="text-muted small">-</span>
<?php endif; ?>
</td>
<td class="fw-semibold text-dark"><?= htmlspecialchars($o['name']) ?></td>
<td class="text-muted small">
<div><i class="bi bi-telephone text-muted"></i> <?= htmlspecialchars($o['phone'] ?: '-') ?></div>
<?php if ($o['ctr_number']): ?><div><strong>CTR:</strong> <?= htmlspecialchars($o['ctr_number']) ?></div><?php endif; ?>
<?php if ($o['vat_number']): ?><div><strong>VAT:</strong> <?= htmlspecialchars($o['vat_number']) ?></div><?php endif; ?>
</td>
<td class="text-muted small"><?= htmlspecialchars($o['address'] ?: '-') ?></td>
<td><span class="badge bg-<?= $o['status'] === 'active' ? 'success' : 'secondary' ?> bg-opacity-10 text-<?= $o['status'] === 'active' ? 'success' : 'secondary' ?> rounded-pill px-2"><?= ucfirst($o['status']) ?></span></td>
<td class="text-end">
<button class="btn btn-light btn-sm rounded-circle me-1" onclick="editOutlet(<?= htmlspecialchars(json_encode($o)) ?>)" title="Edit">
<i class="bi bi-pencil"></i>
</button>
<?php if ($o['id'] !== 1): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this outlet?');">
<input type="hidden" name="id" value="<?= $o['id'] ?>">
<button type="submit" name="delete_outlet" class="btn btn-light btn-sm rounded-circle text-danger" title="Delete">
<i class="bi bi-trash"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
<!-- Add/Edit Modal -->
<div class="modal fade" id="addOutletModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content border-0 shadow rounded-4">
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="id" id="outlet_id">
<div class="modal-header border-bottom-0 pb-0">
<h5 class="modal-title fw-bold" id="outletModalTitle">Add Outlet</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4">
<div class="mb-3">
<label class="form-label small fw-semibold text-muted">Name</label>
<input type="text" name="name" id="outlet_name" class="form-control rounded-3" required>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small fw-semibold text-muted">Phone</label>
<input type="text" name="phone" id="outlet_phone" class="form-control rounded-3">
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-semibold text-muted">CTR Number</label>
<input type="text" name="ctr_number" id="outlet_ctr_number" class="form-control rounded-3">
</div>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label class="form-label small fw-semibold text-muted">VAT Number</label>
<input type="text" name="vat_number" id="outlet_vat_number" class="form-control rounded-3">
</div>
<div class="col-md-6 mb-3">
<label class="form-label small fw-semibold text-muted">Logo Image</label>
<input type="file" name="logo" id="outlet_logo" class="form-control rounded-3" accept="image/*">
</div>
</div>
<div class="mb-3">
<label class="form-label small fw-semibold text-muted">Address</label>
<textarea name="address" id="outlet_address" class="form-control rounded-3" rows="2"></textarea>
</div>
<div class="mb-0">
<label class="form-label small fw-semibold text-muted">Status</label>
<select name="status" id="outlet_status" class="form-select rounded-3">
<option value="active">Active</option>
<option value="inactive">Inactive</option>
</select>
</div>
</div>
<div class="modal-footer border-top-0 pt-0">
<button type="button" class="btn btn-light rounded-pill px-4" data-bs-dismiss="modal">Cancel</button>
<button type="submit" name="add_outlet" id="outletSubmitBtn" class="btn btn-primary rounded-pill px-4">Save Outlet</button>
</div>
</form>
</div>
</div>
</div>
<script>
function editOutlet(o) {
document.getElementById('outlet_id').value = o.id;
document.getElementById('outlet_name').value = o.name;
document.getElementById('outlet_phone').value = o.phone || '';
document.getElementById('outlet_ctr_number').value = o.ctr_number || '';
document.getElementById('outlet_vat_number').value = o.vat_number || '';
document.getElementById('outlet_address').value = o.address || '';
document.getElementById('outlet_status').value = o.status;
document.getElementById('outlet_logo').value = '';
document.getElementById('outletModalTitle').innerText = 'Edit Outlet';
document.getElementById('outletSubmitBtn').name = 'edit_outlet';
document.getElementById('outletSubmitBtn').innerText = 'Update Outlet';
new bootstrap.Modal(document.getElementById('addOutletModal')).show();
}
document.getElementById('addOutletModal').addEventListener('hidden.bs.modal', function () {
document.getElementById('outlet_id').value = '';
document.getElementById('outlet_name').value = '';
document.getElementById('outlet_phone').value = '';
document.getElementById('outlet_ctr_number').value = '';
document.getElementById('outlet_vat_number').value = '';
document.getElementById('outlet_address').value = '';
document.getElementById('outlet_logo').value = '';
document.getElementById('outlet_status').value = 'active';
document.getElementById('outletModalTitle').innerText = 'Add Outlet';
document.getElementById('outletSubmitBtn').name = 'add_outlet';
document.getElementById('outletSubmitBtn').innerText = 'Save Outlet';
});
</script>
<?php elseif ($page === 'data_sync' && ($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
<div class="container-fluid p-0">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2 class="h4 mb-0" data-en="Data Synchronization" data-ar="مزامنة البيانات">Data Synchronization</h2>
</div>
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4">
<form method="POST">
<div class="row g-4">
<div class="col-md-6">
<label class="form-label fw-bold" data-en="Source Outlet" data-ar="الفرع المصدر">Source Outlet</label>
<select name="source_outlet_id" class="form-select form-select-lg" required>
<option value="">-- Select Source --</option>
<?php foreach ($data['outlets'] as $o): ?>
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="form-text mt-2" data-en="Data will be copied FROM this outlet." data-ar="سيتم نسخ البيانات من هذا الفرع.">Data will be copied FROM this outlet.</div>
</div>
<div class="col-md-6">
<label class="form-label fw-bold" data-en="Target Outlet" data-ar="الفرع الهدف">Target Outlet</label>
<select name="target_outlet_id" class="form-select form-select-lg" required>
<option value="">-- Select Target --</option>
<?php foreach ($data['outlets'] as $o): ?>
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="form-text mt-2" data-en="Data will be copied TO this outlet." data-ar="سيتم نسخ البيانات إلى هذا الفرع.">Data will be copied TO this outlet.</div>
</div>
<div class="col-12">
<hr class="my-4">
<h5 class="mb-3" data-en="Select Data to Sync" data-ar="اختر البيانات للمزامنة">Select Data to Sync</h5>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="sync_categories" id="sync_categories" checked>
<label class="form-check-label" for="sync_categories" data-en="Stock Categories" data-ar="فئات المخزون">Stock Categories</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="sync_units" id="sync_units" checked>
<label class="form-check-label" for="sync_units" data-en="Stock Units" data-ar="وحدات المخزون">Stock Units</label>
</div>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" name="sync_items" id="sync_items" checked>
<label class="form-check-label" for="sync_items" data-en="Stock Items" data-ar="أصناف المخزون">Stock Items</label>
</div>
</div>
<div class="col-12 mt-4">
<div class="alert alert-info border-0 shadow-sm">
<i class="bi bi-info-circle me-2"></i>
<span data-en="Note: For items, if they already exist in the target outlet (by SKU), their details will be updated but stock quantity will remain unchanged. If they are new, stock quantity will be set to 0."
data-ar="ملاحظة: بالنسبة للأصناف، إذا كانت موجودة بالفعل في الفرع الهدف (عن طريق SKU)، فسيتم تحديث تفاصيلها ولكن ستبقى كمية المخزون دون تغيير. إذا كانت جديدة، فسيتم تعيين كمية المخزون إلى 0.">
Note: For items, if they already exist in the target outlet (by SKU), their details will be updated but stock quantity will remain unchanged. If they are new, stock quantity will be set to 0.
</span>
</div>
</div>
<div class="col-12 mt-4 text-end">
<button type="submit" name="perform_data_sync" class="btn btn-primary btn-lg px-5 rounded-pill"
onclick="return confirm('Are you sure you want to sync data? This operation cannot be undone.')">
<i class="bi bi-arrow-repeat me-2"></i>
<span data-en="Start Synchronization" data-ar="بدء المزامنة">Start Synchronization</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'customers' || $page === 'suppliers'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="<?= $currTitle['en'] ?> Management" data-ar="إدارة <?= $currTitle['ar'] ?>"><?= $currTitle['en'] ?> Management</h5>
<div>
<?php if (can('customers_add') || can('suppliers_add')): ?>
<a href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>" class="btn btn-outline-success me-2">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Export to Excel" data-ar="تصدير إلى اكسل">Export to Excel</span>
</a>
<button class="btn btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="<?= $page === 'suppliers' ? '#importSuppliersModal' : '#importCustomersModal' ?>">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Import Excel" data-ar="استيراد من اكسل">Import Excel</span>
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCustomerModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add <?= $currTitle['en'] ?>" data-ar="إضافة <?= $currTitle['ar'] ?>">Add <?= $currTitle['en'] ?></span>
</button>
<?php endif; ?>
</div>
</div>
<!-- Search & Filter Bar -->
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-2 align-items-end">
<input type="hidden" name="page" value="<?= $page ?>">
<div class="col-md-4">
<label class="form-label small" data-en="Search" data-ar="بحث">Search</label>
<input type="text" name="search" class="form-control" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Name, email, or phone..." data-en="Name, email, or phone..." data-ar="الاسم، البريد، أو الهاتف...">
</div>
<div class="col-md-2">
<label class="form-label small" data-en="From Date" data-ar="من تاريخ">From Date</label>
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-2">
<label class="form-label small" data-en="To Date" data-ar="إلى تاريخ">To Date</label>
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-4 d-flex gap-1">
<button type="submit" class="btn btn-primary flex-grow-1">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-outline-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
<?php if (!empty($_GET['search']) || !empty($_GET['start_date']) || !empty($_GET['end_date'])): ?>
<a href="index.php?page=<?= $page ?>" class="btn btn-outline-secondary">
<i class="bi bi-x-lg"></i>
</a>
<?php endif; ?>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Name" data-ar="الاسم">Name</th>
<th data-en="Tax ID" data-ar="الرقم الضريبي">Tax ID</th>
<th data-en="Email" data-ar="البريد">Email</th>
<th data-en="Phone" data-ar="الهاتف">Phone</th>
<th data-en="Balance" data-ar="الرصيد" class="text-end">Balance</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['customers'] as $c): ?>
<tr>
<td><?= htmlspecialchars((string)($c['name'] ?? '')) ?></td>
<td><?= htmlspecialchars((string)($c['tax_id'] ?? '---')) ?></td>
<td><?= htmlspecialchars((string)($c['email'] ?? '')) ?></td>
<td><?= htmlspecialchars((string)($c['phone'] ?? '')) ?></td>
<td class="text-end" data-en="OMR <?= number_format((float)$c['balance'], 3) ?>" data-ar="<?= number_format((float)$c['balance'], 3) ?> ر.ع."><?= $lang === 'ar' ? number_format((float)$c['balance'], 3) . ' ر.ع.' : 'OMR ' . number_format((float)$c['balance'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<?php if (can('customers_edit') || can('suppliers_edit')): ?>
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editCustomerModal<?= $c['id'] ?>"><i class="bi bi-pencil"></i></button>
<?php endif; ?>
<?php if (can('customers_delete') || can('suppliers_delete')): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $c['id'] ?>">
<button type="submit" name="delete_customer" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
<?php endif; ?>
</div>
<!-- Edit Customer Modal -->
<div class="modal fade" id="editCustomerModal<?= $c['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">
<span data-en="Edit <?= $currTitle['en'] ?>" data-ar="تعديل <?= $currTitle['ar'] ?>">Edit <?= $currTitle['en'] ?></span>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $c['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Name" data-ar="الاسم">Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($c['name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($c['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" value="<?= htmlspecialchars($c['phone'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label" data-en="Tax ID / VAT No" data-ar="الرقم الضريبي">Tax ID / VAT No</label>
<input type="text" name="tax_id" class="form-control" value="<?= htmlspecialchars($c['tax_id'] ?? '') ?>">
</div>
<div class="mb-3">
<label class="form-label" data-en="Balance" data-ar="الرصيد">Balance</label>
<input type="number" step="0.001" name="balance" class="form-control" value="<?= (float)$c['balance'] ?>">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_customer" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'categories'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Stock Categories Management" data-ar="إدارة فئات المخزون">Stock Categories Management</h5>
<div>
<?php if (can('categories_add')): ?>
<a href="index.php?page=export&type=categories&format=excel" class="btn btn-outline-success me-2">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</a>
<button class="btn btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#importCategoriesModal">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Import Excel" data-ar="استيراد من اكسل">Import Excel</span>
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addCategoryModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Category" data-ar="إضافة فئة">Add Category</span>
</button>
<?php endif; ?>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="ID" data-ar="المعرف">ID</th>
<th data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</th>
<th data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['categories'] as $cat): ?>
<tr>
<td><?= $cat['id'] ?></td>
<td><?= htmlspecialchars($cat['name_en']) ?></td>
<td><?= htmlspecialchars($cat['name_ar']) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<?php if (can('categories_edit')): ?>
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editCatModal<?= $cat['id'] ?>"><i class="bi bi-pencil"></i></button>
<?php endif; ?>
<?php if (can('categories_delete')): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $cat['id'] ?>">
<button type="submit" name="delete_category" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
<?php endif; ?>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editCatModal<?= $cat['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Edit Category</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $cat['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="edit_cat_name_en_<?= $cat['id'] ?>" class="form-control" value="<?= htmlspecialchars($cat['name_en']) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_cat_name_ar_<?= $cat['id'] ?>" data-target="edit_cat_name_en_<?= $cat['id'] ?>" data-to="en">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="edit_cat_name_ar_<?= $cat['id'] ?>" class="form-control" value="<?= htmlspecialchars($cat['name_ar']) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_cat_name_en_<?= $cat['id'] ?>" data-target="edit_cat_name_ar_<?= $cat['id'] ?>" data-to="ar">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_category" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'units'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Stock Units Management" data-ar="إدارة وحدات المخزون">Stock Units Management</h5>
<div>
<?php if (can('units_add')): ?>
<a href="index.php?page=export&type=units&format=excel" class="btn btn-outline-success me-2">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</a>
<button class="btn btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#importUnitsModal">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Import Excel" data-ar="استيراد من اكسل">Import Excel</span>
</button>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addUnitModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Unit" data-ar="إضافة وحدة">Add Unit</span>
</button>
<?php endif; ?>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</th>
<th data-en="Short (EN)" data-ar="الاختصار (إنجليزي)">Short (EN)</th>
<th data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</th>
<th data-en="Short (AR)" data-ar="الاختصار (عربي)">Short (AR)</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['units'] as $u): ?>
<tr>
<td><?= htmlspecialchars($u['name_en']) ?></td>
<td><?= htmlspecialchars($u['short_name_en']) ?></td>
<td><?= htmlspecialchars($u['name_ar']) ?></td>
<td><?= htmlspecialchars($u['short_name_ar']) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<?php if (can('units_edit')): ?>
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editUnitModal<?= $u['id'] ?>"><i class="bi bi-pencil"></i></button>
<?php endif; ?>
<?php if (can('units_delete')): ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $u['id'] ?>">
<button type="submit" name="delete_unit" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
<?php endif; ?>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editUnitModal<?= $u['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Edit Unit</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $u['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="edit_unit_name_en_<?= $u['id'] ?>" class="form-control" value="<?= htmlspecialchars($u['name_en']) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_unit_name_ar_<?= $u['id'] ?>" data-target="edit_unit_name_en_<?= $u['id'] ?>" data-to="en">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label">Short (EN)</label>
<input type="text" name="short_en" class="form-control" value="<?= htmlspecialchars($u['short_name_en']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="edit_unit_name_ar_<?= $u['id'] ?>" class="form-control" value="<?= htmlspecialchars($u['name_ar']) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="edit_unit_name_en_<?= $u['id'] ?>" data-target="edit_unit_name_ar_<?= $u['id'] ?>" data-to="ar">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label">Short (AR)</label>
<input type="text" name="short_ar" class="form-control" value="<?= htmlspecialchars($u['short_name_ar']) ?>" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_unit" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'items'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Stock Items" data-ar="أصناف المخزون">Stock Items (<?= count($data['items'] ?? []) ?>)</h5>
<div class="d-flex align-items-center">
<button class="btn btn-dark me-2" id="bulkBarcodeBtn" style="display:none;" onclick="openAveryModal()">
<i class="bi bi-printer"></i> <span data-en="Avery Labels" data-ar="ملصقات إيفري">Avery Labels</span>
</button>
<a href="promo-catalog.php" class="btn btn-outline-danger me-2">
<i class="bi bi-file-pdf"></i> <span data-en="Promo Catalog" data-ar="كتالوج العروض">Promo Catalog</span>
</a>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to cancel all active promotions?');">
<button type="submit" name="cancel_all_promotions" class="btn btn-outline-danger me-2">
<i class="bi bi-x-circle"></i> <span data-en="Cancel All Promotions" data-ar="إلغاء جميع العروض">Cancel All Promotions</span>
</button>
</form>
<button class="btn btn-outline-success me-2" data-bs-toggle="modal" data-bs-target="#importItemsModal">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Import Excel" data-ar="استيراد من اكسل">Import Excel</span>
</button>
<a href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'items', 'format' => 'excel'])) ?>" class="btn btn-outline-success me-2">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Export to Excel" data-ar="تصدير إلى اكسل">Export to Excel</span>
</a>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addItemModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Item" data-ar="إضافة صنف">Add Item</span>
</button>
</div>
</div>
<!-- Search Bar -->
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-2 align-items-end">
<input type="hidden" name="page" value="items">
<div class="col-md-7">
<label class="form-label small" data-en="Search" data-ar="بحث">Search</label>
<input type="text" name="search" class="form-control" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Name or SKU..." data-en="Name or SKU..." data-ar="الاسم أو الباركود...">
</div>
<div class="col-md-5 d-flex gap-1">
<button type="submit" class="btn btn-primary flex-grow-1">
<i class="bi bi-search"></i> <span data-en="Search" data-ar="بحث">Search</span>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-outline-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'items', 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'items', 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
<?php if (!empty($_GET['search'])): ?>
<a href="index.php?page=items" class="btn btn-outline-secondary">
<i class="bi bi-x-lg"></i>
</a>
<?php endif; ?>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th style="width: 40px;"><input type="checkbox" id="selectAllItems" class="form-check-input"></th>
<th data-en="Image" data-ar="الصورة">Image</th>
<th data-en="SKU" data-ar="الباركود">SKU</th>
<th data-en="Name" data-ar="الاسم">Name</th>
<th data-en="Category" data-ar="الفئة">Category</th>
<th data-en="Supplier" data-ar="المورد">Supplier</th>
<th data-en="Stock Level" data-ar="المخزون">Stock Level</th>
<th data-en="Expiry" data-ar="تاريخ الانتهاء">Expiry</th>
<th data-en="VAT" data-ar="الضريبة">VAT</th>
<th data-en="Actions" data-ar="الإجراءات">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['items'] as $item): ?>
<tr>
<td><input type="checkbox" class="form-check-input item-checkbox" data-id="<?= $item['id'] ?>" data-sku="<?= htmlspecialchars($item['sku']) ?>" data-name-en="<?= htmlspecialchars($item['name_en']) ?>" data-name-ar="<?= htmlspecialchars($item['name_ar']) ?>" data-price="<?= number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3) ?>"></td>
<td>
<?php if ($item['image_path']): ?>
<img src="<?= htmlspecialchars($item['image_path']) ?>" alt="item" style="width: 40px; height: 40px; object-fit: cover;" class="rounded">
<?php else: ?>
<div class="bg-light rounded d-flex align-items-center justify-content-center" style="width: 40px; height: 40px;">
<i class="bi bi-image text-muted"></i>
</div>
<?php endif; ?>
</td>
<td><?= htmlspecialchars($item['sku']) ?></td>
<td>
<div class="fw-bold">
<?= htmlspecialchars((string)($item['name_en'] ?? '')) ?>
<?php if (isset($item['is_promotion']) && $item['is_promotion']): ?>
<span class="badge bg-success ms-1" style="font-size: 0.65rem;" data-en="Promo" data-ar="عرض">Promo</span>
<?php endif; ?>
</div>
<div class="small text-muted"><?= htmlspecialchars((string)($item['name_ar'] ?? '')) ?></div>
</td>
<td><span data-en="<?= htmlspecialchars((string)($item['cat_en'] ?? '')) ?>" data-ar="<?= htmlspecialchars((string)($item['cat_ar'] ?? '')) ?>"><?= htmlspecialchars((string)($item['cat_en'] ?? '')) ?></span></td>
<td><?= htmlspecialchars((string)($item['supplier_name'] ?? '---')) ?></td>
<td>
<div class="text-end">
<strong><?= number_format((float)$item['stock_quantity'], 3) ?></strong>
<div class="small text-muted">Min: <?= number_format((float)$item['min_stock_level'], 3) ?></div>
<?php if ($item['stock_quantity'] <= $item['min_stock_level']): ?>
<span class="badge bg-danger" data-en="Low Stock" data-ar="مخزون منخفض">Low Stock</span>
<?php endif; ?>
</div>
</td>
<td><?= !empty($item['expiry_date']) ? htmlspecialchars((string)$item['expiry_date']) : '---' ?></td>
<td><?= number_format((float)$item['vat_rate'], 2) ?>%</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info" title="View" data-bs-toggle="modal" data-bs-target="#viewItemModal<?= $item['id'] ?>"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editItemModal<?= $item['id'] ?>"><i class="bi bi-pencil"></i></button>
<button class="btn btn-outline-dark" title="Barcode" onclick="printItemBarcode('<?= htmlspecialchars($item['sku']) ?>', '<?= htmlspecialchars($item['name_en']) ?>', '<?= htmlspecialchars($item['name_ar']) ?>', '<?= number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3) ?>')"><i class="bi bi-upc"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $item['id'] ?>">
<button type="submit" name="delete_item" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
<!-- View Item Modal -->
<div class="modal fade" id="viewItemModal<?= $item['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white"><?= htmlspecialchars((string)($item['name_en'] ?? '')) ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="text-center mb-3">
<?php if ($item['image_path']): ?>
<img src="<?= htmlspecialchars((string)$item['image_path']) ?>" class="img-fluid rounded shadow-sm" style="max-height: 200px;">
<?php else: ?>
<div class="bg-light rounded d-flex align-items-center justify-content-center mx-auto" style="width: 150px; height: 150px;">
<i class="bi bi-image text-muted" style="font-size: 3rem;"></i>
</div>
<?php endif; ?>
</div>
<table class="table table-sm">
<tr><th class="text-muted">SKU</th><td><?= htmlspecialchars((string)($item['sku'] ?? '')) ?></td></tr>
<tr><th class="text-muted" data-en="Category" data-ar="الفئة">Category</th><td><?= htmlspecialchars($item['cat_en'] ?? '---') ?></td></tr>
<tr><th class="text-muted" data-en="Supplier" data-ar="المورد">Supplier</th><td><?= htmlspecialchars($item['supplier_name'] ?? '---') ?></td></tr>
<tr><th class="text-muted">Sale Price</th><td>OMR <?= number_format((float)$item['sale_price'], 3) ?></td></tr>
<tr><th class="text-muted">Stock Level</th><td><?= number_format((float)$item['stock_quantity'], 3) ?></td></tr>
<tr><th class="text-muted">VAT Rate</th><td><?= number_format((float)$item['vat_rate'], 2) ?>%</td></tr>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-outline-dark" onclick="printItemBarcode('<?= htmlspecialchars($item['sku']) ?>', '<?= htmlspecialchars($item['name_en']) ?>', '<?= htmlspecialchars($item['name_ar']) ?>', '<?= number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3) ?>')"><i class="bi bi-printer"></i> Print Barcode</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
</div>
</div>
</div>
</div>
<!-- Edit Item Modal -->
<div class="modal fade" id="editItemModal<?= $item['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Edit Item" data-ar="تعديل صنف">Edit Item</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<input type="hidden" name="id" value="<?= $item['id'] ?>">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="editItemNameEn<?= $item['id'] ?>" class="form-control" value="<?= htmlspecialchars((string)($item['name_en'] ?? '')) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="editItemNameAr<?= $item['id'] ?>" data-target="editItemNameEn<?= $item['id'] ?>" data-to="en">
<i class="bi bi-translate"></i> EN
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="editItemNameAr<?= $item['id'] ?>" class="form-control" value="<?= htmlspecialchars((string)($item['name_ar'] ?? '')) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="editItemNameEn<?= $item['id'] ?>" data-target="editItemNameAr<?= $item['id'] ?>" data-to="ar">
<i class="bi bi-translate"></i> AR
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label">SKU / Barcode</label>
<input type="text" name="sku" class="form-control" value="<?= htmlspecialchars((string)($item['sku'] ?? '')) ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select">
<option value="">---</option>
<?php foreach ($data['categories'] ?? [] as $c): ?>
<option value="<?= $c['id'] ?>" <?= $c['id'] == $item['category_id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Unit</label>
<select name="unit_id" class="form-select">
<option value="">---</option>
<?php foreach ($data['units'] ?? [] as $u): ?>
<option value="<?= $u['id'] ?>" <?= $u['id'] == $item['unit_id'] ? 'selected' : '' ?>><?= htmlspecialchars($u['short_name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Supplier" data-ar="المورد">Supplier</label>
<select name="supplier_id" class="form-select">
<option value="">---</option>
<?php foreach ($data['suppliers'] ?? [] as $s): ?>
<option value="<?= $s['id'] ?>" <?= $s['id'] == $item['supplier_id'] ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Sale Price</label>
<input type="number" step="0.001" name="sale_price" class="form-control" value="<?= (float)$item['sale_price'] ?>">
</div>
<div class="col-md-6">
<label class="form-label">Purchase Price</label>
<input type="number" step="0.001" name="purchase_price" class="form-control" value="<?= (float)$item['purchase_price'] ?>">
</div>
<div class="col-md-6">
<label class="form-label">Stock Qty</label>
<input type="number" step="0.001" name="stock_quantity" class="form-control" value="<?= (float)$item['stock_quantity'] ?>">
</div>
<div class="col-md-6">
<label class="form-label">Min Level</label>
<input type="number" step="0.001" name="min_stock_level" class="form-control" value="<?= (float)$item['min_stock_level'] ?>">
</div>
<div class="col-md-6">
<label class="form-label">VAT Rate (%)</label>
<input type="number" step="0.01" name="vat_rate" class="form-control" value="<?= number_format((float)$item['vat_rate'], 2, '.', '') ?>">
</div>
<div class="col-md-6">
<label class="form-label">Item Picture</label>
<input type="file" name="image" class="form-control" accept="image/*">
</div>
<div class="col-md-6">
<div class="form-check form-switch mt-4">
<input class="form-check-input hasExpiryToggleEdit" type="checkbox" id="hasExpiryToggleEdit<?= $item['id'] ?>" <?= !empty($item['expiry_date']) ? 'checked' : '' ?>>
<label class="form-check-label fw-bold" for="hasExpiryToggleEdit<?= $item['id'] ?>" data-en="Has Expiry Date?" data-ar="هل له تاريخ انتهاء؟">Has Expiry Date?</label>
</div>
</div>
<div class="col-md-6 expiryDateContainerEdit" style="display: <?= !empty($item['expiry_date']) ? 'block' : 'none' ?>;">
<label class="form-label" data-en="Expiry Date" data-ar="تاريخ الانتهاء">Expiry Date</label>
<input type="date" name="expiry_date" class="form-control" value="<?= $item['expiry_date'] ?? '' ?>">
</div>
<div class="col-12 mt-3">
<hr>
<h6 data-en="Promotion Details" data-ar="تفاصيل العرض">Promotion Details</h6>
</div>
<div class="col-md-12">
<div class="form-check form-switch mt-2 mb-2">
<input class="form-check-input isPromotionToggleEdit" type="checkbox" name="is_promotion" value="1" <?= $item['is_promotion'] ? 'checked' : '' ?> id="isPromotionToggleEdit<?= $item['id'] ?>" data-id="<?= $item['id'] ?>">
<label class="form-check-label fw-bold" for="isPromotionToggleEdit<?= $item['id'] ?>" data-en="On Promotion?" data-ar="في العرض؟">On Promotion?</label>
</div>
</div>
<div class="col-md-12">
<div class="row g-3 promotionFieldsContainerEdit" id="promotionFieldsContainerEdit<?= $item['id'] ?>" style="display: <?= $item['is_promotion'] ? 'flex' : 'none' ?>;">
<div class="col-md-4">
<label class="form-label">Start Date</label>
<input type="date" name="promotion_start" class="form-control" value="<?= $item['promotion_start'] ?>">
</div>
<div class="col-md-4">
<label class="form-label">End Date</label>
<input type="date" name="promotion_end" class="form-control" value="<?= $item['promotion_end'] ?>">
</div>
<div class="col-md-4">
<label class="form-label">Percent (%)</label>
<input type="number" step="0.01" name="promotion_percent" class="form-control" value="<?= (float)$item['promotion_percent'] ?>">
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer bg-light">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_item" class="btn btn-primary px-4"><i class="bi bi-save"></i> Save Changes</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'expiry_report'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Expiry Report" data-ar="تقرير انتهاء الصلاحية">Expiry Report</h5>
<div class="d-flex align-items-center gap-2">
<div class="dropdown d-inline-block">
<button class="btn btn-outline-success btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'expiry_report', 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'expiry_report', 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
<div class="d-flex gap-2">
<a href="index.php?page=expiry_report&filter=all" class="btn btn-sm <?= !isset($_GET['filter']) || $_GET['filter'] === 'all' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="All" data-ar="الكل">All</a>
<a href="index.php?page=expiry_report&filter=expired" class="btn btn-sm <?= isset($_GET['filter']) && $_GET['filter'] === 'expired' ? 'btn-danger' : 'btn-outline-danger' ?>" data-en="Expired" data-ar="منتهي">Expired</a>
<a href="index.php?page=expiry_report&filter=near_expiry" class="btn btn-sm <?= isset($_GET['filter']) && $_GET['filter'] === 'near_expiry' ? 'btn-warning' : 'btn-outline-warning' ?>" data-en="Near Expiry" data-ar="قريب الانتهاء">Near Expiry</a>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="SKU" data-ar="الباركود">SKU</th>
<th data-en="Item Name" data-ar="اسم الصنف">Item Name</th>
<th data-en="Category" data-ar="الفئة">Category</th>
<th data-en="Stock Level" data-ar="المخزون">Stock Level</th>
<th data-en="Expiry Date" data-ar="تاريخ الانتهاء">Expiry Date</th>
<th data-en="Status" data-ar="الحالة">Status</th>
</tr>
</thead>
<tbody>
<?php if (empty($data['expiry_items'])): ?>
<tr>
<td colspan="6" class="text-center text-muted p-4" data-en="No items found." data-ar="لا توجد أصناف.">No items found.</td>
</tr>
<?php endif; ?>
<?php foreach ($data['expiry_items'] as $item): ?>
<?php
$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>
<td>
<div class="fw-bold"><?= htmlspecialchars($item['name_en']) ?></div>
<div class="small text-muted"><?= htmlspecialchars($item['name_ar']) ?></div>
</td>
<td><?= htmlspecialchars($item['cat_en'] ?? '---') ?></td>
<td><?= number_format((float)$item['stock_quantity'], 3) ?></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>
<?php elseif ($is_near): ?>
<span class="badge bg-warning text-dark" data-en="Near Expiry" data-ar="قريب الانتهاء">Near Expiry</span>
<?php else: ?>
<span class="badge bg-success" data-en="Good" data-ar="صالح">Good</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'low_stock_report'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Low Stock Report" data-ar="تقرير نواقص المخزون">Low Stock Report</h5>
<button class="btn btn-outline-primary btn-sm d-print-none" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="SKU" data-ar="الباركود">SKU</th>
<th data-en="Item Name" data-ar="اسم الصنف">Item Name</th>
<th data-en="Category" data-ar="الفئة">Category</th>
<th data-en="Supplier" data-ar="المورد">Supplier</th>
<th data-en="Min Level" data-ar="أدنى مستوى">Min Level</th>
<th data-en="Current Stock" data-ar="المخزون الحالي">Current Stock</th>
<th data-en="Shortage" data-ar="النقص">Shortage</th>
</tr>
</thead>
<tbody>
<?php if (empty($data['low_stock_items'])): ?>
<tr>
<td colspan="7" class="text-center text-muted p-4" data-en="All items are above minimum levels." data-ar="جميع الأصناف فوق الحد الأدنى.">All items are above minimum levels.</td>
</tr>
<?php endif; ?>
<?php foreach ($data['low_stock_items'] as $item): ?>
<?php $shortage = (float)$item['min_stock_level'] - (float)$item['stock_quantity']; ?>
<tr class="<?= (float)$item['stock_quantity'] <= 0 ? 'table-danger' : 'table-warning' ?>">
<td><?= htmlspecialchars($item['sku']) ?></td>
<td>
<div class="fw-bold"><?= htmlspecialchars($item['name_en']) ?></div>
<div class="small text-muted"><?= htmlspecialchars($item['name_ar']) ?></div>
</td>
<td><?= htmlspecialchars($item['cat_en'] ?? '---') ?></td>
<td><?= htmlspecialchars($item['supplier_name'] ?? '---') ?></td>
<td><?= number_format((float)$item['min_stock_level'], 2) ?></td>
<td>
<span class="badge <?= (float)$item['stock_quantity'] <= 0 ? 'bg-danger' : 'bg-warning text-dark' ?>">
<?= number_format((float)$item['stock_quantity'], 3) ?>
</span>
</td>
<td class="fw-bold text-danger"><?= number_format($shortage, 3) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'loyalty_history'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Loyalty Transaction History" data-ar="سجل عمليات الولاء">Loyalty Transaction History</h5>
<button class="btn btn-outline-primary btn-sm d-print-none" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
</div>
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="row g-3 align-items-end">
<input type="hidden" name="page" value="loyalty_history">
<div class="col-md-4">
<label class="form-label small fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
<select name="customer_id" class="form-select select2">
<option value="">All Customers</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= (($_GET['customer_id'] ?? '') == $c['id']) ? 'selected' : '' ?>><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Type" data-ar="النوع">Type</label>
<select name="type" class="form-select">
<option value="">All Types</option>
<option value="earned" <?= (($_GET['type'] ?? '') == 'earned') ? 'selected' : '' ?>>Earned</option>
<option value="redeemed" <?= (($_GET['type'] ?? '') == 'redeemed') ? 'selected' : '' ?>>Redeemed</option>
<option value="adjustment" <?= (($_GET['type'] ?? '') == 'adjustment') ? 'selected' : '' ?>>Adjustment</option>
</select>
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Customer" data-ar="العميل">Customer</th>
<th data-en="Tier" data-ar="الفئة">Tier</th>
<th data-en="Type" data-ar="النوع">Type</th>
<th data-en="Points" data-ar="النقاط">Points</th>
<th data-en="Description" data-ar="الوصف">Description</th>
</tr>
</thead>
<tbody>
<?php if (empty($data['loyalty_transactions'])): ?>
<tr><td colspan="6" class="text-center py-4 text-muted">No transactions found.</td></tr>
<?php endif; ?>
<?php foreach ($data['loyalty_transactions'] as $lt): ?>
<tr>
<td><?= date('Y-m-d H:i', strtotime($lt['created_at'])) ?></td>
<td>
<div class="fw-bold"><?= htmlspecialchars($lt['customer_name']) ?></div>
<div class="smaller text-muted">Current Balance: <?= number_format($lt['loyalty_points'], 0) ?> pts</div>
</td>
<td>
<?php
$tier = $lt['loyalty_tier'];
$badge = ($tier === 'gold') ? 'bg-warning text-dark' : (($tier === 'silver') ? 'bg-info text-dark' : 'bg-secondary');
?>
<span class="badge text-uppercase <?= $badge ?>"><?= $tier ?></span>
</td>
<td>
<?php
$type = $lt['transaction_type'];
$typeBadge = ($type === 'earned') ? 'bg-success' : (($type === 'redeemed') ? 'bg-danger' : 'bg-info');
?>
<span class="badge <?= $typeBadge ?>"><?= ucfirst($type) ?></span>
</td>
<td class="fw-bold <?= (float)$lt['points_change'] > 0 ? 'text-success' : 'text-danger' ?>">
<?= (float)$lt['points_change'] > 0 ? '+' : '' ?><?= number_format($lt['points_change'], 0) ?>
</td>
<td class="small"><?= htmlspecialchars($lt['description']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'pos'): ?>
<?php
// Check for active session
$stmt = db()->prepare("SELECT * FROM register_sessions WHERE user_id = ? AND status = 'open'");
$stmt->execute([$_SESSION['user_id']]);
$active_session = $stmt->fetch(PDO::FETCH_ASSOC);
$_SESSION['register_session_id'] = $active_session['id'] ?? null;
$registers = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll();
$allow_zero_stock_sell = ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1';
// Optimization: Only load top 50 products initially to prevent heavy load
$sql = "SELECT * FROM stock_items ORDER BY id DESC LIMIT 50";
$products_raw = db()->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 ORDER BY name ASC")->fetchAll(PDO::FETCH_ASSOC);
?>
<div class="pos-container">
<div class="pos-products">
<div class="bg-white p-3 rounded mb-3 shadow-sm d-flex gap-2">
<div class="input-group flex-grow-1">
<span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search"></i></span>
<input type="text" id="productSearch" class="form-control border-start-0" placeholder="Search products by name or SKU..." data-en="Search products..." data-ar="بحث عن منتجات...">
</div>
<div class="input-group flex-grow-1">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-upc-scan"></i></span>
<input type="text" id="barcodeInput" class="form-control border-start-0" placeholder="Scan barcode..." data-en="Scan barcode..." data-ar="امسح الباركود..." autofocus>
</div>
<button class="btn btn-warning d-flex align-items-center gap-2" onclick="cart.openHeldCartsModal()">
<i class="bi bi-pause-btn-fill"></i>
<span class="d-none d-xl-inline" data-en="Held List" data-ar="قائمة الانتظار">Held List</span>
</button>
</div>
<div class="product-grid" id="productGrid">
<?php foreach ($products as $p): ?>
<div class="product-card" data-id="<?= $p['id'] ?>" data-name-en="<?= htmlspecialchars($p['name_en']) ?>" data-name-ar="<?= htmlspecialchars($p['name_ar']) ?>" data-price="<?= $p['sale_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>" data-stock-quantity="<?= (float)$p['stock_quantity'] ?>" data-vat-rate="<?= $p['vat_rate'] ?>">
<?php if ($p['image_path']): ?>
<img src="<?= htmlspecialchars($p['image_path']) ?>" alt="<?= htmlspecialchars($p['name_en']) ?>">
<?php else: ?>
<div class="bg-light d-flex align-items-center justify-content-center rounded mb-2" style="height: 120px;">
<i class="bi bi-box-seam text-muted" style="font-size: 3rem;"></i>
</div>
<?php endif; ?>
<div class="mb-1 product-name">
<div class="fw-bold text-truncate" title="<?= htmlspecialchars($p['name_en']) ?>"><?= htmlspecialchars($p['name_en']) ?></div>
<div class="small text-muted fw-normal text-truncate" title="<?= htmlspecialchars($p['name_ar']) ?>" dir="rtl"><?= htmlspecialchars($p['name_ar']) ?></div>
</div>
<div class="small text-muted mb-2"><?= htmlspecialchars($p['sku']) ?></div>
<div class="d-flex justify-content-between align-items-center mt-auto">
<div class="d-flex flex-column">
<?php if ($p['sale_price'] < $p['original_price']): ?>
<span class="text-muted smaller text-decoration-line-through">OMR <?= number_format($p['original_price'], 3) ?></span>
<?php endif; ?>
<span class="price text-primary fw-bold">OMR <?= number_format((float)$p['sale_price'], 3) ?></span>
</div>
<span class="badge bg-light text-dark small"><?= (float)$p['stock_quantity'] ?> left</span>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="pos-cart">
<div class="p-3 border-bottom d-flex justify-content-between align-items-center">
<h6 class="m-0 fw-bold"><i class="bi bi-cart3 me-2"></i>Cart</h6>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-info" onclick="window.open('customer-display.php?v=<?= time() ?>', 'CustomerDisplay', 'toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,width=' + screen.availWidth + ',height=' + screen.availHeight + ',left=0,top=0')" title="Customer Display"><i class="bi bi-display me-1"></i> Customer Screen</button>
<?php if ($active_session): ?>
<button class="btn btn-sm btn-outline-dark" data-bs-toggle="modal" data-bs-target="#closeRegisterModal" title="Close Register"><i class="bi bi-x-circle me-1"></i data-en="Close" data-ar="إغلاق">Close</button>
<?php endif; ?>
<button class="btn btn-sm btn-outline-warning" onclick="cart.openHeldCartsModal()" title="Held List"><i class="bi bi-list-task"></i></button>
<button class="btn btn-sm btn-outline-secondary" onclick="cart.hold()" title="Hold Cart"><i class="bi bi-pause-circle"></i></button>
<button class="btn btn-sm btn-outline-danger" onclick="cart.clear()" title="Clear Cart"><i class="bi bi-trash"></i></button>
</div>
</div>
<div class="p-3 bg-light border-bottom">
<div class="mb-2">
<label class="small fw-bold mb-1" data-en="Customer" data-ar="العميل">Customer</label>
<div class="d-flex gap-2">
<select id="posCustomer" class="form-select form-select-sm" onchange="cart.onCustomerChange()">
<option value="">Walk-in Customer</option>
<?php foreach ($customers as $c): ?>
<option value="<?= $c['id'] ?>"
data-points="<?= $c['loyalty_points'] ?>"
data-tier="<?= $c['loyalty_tier'] ?>"
data-multiplier="<?= getLoyaltyMultiplier($c['loyalty_tier'] ?? 'bronze') ?>"
data-spent="<?= $c['total_spent'] ?>">
<?= htmlspecialchars($c['name']) ?>
</option>
<?php endforeach; ?>
</select>
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#addCustomerModal"><i class="bi bi-plus"></i></button>
</div>
<div id="loyaltyDisplay" class="mt-2 p-2 rounded bg-light border border-primary-subtle" style="display:none">
<div class="d-flex justify-content-between align-items-center mb-1">
<div>
<span id="tierBadge" class="badge text-uppercase">Bronze</span>
<span class="fw-bold ms-1 text-primary"><span id="customerPoints">0</span> pts</span>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="redeemLoyalty" onchange="cart.render()">
<label class="form-check-label small fw-bold" for="redeemLoyalty">Redeem</label>
</div>
</div>
<div class="progress" style="height: 4px;">
<div id="tierProgress" class="progress-bar bg-primary" role="progressbar" style="width: 0%"></div>
</div>
<div id="nextTierInfo" class="smaller text-muted mt-1">Spend more to unlock Silver</div>
</div>
</div>
<div>
<label class="small fw-bold mb-1">Discount Code</label>
<div class="input-group input-group-sm">
<input type="text" id="discountCode" class="form-control" placeholder="Code">
<button class="btn btn-outline-primary" type="button" onclick="cart.applyDiscount()">Apply</button>
</div>
<div id="appliedDiscountInfo" class="smaller text-primary mt-1" style="display:none"></div>
</div>
</div>
<div class="cart-items" id="cartItems">
<!-- Cart items will be injected here -->
<div class="text-center text-muted mt-5">
<i class="bi bi-cart-x" style="font-size: 3rem;"></i>
<p>Cart is empty</p>
</div>
</div>
<div class="cart-total">
<div class="d-flex justify-content-between mb-1">
<span data-en="Subtotal (Excl. VAT)" data-ar="المجموع (بدون الضريبة)">Subtotal (Excl. VAT)</span>
<span id="posSubtotal"><?= __('currency') ?> 0.000</span>
</div>
<div class="d-flex justify-content-between mb-1">
<span data-en="VAT" data-ar="الضريبة">VAT</span>
<span id="posVat"><?= __('currency') ?> 0.000</span>
</div>
<div class="d-flex justify-content-between mb-3 fw-bold fs-5 border-top pt-2">
<span data-en="Total" data-ar="الإجمالي">Total</span>
<span id="posTotal" class="text-primary"><?= __('currency') ?> 0.000</span>
</div>
<button class="btn btn-primary w-100 py-2 fw-bold" id="checkoutBtn" onclick="cart.checkout()">
PLACE ORDER
</button>
</div>
</div>
</div>
<script>
const cart = {
items: [],
discount: null,
customerPoints: 0,
selectedPaymentMethod: 'cash',
payments: [],
loyaltySettings: {
enabled: <?= json_encode($data['settings']['loyalty_enabled'] ?? '0') ?>,
pointsPerUnit: parseFloat(<?= json_encode($data['settings']['loyalty_points_per_unit'] ?? '1') ?>),
redeemPointsPerUnit: parseFloat(<?= json_encode($data['settings']['loyalty_redeem_points_per_unit'] ?? '100') ?>)
},
allCreditCustomers: <?php
$custData = [];
foreach ($customers as $c) {
$custData[] = [
'value' => (string)$c['id'],
'text' => $c['name'] . ' (' . ($c['phone'] ?? '') . ')'
];
}
echo json_encode($custData);
?>,
broadcast() {
try {
// Ensure items is an array
if (!Array.isArray(this.items)) this.items = [];
const subtotal = this.items.reduce((sum, item) => sum + (parseFloat(item.price) * parseFloat(item.qty)), 0);
const totalVat = this.items.reduce((sum, item) => {
const price = parseFloat(item.price) || 0;
const qty = parseFloat(item.qty) || 0;
const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 0;
return sum + (price * qty * (vatRate / (100 + vatRate)));
}, 0);
let discountAmount = 0;
if (this.discount) {
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
}
const redeemSwitch = document.getElementById('redeemLoyalty');
const redeemRate = (this.loyaltySettings && this.loyaltySettings.redeemPointsPerUnit) ? this.loyaltySettings.redeemPointsPerUnit : 100;
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(Math.max(0, subtotal - discountAmount), (parseFloat(this.customerPoints) || 0) / redeemRate) : 0;
const total = Math.max(0, subtotal - discountAmount - loyaltyRedeemed);
const customerSelect = document.getElementById('posCustomer');
const customerName = customerSelect ? customerSelect.options[customerSelect.selectedIndex].text : '';
const payload = {
items: this.items.map(i => {
const price = parseFloat(i.price) || 0;
const qty = parseFloat(i.qty) || 0;
const vatRate = (i.vatRate !== undefined && i.vatRate !== null) ? i.vatRate : 0;
const vatAmount = price * qty * (vatRate / (100 + vatRate));
return {
name: (document.documentElement.lang === 'ar' ? (i.nameAr || i.nameEn) : (i.nameEn || i.nameAr)) || 'Unknown Item',
price: price,
qty: qty,
vat: vatAmount
};
}),
subtotal: parseFloat(subtotal) || 0,
vat: parseFloat(totalVat) || 0,
discount: parseFloat(discountAmount) || 0,
loyalty: parseFloat(loyaltyRedeemed) || 0,
total: parseFloat(total) || 0,
currency: "<?= __('currency') ?>",
customerName: customerName,
theme: document.body.className,
timestamp: Date.now()
};
localStorage.setItem('pos_cart_update', JSON.stringify(payload));
} catch (e) { console.error('Broadcast error', e); }
},
add(product) {
if (!this.items) this.items = [];
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
const currentStock = parseFloat(product.stock_quantity) || 0;
const existing = this.items.find(item => item.id === product.id);
if (existing) {
if (!allowZeroStock && (existing.qty + 1) > currentStock) {
Swal.fire('Error', 'Insufficient stock!', 'error');
return;
}
existing.qty++;
} else {
if (!allowZeroStock && currentStock <= 0) {
Swal.fire('Error', 'Insufficient stock!', 'error');
return;
}
this.items.push({...product, qty: 1});
}
this.render();
// Add visual feedback
const lang = document.documentElement.lang || 'en';
const displayName = lang === 'ar' ? (product.nameAr || product.nameEn) : (product.nameEn || product.nameAr);
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: (lang === 'ar' ? 'تم إضافة: ' : 'Added: ') + displayName,
showConfirmButton: false,
timer: 800
});
},
remove(id) {
this.items = this.items.filter(item => item.id !== id);
this.render();
},
setQty(id, qty) {
if (!this.items) return;
const item = this.items.find(i => i.id === id);
if (item) {
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
const currentStock = parseFloat(item.stock_quantity) || 0;
if (!allowZeroStock && qty > currentStock) {
Swal.fire('Error', 'Insufficient stock!', 'error');
return;
}
item.qty = qty;
if (item.qty <= 0) this.remove(id);
else this.render();
}
},
async readScale(id) {
if (!('serial' in navigator)) {
Swal.fire('Error', 'Web Serial API is not supported in this browser. Please use Chrome or Edge.', 'error');
return;
}
try {
if (!this.scalePort) {
this.scalePort = await navigator.serial.requestPort();
await this.scalePort.open({ baudRate: 9600 });
} else if (!this.scalePort.readable) {
await this.scalePort.open({ baudRate: 9600 });
}
const reader = this.scalePort.readable.getReader();
let accumulatedData = '';
Swal.fire({
title: 'Reading Scale...',
text: 'Waiting for stable weight...',
allowOutsideClick: false,
didOpen: () => {
Swal.showLoading();
}
});
let weight = null;
const decoder = new TextDecoder('utf-8');
let readCount = 0;
while (readCount < 100) {
const { value, done } = await Promise.race([
reader.read(),
new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 500))
]).catch(e => ({ done: true, value: null }));
if (done) break;
if (value) {
accumulatedData += decoder.decode(value);
const matches = accumulatedData.match(/[\d]+\.\d+/g);
if (matches && matches.length > 0) {
weight = parseFloat(matches[matches.length - 1]);
if (!isNaN(weight) && weight > 0) {
break;
}
}
}
readCount++;
}
reader.releaseLock();
if (weight !== null && weight > 0) {
Swal.close();
this.setQty(id, weight);
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: 'Weight Read: ' + weight.toFixed(3) + ' kg',
showConfirmButton: false,
timer: 1500
});
} else {
Swal.fire('Warning', 'Could not read a valid weight. Please try again.', 'warning');
}
} catch (err) {
console.error('Scale Error:', err);
if (err.name === 'NotFoundError') {
// User cancelled the prompt
return;
}
Swal.fire('Error', 'Failed to read from scale: ' + err.message, 'error');
this.scalePort = null;
}
},
updateQty(id, delta) {
if (!this.items) return;
const item = this.items.find(i => i.id === id);
if (item) {
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
const currentStock = parseFloat(item.stock_quantity) || 0;
if (delta > 0 && !allowZeroStock && (item.qty + delta) > currentStock) {
Swal.fire('Error', 'Insufficient stock!', 'error');
return;
}
item.qty += delta;
if (item.qty <= 0) this.remove(id);
else this.render();
}
},
clear() {
this.items = [];
this.discount = null;
this.customerPoints = 0;
const discInput = document.getElementById('discountCode');
if (discInput) discInput.value = '';
const discInfo = document.getElementById('appliedDiscountInfo');
if (discInfo) discInfo.style.display = 'none';
const redeemSwitch = document.getElementById('redeemLoyalty');
if (redeemSwitch) redeemSwitch.checked = false;
const loyaltyDisplay = document.getElementById('loyaltyDisplay');
if (loyaltyDisplay) loyaltyDisplay.style.display = 'none';
this.render();
},
onCustomerChange() {
const select = document.getElementById('posCustomer');
const option = select.options[select.selectedIndex];
const display = document.getElementById('loyaltyDisplay');
if (!select.value || this.loyaltySettings.enabled !== '1') {
if (display) display.style.display = 'none';
this.customerPoints = 0;
this.customerTier = 'bronze';
this.customerMultiplier = 1.0;
const redeemSwitch = document.getElementById('redeemLoyalty');
if (redeemSwitch) redeemSwitch.checked = false;
this.render();
return;
}
this.customerPoints = parseFloat(option.dataset.points) || 0;
this.customerTier = option.dataset.tier || 'bronze';
this.customerMultiplier = parseFloat(option.dataset.multiplier) || 1.0;
const spent = parseFloat(option.dataset.spent) || 0;
document.getElementById('customerPoints').innerText = Math.floor(this.customerPoints);
const badge = document.getElementById('tierBadge');
badge.innerText = this.customerTier;
badge.className = 'badge text-uppercase ' + (this.customerTier === 'gold' ? 'bg-warning text-dark' : (this.customerTier === 'silver' ? 'bg-info text-dark' : 'bg-secondary'));
const progressBar = document.getElementById('tierProgress');
const nextTierInfo = document.getElementById('nextTierInfo');
let progress = 0;
if (this.customerTier === 'bronze') {
progress = (spent / 500) * 100;
nextTierInfo.innerText = `Spend ${(500 - spent).toFixed(3)} OMR more for Silver (1.2x points)`;
} else if (this.customerTier === 'silver') {
progress = ((spent - 500) / 1000) * 100;
nextTierInfo.innerText = `Spend ${(1500 - spent).toFixed(3)} OMR more for Gold (1.5x points)`;
} else {
progress = 100;
nextTierInfo.innerText = 'You are a Gold member! (1.5x points)';
}
progressBar.style.width = Math.min(100, progress) + '%';
display.style.display = 'block';
this.render();
},
async applyDiscount() {
const code = document.getElementById('discountCode').value.trim();
if (!code) return;
try {
const resp = await fetch(`index.php?action=validate_discount&code=${code}`);
const res = await resp.json();
if (res.success) {
this.discount = res.discount;
const info = document.getElementById('appliedDiscountInfo');
info.innerText = `Applied: ${this.discount.code} (${this.discount.type === 'percentage' ? this.discount.value + '%' : 'OMR ' + parseFloat(this.discount.value).toFixed(3)})`;
info.style.display = 'block';
this.render();
} else {
Swal.fire('Error', res.error, 'error');
}
} catch (err) { console.error(err); }
},
async hold() {
if (this.items.length === 0) return;
const { value: name } = await Swal.fire({
title: 'Hold Cart',
input: 'text',
inputLabel: 'Enter a name for this cart',
inputValue: 'Cart ' + new Date().toLocaleTimeString(),
showCancelButton: true
});
if (name) {
const formData = new FormData();
formData.append('action', 'hold_pos_cart');
formData.append('cart_name', name);
formData.append('items', JSON.stringify(this.items));
formData.append('customer_id', document.getElementById('posCustomer').value);
const resp = await fetch('index.php', { method: 'POST', body: formData });
const res = await resp.json();
if (res.success) {
this.clear();
Swal.fire('Held', 'Cart has been parked', 'success');
}
}
},
async openHeldCartsModal() {
try {
const resp = await fetch('index.php?action=get_held_carts');
const text = await resp.text();
let carts;
try {
carts = JSON.parse(text);
} catch (e) {
console.error('Failed to parse held carts:', text);
throw new Error('Invalid server response');
}
const lang = document.documentElement.lang || 'en';
let html = '<div class="list-group list-group-flush shadow-sm rounded">';
if (carts.length === 0) {
html += `
<div class="text-center p-5 text-muted">
<i class="bi bi-folder2-open mb-3 d-block" style="font-size: 3rem;"></i>
<p data-en="No held carts found" data-ar="لا توجد طلبات معلقة">${lang === 'ar' ? 'لا توجد طلبات معلقة' : 'No held carts found'}</p>
</div>`;
}
carts.forEach(c => {
html += `
<div class="list-group-item d-flex justify-content-between align-items-center p-3 hover-bg-light border-start-0 border-end-0">
<div class="text-start">
<div class="fw-bold text-primary">${c.cart_name}</div>
<div class="small text-muted">
<i class="bi bi-person me-1"></i>${c.customer_name || (lang === 'ar' ? 'عميل عابر' : 'Walk-in')}
<span class="mx-2 text-silver">|</span>
<i class="bi bi-clock me-1"></i>${new Date(c.created_at).toLocaleString()}
</div>
</div>
<div class="btn-group">
<button class="btn btn-sm btn-primary" onclick="cart.resume(${c.id})">
<i class="bi bi-arrow-repeat me-1"></i><span data-en="Resume" data-ar="استرجاع">${lang === 'ar' ? 'استرجاع' : 'Resume'}</span>
</button>
<button class="btn btn-sm btn-outline-danger" onclick="cart.deleteHeld(${c.id})">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
`;
});
html += '</div>';
Swal.fire({
title: lang === 'ar' ? 'الطلبات المعلقة' : 'Held Carts',
html: html,
showConfirmButton: false,
width: '700px',
customClass: {
container: 'held-carts-swal'
}
});
} catch (err) {
console.error(err);
Swal.fire('Error', 'Failed to load held carts: ' + err.message, 'error');
}
},
async resume(id) {
try {
const resp = await fetch('index.php?action=get_held_carts');
const carts = await resp.json();
const c = carts.find(x => x.id == id);
if (c) {
this.items = JSON.parse(c.items_json);
document.getElementById('posCustomer').value = c.customer_id || '';
await this.onCustomerChange();
await this.deleteHeld(id, true);
Swal.close();
}
} catch (err) {
console.error(err);
Swal.fire('Error', 'Failed to resume cart', 'error');
}
},
async deleteHeld(id, silent = false) {
const formData = new FormData();
formData.append('action', 'delete_held_cart');
formData.append('id', id);
await fetch('index.php', { method: 'POST', body: formData });
if (!silent) this.openHeldCartsModal();
},
render() {
try {
const container = document.getElementById('cartItems');
if (!container) return;
const lang = document.documentElement.lang || 'en';
const items = Array.isArray(this.items) ? this.items : [];
if (items.length === 0) {
container.innerHTML = `<div class="text-center text-muted mt-5"><i class="bi bi-cart-x" style="font-size: 3rem;"></i><p data-en="Cart is empty" data-ar="السلة فارغة">${lang === 'ar' ? 'السلة فارغة' : 'Cart is empty'}</p></div>`;
const subtotalEl = document.getElementById('posSubtotal');
const totalEl = document.getElementById('posTotal');
const checkoutBtn = document.getElementById('checkoutBtn');
if (subtotalEl) subtotalEl.innerText = '<?= __('currency') ?> 0.000';
if (totalEl) totalEl.innerText = '<?= __('currency') ?> 0.000';
if (checkoutBtn) checkoutBtn.disabled = true;
this.broadcast();
return;
}
let subtotal = 0;
let totalVat = 0;
container.innerHTML = items.map(item => {
const price = parseFloat(item.price) || 0;
const qty = parseFloat(item.qty) || 0;
const itemTotal = price * qty;
subtotal += itemTotal;
const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 0;
const itemVat = itemTotal * (vatRate / (100 + vatRate));
totalVat += itemVat;
const displayName = (lang === 'ar' ? (item.nameAr || item.nameEn) : (item.nameEn || item.nameAr)) || 'Unknown Item';
return `
<div class="cart-item">
<div class="flex-grow-1">
<div class="fw-bold small">${displayName}</div>
<div class="text-muted smaller"><?= __('currency') ?> ${price.toFixed(3)} <span class="badge bg-light text-dark smaller">VAT ${parseFloat(vatRate || 0).toFixed(2)}%</span></div>
</div>
<div class="qty-controls mx-3">
<button class="qty-btn" onclick="cart.updateQty(${item.id}, -1)">-</button>
<span class="small fw-bold">${qty}</span>
<button class="qty-btn" onclick="cart.updateQty(${item.id}, 1)">+</button>
<button class="qty-btn ms-1 bg-info text-white border-info" style="border-radius: 4px; padding: 0 6px;" onclick="cart.readScale(${item.id})" title="Read from Scale">
<i class="bi bi-speedometer2"></i>
</button>
</div>
<div class="text-end" style="min-width: 80px;">
<div class="fw-bold small"><?= __('currency') ?> ${itemTotal.toFixed(3)}</div>
<div class="smaller text-muted">VAT: ${itemVat.toFixed(2)}</div>
</div>
</div>
`;
}).join('');
let discountAmount = 0;
if (this.discount) {
if (this.discount.type === 'percentage') {
discountAmount = subtotal * (parseFloat(this.discount.value) / 100);
} else {
discountAmount = parseFloat(this.discount.value);
}
}
let loyaltyRedeemedValue = 0;
const redeemSwitch = document.getElementById('redeemLoyalty');
if (redeemSwitch && redeemSwitch.checked) {
const maxRedeemValue = subtotal - discountAmount;
const redeemRate = (this.loyaltySettings && this.loyaltySettings.redeemPointsPerUnit) ? this.loyaltySettings.redeemPointsPerUnit : 100;
const availableRedeemValue = (parseFloat(this.customerPoints) || 0) / redeemRate;
loyaltyRedeemedValue = Math.min(Math.max(0, maxRedeemValue), availableRedeemValue);
}
const total = Math.max(0, subtotal - discountAmount - loyaltyRedeemedValue);
const multiplier = parseFloat(this.customerMultiplier) || 1.0;
const pointsToEarn = Math.floor(total * multiplier);
const subtotalDisplay = document.getElementById('posSubtotal');
if (subtotalDisplay) subtotalDisplay.innerText = '<?= __('currency') ?> ' + (subtotal - totalVat).toFixed(3);
const vatDisplay = document.getElementById('posVat');
if (vatDisplay) vatDisplay.innerText = '<?= __('currency') ?> ' + totalVat.toFixed(2);
let totalHtml = '';
if (discountAmount > 0) totalHtml += `<div class="smaller text-danger">- Disc: <?= __('currency') ?> ${discountAmount.toFixed(3)}</div>`;
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- Loyalty: <?= __('currency') ?> ${loyaltyRedeemedValue.toFixed(3)}</div>`;
const customerId = document.getElementById('posCustomer') ? document.getElementById('posCustomer').value : '';
if (customerId) {
totalHtml += `<div class="smaller text-info">+ Earn: ${pointsToEarn} pts</div>`;
}
totalHtml += '<?= __('currency') ?> ' + total.toFixed(3);
const totalDisplay = document.getElementById('posTotal');
if (totalDisplay) {
totalDisplay.innerHTML = totalHtml;
}
const checkoutBtn = document.getElementById('checkoutBtn');
if (checkoutBtn) checkoutBtn.disabled = false;
this.broadcast();
} catch (e) {
console.error('Cart render error:', e);
}
},
async checkout() {
if (this.items.length === 0) return;
const customerSelect = document.getElementById('posCustomer');
const customerName = customerSelect.options[customerSelect.selectedIndex].text;
document.getElementById('paymentCustomerName').innerText = customerName;
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
let discountAmount = 0;
if (this.discount) {
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
}
const redeemSwitch = document.getElementById('redeemLoyalty');
let loyaltyRedeemedValue = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints / 100) : 0;
const total = subtotal - discountAmount - loyaltyRedeemedValue;
this.payments = [];
this.renderPayments();
document.getElementById('paymentAmountDue').innerText = total.toFixed(3);
document.getElementById('partialAmount').value = total.toFixed(3);
// Sync credit customer selection if credit is default or already selected
const creditSection = document.getElementById('creditCustomerSection');
if (this.selectedPaymentMethod === 'credit') {
creditSection.style.display = 'block';
const creditSelect = $('#paymentCreditCustomer');
creditSelect.val(customerSelect.value).trigger('change');
} else {
creditSection.style.display = 'none';
}
this.updateRemaining();
const modal = new bootstrap.Modal(document.getElementById('posPaymentModal'));
modal.show();
},
selectMethod(method, btn) {
this.selectedPaymentMethod = method;
document.querySelectorAll('.payment-method-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
const creditSection = document.getElementById('creditCustomerSection');
if (method === 'credit') {
creditSection.style.display = 'block';
// Sync with main customer select
const creditSelect = $('#paymentCreditCustomer');
creditSelect.val(document.getElementById('posCustomer').value).trigger('change');
} else {
creditSection.style.display = 'none';
}
this.updateRemaining();
},
fillPartial(amount) {
const input = document.getElementById('partialAmount');
input.value = parseFloat(amount).toFixed(3);
this.updateRemaining();
},
addPaymentLine() {
const amount = parseFloat(document.getElementById('partialAmount').value) || 0;
if (amount <= 0) return;
this.payments.push({
method: this.selectedPaymentMethod,
amount: amount
});
this.renderPayments();
this.updateRemaining();
// Auto-fill remaining for next line if any
const remaining = this.getRemaining();
document.getElementById('partialAmount').value = remaining > 0 ? remaining.toFixed(3) : '0.000';
},
removePaymentLine(index) {
this.payments.splice(index, 1);
this.renderPayments();
this.updateRemaining();
},
getGrandTotal() {
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
let discountAmount = 0;
if (this.discount) {
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
}
const redeemSwitch = document.getElementById('redeemLoyalty');
let loyaltyRedeemedValue = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints / this.loyaltySettings.redeemPointsPerUnit) : 0;
return subtotal - discountAmount - loyaltyRedeemedValue;
},
getRemaining() {
const total = this.getGrandTotal();
const paid = this.payments.reduce((sum, p) => sum + p.amount, 0);
return total - paid;
},
renderPayments() {
const container = document.getElementById('paymentList');
const methodLabels = {
'cash': 'Cash',
'card': 'Credit Card',
'credit': 'Credit',
'transfer': 'Bank Transfer'
};
container.innerHTML = this.payments.map((p, i) => `
<div class="payment-line">
<div>
<span class="method">${methodLabels[p.method] || p.method}</span>
<span class="ms-2 badge bg-secondary small">${p.amount.toFixed(3)}</span>
</div>
<button class="btn btn-sm btn-outline-danger border-0" onclick="cart.removePaymentLine(${i})">
<i class="bi bi-trash"></i>
</button>
</div>
`).join('');
},
updateRemaining() {
const remaining = this.getRemaining();
const currentInput = parseFloat(document.getElementById('partialAmount').value) || 0;
const display = document.getElementById('paymentRemaining');
display.innerText = Math.max(0, remaining).toFixed(3);
// Calculate potential change if the user types an amount > remaining
const totalPaid = this.payments.reduce((sum, p) => sum + p.amount, 0);
const grandTotal = this.getGrandTotal();
const actualChange = Math.max(0, totalPaid - grandTotal);
const potentialChange = Math.max(0, currentInput - remaining);
const displayChange = Math.max(actualChange, potentialChange);
const changeDisplay = document.getElementById('changeDue');
if (changeDisplay) {
changeDisplay.innerText = displayChange.toFixed(3);
const cashSection = document.getElementById('cashPaymentSection');
if (displayChange > 0 || this.selectedPaymentMethod === 'cash' || this.payments.some(p => p.method === 'cash')) {
cashSection.style.display = 'block';
} else {
cashSection.style.display = 'none';
}
}
if (remaining <= 0.0001 || currentInput >= remaining - 0.0001) {
display.classList.remove('text-danger');
display.classList.add('text-success');
document.getElementById('confirmPaymentBtn').disabled = false;
} else {
display.classList.remove('text-success');
display.classList.add('text-danger');
document.getElementById('confirmPaymentBtn').disabled = true;
}
},
async completeOrder() {
if (this.items.length === 0) {
Swal.fire('Error', 'Cart is empty', 'error');
return;
}
// If there's an amount in the input and payments are not enough, add it
const remainingBefore = this.getRemaining();
const currentInput = parseFloat(document.getElementById('partialAmount').value) || 0;
if (remainingBefore > 0.0001 && currentInput >= remainingBefore - 0.0001) {
this.payments.push({
method: this.selectedPaymentMethod,
amount: currentInput
});
} else if (this.payments.length === 0) {
const total = this.getGrandTotal();
this.payments.push({
method: this.selectedPaymentMethod,
amount: total
});
}
const remaining = this.getRemaining();
if (remaining > 0.001) {
Swal.fire('Error', 'Payment is incomplete', 'error');
return;
}
const customerId = document.getElementById('posCustomer').value;
if (this.payments.some(p => p.method === 'credit') && !customerId) {
Swal.fire('Error', 'Credit payment is only allowed for registered customers', 'error');
return;
}
const btn = document.getElementById('confirmPaymentBtn');
const originalText = btn.innerText;
btn.disabled = true;
btn.innerText = 'PROCESSING...';
const subtotal = this.items.reduce((sum, item) => sum + (parseFloat(item.price) * item.qty), 0);
const totalVat = this.items.reduce((sum, item) => {
const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 0;
return sum + ((parseFloat(item.price) * item.qty) * (vatRate / (100 + vatRate)));
}, 0);
let discountAmount = 0;
if (this.discount) {
discountAmount = this.discount.type === 'percentage' ? subtotal * (parseFloat(this.discount.value) / 100) : parseFloat(this.discount.value);
}
const redeemSwitch = document.getElementById('redeemLoyalty');
let loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked) ? Math.min(subtotal - discountAmount, this.customerPoints / this.loyaltySettings.redeemPointsPerUnit) : 0;
const formData = new FormData();
formData.append('action', 'save_pos_transaction');
formData.append('customer_id', customerId);
formData.append('payments', JSON.stringify(this.payments));
formData.append('total_amount', subtotal);
formData.append('tax_amount', totalVat);
formData.append('discount_code_id', this.discount ? this.discount.id : '');
formData.append('discount_amount', discountAmount);
formData.append('loyalty_redeemed', loyaltyRedeemed);
formData.append('items', JSON.stringify(this.items.map(i => {
const vr = (i.vatRate !== undefined && i.vatRate !== null) ? i.vatRate : 0;
const va = (parseFloat(i.price) * i.qty) * (vr / (100 + vr));
return {id: i.id, qty: i.qty, price: i.price, vat_rate: vr, vat_amount: va};
})));
try {
const resp = await fetch('index.php', { method: 'POST', body: formData });
const text = await resp.text();
let result;
try {
result = JSON.parse(text);
} catch (e) {
console.error('Invalid JSON response:', text);
throw new Error('Server returned an invalid response');
}
if (result.success) {
const payModal = bootstrap.Modal.getInstance(document.getElementById('posPaymentModal'));
if (payModal) payModal.hide();
this.showReceipt(result.invoice_id, discountAmount, loyaltyRedeemed, result.transaction_no, result.outlet_name, result.outlet_phone, result.outlet_address, result.outlet_vat_number, result.outlet_ctr_number, result.outlet_logo);
} else {
Swal.fire('Error', result.error, 'error');
btn.disabled = false;
btn.innerText = originalText;
}
} catch (err) {
console.error(err);
Swal.fire('Error', err.message || 'Something went wrong', 'error');
btn.disabled = false;
btn.innerText = originalText;
}
},
showReceipt(invId, discountAmount, loyaltyRedeemed, transactionNo, outletName, outletPhone, outletAddress, outletVatNumber, outletCtrNumber, outletLogo) {
const container = document.getElementById('posReceiptContent');
const customerSelect = document.getElementById('posCustomer');
const customerName = (customerSelect && customerSelect.selectedIndex >= 0 && customerSelect.value !== '') ? customerSelect.options[customerSelect.selectedIndex].text : '<?= $translations['ar']['walk_in_customer'] ?> / <?= $translations['en']['walk_in_customer'] ?>';
const paymentsHtml = this.payments.map(p => {
let m = p.method.toLowerCase();
let methodAr = m === 'cash' ? 'نقد' : (m === 'card' ? 'بطاقة ائتمان' : (m === 'credit' ? 'آجل' : (m === 'transfer' ? 'تحويل بنكي' : m)));
let methodEn = m === 'cash' ? 'Cash' : (m === 'card' ? 'Credit Card' : (m === 'credit' ? 'Credit' : (m === 'transfer' ? 'Bank Transfer' : m.charAt(0).toUpperCase() + m.slice(1))));
return `
<div class="d-flex justify-content-between small">
<span class="text-uppercase">${methodAr} / ${methodEn}</span>
<span><?= __('currency') ?> ${p.amount.toFixed(3)}</span>
</div>
`;
}).join('');
const date = new Date().toLocaleString();
let itemsHtml = this.items.map(item => {
const itemTotal = item.price * item.qty;
const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 0; // Default to 5 if not set
const vatAmount = itemTotal * (vatRate / (100 + vatRate));
const exclVat = itemTotal - vatAmount;
return `
<tr>
<td>
<div class="fw-bold">${item.nameAr || ''}</div>
<div>${item.nameEn}</div>
<small>${item.qty} x ${parseFloat(item.price).toFixed(3)}</small>
</td>
<td style="text-align: right; vertical-align: bottom;">${vatAmount.toFixed(2)}</td>
<td style="text-align: right; vertical-align: bottom;">${itemTotal.toFixed(3)}</td>
</tr>
`;
}).join('');
const subtotal = this.items.reduce((sum, item) => sum + (item.price * item.qty), 0);
const totalVat = this.items.reduce((sum, item) => {
const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 0;
return sum + ((item.price * item.qty) * (vatRate / (100 + vatRate)));
}, 0);
const total = subtotal - discountAmount - loyaltyRedeemed;
const companyName = outletName || "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const companyPhone = outletPhone || "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyAddress = outletAddress ? outletAddress.replace(/\n/g, '<br>') : (typeof companySettings !== 'undefined' ? (companySettings.company_address || '').replace(/\n/g, '<br>') : '');
const companyVat = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const companyLogo = "<?= htmlspecialchars($data['settings']['company_logo'] ?? '') ?>";
container.innerHTML = `
<div class="thermal-receipt <?= $lang === 'ar' ? 'rtl' : '' ?>">
<div class="center">
${companyLogo ? `<img src="${companyLogo}" alt="Logo" style="max-height: 60px; width: auto; margin-bottom: 10px; display: block; margin-left: auto; margin-right: auto;">` : ''}
<h5 class="mb-0 fw-bold">${companyName}</h5>
${companyAddress ? `<div>${companyAddress}</div>` : ''}
${companyPhone ? `<div>هاتف / Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>الرقم الضريبي / VAT No: ${companyVat}</div>` : ''}
<div class="separator"></div>
<h6 class="fw-bold text-uppercase">فاتورة ضريبية / TAX INVOICE</h6>
<div>رقم الفاتورة / Invoice No: ${transactionNo || 'POS-'+invId}</div>
<div>التاريخ / Date: ${date}</div>
<div class="separator"></div>
</div>
<div>
<strong>العميل / Customer:</strong> ${customerName}
</div>
<div class="mt-1">
<strong>المدفوعات / Payments:</strong>
${paymentsHtml}
</div>
<div class="separator"></div>
<table class="table-borderless">
<thead>
<tr>
<th>البند / Item</th>
<th style="text-align: right;">ضريبة / VAT</th>
<th style="text-align: right;">الإجمالي / Total</th>
</tr>
</thead>
<tbody>
${itemsHtml}
</tbody>
</table>
<div class="separator"></div>
<div class="d-flex justify-content-between">
<span>المجموع الفرعي (غير شامل الضريبة) / Subtotal (Excl. VAT)</span>
<span><?= __('currency') ?> ${(subtotal - totalVat).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between">
<span>الضريبة / VAT</span>
<span><?= __('currency') ?> ${totalVat.toFixed(2)}</span>
</div>
<div class="d-flex justify-content-between fw-bold">
<span>المجموع شامل الضريبة / Total (Incl. VAT)</span>
<span><?= __('currency') ?> ${subtotal.toFixed(3)}</span>
</div>
${discountAmount > 0 ? `<div class="d-flex justify-content-between text-danger"><span>خصم / Discount</span><span>- <?= __('currency') ?> ${parseFloat(discountAmount).toFixed(3)}</span></div>` : ''}
${loyaltyRedeemed > 0 ? `<div class="d-flex justify-content-between text-success"><span>الولاء / Loyalty</span><span>- <?= __('currency') ?> ${parseFloat(loyaltyRedeemed).toFixed(3)}</span></div>` : ''}
<div class="separator"></div>
<div class="d-flex justify-content-between total-row">
<span>الإجمالي / Total</span>
<span><?= __('currency') ?> ${total.toFixed(3)}</span>
</div>
<div class="separator"></div>
<div class="center small">
شكراً لتعاملكم معنا! / Thank you for your business!<br>
يرجى الاحتفاظ بالإيصال. / Please keep the receipt.
</div>
</div>
`;
const modal = new bootstrap.Modal(document.getElementById('posReceiptModal'));
modal.show();
this.clear();
document.getElementById('posReceiptModal').addEventListener('hidden.bs.modal', function () {
location.reload();
}, { once: true });
},
async syncCustomer(val) {
document.getElementById('posCustomer').value = val;
const customerSelect = document.getElementById('posCustomer');
const customerName = customerSelect.options[customerSelect.selectedIndex].text;
document.getElementById('paymentCustomerName').innerText = customerName;
await this.onCustomerChange();
}
};
function attachProductClickListeners() {
document.querySelectorAll('.product-grid .product-card').forEach(card => {
card.onclick = () => {
const product = {
id: parseInt(card.dataset.id),
nameEn: card.dataset.nameEn,
nameAr: card.dataset.nameAr,
price: parseFloat(card.dataset.price),
sku: card.dataset.sku,
stock_quantity: parseFloat(card.dataset.stockQuantity),
vatRate: parseFloat(card.dataset.vatRate) || 0
};
cart.add(product);
};
});
}
attachProductClickListeners();
let searchTimeout = null;
document.getElementById('productSearch').addEventListener('input', (e) => {
clearTimeout(searchTimeout);
const q = e.target.value.trim();
searchTimeout = setTimeout(async () => {
try {
const resp = await fetch('index.php?action=search_items&q=' + encodeURIComponent(q));
const products = await resp.json();
renderProductGrid(products);
} catch (err) { console.error(err); }
}, 300);
});
function renderProductGrid(products) {
const grid = document.getElementById('productGrid');
const lang = document.documentElement.lang || 'en';
if (products.length === 0) {
grid.innerHTML = '<div class="text-center w-100 p-5 text-muted"><i class="bi bi-search mb-3 d-block" style="font-size: 3rem;"></i> No products found.</div>';
return;
}
grid.innerHTML = products.map(p => {
const salePrice = parseFloat(p.sale_price);
const originalPrice = parseFloat(p.original_price || p.sale_price);
const img = p.image_path ? `<img src="${p.image_path}" alt="${p.name_en}">` : `
<div class="bg-light d-flex align-items-center justify-content-center rounded mb-2" style="height: 120px;">
<i class="bi bi-box-seam text-muted" style="font-size: 3rem;"></i>
</div>`;
const promoHtml = salePrice < originalPrice ? `<span class="text-muted smaller text-decoration-line-through">OMR ${originalPrice.toFixed(3)}</span>` : '';
return `
<div class="product-card" data-id="${p.id}" data-name-en="${p.name_en}" data-name-ar="${p.name_ar}" data-price="${p.sale_price}" data-sku="${p.sku}" data-stock-quantity="${p.stock_quantity}" data-vat-rate="${p.vat_rate}">
${img}
<div class="mb-1 product-name">
<div class="fw-bold text-truncate" title="${p.name_en}">${p.name_en}</div>
<div class="small text-muted fw-normal text-truncate" title="${p.name_ar}" dir="rtl">${p.name_ar}</div>
</div>
<div class="small text-muted mb-2">${p.sku}</div>
<div class="d-flex justify-content-between align-items-center mt-auto">
<div class="d-flex flex-column">
${promoHtml}
<span class="price text-primary fw-bold">OMR ${salePrice.toFixed(3)}</span>
</div>
<span class="badge bg-light text-dark small">${parseFloat(p.stock_quantity)} left</span>
</div>
</div>`;
}).join('');
attachProductClickListeners();
}
document.getElementById('barcodeInput').addEventListener('keypress', async (e) => {
if (e.key === 'Enter') {
const barcode = e.target.value.trim();
if (!barcode) return;
// First check local DOM
const card = Array.from(document.querySelectorAll('.product-card')).find(c => c.dataset.sku === barcode);
if (card) {
const product = {
id: parseInt(card.dataset.id),
nameEn: card.dataset.nameEn,
nameAr: card.dataset.nameAr,
price: parseFloat(card.dataset.price),
sku: card.dataset.sku,
stock_quantity: parseFloat(card.dataset.stockQuantity),
vatRate: parseFloat(card.dataset.vatRate) || 0
};
cart.add(product);
e.target.value = '';
Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: 'Added: ' + product.nameEn, showConfirmButton: false, timer: 1000 });
} else {
// Not in DOM, check server
try {
const resp = await fetch('index.php?action=search_items&q=' + encodeURIComponent(barcode));
const products = await resp.json();
const found = products.find(p => p.sku === barcode);
if (found) {
const product = {
id: found.id,
nameEn: found.name_en,
nameAr: found.name_ar,
price: parseFloat(found.sale_price),
sku: found.sku,
stock_quantity: parseFloat(found.stock_quantity),
vatRate: parseFloat(found.vat_rate) || 0
};
cart.add(product);
e.target.value = '';
Swal.fire({ toast: true, position: 'top-end', icon: 'success', title: 'Added: ' + product.nameEn, showConfirmButton: false, timer: 1000 });
} else {
Swal.fire({ toast: true, position: 'top-end', icon: 'error', title: 'Product not found', showConfirmButton: false, timer: 1500 });
e.target.select();
}
} catch (err) { console.error(err); }
}
}
});
// Keep barcode input focused
document.addEventListener('click', () => {
if (document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'SELECT' && document.activeElement.tagName !== 'TEXTAREA') {
const bc = document.getElementById('barcodeInput');
if (bc) bc.focus();
}
});
$(document).ready(function() {
$('#posCustomer').select2({
width: '100%',
placeholder: 'Select Customer'
});
$('#paymentCreditCustomer').select2({
width: '100%',
placeholder: 'Select Customer',
dropdownParent: $('#posPaymentModal')
});
// Initial broadcast to sync customer display (theme & empty state)
if (typeof cart !== 'undefined' && cart.broadcast) {
cart.broadcast();
}
});
</script>
<?php if (!$active_session): ?>
<!-- Open Register Modal -->
<div class="modal fade" id="openRegisterModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Open Cash Register</h5>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="open_register" value="1">
<div class="mb-3">
<label class="form-label">Select Register</label>
<select name="register_id" class="form-select" required>
<?php if (isset($registers)): foreach ($registers as $r): ?>
<option value="<?= $r['id'] ?>"><?= htmlspecialchars($r['name']) ?></option>
<?php endforeach; endif; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Opening Balance</label>
<div class="input-group">
<span class="input-group-text">OMR</span>
<input type="number" step="0.001" name="opening_balance" class="form-control" required placeholder="0.000">
</div>
</div>
</div>
<div class="modal-footer">
<a href="index.php?page=dashboard" class="btn btn-secondary">Cancel & Go to Dashboard</a>
<button type="submit" class="btn btn-primary">Open Session</button>
</div>
</form>
</div>
</div>
</div>
<?php endif; ?>
<?php if ($active_session): ?>
<!-- Close Register Modal -->
<div class="modal fade" id="closeRegisterModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Close Cash Register</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="close_register" value="1">
<input type="hidden" name="session_id" value="<?= $_SESSION['register_session_id'] ?? '' ?>">
<div class="mb-3">
<label class="form-label">Cash in Hand (Counted)</label>
<div class="input-group">
<span class="input-group-text">OMR</span>
<input type="number" step="0.001" name="cash_in_hand" class="form-control" required placeholder="0.000">
</div>
</div>
<div class="mb-3">
<label class="form-label">Notes</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Any discrepancies or notes..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" class="btn btn-danger">Close Session</button>
</div>
</form>
</div>
</div>
</div>
<?php endif; ?>
<script>
document.addEventListener('DOMContentLoaded', function() {
<?php if (!$active_session): ?>
var openModal = new bootstrap.Modal(document.getElementById('openRegisterModal'));
openModal.show();
<?php endif; ?>
});
</script>
<?php elseif ($page === 'quotations'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Quotations" data-ar="عروض الأسعار">Quotations</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addQuotationModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Quotation" data-ar="إنشاء عرض سعر جديد">Create New Quotation</span>
</button>
</div>
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="quotations">
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Search" data-ar="بحث">Search</label>
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Quot # or Name...">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
<select name="customer_id" class="form-select form-select-sm">
<option value="" data-en="All" data-ar="الكل">All</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= (($_GET['customer_id'] ?? '') == $c['id']) ? 'selected' : '' ?>><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="Start Date" data-ar="من تاريخ">Start Date</label>
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="End Date" data-ar="إلى تاريخ">End Date</label>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-primary btn-sm flex-grow-1">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-outline-success btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'quotations', 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'quotations', 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
<a href="index.php?page=quotations" class="btn btn-outline-secondary btn-sm flex-grow-1">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Quotation #" data-ar="رقم العرض">Quotation #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Valid Until" data-ar="صالح حتى">Valid Until</th>
<th data-en="Customer" data-ar="العميل">Customer</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Total" data-ar="الإجمالي" class="text-end">Total</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php
foreach ($data['quotations'] as $q):
$items = db()->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);
?>
<tr>
<td>QUO-<?= str_pad((string)$q['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $q['quotation_date'] ?></td>
<td><?= $q['valid_until'] ?: '---' ?></td>
<td><?= htmlspecialchars($q['customer_name'] ?? '---') ?></td>
<td>
<?php
$statusClass = 'bg-secondary';
if ($q['status'] === 'converted') $statusClass = 'bg-success';
elseif ($q['status'] === 'pending') $statusClass = 'bg-warning text-dark';
elseif ($q['status'] === 'expired' || $q['status'] === 'cancelled') $statusClass = 'bg-danger';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars($q['status']) ?></span>
</td>
<td class="text-end fw-bold">OMR <?= number_format((float)$q['total_with_vat'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" title="View"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-secondary" onclick="window.viewAndPrintQuotation(<?= htmlspecialchars(json_encode($q)) ?>)" title="Print"><i class="bi bi-printer"></i></button>
<button class="btn btn-outline-primary edit-quotation-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" data-bs-toggle="modal" data-bs-target="#editQuotationModal" title="Edit"><i class="bi bi-pencil-square"></i></button>
<?php if ($q['status'] === 'pending'): ?>
<button class="btn btn-outline-success convert-quotation-btn" data-id="<?= $q['id'] ?>" title="Convert to Invoice"><i class="bi bi-receipt"></i></button>
<?php endif; ?>
<button class="btn btn-outline-danger" onclick="if(confirm('Delete this quotation?')) { const f = document.createElement('form'); f.method='POST'; f.innerHTML='<input type=hidden name=delete_quotation><input type=hidden name=id value=<?= $q['id'] ?>>'; document.body.appendChild(f); f.submit(); }" title="Delete"><i class="bi bi-trash"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($data['quotations'])): ?>
<tr><td colspan="7" class="text-center py-4 text-muted" data-en="No quotations found" data-ar="لا توجد عروض أسعار">No quotations found</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($page === 'lpos'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Local Purchase Orders (LPO)" data-ar="أوامر الشراء المحلية (LPO)">Local Purchase Orders (LPO)</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addLpoModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New LPO" data-ar="إنشاء أمر شراء جديد">Create New LPO</span>
</button>
</div>
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="lpos">
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Search" data-ar="بحث">Search</label>
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="LPO # or Name...">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Supplier" data-ar="المورد">Supplier</label>
<select name="supplier_id" class="form-select form-select-sm">
<option value="" data-en="All" data-ar="الكل">All</option>
<?php foreach ($data['suppliers'] as $s): ?>
<option value="<?= $s['id'] ?>" <?= (($_GET['supplier_id'] ?? '') == $s['id']) ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="Start Date" data-ar="من تاريخ">Start Date</label>
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="End Date" data-ar="إلى تاريخ">End Date</label>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-primary btn-sm flex-grow-1">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-outline-success btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'lpos', 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'lpos', 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
<a href="index.php?page=lpos" class="btn btn-outline-secondary btn-sm flex-grow-1">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="LPO #" data-ar="رقم الأمر">LPO #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Delivery Date" data-ar="تاريخ التسليم">Delivery Date</th>
<th data-en="Supplier" data-ar="المورد">Supplier</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Total" data-ar="الإجمالي" class="text-end">Total</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php
foreach ($data['lpos'] as $q):
$items = db()->prepare("SELECT li.*, i.name_en, i.name_ar, i.vat_rate
FROM lpo_items li
JOIN stock_items i ON li.item_id = i.id
WHERE li.lpo_id = ?");
$items->execute([$q['id']]);
$q['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
?>
<tr>
<td>LPO-<?= str_pad((string)$q['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $q['lpo_date'] ?></td>
<td><?= $q['delivery_date'] ?: '---' ?></td>
<td><?= htmlspecialchars($q['supplier_name'] ?? '---') ?></td>
<td>
<?php
$statusClass = 'bg-secondary';
if ($q['status'] === 'converted') $statusClass = 'bg-success';
elseif ($q['status'] === 'pending') $statusClass = 'bg-warning text-dark';
elseif ($q['status'] === 'cancelled') $statusClass = 'bg-danger';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars($q['status']) ?></span>
</td>
<td class="text-end fw-bold">OMR <?= number_format((float)$q['total_with_vat'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-lpo-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" title="View"><i class="bi bi-eye"></i></button>
<?php if ($q['status'] !== 'converted'): ?>
<button class="btn btn-outline-success" onclick="if(confirm('Convert this LPO to Purchase Invoice?')) { const f = document.createElement('form'); f.method='POST'; f.innerHTML='<input type=hidden name=convert_lpo_to_purchase><input type=hidden name=lpo_id value=<?= $q['id'] ?>>'; document.body.appendChild(f); f.submit(); }" title="Convert to Purchase"><i class="bi bi-arrow-repeat"></i></button>
<button class="btn btn-outline-primary edit-lpo-btn" data-json="<?= htmlspecialchars(json_encode($q)) ?>" data-bs-toggle="modal" data-bs-target="#editLpoModal" title="Edit"><i class="bi bi-pencil-square"></i></button>
<?php endif; ?>
<button class="btn btn-outline-danger" onclick="if(confirm('Delete this LPO?')) { const f = document.createElement('form'); f.method='POST'; f.innerHTML='<input type=hidden name=delete_lpo><input type=hidden name=id value=<?= $q['id'] ?>>'; document.body.appendChild(f); f.submit(); }" title="Delete"><i class="bi bi-trash"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($data['lpos'])): ?>
<tr><td colspan="7" class="text-center py-4 text-muted" data-en="No LPOs found" data-ar="لا توجد أوامر شراء">No LPOs found</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($page === 'sales' || $page === 'purchases'): ?>
<div class="card p-4">
<!-- Print Header -->
<div class="print-only mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>" alt="Logo" style="max-height: 80px;" class="mb-2">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted"><?= $currTitle['en'] ?> Report</h2>
<p class="mb-0">Date: <?= date('Y-m-d') ?></p>
<?php if (!empty($_GET['start_date']) || !empty($_GET['end_date']) || !empty($_GET['customer_id'])): ?>
<p class="small text-muted mb-0">
<?php if (!empty($_GET['customer_id'])): ?>
<strong><?= $page === 'sales' ? 'Customer' : 'Supplier' ?>:</strong>
<?php
foreach ($data['customers_list'] as $c) {
if ($c['id'] == $_GET['customer_id']) {
echo htmlspecialchars($c['name']);
break;
}
}
?> |
<?php endif; ?>
<strong>Period:</strong> <?= !empty($_GET['start_date']) ? $_GET['start_date'] : 'All' ?> to <?= !empty($_GET['end_date']) ? $_GET['end_date'] : 'All' ?>
</p>
<?php endif; ?>
</div>
</div>
<hr>
</div>
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0" data-en="<?= $currTitle['en'] ?>" data-ar="<?= $currTitle['ar'] ?>"><?= $currTitle['en'] ?></h5>
<div class="d-flex gap-2">
<a href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>" class="btn btn-outline-success">
<i class="bi bi-file-earmark-excel"></i> <span data-en="Export to Excel" data-ar="تصدير إلى اكسل">Export to Excel</span>
</a>
<?php if (can($page . '_add')): ?>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addInvoiceModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Tax Invoice" data-ar="إنشاء فاتورة ضريبية جديدة">Create New Tax Invoice</span>
</button>
<?php endif; ?>
</div>
</div>
<!-- Filters Section -->
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="<?= $page ?>">
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Search" data-ar="بحث">Search</label>
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Inv # or Name...">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></label>
<select name="customer_id" class="form-select form-select-sm">
<option value="" data-en="All" data-ar="الكل">All</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= (($_GET['customer_id'] ?? '') == $c['id']) ? 'selected' : '' ?>><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="Start Date" data-ar="من تاريخ">Start Date</label>
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-2">
<label class="form-label small fw-bold" data-en="End Date" data-ar="إلى تاريخ">End Date</label>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-2 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-primary btn-sm flex-grow-1">
<i class="bi bi-filter"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<div class="dropdown d-inline-block">
<button class="btn btn-success btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => $page, 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
<button type="button" class="btn btn-outline-primary btn-sm" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
<a href="index.php?page=<?= $page ?>" class="btn btn-outline-secondary btn-sm flex-grow-1">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Invoice #" data-ar="رقم الفاتورة">Invoice #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Due Date" data-ar="تاريخ الاستحقاق">Due Date</th>
<th data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Total" data-ar="الإجمالي" class="text-end">Total</th>
<th data-en="Paid" data-ar="المدفوع" class="text-end">Paid</th>
<th data-en="Balance" data-ar="المتبقي" class="text-end">Balance</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end d-print-none">Actions</th>
</tr>
</thead>
<tbody>
<?php
$itemTable = ($page === 'purchases') ? 'purchase_items' : 'invoice_items';
$fkCol = ($page === 'purchases') ? 'purchase_id' : 'invoice_id';
$total_all = 0; $total_paid = 0; $total_balance = 0;
foreach ($data['invoices'] as $inv):
$total_all += (float)$inv['total_with_vat'];
$total_paid += (float)$inv['paid_amount'];
$total_balance += ((float)$inv['total_with_vat'] - (float)$inv['paid_amount']);
$items = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate
FROM $itemTable ii
JOIN stock_items i ON ii.item_id = i.id
WHERE ii.$fkCol = ?");
$items->execute([$inv['id']]);
$inv['items'] = $items->fetchAll(PDO::FETCH_ASSOC);
$prefix = ($page === 'purchases') ? 'PUR' : 'INV';
?>
<tr>
<td><?= !empty($inv['is_pos']) && !empty($inv['transaction_no']) ? htmlspecialchars($inv['transaction_no']) : $prefix . '-' . str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $inv['invoice_date'] ?></td>
<td>
<?php if ($inv['due_date']): ?>
<?php
$isOverdue = strtotime($inv['due_date']) < time() && $inv['status'] !== 'paid';
?>
<span class="<?= $isOverdue ? 'text-danger fw-bold' : '' ?>">
<?= $inv['due_date'] ?>
<?php if ($isOverdue): ?>
<i class="bi bi-exclamation-triangle-fill ms-1" title="Overdue"></i>
<?php endif; ?>
</span>
<?php else: ?>
---
<?php endif; ?>
</td>
<td><?= htmlspecialchars($inv['customer_name'] ?? '---') ?></td>
<td>
<?php
$statusClass = 'bg-secondary';
if ($inv['status'] === 'paid') $statusClass = 'bg-success';
elseif ($inv['status'] === 'unpaid') $statusClass = 'bg-danger';
elseif ($inv['status'] === 'partially_paid') $statusClass = 'bg-warning text-dark';
?>
<span class="badge text-uppercase <?= $statusClass ?>"><?= htmlspecialchars(str_replace('_', ' ', $inv['status'])) ?></span>
</td>
<td class="text-end fw-bold">OMR <?= number_format((float)$inv['total_with_vat'], 3) ?></td>
<td class="text-end text-success">OMR <?= number_format((float)$inv['paid_amount'], 3) ?></td>
<td class="text-end text-danger fw-bold">OMR <?= number_format((float)($inv['total_with_vat'] - $inv['paid_amount']), 3) ?></td>
<td class="text-end d-print-none">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="View"><i class="bi bi-eye"></i></button>
<button class="btn btn-outline-warning return-invoice-btn" data-id="<?= $inv['id'] ?>" data-bs-toggle="modal" data-bs-target="<?= $page === 'sales' ? '#addSalesReturnModal' : '#addPurchaseReturnModal' ?>" title="Return"><i class="bi bi-arrow-return-left"></i></button>
<button class="btn btn-outline-primary edit-invoice-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" data-bs-toggle="modal" data-bs-target="#editInvoiceModal" title="Edit"><i class="bi bi-pencil"></i></button>
<?php if ($inv['status'] !== 'paid'): ?>
<button class="btn btn-outline-success pay-invoice-btn" data-id="<?= $inv['id'] ?>" data-total="<?= $inv['total_with_vat'] ?>" data-paid="<?= $inv['paid_amount'] ?>" data-bs-toggle="modal" data-bs-target="#payInvoiceModal" title="Payment"><i class="bi bi-cash-coin"></i></button>
<?php endif; ?>
<button class="btn btn-outline-secondary print-a4-btn" data-json="<?= htmlspecialchars(json_encode($inv)) ?>" title="Print A4 Invoice"><i class="bi bi-printer"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure you want to delete this invoice?')">
<input type="hidden" name="id" value="<?= $inv['id'] ?>">
<button type="submit" name="delete_invoice" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot>
<tr class="fw-bold bg-light">
<td colspan="5" class="text-end" data-en="Totals" data-ar="الإجمالي">Totals</td>
<td class="text-end">OMR <?= number_format($total_all, 3) ?></td>
<td class="text-end text-success">OMR <?= number_format($total_paid, 3) ?></td>
<td class="text-end text-danger">OMR <?= number_format($total_balance, 3) ?></td>
<td class="d-print-none"></td>
</tr>
</tfoot>
</table>
</div>
</div>
<?php elseif ($page === 'customer_statement' || $page === 'supplier_statement'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<?php
$entity_suffix = isset($data['selected_entity']['name']) ? ' - ' . htmlspecialchars($data['selected_entity']['name']) : '';
$page_title_en = $currTitle['en'] . $entity_suffix;
$page_title_ar = $currTitle['ar'] . $entity_suffix;
?>
<h5 class="m-0" data-en="<?= $page_title_en ?>" data-ar="<?= $page_title_ar ?>"><?= $lang === 'ar' ? $page_title_ar : $page_title_en ?></h5>
<button class="btn btn-outline-secondary d-print-none" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة"><?= $lang === 'ar' ? 'طباعة' : 'Print' ?></span>
</button>
</div>
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="row g-3 align-items-end">
<input type="hidden" name="page" value="<?= $page ?>">
<div class="col-md-4">
<label class="form-label small fw-bold" data-en="Select <?= $page === 'customer_statement' ? 'Customer' : 'Supplier' ?>" data-ar="اختر <?= $page === 'customer_statement' ? 'العميل' : 'المورد' ?>"><?= $lang === 'ar' ? ($page === 'customer_statement' ? 'اختر العميل' : 'اختر المورد') : 'Select ' . ($page === 'customer_statement' ? 'Customer' : 'Supplier') ?></label>
<select name="entity_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['entities'] as $e): ?>
<option value="<?= $e['id'] ?>" <?= (($_GET['entity_id'] ?? '') == $e['id']) ? 'selected' : '' ?>><?= htmlspecialchars($e['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="From Date" data-ar="من تاريخ"><?= $lang === 'ar' ? 'من تاريخ' : 'From Date' ?></label>
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="To Date" data-ar="إلى تاريخ"><?= $lang === 'ar' ? 'إلى تاريخ' : 'To Date' ?></label>
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search"></i> <span data-en="View Report" data-ar="عرض التقرير"><?= $lang === 'ar' ? 'عرض التقرير' : 'View Report' ?></span>
</button>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<?php if (isset($data['transactions'])): ?>
<div id="statement-print">
<div class="row mb-4">
<div class="col-6">
<h3 class="mb-0"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted" data-en="Statement of Account - <?= htmlspecialchars($data['selected_entity']['name']) ?>" data-ar="كشف حساب - <?= htmlspecialchars($data['selected_entity']['name']) ?>"><?= $lang === 'ar' ? 'كشف حساب' : 'Statement of Account' ?> - <?= htmlspecialchars($data['selected_entity']['name']) ?></h2>
<p class="mb-0"><strong><?= htmlspecialchars($data['selected_entity']['name']) ?></strong></p>
<p class="text-muted small"><?= htmlspecialchars($data['selected_entity']['email']) ?> | <?= htmlspecialchars($data['selected_entity']['phone']) ?><br><span data-en="Period" data-ar="الفترة"><?= $lang === 'ar' ? 'الفترة' : 'Period' ?></span>: <?= $_GET['start_date'] ?> <span data-en="to" data-ar="إلى"><?= $lang === 'ar' ? 'إلى' : 'to' ?></span> <?= $_GET['end_date'] ?></p>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-sm">
<thead class="bg-light">
<tr>
<th data-en="Date" data-ar="التاريخ"><?= $lang === 'ar' ? 'التاريخ' : 'Date' ?></th>
<th data-en="Reference" data-ar="المرجع"><?= $lang === 'ar' ? 'المرجع' : 'Reference' ?></th>
<th data-en="Description" data-ar="الوصف"><?= $lang === 'ar' ? 'الوصف' : 'Description' ?></th>
<th data-en="Debit" data-ar="مدين" class="text-end"><?= $lang === 'ar' ? 'مدين' : 'Debit' ?></th>
<th data-en="Credit" data-ar="دائن" class="text-end"><?= $lang === 'ar' ? 'دائن' : 'Credit' ?></th>
<th data-en="Balance" data-ar="الرصيد" class="text-end"><?= $lang === 'ar' ? 'الرصيد' : 'Balance' ?></th>
</tr>
</thead>
<tbody>
<?php
$running_balance = 0;
foreach ($data['transactions'] as $t):
$debit = 0; $credit = 0;
if ($t['trans_type'] === 'invoice') {
if ($page === 'customer_statement') $debit = (float)$t['amount']; else $credit = (float)$t['amount'];
} else {
if ($page === 'customer_statement') $credit = (float)$t['amount']; else $debit = (float)$t['amount'];
}
$running_balance += ($debit - $credit);
?>
<tr>
<td><?= $t['trans_date'] ?></td>
<td><?= $t['trans_type'] === 'invoice' ? ($lang === 'ar' ? ($page === 'supplier_statement' ? 'شراء-' : 'بيع-') : ($page === 'supplier_statement' ? 'PUR-' : 'INV-')).str_pad((string)$t['ref_no'], 5, '0', STR_PAD_LEFT) : ($lang === 'ar' ? 'قبض-' : 'RCP-').str_pad((string)$t['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td>
<?php if ($t['trans_type'] === 'invoice'): ?>
<span data-en="Tax Invoice" data-ar="فاتورة ضريبية"><?= $lang === 'ar' ? 'فاتورة ضريبية' : 'Tax Invoice' ?></span>
<?php else: ?>
<span data-en="Payment" data-ar="دفع"><?= $lang === 'ar' ? 'دفع' : 'Payment' ?></span> - <span data-en="<?= $t['payment_method'] ?>" data-ar="<?= $t['payment_method'] === 'cash' ? 'نقد' : ($t['payment_method'] === 'card' ? 'بطاقة ائتمان' : 'آجل') ?>"><?= $lang === 'ar' ? ($t['payment_method'] === 'cash' ? 'نقد' : ($t['payment_method'] === 'card' ? 'بطاقة ائتمان' : 'آجل')) : $t['payment_method'] ?></span>
<?php endif; ?>
</td>
<td class="text-end"><?= number_format($debit, 3) ?></td>
<td class="text-end"><?= number_format($credit, 3) ?></td>
<td class="text-end"><?= number_format($running_balance, 3) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot class="bg-light fw-bold">
<tr>
<td colspan="5" class="text-end" data-en="Closing Balance" data-ar="رصيد الإقفال"><?= $lang === 'ar' ? 'رصيد الإقفال' : 'Closing Balance' ?></td>
<td class="text-end" data-en="OMR <?= number_format($running_balance, 3) ?>" data-ar="<?= number_format($running_balance, 3) ?> ر.ع."><?= $lang === 'ar' ? number_format($running_balance, 3) . ' ر.ع.' : 'OMR ' . number_format($running_balance, 3) ?></td>
</tr>
</tfoot>
</table>
</div>
<!-- Print Footer / Signatures -->
<div class="print-only mt-5">
<div class="row text-center mt-5">
<div class="col-4">
<div class="border-top pt-2" data-en="Prepared By" data-ar="أعد بواسطة">Prepared By</div>
</div>
<div class="col-4">
<div class="border-top pt-2" data-en="Checked By" data-ar="روجع بواسطة">Checked By</div>
</div>
<div class="col-4">
<div class="border-top pt-2" data-en="Authorized Signature" data-ar="التوقيع المعتمد">Authorized Signature</div>
</div>
</div>
<div class="mt-4 text-center text-muted small">
<span data-en="Printed on" data-ar="طبع في">Printed on</span> <?= date('Y-m-d H:i:s') ?>
</div>
</div>
</div>
<?php else: ?>
<div class="text-center py-5 text-muted"><p data-en="Please select an entity and date range to generate the statement." data-ar="يرجى اختيار جهة ونطاق تاريخي لتوليد كشف الحساب.">Please select an entity and date range to generate the statement.</p></div>
<?php endif; ?>
</div>
<?php elseif ($page === 'cashflow_report'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0" data-en="Cashflow Statement" data-ar="قائمة التدفقات النقدية">Cashflow Statement</h5>
<button class="btn btn-outline-secondary d-print-none" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
</div>
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="row g-3 align-items-end">
<input type="hidden" name="page" value="<?= $page ?>">
<div class="col-md-5">
<label class="form-label small fw-bold" data-en="From Date" data-ar="من تاريخ">From Date</label>
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?>">
</div>
<div class="col-md-5">
<label class="form-label small fw-bold" data-en="To Date" data-ar="إلى تاريخ">To Date</label>
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-search"></i> <span data-en="Generate" data-ar="توليد">Generate</span>
</button>
</div>
<div class="col-md-auto ms-auto d-flex align-items-end mt-2 mt-md-0">
<div class="input-group input-group-sm w-auto">
<span class="input-group-text" data-en="Limit" data-ar="الحد">Limit</span>
<select name="limit" class="form-select" onchange="this.form.submit()">
<option value="20" <?= (($_GET['limit'] ?? 20) == 20) ? 'selected' : '' ?>>20</option>
<option value="40" <?= (($_GET['limit'] ?? 20) == 40) ? 'selected' : '' ?>>40</option>
<option value="60" <?= (($_GET['limit'] ?? 20) == 60) ? 'selected' : '' ?>>60</option>
<option value="100" <?= (($_GET['limit'] ?? 20) == 100) ? 'selected' : '' ?>>100</option>
</select>
</div>
</div>
</form>
</div>
<div id="cashflow-print">
<div class="row mb-4">
<div class="col-6">
<h3 class="mb-0"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted" data-en="Cashflow Statement" data-ar="قائمة التدفقات النقدية">Cashflow Statement</h2>
<p class="text-muted small">Period: <?= htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?> to <?= htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?></p>
</div>
</div>
<table class="table table-bordered">
<thead class="bg-light">
<tr>
<th data-en="Description" data-ar="الوصف">Description</th>
<th data-en="Amount (OMR)" data-ar="المبلغ (ريال عماني)" class="text-end">Amount (OMR)</th>
</tr>
</thead>
<tbody>
<tr class="fw-bold table-light">
<td data-en="Opening Cash Balance" data-ar="رصيد النقدية الافتتاحي">Opening Cash Balance</td>
<td class="text-end"><?= number_format($data['opening_balance'], 3) ?></td>
</tr>
<tr>
<td colspan="2" class="fw-bold bg-light" data-en="Operating Activities" data-ar="الأنشطة التشغيلية">Operating Activities</td>
</tr>
<?php
$op_inflow = 0; $op_outflow = 0;
$inv_inflow = 0; $inv_outflow = 0;
$fin_inflow = 0; $fin_outflow = 0;
foreach ($data['cash_transactions'] as $t) {
$amt = (float)$t['inflow'] - (float)$t['outflow'];
// Very simple categorization based on account type
if ($t['other_type'] === 'revenue' || $t['other_type'] === 'expense' || in_array($t['other_account'], ['Accounts Receivable', 'Accounts Payable', 'VAT Input', 'VAT Payable'])) {
if ($amt > 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);
}
}
?>
<tr>
<td class="ps-4" data-en="Cash Received from Customers & Others" data-ar="المقبوضات النقدية من العملاء وغيرهم">Cash Received from Customers & Others</td>
<td class="text-end text-success"><?= number_format($op_inflow, 3) ?></td>
</tr>
<tr>
<td class="ps-4" data-en="Cash Paid to Suppliers & Expenses" data-ar="المدفوعات النقدية للموردين والمصروفات">Cash Paid to Suppliers & Expenses</td>
<td class="text-end text-danger">(<?= number_format($op_outflow, 3) ?>)</td>
</tr>
<tr class="fw-bold">
<td data-en="Net Cash from Operating Activities" data-ar="صافي النقد من الأنشطة التشغيلية">Net Cash from Operating Activities</td>
<td class="text-end border-top"><?= number_format($op_inflow - $op_outflow, 3) ?></td>
</tr>
<tr>
<td colspan="2" class="fw-bold bg-light" data-en="Investing Activities" data-ar="الأنشطة الاستثمارية">Investing Activities</td>
</tr>
<tr>
<td class="ps-4" data-en="Net Cash from Investing Activities" data-ar="صافي النقد من الأنشطة الاستثمارية">Net Cash from Investing Activities</td>
<td class="text-end"><?= number_format($inv_inflow - $inv_outflow, 3) ?></td>
</tr>
<tr>
<td colspan="2" class="fw-bold bg-light" data-en="Financing Activities" data-ar="الأنشطة التمويلية">Financing Activities</td>
</tr>
<tr>
<td class="ps-4" data-en="Net Cash from Financing Activities" data-ar="صافي النقد من الأنشطة التمويلية">Net Cash from Financing Activities</td>
<td class="text-end"><?= number_format($fin_inflow - $fin_outflow, 3) ?></td>
</tr>
<tr class="fw-bold table-primary">
<?php $net_change = ($op_inflow - $op_outflow) + ($inv_inflow - $inv_outflow) + ($fin_inflow - $fin_outflow); ?>
<td data-en="Net Change in Cash" data-ar="صافي التغير في النقدية">Net Change in Cash</td>
<td class="text-end"><?= number_format($net_change, 3) ?></td>
</tr>
<tr class="fw-bold table-success">
<td data-en="Closing Cash Balance" data-ar="رصيد النقدية الختامي">Closing Cash Balance</td>
<td class="text-end"><?= number_format($data['opening_balance'] + $net_change, 3) ?></td>
</tr>
</tbody>
</table>
<div class="mt-4 d-none d-print-block">
<div class="row">
<div class="col-6">
<p>___________________<br>Prepared By</p>
</div>
<div class="col-6 text-end">
<p>___________________<br>Approved By</p>
</div>
</div>
</div>
</div>
</div>
<div class="card p-4 d-print-none">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Payment Methods" data-ar="طرق الدفع">Payment Methods</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPaymentMethodModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Payment Method" data-ar="إضافة طريقة دفع">Add Payment Method</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="ID" data-ar="المعرف">ID</th>
<th data-en="Name (EN)" data-ar="الاسم (EN)">Name (EN)</th>
<th data-en="Name (AR)" data-ar="الاسم (AR)">Name (AR)</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['payment_methods'] as $pm): ?>
<tr>
<td><?= $pm['id'] ?></td>
<td><?= htmlspecialchars($pm['name_en'] ?? '') ?></td>
<td><?= htmlspecialchars($pm['name_ar'] ?? '') ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" title="Edit" data-bs-toggle="modal" data-bs-target="#editPaymentMethodModal<?= $pm['id'] ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $pm['id'] ?>">
<button type="submit" name="delete_payment_method" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Add Payment Method Modal -->
<div class="modal fade" id="addPaymentMethodModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add Payment Method" data-ar="إضافة طريقة دفع">Add Payment Method</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (EN)">Name (EN)</label>
<input type="text" name="name_en" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (AR)">Name (AR)</label>
<input type="text" name="name_ar" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_payment_method" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'expense_categories'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Expense Categories" data-ar="فئات المصروفات">Expense Categories</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addExpenseCategoryModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Category" data-ar="إضافة فئة">Add Category</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="ID" data-ar="المعرف">ID</th>
<th data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</th>
<th data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['expense_categories'] as $cat): ?>
<tr>
<td><?= $cat['id'] ?></td>
<td><?= htmlspecialchars($cat['name_en']) ?></td>
<td><?= htmlspecialchars($cat['name_ar']) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editExpCatModal<?= $cat['id'] ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $cat['id'] ?>">
<button type="submit" name="delete_expense_category" class="btn btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editExpCatModal<?= $cat['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Edit Category</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $cat['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="editExpCatEn<?= $cat['id'] ?>" class="form-control" value="<?= htmlspecialchars($cat['name_en']) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="editExpCatAr<?= $cat['id'] ?>" data-target="editExpCatEn<?= $cat['id'] ?>" data-to="en">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="editExpCatAr<?= $cat['id'] ?>" class="form-control" value="<?= htmlspecialchars($cat['name_ar']) ?>" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="editExpCatEn<?= $cat['id'] ?>" data-target="editExpCatAr<?= $cat['id'] ?>" data-to="ar">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_expense_category" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Add Modal -->
<div class="modal fade" id="addExpenseCategoryModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Add Category</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name (EN)</label>
<input type="text" name="name_en" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Name (AR)</label>
<input type="text" name="name_ar" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_expense_category" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'accounting'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0"><i class="fas fa-calculator me-2"></i> <span data-en="Accounting Module" data-ar="وحدة المحاسبة">Accounting Module</span></h5>
<div class="d-flex gap-2">
<form method="POST" onsubmit="return confirm('This will re-calculate all automatic journal entries from scratch. Continue?')">
<button type="submit" name="sync_accounting" class="btn btn-outline-warning">
<i class="bi bi-arrow-repeat"></i> <span data-en="Sync All" data-ar="مزامنة الكل">Sync All</span>
</button>
</form>
<button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#addManualJournalModal">
<i class="bi bi-plus-lg"></i> <span data-en="Manual Entry" data-ar="قيد يدوي">Manual Entry</span>
</button>
<div class="btn-group">
<a href="index.php?page=accounting" class="btn <?= !isset($_GET['view']) ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Journal" data-ar="اليومية">Journal</a>
<a href="index.php?page=accounting&view=coa" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'coa' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Accounts" data-ar="الحسابات">Accounts</a>
<a href="index.php?page=accounting&view=trial_balance" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'trial_balance' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Trial Balance" data-ar="ميزان المراجعة">Trial Balance</a>
<a href="index.php?page=accounting&view=profit_loss" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'profit_loss' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="P&L" data-ar="الأرباح">P&L</a>
<a href="index.php?page=accounting&view=balance_sheet" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'balance_sheet' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="Balance Sheet" data-ar="الميزانية">Balance Sheet</a>
<a href="index.php?page=accounting&view=vat_report" class="btn <?= isset($_GET['view']) && $_GET['view'] === 'vat_report' ? 'btn-primary' : 'btn-outline-primary' ?>" data-en="VAT Report" data-ar="تقرير الضريبة">VAT Report</a>
</div>
<button class="btn btn-outline-secondary" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
</div>
</div>
<div class="d-none d-print-block mb-4">
<div class="row">
<div class="col-6">
<h3 class="mb-0"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted"><?= isset($_GET['view']) ? ucwords(str_replace('_', ' ', $_GET['view'])) : 'Journal' ?></h2>
</div>
</div>
</div>
<?php if (!isset($_GET['view'])): ?>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Description" data-ar="الوصف">Description</th>
<th data-en="Reference" data-ar="المرجع">Reference</th>
<th data-en="Amount" data-ar="المبلغ">Amount</th>
<th data-en="Action" data-ar="الإجراء">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['journal_entries'] as $entry): ?>
<tr>
<td><?= $entry['entry_date'] ?></td>
<td><?= htmlspecialchars($entry['description']) ?></td>
<td><span class="badge bg-secondary"><?= htmlspecialchars($entry['reference']) ?></span></td>
<td><?= number_format((float)$entry['total_debit'], 3) ?></td>
<td>
<button class="btn btn-sm btn-outline-info" onclick="viewJournalEntry(<?= $entry['id'] ?>)">
<i class="bi bi-eye"></i> <span data-en="View" data-ar="عرض">View</span>
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
<?php elseif ($_GET['view'] === 'coa'): ?>
<div class="d-flex justify-content-end mb-3">
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addAccountModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Account" data-ar="إضافة حساب">Add Account</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th data-en="Code" data-ar="الكود">Code</th>
<th data-en="Name" data-ar="الاسم">Name</th>
<th data-en="Type" data-ar="النوع">Type</th>
<th data-en="Parent" data-ar="الحساب الأب">Parent</th>
<th data-en="Balance" data-ar="الرصيد" class="text-end">Balance</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['coa'] as $acc): ?>
<tr>
<td class="fw-bold"><?= $acc['code'] ?></td>
<td>
<?= htmlspecialchars($acc['name_en']) ?><br>
<small class="text-muted"><?= htmlspecialchars($acc['name_ar']) ?></small>
</td>
<td><span class="badge bg-light text-dark border text-uppercase"><?= $acc['type'] ?></span></td>
<td><?= htmlspecialchars($acc['parent_name'] ?? '---') ?></td>
<td class="text-end fw-bold"><?= number_format(getAccountBalance($acc['code']), 3) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
<?php elseif ($_GET['view'] === 'vat_report'): ?>
<div class="row">
<div class="col-md-6 mx-auto">
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="row g-2">
<input type="hidden" name="page" value="accounting">
<input type="hidden" name="view" value="vat_report">
<div class="col">
<input type="date" name="start_date" class="form-control" value="<?= $data['start_date'] ?>">
</div>
<div class="col">
<input type="date" name="end_date" class="form-control" value="<?= $data['end_date'] ?>">
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary" data-en="Filter" data-ar="تصفية">Filter</button>
</div>
</form>
</div>
<div class="card shadow-sm border-0">
<div class="card-body">
<h4 class="text-center mb-4 d-print-none">VAT Summary Report</h4>
<table class="table">
<tr>
<th data-en="VAT Input (Purchases)" data-ar="ضريبة المدخلات (المشتريات)">VAT Input (Purchases)</th>
<td class="text-end text-success fw-bold"><?= number_format($data['vat_report']['input_vat'], 2) ?></td>
</tr>
<tr>
<th data-en="VAT Output (Sales)" data-ar="ضريبة المخرجات (المبيعات)">VAT Output (Sales)</th>
<td class="text-end text-danger fw-bold"><?= number_format($data['vat_report']['output_vat'], 2) ?></td>
</tr>
<tr class="table-dark h5">
<th data-en="Net VAT Payable / (Refundable)" data-ar="صافي الضريبة المستحقة / (المستردة)">Net VAT Payable / (Refundable)</th>
<td class="text-end"><?= number_format($data['vat_report']['net_vat'], 2) ?></td>
</tr>
</table>
<div class="alert alert-info small mt-3">
<i class="bi bi-info-circle me-1"></i>
This report calculates the difference between VAT collected on sales and VAT paid on purchases for the selected period.
</div>
</div>
</div>
</div>
</div>
<?php elseif ($_GET['view'] === 'trial_balance'): ?>
<div class="table-responsive">
<table class="table table-bordered">
<thead class="table-light">
<tr>
<th data-en="Code" data-ar="الكود">Code</th>
<th data-en="Account Name" data-ar="اسم الحساب">Account Name</th>
<th class="text-end" data-en="Debit" data-ar="مدين">Debit</th>
<th class="text-end" data-en="Credit" data-ar="دائن">Credit</th>
</tr>
</thead>
<tbody>
<?php
$total_d = 0; $total_c = 0;
foreach ($data['trial_balance'] as $row):
$total_d += (float)$row['total_debit'];
$total_c += (float)$row['total_credit'];
?>
<tr>
<td><?= $row['code'] ?></td>
<td><?= htmlspecialchars($row['name_en']) ?></td>
<td class="text-end"><?= number_format((float)$row['total_debit'], 3) ?></td>
<td class="text-end"><?= number_format((float)$row['total_credit'], 3) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
<tfoot class="table-light fw-bold">
<tr>
<td colspan="2" class="text-end" data-en="Total" data-ar="الإجمالي">Total</td>
<td class="text-end"><?= number_format($total_d, 3) ?></td>
<td class="text-end"><?= number_format($total_c, 3) ?></td>
</tr>
</tfoot>
</table>
</div>
<?php elseif ($_GET['view'] === 'profit_loss'): ?>
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card bg-light">
<div class="card-body">
<h4 class="text-center mb-4 d-print-none" data-en="Profit & Loss Statement" data-ar="قائمة الأرباح والخسائر">Profit & Loss Statement</h4>
<table class="table">
<tr class="table-primary"><th colspan="2" data-en="Revenue" data-ar="الإيرادات">Revenue</th></tr>
<?php
$total_rev = 0;
foreach ($data['revenue_accounts'] as $acc):
$bal = getAccountBalance($acc['code']);
if ($bal == 0) continue;
$total_rev += $bal;
?>
<tr>
<td><?= htmlspecialchars($acc['name_en']) ?></td>
<td class="text-end"><?= number_format($bal, 3) ?></td>
</tr>
<?php endforeach; ?>
<tr class="fw-bold">
<td data-en="Total Revenue" data-ar="إجمالي الإيرادات">Total Revenue</td>
<td class="text-end border-top"><?= number_format($total_rev, 3) ?></td>
</tr>
<tr class="table-warning"><th colspan="2" class="pt-4" data-en="Expenses" data-ar="المصروفات">Expenses</th></tr>
<?php
$total_exp = 0;
foreach ($data['expense_accounts'] as $acc):
$bal = getAccountBalance($acc['code']);
if ($bal == 0) continue;
$total_exp += $bal;
?>
<tr>
<td><?= htmlspecialchars($acc['name_en']) ?></td>
<td class="text-end"><?= number_format($bal, 3) ?></td>
</tr>
<?php endforeach; ?>
<tr class="fw-bold">
<td data-en="Total Expenses" data-ar="إجمالي المصروفات">Total Expenses</td>
<td class="text-end border-top"><?= number_format($total_exp, 3) ?></td>
</tr>
<tr class="table-success h4">
<td data-en="Net Profit / Loss" data-ar="صافي الربح / الخسارة">Net Profit / Loss</td>
<td class="text-end"><?= number_format($total_rev - $total_exp, 3) ?></td>
</tr>
</table>
</div>
</div>
</div>
</div>
<?php elseif ($_GET['view'] === 'balance_sheet'): ?>
<div class="row">
<div class="col-md-10 mx-auto">
<div class="card bg-light">
<div class="card-body">
<h4 class="text-center mb-4 d-print-none" data-en="Balance Sheet" data-ar="الميزانية العمومية">Balance Sheet</h4>
<div class="row">
<div class="col-md-6 border-end">
<h5 class="text-primary border-bottom pb-2" data-en="Assets" data-ar="الأصول">Assets</h5>
<table class="table table-sm">
<?php
$total_assets = 0;
foreach ($data['asset_accounts'] as $acc):
$bal = getAccountBalance($acc['code']);
if ($bal == 0) continue;
$total_assets += $bal;
?>
<tr>
<td><?= htmlspecialchars($acc['name_en']) ?></td>
<td class="text-end"><?= number_format($bal, 3) ?></td>
</tr>
<?php endforeach; ?>
<tr class="fw-bold h5">
<td data-en="Total Assets" data-ar="إجمالي الأصول">Total Assets</td>
<td class="text-end border-top"><?= number_format($total_assets, 3) ?></td>
</tr>
</table>
</div>
<div class="col-md-6">
<h5 class="text-danger border-bottom pb-2" data-en="Liabilities & Equity" data-ar="الالتزامات وحقوق الملكية">Liabilities & Equity</h5>
<table class="table table-sm">
<tr class="bg-light"><td colspan="2" class="small fw-bold text-muted">Liabilities</td></tr>
<?php
$total_liab = 0;
foreach ($data['liability_accounts'] as $acc):
$bal = getAccountBalance($acc['code']);
if ($bal == 0) continue;
$total_liab += $bal;
?>
<tr>
<td><?= htmlspecialchars($acc['name_en']) ?></td>
<td class="text-end"><?= number_format($bal, 3) ?></td>
</tr>
<?php endforeach; ?>
<tr class="bg-light"><td colspan="2" class="small fw-bold text-muted pt-3">Equity</td></tr>
<?php
$total_equity = 0;
foreach ($data['equity_accounts'] as $acc):
$bal = getAccountBalance($acc['code']);
if ($bal == 0) continue;
$total_equity += $bal;
?>
<tr>
<td><?= htmlspecialchars($acc['name_en']) ?></td>
<td class="text-end"><?= number_format($bal, 3) ?></td>
</tr>
<?php endforeach; ?>
<?php
// Current Year Earnings (Revenue - Expenses)
$rev = 0; foreach(db()->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;
?>
<tr>
<td data-en="Retained Earnings (Current)" data-ar="الأرباح المحتجزة (الحالية)">Retained Earnings (Current)</td>
<td class="text-end"><?= number_format($earnings, 3) ?></td>
</tr>
<tr class="fw-bold h5 pt-3">
<td data-en="Total Liab. & Equity" data-ar="إجمالي الالتزامات وحقوق الملكية">Total Liab. & Equity</td>
<td class="text-end border-top"><?= number_format($total_liab + $total_equity, 3) ?></td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<?php endif; ?>
</div>
<!-- Journal Entry Details Modal -->
<div class="modal fade" id="journalModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Journal Entry Details" data-ar="تفاصيل قيد اليومية">Journal Entry Details</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<table class="table table-bordered">
<thead>
<tr>
<th data-en="Account" data-ar="الحساب">Account</th>
<th class="text-end" data-en="Debit" data-ar="مدين">Debit</th>
<th class="text-end" data-en="Credit" data-ar="دائن">Credit</th>
</tr>
</thead>
<tbody id="journalDetailsBody"></tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function viewJournalEntry(id) {
fetch('index.php?page=accounting&action=get_entry_details&id=' + id)
.then(r => r.json())
.then(data => {
let html = '';
data.forEach(row => {
html += `<tr>
<td>${row.code} - ${row.name_en}</td>
<td class="text-end">${parseFloat(row.debit).toFixed(3)}</td>
<td class="text-end">${parseFloat(row.credit).toFixed(3)}</td>
</tr>`;
});
document.getElementById('journalDetailsBody').innerHTML = html;
new bootstrap.Modal(document.getElementById('journalModal')).show();
});
}
</script>
<?php elseif ($page === 'expenses'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Expenses List" data-ar="قائمة المصروفات">Expenses List</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addExpenseModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Expense" data-ar="إضافة مصروف">Add Expense</span>
</button>
</div>
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="expenses">
<div class="col-md-3">
<label class="form-label small" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select">
<option value="">All</option>
<?php foreach ($data['expense_categories'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= ($_GET['category_id'] ?? '') == $c['id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label small">Start Date</label>
<input type="date" name="start_date" class="form-control" value="<?= htmlspecialchars($_GET['start_date'] ?? '') ?>">
</div>
<div class="col-md-3">
<label class="form-label small">End Date</label>
<input type="date" name="end_date" class="form-control" value="<?= htmlspecialchars($_GET['end_date'] ?? '') ?>">
</div>
<div class="col-md-3 d-flex align-items-end gap-1">
<button type="submit" class="btn btn-primary flex-grow-1" data-en="Filter" data-ar="تصفية">Filter</button>
<div class="dropdown d-inline-block">
<button class="btn btn-outline-success dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'expenses', 'format' => 'csv'])) ?>"><i class="bi bi-filetype-csv me-2"></i> CSV</a></li>
<li><a class="dropdown-item" href="index.php?<?= http_build_query(array_merge($_GET, ['page' => 'export', 'type' => 'expenses', 'format' => 'excel'])) ?>"><i class="bi bi-file-earmark-excel me-2"></i> Excel</a></li>
</ul>
</div>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th>Reference</th>
<th data-en="Category" data-ar="الفئة">Category</th>
<th data-en="Description" data-ar="الوصف">Description</th>
<th class="text-end" data-en="Amount" data-ar="المبلغ">Amount</th>
<th class="text-end" data-en="Actions" data-ar="إجراءات">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['expenses'] as $exp): ?>
<tr>
<td><?= $exp['expense_date'] ?></td>
<td><?= htmlspecialchars($exp['reference_no'] ?: '---') ?></td>
<td><?= htmlspecialchars($exp['cat_en'] ?? 'Unknown') ?></td>
<td><?= htmlspecialchars($exp['description']) ?></td>
<td class="text-end fw-bold">OMR <?= number_format((float)$exp['amount'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editExpenseModal<?= $exp['id'] ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $exp['id'] ?>">
<button type="submit" name="delete_expense" class="btn btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</div>
<!-- Edit Modal -->
<div class="modal fade" id="editExpenseModal<?= $exp['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Edit Expense</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $exp['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select select2" required>
<?php foreach ($data['expense_categories'] as $c): ?>
<option value="<?= $c['id'] ?>" <?= $c['id'] == $exp['category_id'] ? 'selected' : '' ?>><?= htmlspecialchars($c['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="expense_date" class="form-control" value="<?= $exp['expense_date'] ?>" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Amount" data-ar="المبلغ">Amount</label>
<input type="number" step="0.001" name="amount" class="form-control" value="<?= (float)$exp['amount'] ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Reference No</label>
<input type="text" name="reference_no" class="form-control" value="<?= htmlspecialchars($exp['reference_no']) ?>">
</div>
<div class="mb-3">
<label class="form-label" data-en="Description" data-ar="الوصف">Description</label>
<textarea name="description" class="form-control"><?= htmlspecialchars($exp['description']) ?></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_expense" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Add Expense Modal -->
<div class="modal fade" id="addExpenseModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Add Expense</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select" required>
<option value="">Select Category</option>
<?php foreach ($data['expense_categories'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="expense_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Amount" data-ar="المبلغ">Amount</label>
<input type="number" step="0.001" name="amount" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Reference No</label>
<input type="text" name="reference_no" class="form-control">
</div>
<div class="mb-3">
<label class="form-label" data-en="Description" data-ar="الوصف">Description</label>
<textarea name="description" class="form-control"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_expense" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'expense_report'): ?>
<div class="card p-4">
<!-- Formal Print Header -->
<div class="print-only mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>" alt="Logo" style="max-height: 80px;" class="mb-2">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted" data-en="Expense Report" data-ar="تقرير المصروفات">Expense Report</h2>
<p class="mb-0">Date: <?= date('Y-m-d') ?></p>
<p class="mb-0">Period: <?= htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?> - <?= htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?></p>
</div>
</div>
<hr>
</div>
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0" data-en="Expense Report" data-ar="تقرير المصروفات">Expense Report</h5>
<button class="btn btn-outline-secondary" onclick="window.print()">
<i class="bi bi-printer"></i> <span data-en="Print" data-ar="طباعة">Print</span>
</button>
</div>
<div class="bg-light p-3 rounded mb-4 d-print-none">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="expense_report">
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="From Date" data-ar="من تاريخ">From Date</label>
<input type="date" name="start_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['start_date'] ?? date('Y-m-01')) ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="To Date" data-ar="إلى تاريخ">To Date</label>
<input type="date" name="end_date" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['end_date'] ?? date('Y-m-d')) ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select form-select-sm">
<option value="" data-en="All Categories" data-ar="كل الفئات">All Categories</option>
<?php foreach ($data['expense_categories'] as $cat): ?>
<option value="<?= $cat['id'] ?>" <?= (isset($_GET['category_id']) && $_GET['category_id'] == $cat['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name_en']) ?> / <?= htmlspecialchars($cat['name_ar']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3 d-flex align-items-end">
<button type="submit" class="btn btn-primary btn-sm w-100" data-en="Generate Report" data-ar="توليد التقرير">Generate Report</button>
</div>
</form>
</div>
<div class="row mb-4">
<div class="col-md-12">
<div class="card bg-light border-0 shadow-sm">
<div class="card-body text-center">
<h6 class="text-muted text-uppercase mb-2" data-en="Total Expenses" data-ar="إجمالي المصروفات">Total Expenses</h6>
<h2 class="text-danger mb-0">OMR <?= number_format((float)$data['total_expenses'], 3) ?></h2>
<small class="text-muted" data-en="For the selected period" data-ar="للفترة المختارة">For the selected period</small>
</div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered table-striped">
<thead class="bg-light">
<tr>
<th data-en="Category" data-ar="الفئة">Category</th>
<th class="text-end" data-en="Total Amount" data-ar="إجمالي المبلغ">Total Amount</th>
<th class="text-end" data-en="% of Total" data-ar="نسبة الإجمالي">% of Total</th>
</tr>
</thead>
<tbody>
<?php if (empty($data['report_by_category'])): ?>
<tr><td colspan="3" class="text-center text-muted" data-en="No expenses found for this period." data-ar="لم يتم العثور على مصروفات لهذه الفترة.">No expenses found for this period.</td></tr>
<?php else: ?>
<?php foreach ($data['report_by_category'] as $row):
$percent = $data['total_expenses'] > 0 ? ($row['total'] / $data['total_expenses'] * 100) : 0;
?>
<tr>
<td>
<div class="fw-bold"><?= htmlspecialchars($row['name_en']) ?></div>
<div class="text-muted small"><?= htmlspecialchars($row['name_ar']) ?></div>
</td>
<td class="text-end fw-bold text-dark">OMR <?= number_format((float)$row['total'], 3) ?></td>
<td class="text-end">
<div class="progress" style="height: 5px;">
<div class="progress-bar bg-danger" role="progressbar" style="width: <?= $percent ?>%"></div>
</div>
<small><?= number_format($percent, 1) ?>%</small>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
<div class="print-only mt-5">
<div class="row">
<div class="col-4 text-center">
<hr class="mx-4">
<p data-en="Prepared By" data-ar="أعد بواسطة">Prepared By</p>
</div>
<div class="col-4 text-center">
</div>
<div class="col-4 text-center">
<hr class="mx-4">
<p data-en="Approved By" data-ar="اعتمد بواسطة">Approved By</p>
</div>
</div>
</div>
</div>
<?php elseif ($page === 'sales_returns'): ?>
<div class="card p-4">
<!-- Print Header -->
<div class="print-only mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>" alt="Logo" style="max-height: 80px;" class="mb-2">
<?php endif; ?>
<h3 class="mb-1 fw-bold"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
</div>
<div class="col-6 text-end">
<h2 class="text-uppercase text-muted">Sales Returns Report</h2>
<p class="mb-0">Date: <?= date('Y-m-d') ?></p>
</div>
</div>
<hr>
</div>
<div class="d-flex justify-content-between align-items-center mb-4 d-print-none">
<h5 class="m-0" data-en="Sales Returns" data-ar="مرتجع المبيعات">Sales Returns</h5>
<div class="d-flex gap-2">
<a href="index.php?page=export&type=sales_returns&format=excel" class="btn btn-outline-success">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</a>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addSalesReturnModal" id="createSalesReturnBtn">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Return" data-ar="إنشاء مرتجع جديد">Create New Return</span>
</button>
</div>
</div>
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="sales_returns">
<div class="col-md-9">
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Search by Return ID, Customer or Invoice ID...">
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary btn-sm w-100" data-en="Filter" data-ar="تصفية">Filter</button>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Return #" data-ar="رقم المرتجع">Return #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Invoice #" data-ar="رقم الفاتورة">Invoice #</th>
<th data-en="Customer" data-ar="العميل">Customer</th>
<th data-en="Total Amount" data-ar="إجمالي المرتجع" class="text-end">Total Amount</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['returns'] as $ret): ?>
<tr>
<td>RET-<?= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $ret['return_date'] ?></td>
<td>INV-<?= str_pad((string)$ret['invoice_id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= htmlspecialchars($ret['customer_name'] ?? 'Walk-in') ?></td>
<td class="text-end fw-bold text-danger">OMR <?= number_format((float)$ret['total_amount'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-return-btn" data-id="<?= $ret['id'] ?>"><i class="bi bi-eye"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($data['returns'])): ?>
<tr><td colspan="6" class="text-center py-4 text-muted">No returns found</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($page === 'purchase_returns'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Purchase Returns" data-ar="مرتجع المشتريات">Purchase Returns</h5>
<div class="d-flex gap-2">
<a href="index.php?page=export&type=purchase_returns&format=excel" class="btn btn-outline-success">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</a>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addPurchaseReturnModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create New Return" data-ar="إنشاء مرتجع جديد">Create New Return</span>
</button>
</div>
</div>
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="row g-3">
<input type="hidden" name="page" value="purchase_returns">
<div class="col-md-9">
<input type="text" name="search" class="form-control form-control-sm" value="<?= htmlspecialchars($_GET['search'] ?? '') ?>" placeholder="Search by Return ID, Supplier or Invoice ID...">
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary btn-sm w-100" data-en="Filter" data-ar="تصفية">Filter</button>
</div>
</form>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Return #" data-ar="رقم المرتجع">Return #</th>
<th data-en="Date" data-ar="التاريخ">Date</th>
<th data-en="Invoice #" data-ar="رقم الفاتورة">Invoice #</th>
<th data-en="Supplier" data-ar="المورد">Supplier</th>
<th data-en="Total Amount" data-ar="إجمالي المرتجع" class="text-end">Total Amount</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['returns'] as $ret): ?>
<tr>
<td>PRET-<?= str_pad((string)$ret['id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= $ret['return_date'] ?></td>
<td>PUR-<?= str_pad((string)$ret['purchase_id'], 5, '0', STR_PAD_LEFT) ?></td>
<td><?= htmlspecialchars($ret['supplier_name'] ?? 'Unknown') ?></td>
<td class="text-end fw-bold text-danger">OMR <?= number_format((float)$ret['total_amount'], 3) ?></td>
<td class="text-end">
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-info view-return-btn" data-id="<?= $ret['id'] ?>"><i class="bi bi-eye"></i></button>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php if (empty($data['returns'])): ?>
<tr><td colspan="6" class="text-center py-4 text-muted">No returns found</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<?php elseif ($page === 'hr_departments'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="HR Departments" data-ar="أقسام الموارد البشرية">HR Departments</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addHrDepartmentModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Department" data-ar="إضافة قسم">Add Department</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="ID" data-ar="المعرف">ID</th>
<th data-en="Department Name" data-ar="اسم القسم">Department Name</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['departments'] as $d): ?>
<tr>
<td><?= $d['id'] ?></td>
<td><?= htmlspecialchars($d['name']) ?></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editHrDepartmentModal<?= $d['id'] ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<button type="submit" name="delete_hr_department" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
<!-- Edit Dept Modal -->
<div class="modal fade" id="editHrDepartmentModal<?= $d['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Edit Department" data-ar="تعديل القسم">Edit Department</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Department Name" data-ar="اسم القسم">Department Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($d['name']) ?>" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_hr_department" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'hr_employees'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="HR Employees" data-ar="موظفي الموارد البشرية">HR Employees</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addHrEmployeeModal">
<i class="bi bi-plus-lg"></i> <span data-en="Add Employee" data-ar="إضافة موظف">Add Employee</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Name" data-ar="الاسم">Name</th>
<th data-en="Biometric ID" data-ar="معرف البصمة">Biometric ID</th>
<th data-en="Department" data-ar="القسم">Department</th>
<th data-en="Position" data-ar="المنصب">Position</th>
<th data-en="Salary" data-ar="الراتب">Salary</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['employees'] as $e): ?>
<tr>
<td>
<div class="fw-bold"><?= htmlspecialchars($e['name']) ?></div>
<div class="small text-muted"><?= htmlspecialchars($e['email']) ?></div>
</td>
<td><span class="badge bg-light text-dark border"><?= htmlspecialchars($e['biometric_id'] ?? '---') ?></span></td>
<td><?= htmlspecialchars($e['dept_name'] ?? '---') ?></td>
<td><?= htmlspecialchars($e['position']) ?></td>
<td>OMR <?= number_format($e['salary'], 3) ?></td>
<td>
<span class="badge <?= $e['status'] === 'active' ? 'bg-success' : 'bg-danger' ?> text-uppercase">
<?= $e['status'] ?>
</span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editHrEmployeeModal<?= $e['id'] ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $e['id'] ?>">
<button type="submit" name="delete_hr_employee" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
<!-- Edit Employee Modal -->
<div class="modal fade" id="editHrEmployeeModal<?= $e['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Edit Employee" data-ar="تعديل الموظف">Edit Employee</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $e['id'] ?>">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Full Name" data-ar="الاسم الكامل">Full Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($e['name']) ?>" required>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Department" data-ar="القسم">Department</label>
<select name="department_id" class="form-select">
<option value="">--- Select ---</option>
<?php foreach ($data['departments'] as $d): ?>
<option value="<?= $d['id'] ?>" <?= $e['department_id'] == $d['id'] ? 'selected' : '' ?>><?= htmlspecialchars($d['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Email" data-ar="البريد">Email</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($e['email']) ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
<input type="text" name="phone" class="form-control" value="<?= htmlspecialchars($e['phone']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Position" data-ar="المنصب">Position</label>
<input type="text" name="position" class="form-control" value="<?= htmlspecialchars($e['position']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Basic Salary" data-ar="الراتب الأساسي">Basic Salary</label>
<input type="number" step="0.001" name="salary" class="form-control" value="<?= $e['salary'] ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Biometric ID" data-ar="معرف البصمة">Biometric ID</label>
<input type="text" name="biometric_id" class="form-control" value="<?= htmlspecialchars($e['biometric_id'] ?? '') ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Joining Date" data-ar="تاريخ الانضمام">Joining Date</label>
<input type="date" name="joining_date" class="form-control" value="<?= $e['joining_date'] ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" class="form-select">
<option value="active" <?= $e['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="inactive" <?= $e['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_hr_employee" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'hr_attendance'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="HR Attendance" data-ar="حضور الموارد البشرية">HR Attendance</h5>
<div class="d-flex gap-2">
<form method="POST" class="d-inline">
<button type="submit" name="pull_biometric_data" class="btn btn-primary btn-sm">
<i class="bi bi-cloud-download"></i> <span data-en="Pull Data from Devices" data-ar="سحب البيانات من الأجهزة">Pull Data from Devices</span>
</button>
</form>
<button class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#biometricInfoModal">
<i class="bi bi-fingerprint"></i> <span data-en="Biometric Sync" data-ar="مزامنة البصمة">Biometric Sync</span>
</button>
<form method="GET" class="d-flex gap-2">
<input type="hidden" name="page" value="hr_attendance">
<input type="date" name="date" class="form-control form-control-sm" value="<?= $data['attendance_date'] ?>" onchange="this.form.submit()">
</form>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Employee" data-ar="الموظف">Employee</th>
<th data-en="Department" data-ar="القسم">Department</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Clock In" data-ar="وقت الدخول">Clock In</th>
<th data-en="Clock Out" data-ar="وقت الخروج">Clock Out</th>
<th data-en="Action" data-ar="إجراء" class="text-end">Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['employees'] as $e): ?>
<tr>
<td><?= htmlspecialchars($e['name']) ?></td>
<td><?= htmlspecialchars($e['dept_name'] ?? '---') ?></td>
<td>
<?php if ($e['status']): ?>
<span class="badge <?= $e['status'] === 'present' ? 'bg-success' : ($e['status'] === 'absent' ? 'bg-danger' : 'bg-warning') ?> text-uppercase">
<?= $e['status'] ?>
</span>
<?php else: ?>
<span class="badge bg-secondary text-uppercase">Not Marked</span>
<?php endif; ?>
</td>
<td><?= $e['clock_in'] ?? '---' ?></td>
<td><?= $e['clock_out'] ?? '---' ?></td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#markAttendanceModal<?= $e['id'] ?>">
<i class="bi bi-calendar-check"></i> <span data-en="Mark" data-ar="تسجيل">Mark</span>
</button>
</td>
</tr>
<!-- Attendance Modal -->
<div class="modal fade" id="markAttendanceModal<?= $e['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Mark Attendance" data-ar="تسجيل الحضور">Mark Attendance - <?= htmlspecialchars($e['name']) ?></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="employee_id" value="<?= $e['id'] ?>">
<input type="hidden" name="attendance_date" value="<?= $data['attendance_date'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" class="form-select">
<option value="present" <?= $e['status'] === 'present' ? 'selected' : '' ?>>Present</option>
<option value="absent" <?= $e['status'] === 'absent' ? 'selected' : '' ?>>Absent</option>
<option value="on_leave" <?= $e['status'] === 'on_leave' ? 'selected' : '' ?>>On Leave</option>
</select>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Clock In" data-ar="وقت الدخول">Clock In</label>
<input type="time" name="clock_in" class="form-control" value="<?= $e['clock_in'] ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Clock Out" data-ar="وقت الخروج">Clock Out</label>
<input type="time" name="clock_out" class="form-control" value="<?= $e['clock_out'] ?>">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="mark_attendance" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Biometric Info Modal -->
<div class="modal fade" id="biometricInfoModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Biometric Integration Info" data-ar="معلومات تكامل البصمة">Biometric Integration Info</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p data-en="To sync attendance from your biometric device, use the following API endpoint:" data-ar="لمزامنة الحضور من جهاز البصمة الخاص بك، استخدم نقطة نهاية API التالية:">
To sync attendance from your biometric device, use the following API endpoint:
</p>
<div class="bg-light p-3 rounded mb-3 border">
<code><?= (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://$_SERVER[HTTP_HOST]" ?>/api/biometric_sync.php</code>
</div>
<p data-en="Expected JSON format:" data-ar="تنسيق JSON المتوقع:">Expected JSON format:</p>
<pre class="bg-dark text-light p-3 rounded">
[
{
"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"
}
]
</pre>
<p class="small text-muted" data-en="Note: Ensure Employee Biometric IDs match those in the device logs." data-ar="ملاحظة: تأكد من مطابقة معرفات الموظفين الحيوية مع تلك الموجودة في سجلات الجهاز.">
Note: Ensure Employee Biometric IDs match those in the device logs.
</p>
</div>
</div>
</div>
</div>
<?php elseif ($page === 'hr_payroll'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="HR Payroll" data-ar="رواتب الموارد البشرية">HR Payroll</h5>
<div class="d-flex gap-2">
<form method="GET" class="d-flex gap-2">
<input type="hidden" name="page" value="hr_payroll">
<select name="month" class="form-select form-select-sm" onchange="this.form.submit()">
<?php for($m=1; $m<=12; $m++): ?>
<option value="<?= $m ?>" <?= $data['month'] == $m ? 'selected' : '' ?>><?= date('F', mktime(0, 0, 0, $m, 1)) ?></option>
<?php endfor; ?>
</select>
<select name="year" class="form-select form-select-sm" onchange="this.form.submit()">
<?php for($y=date('Y'); $y>=date('Y')-2; $y--): ?>
<option value="<?= $y ?>" <?= $data['year'] == $y ? 'selected' : '' ?>><?= $y ?></option>
<?php endfor; ?>
</select>
</form>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#generatePayrollModal">
<i class="bi bi-gear"></i> <span data-en="Generate" data-ar="توليد">Generate</span>
</button>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Employee" data-ar="الموظف">Employee</th>
<th data-en="Basic" data-ar="الأساسي">Basic</th>
<th data-en="Bonus" data-ar="مكافأة">Bonus</th>
<th data-en="Deductions" data-ar="استقطاعات">Deductions</th>
<th data-en="Net Salary" data-ar="صافي الراتب">Net Salary</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['payroll'] as $p): ?>
<tr>
<td><?= htmlspecialchars($p['emp_name']) ?></td>
<td>OMR <?= number_format($p['basic_salary'], 3) ?></td>
<td class="text-success">+ OMR <?= number_format($p['bonus'], 3) ?></td>
<td class="text-danger">- OMR <?= number_format($p['deductions'], 3) ?></td>
<td class="fw-bold">OMR <?= number_format($p['net_salary'], 3) ?></td>
<td>
<span class="badge <?= $p['status'] === 'paid' ? 'bg-success' : 'bg-warning' ?> text-uppercase">
<?= $p['status'] ?>
</span>
</td>
<td class="text-end">
<?php if ($p['status'] === 'pending'): ?>
<form method="POST" class="d-inline">
<input type="hidden" name="id" value="<?= $p['id'] ?>">
<button type="submit" name="pay_payroll" class="btn btn-sm btn-success" title="Mark Paid"><i class="bi bi-check-circle"></i></button>
</form>
<?php endif; ?>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $p['id'] ?>">
<button type="submit" name="delete_payroll" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Generate Payroll Modal -->
<div class="modal fade" id="generatePayrollModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Generate Payroll" data-ar="توليد الرواتب">Generate Payroll</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="month" value="<?= $data['month'] ?>">
<input type="hidden" name="year" value="<?= $data['year'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Employee" data-ar="الموظف">Employee</label>
<select name="employee_id" class="form-select select2" required>
<option value="">--- Select ---</option>
<?php foreach ($data['employees'] as $e): ?>
<option value="<?= $e['id'] ?>"><?= htmlspecialchars($e['name']) ?> (Basic: <?= number_format($e['salary'], 3) ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Bonus" data-ar="مكافأة">Bonus</label>
<input type="number" step="0.001" name="bonus" class="form-control" value="0.000">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Deductions" data-ar="استقطاعات">Deductions</label>
<input type="number" step="0.001" name="deductions" class="form-control" value="0.000">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="generate_payroll" class="btn btn-primary" data-en="Generate" data-ar="توليد">Generate</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'devices'): ?>
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Biometric Devices" data-ar="أجهزة البصمة">Biometric Devices</h5>
<button class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#addDeviceModal">
<i class="bi bi-plus-circle"></i> <span data-en="Add Device" data-ar="إضافة جهاز">Add Device</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="Device Name" data-ar="اسم الجهاز">Device Name</th>
<th data-en="IP / IO Address" data-ar="عنوان IP / IO">IP / IO Address</th>
<th data-en="Port" data-ar="المنفذ">Port</th>
<th data-en="Serial" data-ar="الرقم التسلسلي">Serial</th>
<th data-en="Last Sync" data-ar="آخر مزامنة">Last Sync</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['devices'] as $d): ?>
<tr>
<td>
<strong><?= htmlspecialchars($d['device_name']) ?></strong>
</td>
<td>
<div><small class="text-muted">IP:</small> <?= htmlspecialchars($d['ip_address']) ?></div>
<?php if ($d['io_address']): ?>
<div><small class="text-muted">IO:</small> <?= htmlspecialchars($d['io_address']) ?></div>
<?php endif; ?>
</td>
<td><?= $d['port'] ?></td>
<td><?= htmlspecialchars($d['serial_number'] ?? '---') ?></td>
<td><?= $d['last_sync'] ?? 'Never' ?></td>
<td>
<span class="badge <?= $d['status'] === 'active' ? 'bg-success' : 'bg-danger' ?> text-uppercase">
<?= $d['status'] ?>
</span>
</td>
<td class="text-end">
<form method="POST" class="d-inline">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<button type="submit" name="test_device_connection" class="btn btn-sm btn-outline-info" title="Test Connection"><i class="bi bi-broadcast"></i></button>
</form>
<button class="btn btn-sm btn-outline-primary" data-bs-toggle="modal" data-bs-target="#editDeviceModal<?= $d['id'] ?>"><i class="bi bi-pencil"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<button type="submit" name="delete_biometric_device" class="btn btn-sm btn-outline-danger"><i class="bi bi-trash"></i></button>
</form>
</td>
</tr>
<!-- Edit Device Modal -->
<div class="modal fade" id="editDeviceModal<?= $d['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Edit Device" data-ar="تعديل الجهاز">Edit Device</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Device Name" data-ar="اسم الجهاز">Device Name</label>
<input type="text" name="device_name" class="form-control" value="<?= htmlspecialchars($d['device_name']) ?>" required>
</div>
<div class="row g-3 mb-3">
<div class="col-md-8">
<label class="form-label" data-en="IP Address" data-ar="عنوان IP">IP Address</label>
<input type="text" name="ip_address" class="form-control" value="<?= htmlspecialchars($d['ip_address']) ?>" required>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Port" data-ar="المنفذ">Port</label>
<input type="number" name="port" class="form-control" value="<?= $d['port'] ?>" required>
</div>
</div>
<div class="mb-3">
<label class="form-label" data-en="IO Address" data-ar="عنوان IO">IO Address</label>
<input type="text" name="io_address" class="form-control" value="<?= htmlspecialchars($d['io_address']) ?>">
</div>
<div class="mb-3">
<label class="form-label" data-en="Serial Number" data-ar="الرقم التسلسلي">Serial Number</label>
<input type="text" name="serial_number" class="form-control" value="<?= htmlspecialchars($d['serial_number']) ?>">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_biometric_device" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Add Device Modal -->
<div class="modal fade" id="addDeviceModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add Biometric Device" data-ar="إضافة جهاز بصمة">Add Biometric Device</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Device Name" data-ar="اسم الجهاز">Device Name</label>
<input type="text" name="device_name" class="form-control" required placeholder="e.g. Main Entrance">
</div>
<div class="row g-3 mb-3">
<div class="col-md-8">
<label class="form-label" data-en="IP Address" data-ar="عنوان IP">IP Address</label>
<input type="text" name="ip_address" class="form-control" required placeholder="192.168.1.201">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Port" data-ar="المنفذ">Port</label>
<input type="number" name="port" class="form-control" value="4370" required>
</div>
</div>
<div class="mb-3">
<label class="form-label" data-en="IO Address" data-ar="عنوان IO">IO Address</label>
<input type="text" name="io_address" class="form-control" placeholder="Optional IO address">
</div>
<div class="mb-3">
<label class="form-label" data-en="Serial Number" data-ar="الرقم التسلسلي">Serial Number</label>
<input type="text" name="serial_number" class="form-control" placeholder="Device Serial Number">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_biometric_device" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'scale_devices'): ?>
<div class="card border-0 shadow-sm rounded-4 overflow-hidden">
<div class="card-header bg-white border-0 py-3 d-flex justify-content-between align-items-center">
<h5 class="mb-0 fw-bold" data-en="POS Devices" data-ar="أجهزة نقاط البيع">POS Devices</h5>
<button class="btn btn-primary rounded-3" data-bs-toggle="modal" data-bs-target="#addScaleDeviceModal">
<i class="fas fa-plus me-1"></i> <span data-en="Add Device" data-ar="إضافة جهاز">Add Device</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th data-en="Device Name" data-ar="اسم الجهاز">Device Name</th>
<th data-en="Type" data-ar="النوع">Type</th>
<th data-en="Connection" data-ar="الاتصال">Connection</th>
<th data-en="Details" data-ar="التفاصيل">Details</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th class="text-end" data-en="Actions" data-ar="الإجراءات">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['scale_devices'] as $d): ?>
<tr>
<td>
<div class="fw-bold"><?= htmlspecialchars($d['device_name']) ?></div>
</td>
<td>
<span class="badge bg-info-subtle text-info text-capitalize"><?= $d['device_type'] ?></span>
</td>
<td>
<span class="badge bg-secondary-subtle text-secondary text-uppercase"><?= $d['connection_type'] ?></span>
</td>
<td class="small text-muted">
<?php if ($d['connection_type'] === 'network'): ?>
<?= htmlspecialchars((string)$d['ip_address']) ?>:<?= $d['port'] ?>
<?php elseif ($d['connection_type'] === 'serial'): ?>
Baud: <?= $d['baud_rate'] ?>
<?php else: ?>
USB Interface
<?php endif; ?>
</td>
<td>
<span class="badge bg-<?= $d['status'] === 'active' ? 'success' : 'danger' ?>-subtle text-<?= $d['status'] === 'active' ? 'success' : 'danger' ?> text-capitalize"><?= $d['status'] ?></span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-light rounded-pill px-3" data-bs-toggle="modal" data-bs-target="#editScaleDeviceModal<?= $d['id'] ?>">
<i class="fas fa-edit me-1"></i> <span data-en="Edit" data-ar="تعديل">Edit</span>
</button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<button type="submit" name="delete_pos_device" class="btn btn-sm btn-outline-danger border-0 rounded-pill">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
<!-- Edit Device Modal -->
<div class="modal fade" id="editScaleDeviceModal<?= $d['id'] ?>" tabindex="-1">
<div class="modal-dialog border-0">
<div class="modal-content shadow border-0 rounded-4">
<div class="modal-header border-0">
<h5 class="modal-title fw-bold" data-en="Edit Device" data-ar="تعديل الجهاز">Edit Device</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $d['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Device Name" data-ar="اسم الجهاز">Device Name</label>
<input type="text" name="device_name" class="form-control" value="<?= htmlspecialchars($d['device_name']) ?>" required>
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label" data-en="Device Type" data-ar="نوع الجهاز">Device Type</label>
<select name="device_type" class="form-select">
<option value="scale" <?= $d['device_type'] === 'scale' ? 'selected' : '' ?>>Weight Scale</option>
<option value="printer" <?= $d['device_type'] === 'printer' ? 'selected' : '' ?>>Receipt Printer</option>
<option value="display" <?= $d['device_type'] === 'display' ? 'selected' : '' ?>>Customer Display</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Connection Type" data-ar="نوع الاتصال">Connection Type</label>
<select name="connection_type" class="form-select">
<option value="usb" <?= $d['connection_type'] === 'usb' ? 'selected' : '' ?>>USB</option>
<option value="network" <?= $d['connection_type'] === 'network' ? 'selected' : '' ?>>Network (TCP/IP)</option>
<option value="serial" <?= $d['connection_type'] === 'serial' ? 'selected' : '' ?>>Serial (RS232)</option>
</select>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-8">
<label class="form-label" data-en="IP Address" data-ar="عنوان IP">IP Address</label>
<input type="text" name="ip_address" class="form-control" value="<?= htmlspecialchars((string)$d['ip_address']) ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Port" data-ar="المنفذ">Port</label>
<input type="number" name="port" class="form-control" value="<?= $d['port'] ?>">
</div>
</div>
<div class="mb-3">
<label class="form-label" data-en="Baud Rate" data-ar="معدل الباود">Baud Rate</label>
<input type="number" name="baud_rate" class="form-control" value="<?= $d['baud_rate'] ?>">
</div>
<div class="mb-3">
<label class="form-label" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" class="form-select">
<option value="active" <?= $d['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="inactive" <?= $d['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
</select>
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_pos_device" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Add Scale Device Modal -->
<div class="modal fade" id="addScaleDeviceModal" tabindex="-1">
<div class="modal-dialog border-0">
<div class="modal-content shadow border-0 rounded-4">
<div class="modal-header border-0">
<h5 class="modal-title fw-bold" data-en="Add POS Device" data-ar="إضافة جهاز">Add POS Device</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Device Name" data-ar="اسم الجهاز">Device Name</label>
<input type="text" name="device_name" class="form-control" required placeholder="e.g. Counter 1 Scale">
</div>
<div class="row g-3 mb-3">
<div class="col-md-6">
<label class="form-label" data-en="Device Type" data-ar="نوع الجهاز">Device Type</label>
<select name="device_type" class="form-select">
<option value="scale">Weight Scale</option>
<option value="printer">Receipt Printer</option>
<option value="display">Customer Display</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Connection Type" data-ar="نوع الاتصال">Connection Type</label>
<select name="connection_type" class="form-select">
<option value="usb">USB</option>
<option value="network">Network (TCP/IP)</option>
<option value="serial">Serial (RS232)</option>
</select>
</div>
</div>
<div class="row g-3 mb-3">
<div class="col-md-8">
<label class="form-label" data-en="IP Address" data-ar="عنوان IP">IP Address</label>
<input type="text" name="ip_address" class="form-control" placeholder="192.168.1.50">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Port" data-ar="المنفذ">Port</label>
<input type="number" name="port" class="form-control" placeholder="9100">
</div>
</div>
<div class="mb-3">
<label class="form-label" data-en="Baud Rate" data-ar="معدل الباود">Baud Rate</label>
<input type="number" name="baud_rate" class="form-control" placeholder="9600">
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_pos_device" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'my_profile'): ?>
<div class="row">
<div class="col-md-4">
<div class="card p-4 text-center border-0 shadow-sm rounded-4">
<h5 class="mb-4 fw-bold" data-en="Profile Picture" data-ar="صورة الملف الشخصي">Profile Picture</h5>
<div class="mb-3">
<?php if (!empty($data['user']['profile_pic'])): ?>
<img src="<?= htmlspecialchars($data['user']['profile_pic']) ?>?v=<?= time() ?>" alt="Profile" class="rounded-circle shadow-sm" style="width: 150px; height: 150px; object-fit: cover; border: 5px solid #fff;">
<?php else: ?>
<div class="rounded-circle bg-light d-inline-flex align-items-center justify-content-center shadow-sm" style="width: 150px; height: 150px; border: 5px solid #fff;">
<i class="bi bi-person text-muted" style="font-size: 5rem;"></i>
</div>
<?php endif; ?>
</div>
<div class="mt-3">
<h6 class="fw-bold mb-0"><?= htmlspecialchars($data['user']['username']) ?></h6>
<p class="text-muted small"><?= htmlspecialchars($_SESSION['user_role_name'] ?? 'User') ?></p>
</div>
</div>
</div>
<div class="col-md-8">
<div class="card p-4 border-0 shadow-sm rounded-4">
<h5 class="mb-4 fw-bold" data-en="Edit Profile" data-ar="تعديل الملف الشخصي">Edit Profile</h5>
<form method="POST" enctype="multipart/form-data">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold" data-en="Username" data-ar="اسم المستخدم">Username</label>
<input type="text" name="username" class="form-control rounded-3" value="<?= htmlspecialchars($data['user']['username'] ?? '') ?>" required>
</div>
<div class="col-md-6">
<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">
</div>
<div class="col-md-6">
<label class="form-label fw-semibold" data-en="Change Profile Picture" data-ar="تغيير صورة الملف الشخصي">Change Profile Picture</label>
<input type="file" name="profile_pic" class="form-control rounded-3" accept="image/*">
</div>
<div class="col-md-12 mt-4">
<button type="submit" name="update_profile" class="btn btn-primary rounded-pill px-4">
<i class="bi bi-check-circle me-1"></i> <span data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'settings'): ?>
<div class="card p-4">
<h5 class="mb-4" data-en="Company Profile" data-ar="ملف الشركة">Company Profile</h5>
<form method="POST" enctype="multipart/form-data">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Company Name" data-ar="اسم الشركة">Company Name</label>
<input type="text" name="settings[company_name]" class="form-control" value="<?= htmlspecialchars($data['settings']['company_name'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
<input type="text" name="settings[company_phone]" class="form-control" value="<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
<input type="email" name="settings[company_email]" class="form-control" value="<?= htmlspecialchars($data['settings']['company_email'] ?? '') ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="VAT Number" data-ar="الرقم الضريبي">VAT Number</label>
<input type="text" name="settings[vat_number]" class="form-control" value="<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>">
</div>
<div class="col-md-12">
<label class="form-label" data-en="Address" data-ar="العنوان">Address</label>
<textarea name="settings[company_address]" class="form-control" rows="3"><?= htmlspecialchars($data['settings']['company_address'] ?? '') ?></textarea>
</div>
<div class="col-md-4 mt-3">
<label class="form-label" data-en="Allow Selling Out of Stock" data-ar="السماح بالبيع عند نفاذ المخزون">Allow Selling Out of Stock</label>
<select name="settings[allow_zero_stock_sell]" class="form-select">
<option value="0" <?= ($data['settings']['allow_zero_stock_sell'] ?? '1') === '0' ? 'selected' : '' ?> data-en="Disabled" data-ar="معطل">Disabled</option>
<option value="1" <?= ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1' ? 'selected' : '' ?> data-en="Enabled" data-ar="مفعل">Enabled</option>
</select>
</div>
<div class="col-md-4 mt-3">
<label class="form-label" data-en="Company Logo" data-ar="شعار الشركة">Company Logo</label>
<input type="file" name="company_logo" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['company_logo'])): ?>
<div class="mt-2">
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>?v=<?= time() ?>" alt="Logo" class="img-thumbnail" style="max-height: 80px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Favicon" data-ar="أيقونة الموقع">Favicon</label>
<input type="file" name="favicon" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['favicon'])): ?>
<div class="mt-2">
<img src="<?= htmlspecialchars($data['settings']['favicon']) ?>?v=<?= time() ?>" alt="Favicon" class="img-thumbnail" style="max-height: 32px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Manager Signature" data-ar="توقيع المدير">Manager Signature</label>
<input type="file" name="manager_signature" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['manager_signature'])): ?>
<div class="mt-2">
<img src="<?= htmlspecialchars($data['settings']['manager_signature']) ?>?v=<?= time() ?>" alt="Signature" class="img-thumbnail" style="max-height: 80px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-12 mt-4">
<h5 class="mb-3" data-en="Loyalty Configuration" data-ar="إعدادات الولاء">Loyalty Configuration</h5>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" data-en="Loyalty System" data-ar="نظام الولاء">Loyalty System</label>
<select name="settings[loyalty_enabled]" class="form-select">
<option value="0" <?= ($data['settings']['loyalty_enabled'] ?? '0') === '0' ? 'selected' : '' ?> data-en="Disabled" data-ar="معطل">Disabled</option>
<option value="1" <?= ($data['settings']['loyalty_enabled'] ?? '0') === '1' ? 'selected' : '' ?> data-en="Enabled" data-ar="مفعل">Enabled</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Points per 1 OMR" data-ar="النقاط لكل 1 ريال">Points per 1 OMR</label>
<input type="number" step="0.01" name="settings[loyalty_points_per_unit]" class="form-control" value="<?= htmlspecialchars($data['settings']['loyalty_points_per_unit'] ?? '1') ?>">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Points for 1 OMR Discount" data-ar="النقاط لخصم 1 ريال">Points for 1 OMR Discount</label>
<input type="number" step="0.01" name="settings[loyalty_redeem_points_per_unit]" class="form-control" value="<?= htmlspecialchars($data['settings']['loyalty_redeem_points_per_unit'] ?? '100') ?>">
</div>
</div>
</div>
<div class="col-md-12 mt-4">
<button type="submit" name="update_settings" class="btn btn-primary">
<i class="bi bi-save"></i> <span data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</span>
</button>
</div>
</div>
</form>
</div>
<?php if (($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
<div class="card p-4 mt-4">
<h5 class="mb-3" data-en="Advanced Tools" data-ar="أدوات متقدمة">Advanced Tools</h5>
<div class="row">
<div class="col-md-6">
<div class="p-3 border rounded bg-light">
<h6 class="fw-bold mb-2" data-en="Data Synchronization" data-ar="مزامنة البيانات">Data Synchronization</h6>
<p class="text-muted small" data-en="Copy items, categories, and units between outlets easily." data-ar="نسخ الأصناف والفئات والوحدات بين الفروع بسهولة.">Copy items, categories, and units between outlets easily.</p>
<a href="index.php?page=data_sync" class="btn btn-outline-primary btn-sm rounded-pill">
<i class="bi bi-arrow-repeat me-1"></i> <span data-en="Go to Data Sync" data-ar="الذهاب إلى مزامنة البيانات">Go to Data Sync</span>
</a>
</div>
</div>
</div>
</div>
<?php endif; ?>
<?php elseif ($page === 'role_groups'): ?>
<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">
<div>
<h5 class="m-0 fw-bold text-primary" data-en="Role Groups" data-ar="مجموعات الأدوار">Role Groups</h5>
<p class="text-muted small mb-0" data-en="Manage access levels and permissions" data-ar="إدارة مستويات الوصول والصلاحيات">Manage access levels and permissions</p>
</div>
<button class="btn btn-primary rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addRoleGroupModal">
<i class="bi bi-shield-plus me-1"></i> <span data-en="Create New Group" data-ar="إنشاء مجموعة جديدة">Create New Group</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<tr>
<th class="ps-4" data-en="Group Name" data-ar="اسم المجموعة">Group Name</th>
<th data-en="Created Date" data-ar="تاريخ الإنشاء">Created Date</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Actions" data-ar="الإجراءات" class="text-end pe-4">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['role_groups'] as $group): ?>
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<div class="rounded-circle bg-primary bg-opacity-10 p-2 me-3 text-primary">
<i class="bi bi-shield-check"></i>
</div>
<span class="fw-semibold text-dark"><?= htmlspecialchars((string)$group['name']) ?></span>
</div>
</td>
<td><span class="text-muted small"><?= date('M d, Y', strtotime((string)$group['created_at'])) ?></span></td>
<td><span class="badge rounded-pill bg-success bg-opacity-10 text-success px-3">Active</span></td>
<td class="text-end pe-4">
<div class="dropdown">
<button class="btn btn-light btn-sm rounded-circle" type="button" data-bs-toggle="dropdown">
<i class="bi bi-three-dots-vertical"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0">
<?php if (can('users_edit')): ?>
<li><a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#editRoleGroupModal<?= $group['id'] ?>"><i class="bi bi-pencil me-2 text-primary"></i> Edit Permissions</a></li>
<?php endif; ?>
<?php if (can('users_delete')): ?>
<li><hr class="dropdown-divider"></li>
<li>
<form method="POST" onsubmit="return confirm('Delete this role group? This cannot be undone.')">
<input type="hidden" name="id" value="<?= $group['id'] ?>">
<button type="submit" name="delete_role_group" class="dropdown-item text-danger"><i class="bi bi-trash me-2"></i> Delete Group</button>
</form>
</li>
<?php endif; ?>
</ul>
</div>
<!-- Edit Role Group Modal -->
<div class="modal fade" id="editRoleGroupModal<?= $group['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title fw-bold" data-en="Edit Role Group" data-ar="تعديل مجموعة الأدوار">Edit Role Group</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $group['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold" data-en="Group Name" data-ar="اسم المجموعة">Group Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($group['name']) ?>" required>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label fw-semibold mb-0" data-en="Permissions" data-ar="الصلاحيات">Permissions</label>
<div class="d-flex gap-2">
<button type="button" class="btn btn-xs btn-outline-primary py-0 px-2 small select-all-btn" data-modal="#editRoleGroupModal<?= $group['id'] ?>">Select All</button>
<button type="button" class="btn btn-xs btn-outline-secondary py-0 px-2 small deselect-all-btn" data-modal="#editRoleGroupModal<?= $group['id'] ?>">Deselect All</button>
</div>
</div>
<div class="mb-3 p-2 bg-light rounded d-flex justify-content-between align-items-center flex-wrap gap-2">
<span class="small fw-bold me-2">Global Actions:</span>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="view" id="selectAllView<?= $group['id'] ?>">
<label class="form-check-label small" for="selectAllView<?= $group['id'] ?>">View</label>
</div>
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="add" id="selectAllAdd<?= $group['id'] ?>">
<label class="form-check-label small" for="selectAllAdd<?= $group['id'] ?>">Add</label>
</div>
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="edit" id="selectAllEdit<?= $group['id'] ?>">
<label class="form-check-label small" for="selectAllEdit<?= $group['id'] ?>" data-en="Edit" data-ar="تعديل">Edit</label>
</div>
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="delete" id="selectAllDelete<?= $group['id'] ?>">
<label class="form-check-label small" for="selectAllDelete<?= $group['id'] ?>" data-en="Delete" data-ar="حذف">Delete</label>
</div>
</div>
</div>
<div class="row overflow-auto pe-2" style="max-height: 500px;">
<?php
$stmtP = db()->prepare("SELECT permission FROM role_permissions WHERE role_id = ?");
$stmtP->execute([$group['id']]);
$perms = $stmtP->fetchAll(PDO::FETCH_COLUMN);
foreach ($permission_groups as $group_name => $modules): ?>
<div class="permission-group-container col-12 mb-4">
<div class="mt-3 mb-2 bg-secondary bg-opacity-10 p-2 d-flex justify-content-between align-items-center rounded border-start border-primary border-3">
<span class="fw-bold text-uppercase small text-primary"><?= $group_name ?></span>
<div class="form-check mb-0">
<input class="form-check-input select-all-group" type="checkbox" id="group_<?= $group['id'] ?>_<?= strtolower(str_replace(' ', '_', $group_name)) ?>">
<label class="form-check-label small fw-bold" for="group_<?= $group['id'] ?>_<?= strtolower(str_replace(' ', '_', $group_name)) ?>">Group All</label>
</div>
</div>
<div class="row g-3">
<?php foreach ($modules as $m => $label): ?>
<div class="col-md-6 mb-2 border-bottom pb-2 module-row">
<div class="small fw-bold mb-2 text-dark border-start border-2 ps-2 border-info d-flex justify-content-between align-items-center">
<span><?= $label ?></span>
<div class="form-check mb-0">
<input class="form-check-input select-all-row" type="checkbox" id="row_all_<?= $group['id'] ?>_<?= $m ?>">
<label class="form-check-label smaller text-muted mb-0 ms-1" style="font-size: 0.7rem;" for="row_all_<?= $group['id'] ?>_<?= $m ?>">Select All</label>
</div>
</div>
<div class="d-flex gap-3 flex-wrap ps-2">
<?php foreach (['view', 'add', 'edit', 'delete'] as $a):
$p = $m . '_' . $a;
?>
<div class="form-check">
<input class="form-check-input perm-check" type="checkbox" name="permissions[]" value="<?= $p ?>" data-action="<?= $a ?>" id="perm_<?= $group['id'] ?>_<?= $p ?>" <?= in_array($p, (array)$perms) ? 'checked' : '' ?>>
<label class="form-check-label small" for="perm_<?= $group['id'] ?>_<?= $p ?>"><?= ucfirst($a) ?></label>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light rounded-pill px-3" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_role_group" class="btn btn-primary rounded-pill px-4" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<script>
$(document).ready(function() {
// Select All by Action (View, Add, Edit, Delete)
$('.select-all-action').on('change', function() {
const action = $(this).data('action');
const checked = $(this).is(':checked');
const modal = $(this).closest('.modal');
modal.find('.perm-check[data-action="' + action + '"]').prop('checked', checked);
});
// Select All by Row
$('.select-all-row').on('change', function() {
const checked = $(this).is(':checked');
$(this).closest('.module-row').find('.perm-check').prop('checked', checked);
});
// Select All by Group
$('.select-all-group').on('change', function() {
const checked = $(this).is(':checked');
$(this).closest('.permission-group-container').find('.perm-check, .select-all-row').prop('checked', checked);
});
// Select All Button
$('.select-all-btn').on('click', function() {
const modal = $($(this).data('modal'));
modal.find('.perm-check, .select-all-action, .select-all-group, .select-all-row').prop('checked', true);
});
// Deselect All Button
$('.deselect-all-btn').on('click', function() {
const modal = $($(this).data('modal'));
modal.find('.perm-check, .select-all-action, .select-all-group, .select-all-row').prop('checked', false);
});
});
</script>
<?php elseif ($page === 'customer_display_settings'): ?>
<div class="card p-4">
<h5 class="mb-4" data-en="Customer Display Settings" data-ar="إعدادات شاشة العميل">Customer Display Settings</h5>
<form method="POST" enctype="multipart/form-data">
<div class="row g-3">
<div class="col-md-12">
<h6 class="text-muted border-bottom pb-2" data-en="Greeting Message" data-ar="رسالة الترحيب">Greeting Message</h6>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Title" data-ar="العنوان">Title</label>
<input type="text" name="settings[customer_display_greeting_title]" class="form-control" value="<?= htmlspecialchars($data['settings']['customer_display_greeting_title'] ?? 'Welcome') ?>">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Subtitle" data-ar="العنوان الفرعي">Subtitle</label>
<input type="text" name="settings[customer_display_greeting_text]" class="form-control" value="<?= htmlspecialchars($data['settings']['customer_display_greeting_text'] ?? 'We are ready to serve you.') ?>">
</div>
<div class="col-md-12 mt-4">
<h6 class="text-muted border-bottom pb-2" data-en="Slideshow Images" data-ar="صور العرض">Slideshow Images</h6>
<p class="text-muted small">Upload images for the customer display slideshow (1920x1080 recommended).</p>
</div>
<div class="col-md-4">
<label class="form-label">Slide 1</label>
<input type="file" name="display_slide_1" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['display_slide_1'])): ?>
<div class="mt-2 position-relative">
<img src="<?= htmlspecialchars($data['settings']['display_slide_1']) ?>?v=<?= time() ?>" alt="Slide 1" class="img-thumbnail" style="max-height: 120px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label">Slide 2</label>
<input type="file" name="display_slide_2" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['display_slide_2'])): ?>
<div class="mt-2 position-relative">
<img src="<?= htmlspecialchars($data['settings']['display_slide_2']) ?>?v=<?= time() ?>" alt="Slide 2" class="img-thumbnail" style="max-height: 120px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-4">
<label class="form-label">Slide 3</label>
<input type="file" name="display_slide_3" class="form-control" accept="image/*">
<?php if (!empty($data['settings']['display_slide_3'])): ?>
<div class="mt-2 position-relative">
<img src="<?= htmlspecialchars($data['settings']['display_slide_3']) ?>?v=<?= time() ?>" alt="Slide 3" class="img-thumbnail" style="max-height: 120px;">
</div>
<?php endif; ?>
</div>
<div class="col-md-12 mt-4">
<button type="submit" name="update_settings" class="btn btn-primary">
<i class="bi bi-save"></i> <span data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</span>
</button>
</div>
</div>
</form>
</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">
<a href="index.php?download_backup=<?= urlencode($b['name']) ?>" class="btn btn-sm btn-outline-primary" title="Local Download" data-en="Local Download" data-ar="تحميل محلي"><i class="fas fa-download"></i></a>
<form method="POST" class="d-inline" onsubmit="return confirm(document.documentElement.lang === 'ar' ? 'هل تريد استعادة هذه النسخة؟ سيتم الكتابة فوق البيانات الحالية!' : '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 ms-1" 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(document.documentElement.lang === 'ar' ? 'هل تريد حذف هذه النسخة نهائياً؟' : '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>
<?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">
<div>
<h5 class="m-0 fw-bold text-primary" data-en="User Management" data-ar="إدارة المستخدمين">User Management</h5>
<p class="text-muted small mb-0" data-en="Maintain your team accounts and security" data-ar="صيانة حسابات فريقك وأمنها">Maintain your team accounts and security</p>
</div>
<?php if (can('users_add')): ?>
<button class="btn btn-primary rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addUserModal">
<i class="bi bi-person-plus me-1"></i> <span data-en="Add User" data-ar="دعوة مستخدم">Add User</span>
</button>
<?php endif; ?>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle mb-0">
<thead class="bg-light">
<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="Outlet" data-ar="الفرع">Outlet</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>
</thead>
<tbody>
<?php foreach ($data['users'] as $u): ?>
<tr>
<td class="ps-4">
<div class="d-flex align-items-center">
<?php if (!empty($u['profile_pic'])): ?>
<img src="<?= htmlspecialchars((string)$u['profile_pic']) ?>?v=<?= time() ?>" alt="Avatar" class="rounded-circle me-3 shadow-sm" style="width: 35px; height: 35px; object-fit: cover;">
<?php else: ?>
<div class="avatar-sm bg-gradient-primary rounded-circle me-3 text-white d-flex align-items-center justify-content-center fw-bold" style="width: 35px; height: 35px; background: linear-gradient(135deg, #6e8efb, #a777e3);">
<?= strtoupper(substr((string)$u['username'], 0, 1)) ?>
</div>
<?php endif; ?>
<div>
<div class="fw-bold text-dark"><?= htmlspecialchars((string)$u['username']) ?></div>
<div class="text-muted small">ID: #<?= str_pad((string)$u['id'], 4, '0', STR_PAD_LEFT) ?></div>
</div>
</div>
</td>
<td>
<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
<?= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
</span>
</td>
<td>
<div class="d-flex flex-wrap gap-1">
<?php
$out_names = [];
$assigned = array_filter(explode(',', $u['assigned_outlets'] ?? ''));
if (empty($assigned) && !empty($u['outlet_id'])) {
$assigned = [$u['outlet_id']];
}
if (empty($assigned)) {
echo '<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">Global / All Outlets</span>';
} else {
foreach (($data["outlets"] ?? []) as $out) {
if (in_array($out["id"], $assigned)) {
echo '<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-2 py-1">' . htmlspecialchars($out["name"]) . '</span>';
}
}
}
?>
</div>
</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>
<?php else: ?>
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">Suspended</span>
<?php endif; ?>
</td>
<td class="text-end pe-4">
<div class="d-flex justify-content-end gap-2">
<?php if (can('users_edit')): ?>
<button class="btn btn-light btn-sm text-primary" data-bs-toggle="modal" data-bs-target="#editUserModal<?= $u['id'] ?>" title="Edit Profile">
<i class="bi bi-pencil"></i>
</button>
<?php endif; ?>
<?php if (can('users_delete')): ?>
<form method="POST" onsubmit="return confirm('Deactivate this user account?')" class="d-inline">
<input type="hidden" name="id" value="<?= $u['id'] ?>">
<button type="submit" name="delete_user" class="btn btn-light btn-sm text-danger" title="Remove Access"><i class="bi bi-trash"></i></button>
</form>
<?php endif; ?>
</div>
<!-- Edit User Modal -->
<div class="modal fade" id="editUserModal<?= $u['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title fw-bold" data-en="Edit User Account" data-ar="تعديل حساب المستخدم">Edit User Account</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $u['id'] ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label fw-semibold" data-en="Username" data-ar="اسم المستخدم">Username</label>
<input type="text" name="username" class="form-control" value="<?= htmlspecialchars((string)$u['username']) ?>" required>
</div>
<div class="mb-3">
<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">
<option value="">--- No Group ---</option>
<?php foreach (($data['role_groups'] ?? []) as $g): ?>
<option value="<?= $g['id'] ?>" <?= ($u['group_id'] ?? null) == $g['id'] ? 'selected' : '' ?>><?= htmlspecialchars((string)$g['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-en="Assigned Outlets" data-ar="الفروع المخصصة">Assigned Outlets</label>
<select name="outlet_ids[]" class="form-select" multiple size="4">
<option value="">--- Global (Admin only) ---</option>
<?php
$assigned = explode(',', $u['assigned_outlets'] ?? '');
foreach (($data['outlets'] ?? []) as $o): ?>
<option value="<?= $o['id'] ?>" <?= in_array($o['id'], $assigned) || ($u['outlet_id'] ?? null) == $o['id'] ? 'selected' : '' ?>><?= htmlspecialchars($o['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="form-text small" data-en="Hold Ctrl/Cmd to select multiple outlets." data-ar="اضغط على Ctrl/Cmd لتحديد فروع متعددة.">Hold Ctrl/Cmd to select multiple outlets.</div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-en="Account Status" data-ar="حالة الحساب">Account Status</label>
<select name="status" class="form-select">
<option value="active" <?= $u['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="inactive" <?= $u['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
</select>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" data-en="New Password" data-ar="كلمة مرور جديدة">New Password</label>
<input type="password" name="password" class="form-control" placeholder="Leave blank to keep current" autocomplete="new-password">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light rounded-pill px-3" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_user" class="btn btn-primary rounded-pill px-4" data-en="Save Changes" data-ar="حفظ التغييرات">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'cash_registers'): ?>
<div class="card p-4 rounded-4 shadow-sm border-0">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h5 class="m-0 fw-bold text-primary" data-en="Cash Registers Management" data-ar="إدارة خزائن الكاشير">Cash Registers Management</h5>
<p class="text-muted small mb-0">Define your shop counters and registers.</p>
<div class="mt-2">
<?php
$allowed_acts = LicenseService::getAllowedActivations();
$current_regs = count($data['cash_registers'] ?? []);
?>
<span class="badge bg-light text-dark border">
<i class="bi bi-info-circle me-1"></i>
<span data-en="License Limit:" data-ar="حد الترخيص:">License Limit:</span> <?= $current_regs ?> / <?= $allowed_acts ?>
<span data-en="Registers" data-ar="خزينة">Registers</span>
</span>
</div>
</div>
<button class="btn btn-primary rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#addRegisterModal">
<i class="bi bi-plus-lg me-2"></i> <span data-en="Add Register" data-ar="إضافة خزينة">Add Register</span>
</button>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="ID" data-ar="المعرف">ID</th>
<th data-en="Name" data-ar="الاسم">Name</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th data-en="Created At" data-ar="تاريخ الإنشاء">Created At</th>
<th class="text-end" data-en="Actions" data-ar="الإجراءات">Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['cash_registers'] as $r): ?>
<tr>
<td>#<?= $r['id'] ?></td>
<td class="fw-bold"><?= htmlspecialchars($r['name']) ?></td>
<td>
<span class="badge rounded-pill <?= $r['status'] === 'active' ? 'bg-success' : 'bg-danger' ?>">
<?= ucfirst($r['status']) ?>
</span>
</td>
<td><?= $r['created_at'] ?></td>
<td class="text-end">
<button class="btn btn-sm btn-light rounded-circle" data-bs-toggle="modal" data-bs-target="#editRegisterModal<?= $r['id'] ?>"><i class="bi bi-pencil text-primary"></i></button>
<form method="POST" class="d-inline" onsubmit="return confirm('Are you sure?')">
<input type="hidden" name="id" value="<?= $r['id'] ?>">
<button type="submit" name="delete_cash_register" class="btn btn-sm btn-light rounded-circle"><i class="bi bi-trash text-danger"></i></button>
</form>
<!-- Edit Modal -->
<div class="modal fade" id="editRegisterModal<?= $r['id'] ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Edit Register</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body text-start">
<input type="hidden" name="id" value="<?= $r['id'] ?>">
<div class="mb-3">
<label class="form-label">Register Name</label>
<input type="text" name="name" class="form-control" value="<?= htmlspecialchars($r['name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" class="form-select">
<option value="active" <?= $r['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="inactive" <?= $r['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_cash_register" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Add Modal -->
<div class="modal fade" id="addRegisterModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Add Cash Register</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Register Name</label>
<input type="text" name="name" class="form-control" placeholder="Counter 1" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_cash_register" class="btn btn-primary">Add Register</button>
</div>
</form>
</div>
</div>
</div>
<?php elseif ($page === 'register_sessions'): ?>
<div class="card p-4 rounded-4 shadow-sm border-0 mb-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<h5 class="m-0 fw-bold text-primary"><span data-en="Register Sessions" data-ar="جلسات الكاشير">Register Sessions</span> (<?= count($data['sessions']) ?>)</h5>
<p class="text-muted small mb-0">Manage daily opening and closing of cash registers.</p>
</div>
<div>
<?php
$active_session = db()->prepare("SELECT s.*, r.name as register_name FROM register_sessions s JOIN cash_registers r ON s.register_id = r.id WHERE s.user_id = ? AND s.status = 'open'");
$active_session->execute([$_SESSION['user_id']]);
$session = $active_session->fetch();
?>
<?php if (!$session): ?>
<button class="btn btn-success rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#openRegisterModal">
<i class="bi bi-unlock me-2"></i> <span data-en="Open Register" data-ar="فتح الخزينة">Open Register</span>
</button>
<?php else: ?>
<button class="btn btn-danger rounded-pill px-4" data-bs-toggle="modal" data-bs-target="#closeRegisterModal">
<i class="bi bi-lock me-2"></i> <span data-en="Close Register" data-ar="إغلاق الخزينة">Close Register</span>
</button>
<?php endif; ?>
</div>
</div>
<?php if ($session): ?>
<div class="alert alert-info border-0 shadow-sm d-flex justify-content-between align-items-center mb-4">
<div>
<i class="bi bi-info-circle-fill me-2"></i>
Current Open Register: <strong><?= htmlspecialchars($session['register_name']) ?></strong> |
Opened At: <strong><?= $session['opened_at'] ?></strong> |
Opening Balance: <strong>OMR <?= number_format((float)$session['opening_balance'], 3) ?></strong>
</div>
<div>
<a href="index.php?page=pos" class="btn btn-sm btn-primary rounded-pill px-3">Go to POS</a>
</div>
</div>
<?php endif; ?>
<!-- Filter Form -->
<div class="card border-0 bg-light rounded-4 mb-4">
<div class="card-body">
<form action="index.php" method="GET" class="row g-3 align-items-end">
<input type="hidden" name="page" value="register_sessions">
<?php if (can('users_view')): ?>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Cashier" data-ar="الكاشير">Cashier</label>
<select name="user_id" class="form-select rounded-3">
<option value="" data-en="All Cashiers" data-ar="كل الكاشير">All Cashiers</option>
<?php foreach ($data['users'] as $u): ?>
<option value="<?= $u['id'] ?>" <?= (isset($_GET['user_id']) && $_GET['user_id'] == $u['id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($u['username']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<?php endif; ?>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Date From" data-ar="من تاريخ">Date From</label>
<input type="date" name="date_from" class="form-control rounded-3" value="<?= htmlspecialchars($_GET['date_from'] ?? '') ?>">
</div>
<div class="col-md-3">
<label class="form-label small fw-bold" data-en="Date To" data-ar="إلى تاريخ">Date To</label>
<input type="date" name="date_to" class="form-control rounded-3" value="<?= htmlspecialchars($_GET['date_to'] ?? '') ?>">
</div>
<div class="col-md-3">
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary rounded-3 w-100">
<i class="bi bi-filter me-1"></i> <span data-en="Filter" data-ar="تصفية">Filter</span>
</button>
<a href="index.php?page=register_sessions" class="btn btn-outline-secondary rounded-3 w-100">
<i class="bi bi-x-circle me-1"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</div>
</div>
</form>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th data-en="ID" data-ar="المعرف">ID</th>
<th data-en="Register" data-ar="الخزينة">Register</th>
<th data-en="Cashier" data-ar="الكاشير">Cashier</th>
<th data-en="Opened At" data-ar="وقت الفتح">Opened At</th>
<th data-en="Closed At" data-ar="وقت الإغلاق">Closed At</th>
<th data-en="Opening Bal." data-ar="رصيد الافتتاح">Opening Bal.</th>
<th data-en="Cash Sale" data-ar="مبيعات نقدية">Cash Sale</th>
<th data-en="Credit Card" data-ar="بطاقة ائتمان">Credit Card</th>
<th data-en="Credit" data-ar="آجل">Credit</th>
<th data-en="Total Sale" data-ar="إجمالي المبيعات">Total Sale</th>
<th data-en="Balance" data-ar="الرصيد">Balance</th>
<th data-en="Status" data-ar="الحالة">Status</th>
<th class="text-end" data-en="Report" data-ar="تقرير">Report</th>
</tr>
</thead>
<tbody>
<?php foreach ($data['sessions'] as $s): ?>
<tr>
<td>#<?= $s['id'] ?></td>
<td class="fw-bold"><?= htmlspecialchars($s['register_name'] ?? 'N/A') ?></td>
<td><?= htmlspecialchars($s['username'] ?? 'N/A') ?></td>
<td><?= $s['opened_at'] ?></td>
<td><?= $s['closed_at'] ?? '---' ?></td>
<td>OMR <?= number_format((float)$s['opening_balance'], 3) ?></td>
<?php
$stats_stmt = db()->prepare("SELECT
SUM(CASE WHEN LOWER(payment_method) = 'cash' THEN amount ELSE 0 END) as cash_total,
SUM(CASE WHEN LOWER(payment_method) IN ('card', 'credit card', 'visa', 'mastercard') THEN amount ELSE 0 END) as card_total,
SUM(CASE WHEN LOWER(payment_method) = 'credit' THEN amount ELSE 0 END) as credit_total,
SUM(CASE WHEN LOWER(payment_method) LIKE '%transfer%' OR LOWER(payment_method) LIKE '%bank%' THEN amount ELSE 0 END) as transfer_total,
SUM(amount) as total_sales
FROM (
SELECT p.payment_method, p.amount FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed'
UNION ALL
SELECT p.payment_method, p.amount FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1
) as combined_payments");
$stats_stmt->execute([$s['id'], $s['id']]);
$st = $stats_stmt->fetch();
$c_total = (float)($st['cash_total'] ?? 0);
$cd_total = (float)($st['card_total'] ?? 0);
$cr_total = (float)($st['credit_total'] ?? 0);
$tr_total = (float)($st['transfer_total'] ?? 0);
$t_sales = (float)($st['total_sales'] ?? 0);
$row_expected_cash = (float)$s['opening_balance'] + $c_total;
?>
<td>OMR <?= number_format($c_total, 3) ?></td>
<td>OMR <?= number_format($cd_total, 3) ?></td>
<td>OMR <?= number_format($cr_total, 3) ?></td>
<td class="fw-bold">OMR <?= number_format($t_sales, 3) ?></td>
<td>
<?php if ($s['status'] === 'closed'):
$diff = (float)($s['cash_in_hand'] ?? 0) - $row_expected_cash;
$color = $diff == 0 ? 'text-success' : ($diff > 0 ? 'text-info' : 'text-danger');
?>
<span class="<?= $color ?> fw-bold">OMR <?= number_format($diff, 3) ?></span>
<?php else: ?>---<?php endif; ?>
</td>
<td>
<span class="badge rounded-pill <?= $s['status'] === 'open' ? 'bg-success' : 'bg-secondary' ?>">
<?= ucfirst($s['status']) ?>
</span>
</td>
<td class="text-end">
<button class="btn btn-sm btn-light rounded-circle" data-bs-toggle="modal" data-bs-target="#sessionReportModal<?= $s['id'] ?>"><i class="bi bi-file-earmark-text text-primary"></i></button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<!-- Modals outside loop for better structure -->
<?php foreach ($data['sessions'] as $s): ?>
<!-- Session Report Modal -->
<div class="modal fade" id="sessionReportModal<?= $s['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Session Cashflow Report (#<?= $s['id'] ?>)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-start">
<div class="row g-3">
<div class="col-md-6">
<p class="mb-1 text-muted small">Register</p>
<h6><?= htmlspecialchars($s['register_name']) ?></h6>
</div>
<div class="col-md-6">
<p class="mb-1 text-muted small">Cashier</p>
<h6><?= htmlspecialchars($s['username']) ?></h6>
</div>
<div class="col-md-6">
<p class="mb-1 text-muted small">Opened At</p>
<h6><?= $s['opened_at'] ?></h6>
</div>
<div class="col-md-6">
<p class="mb-1 text-muted small">Closed At</p>
<h6><?= $s['closed_at'] ?? 'Still Open' ?></h6>
</div>
</div>
<hr>
<?php
// Breakdown of sales by payment method (Unified)
$breakdown = db()->prepare("SELECT payment_method, SUM(amount) as total FROM (
SELECT p.payment_method, p.amount FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed'
UNION ALL
SELECT p.payment_method, p.amount FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1
) as combined_payments GROUP BY payment_method");
$breakdown->execute([$s['id'], $s['id']]);
$methods = $breakdown->fetchAll();
$cash_sales = 0;
$card_sales = 0;
$credit_sales = 0;
$bank_transfer_sales = 0;
foreach ($methods as $m) {
$method = strtolower($m['payment_method']);
if ($method === 'cash') $cash_sales = $m['total'];
elseif ($method === 'card' || strpos($method, 'card') !== false) $card_sales = $m['total'];
elseif ($method === 'credit') $credit_sales = $m['total'];
elseif (strpos($method, 'transfer') !== false || strpos($method, 'bank') !== false) $bank_transfer_sales = $m['total'];
else $cash_sales += $m['total'];
}
$total_sales = $cash_sales + $card_sales + $credit_sales + $bank_transfer_sales;
$expected_cash_total = (float)$s['opening_balance'] + $cash_sales;
$total_all = (float)$s['opening_balance'] + $total_sales;
?>
<div class="card border-0 bg-light mb-4">
<div class="card-body">
<h6 class="fw-bold text-uppercase small mb-3">Sales Summary</h6>
<div class="d-flex justify-content-between mb-2">
<span>Opening Balance:</span>
<span class="fw-bold">OMR <?= number_format((float)$s['opening_balance'], 3) ?></span>
</div>
<?php
// Additional totals (Unified)
$extra_stmt = db()->prepare("SELECT SUM(vat_amount) as total_tax, SUM(discount_amount) as total_discount, SUM(total_net) as total_net FROM (
SELECT tax_amount as vat_amount, discount_amount, net_amount as total_net FROM pos_transactions WHERE register_session_id = ? AND status = 'completed'
UNION ALL
SELECT vat_amount, discount_amount, total_with_vat as total_net FROM invoices WHERE register_session_id = ? AND status = 'paid' AND is_pos = 1
) as combined_totals");
$extra_stmt->execute([$s['id'], $s['id']]);
$extra = $extra_stmt->fetch();
?>
<div class="d-flex justify-content-between mb-2 small text-muted">
<span>Tax Amount:</span>
<span>OMR <?= number_format((float)($extra['total_tax'] ?? 0), 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2 small text-muted">
<span>Discount:</span>
<span>OMR <?= number_format((float)($extra['total_discount'] ?? 0), 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Cash Sale:</span>
<span class="fw-bold">OMR <?= number_format($cash_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Credit Card:</span>
<span class="fw-bold">OMR <?= number_format($card_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Credit:</span>
<span class="fw-bold">OMR <?= number_format($credit_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>Bank Transfer:</span>
<span class="fw-bold">OMR <?= number_format($bank_transfer_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2 border-top pt-2 mt-2 h6 text-primary">
<span>Total Sale:</span>
<span class="fw-bold">OMR <?= number_format($total_sales, 3) ?></span>
</div>
</div>
</div>
<div class="card border-0 bg-primary bg-opacity-10 mb-4">
<div class="card-body">
<h6 class="fw-bold text-uppercase small mb-3 text-primary">Cash Reconciliation</h6>
<div class="d-flex justify-content-between mb-2">
<span>Opening Balance:</span>
<span>OMR <?= number_format((float)$s['opening_balance'], 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span>(+) Cash Sale:</span>
<span>OMR <?= number_format($cash_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-2 border-top pt-2 mt-2 h6">
<span>Expected Cash:</span>
<span class="fw-bold">OMR <?= number_format($expected_cash_total, 3) ?></span>
</div>
<?php if ($s['status'] === 'closed'): ?>
<div class="d-flex justify-content-between mb-2">
<span>Actual Cash:</span>
<span>OMR <?= number_format((float)$s['cash_in_hand'], 3) ?></span>
</div>
<?php
$shortage = (float)$s['cash_in_hand'] - $expected_cash_total;
$shortage_class = $shortage < 0 ? 'text-danger' : 'text-success';
?>
<div class="d-flex justify-content-between mb-0 h5 mt-2 <?= $shortage_class ?>">
<span>Balance:</span>
<span class="fw-bold">OMR <?= number_format($shortage, 3) ?></span>
</div>
<?php endif; ?>
</div>
</div>
<div class="mt-4">
<h6 class="fw-bold small text-uppercase mb-2">Transaction Details</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered small">
<thead class="bg-light">
<tr>
<th>Time</th>
<th>Order #</th>
<th data-en="Customer" data-ar="العميل">Customer</th>
<th>Items</th>
<th>Method</th>
<th class="text-end" data-en="Amount" data-ar="المبلغ">Amount</th>
</tr>
</thead>
<tbody>
<?php
$txs_stmt = db()->prepare("SELECT i.*, c.name as customer_name, GROUP_CONCAT(p.payment_method SEPARATOR ', ') as methods FROM invoices i LEFT JOIN payments p ON i.id = p.invoice_id LEFT JOIN customers c ON i.customer_id = c.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1 GROUP BY i.id ORDER BY i.created_at DESC");
$txs_stmt->execute([$s['id']]);
$txs = $txs_stmt->fetchAll();
foreach ($txs as $tx):
?>
<tr>
<td><?= date('H:i', strtotime($tx['created_at'])) ?></td>
<td><?= htmlspecialchars($tx['transaction_no']) ?></td>
<td><?= htmlspecialchars($tx['customer_name'] ?: 'Walk-in') ?></td>
<td>
<?php
$items_stmt = db()->prepare("SELECT si.name_en, ii.quantity FROM invoice_items ii JOIN stock_items si ON ii.item_id = si.id WHERE ii.invoice_id = ?");
$items_stmt->execute([$tx['id']]);
$items = $items_stmt->fetchAll();
foreach ($items as $item) {
echo "<small class='d-block'>" . htmlspecialchars($item['name_en']) . " x " . (float)$item['quantity'] . "</small>";
}
?>
</td>
<td class="text-capitalize small"><?= htmlspecialchars($tx['methods'] ?: '---') ?></td>
<td class="text-end"><?= number_format($tx['total_with_vat'], 3) ?></td>
</tr>
<?php endforeach; ?>
<?php
$txs_stmt = db()->prepare("SELECT t.*, c.name as customer_name, GROUP_CONCAT(p.payment_method SEPARATOR ', ') as methods FROM pos_transactions t LEFT JOIN pos_payments p ON t.id = p.transaction_id LEFT JOIN customers c ON t.customer_id = c.id WHERE t.register_session_id = ? AND t.status = 'completed' GROUP BY t.id ORDER BY t.created_at DESC");
$txs_stmt->execute([$s['id']]);
$txs = $txs_stmt->fetchAll();
foreach ($txs as $tx):
?>
<tr>
<td><?= date('H:i', strtotime($tx['created_at'])) ?></td>
<td><?= htmlspecialchars($tx['transaction_no']) ?></td>
<td><?= htmlspecialchars($tx['customer_name'] ?: 'Walk-in') ?></td>
<td>
<?php
$items_stmt = db()->prepare("SELECT i.name_en, ti.quantity FROM pos_items ti JOIN stock_items i ON ti.item_id = i.id WHERE ti.transaction_id = ?");
$items_stmt->execute([$tx['id']]);
$items = $items_stmt->fetchAll();
foreach ($items as $item) {
echo "<small class='d-block'>" . htmlspecialchars($item['name_en']) . " x " . (float)$item['quantity'] . "</small>";
}
?>
</td>
<td class="text-capitalize small"><?= htmlspecialchars($tx['methods'] ?: '---') ?></td>
<td class="text-end"><?= number_format($tx['net_amount'], 3) ?></td>
</tr>
<?php endforeach; if(empty($txs)): ?>
<tr><td colspan="5" class="text-center text-muted">No transactions</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<hr>
<div class="row g-2 small text-muted">
<div class="col-6">Expected Cash: OMR <?= number_format($expected_cash_total, 3) ?></div>
<div class="col-6 text-end">Actual Cash: OMR <?= number_format((float)($s['cash_in_hand'] ?? 0), 3) ?></div>
</div>
<?php if ($s['notes']): ?>
<hr>
<p class="mb-1 text-muted small">Notes:</p>
<p class="mb-0 small bg-light p-2 rounded"><?= nl2br(htmlspecialchars($s['notes'])) ?></p>
<?php endif; ?>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="window.print()">Print Report</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
<!-- Open Register Modal -->
<div class="modal fade" id="openRegisterModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Open Register Session</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Select Register / Counter</label>
<select name="register_id" class="form-select" required>
<?php foreach ($data['cash_registers'] as $reg): ?>
<option value="<?= $reg['id'] ?>"><?= htmlspecialchars($reg['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label">Opening Cash Balance (OMR)</label>
<input type="number" step="0.001" name="opening_balance" class="form-control" value="0.000" required>
<div class="form-text">Enter the amount of cash already in the drawer.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="open_register" class="btn btn-success">Open Register</button>
</div>
</form>
</div>
</div>
</div>
<!-- Close Register Modal -->
<?php if ($session): ?>
<div class="modal fade" id="closeRegisterModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Close Register Session (#<?= $session['id'] ?>)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="session_id" value="<?= $session['id'] ?>">
<div class="alert alert-warning py-2 small border-0 shadow-sm">
<i class="bi bi-exclamation-triangle me-2"></i> Before closing, please count all cash in your register.
</div>
<?php
// Unified breakdown for closing
$curBreakdown = db()->prepare("SELECT payment_method, SUM(amount) as total FROM (
SELECT p.payment_method, p.amount FROM pos_payments p JOIN pos_transactions t ON p.transaction_id = t.id WHERE t.register_session_id = ? AND t.status = 'completed'
UNION ALL
SELECT p.payment_method, p.amount FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE i.register_session_id = ? AND i.status = 'paid' AND i.is_pos = 1
) as combined GROUP BY payment_method");
$curBreakdown->execute([$session['id'], $session['id']]);
$curMethods = $curBreakdown->fetchAll();
$cash_sales = 0;
$card_sales = 0;
$credit_sales = 0;
$bank_transfer_sales = 0;
foreach ($curMethods as $m) {
$method = strtolower($m['payment_method']);
if ($method === 'cash') $cash_sales = $m['total'];
elseif ($method === 'card' || strpos($method, 'card') !== false) $card_sales = $m['total'];
elseif ($method === 'credit') $credit_sales = $m['total'];
elseif (strpos($method, 'transfer') !== false || strpos($method, 'bank') !== false) $bank_transfer_sales = $m['total'];
else $cash_sales += $m['total'];
}
$total_sales = $cash_sales + $card_sales + $credit_sales + $bank_transfer_sales;
$expected_cash = (float)$session['opening_balance'] + $cash_sales;
$total_all = (float)$session['opening_balance'] + $total_sales;
?>
<div class="card bg-light border-0 mb-3 small">
<div class="card-body">
<h6 class="card-title fw-bold small text-uppercase mb-2">Session Summary</h6>
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Opening Balance:</span>
<span class="fw-bold">OMR <?= number_format((float)$session['opening_balance'], 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Cash Sales:</span>
<span class="fw-bold">OMR <?= number_format($cash_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Credit Card Sales:</span>
<span class="fw-bold">OMR <?= number_format($card_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Credit:</span>
<span class="fw-bold">OMR <?= number_format($credit_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-1">
<span class="text-muted">Bank Transfer:</span>
<span class="fw-bold">OMR <?= number_format($bank_transfer_sales, 3) ?></span>
</div>
<div class="d-flex justify-content-between mb-1 border-top pt-1 mt-1 fw-bold text-dark">
<span>Total Sales:</span>
<span>OMR <?= number_format($total_sales, 3) ?></span>
</div>
<hr class="my-2">
<div class="d-flex justify-content-between fw-bold text-primary mb-1 h6">
<span>Balance (Total):</span>
<span>OMR <?= number_format($total_all, 3) ?></span>
</div>
<div class="d-flex justify-content-between fw-bold text-success">
<span>Expected Cash:</span>
<span>OMR <?= number_format($expected_cash, 3) ?></span>
</div>
</div>
</div>
<div class="mb-3">
<h6 class="fw-bold small text-uppercase mb-2">Transaction Details</h6>
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
<table class="table table-sm table-bordered mb-0 small">
<thead class="bg-light sticky-top">
<tr>
<th>Time</th>
<th>Order #</th>
<th data-en="Customer" data-ar="العميل">Customer</th>
<th>Method</th>
<th class="text-end" data-en="Amount" data-ar="المبلغ">Amount</th>
</tr>
</thead>
<tbody>
<?php
$txs_stmt = db()->prepare("SELECT t.*, c.name as customer_name, GROUP_CONCAT(p.payment_method SEPARATOR ', ') as methods FROM pos_transactions t LEFT JOIN pos_payments p ON t.id = p.transaction_id LEFT JOIN customers c ON t.customer_id = c.id WHERE t.register_session_id = ? AND t.status = 'completed' GROUP BY t.id ORDER BY t.created_at DESC");
$txs_stmt->execute([$session['id']]);
$txs = $txs_stmt->fetchAll();
foreach ($txs as $tx):
?>
<tr>
<td><?= date('H:i', strtotime($tx['created_at'])) ?></td>
<td><?= htmlspecialchars($tx['transaction_no']) ?></td>
<td><?= htmlspecialchars($tx['customer_name'] ?: 'Walk-in') ?></td>
<td class="text-capitalize small"><?= htmlspecialchars($tx['methods'] ?: '---') ?></td>
<td class="text-end"><?= number_format($tx['net_amount'], 3) ?></td>
</tr>
<?php endforeach; if(empty($txs)): ?>
<tr><td colspan="5" class="text-center text-muted">No transactions</td></tr>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<div class="mb-3">
<label class="form-label">Total Cash in Hand (Actual Counted)</label>
<input type="number" step="0.001" name="cash_in_hand" class="form-control" required placeholder="0.000">
</div>
<div class="mb-3">
<label class="form-label">Closing Notes / Comments</label>
<textarea name="notes" class="form-control" rows="3" placeholder="Any discrepancies or remarks..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="close_register" class="btn btn-danger">Close Register & Submit</button>
</div>
</form>
</div>
</div>
</div>
<?php endif; ?>
<?php elseif ($page === 'logs'): ?>
<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">
<div>
<h5 class="m-0 fw-bold text-primary" data-en="System Logs" data-ar="سجلات النظام">System Logs</h5>
<p class="text-muted small mb-0" data-en="Monitor system activity and errors" data-ar="مراقبة نشاط النظام والأخطاء">Monitor system activity and errors</p>
</div>
<div class="d-flex gap-2">
<button onclick="window.location.reload()" class="btn btn-outline-primary btn-sm rounded-pill">
<i class="bi bi-arrow-clockwise"></i> Refresh
</button>
</div>
</div>
<div class="card-body p-0">
<div class="bg-dark text-light p-4 font-monospace small" style="max-height: 600px; overflow-y: auto;">
<?php
$log_files = ['error_log', 'login_debug.log', 'post_debug.log'];
$found_logs = false;
foreach ($log_files as $file) {
$path = __DIR__ . '/' . $file;
if (file_exists($path) && is_readable($path)) {
$found_logs = true;
echo "<div class='mb-4'><h6 class='text-warning border-bottom border-secondary pb-2'>--- " . htmlspecialchars(basename($file)) . " ---</h6>";
$lines = shell_exec("tail -n 50 " . escapeshellarg($path));
echo "<pre class='mb-0 text-info'>" . htmlspecialchars((string)$lines) . "</pre></div>";
}
}
if (!$found_logs) {
echo "<div class='text-center py-5'><i class='bi bi-journal-x fs-1 opacity-25'></i><p class='mt-2 opacity-50'>No accessible log files found.</p></div>";
}
?>
</div>
</div>
</div>
<?php elseif ($page === 'help'): ?>
<?php include 'help.php'; ?>
<?php endif; ?>
</div>
<!-- Add HR Department Modal -->
<div class="modal fade" id="addHrDepartmentModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add HR Department" data-ar="إضافة قسم">Add HR Department</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Department Name" data-ar="اسم القسم">Department Name</label>
<input type="text" name="name" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_hr_department" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add HR Employee Modal -->
<div class="modal fade" id="addHrEmployeeModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add HR Employee" data-ar="إضافة موظف">Add HR Employee</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Full Name" data-ar="الاسم الكامل">Full Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Department" data-ar="القسم">Department</label>
<select name="department_id" class="form-select">
<option value="">--- Select ---</option>
<?php foreach ($data['departments'] ?? [] as $d): ?>
<option value="<?= $d['id'] ?>"><?= htmlspecialchars($d['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Email" data-ar="البريد">Email</label>
<input type="email" name="email" class="form-control">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
<input type="text" name="phone" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Position" data-ar="المنصب">Position</label>
<input type="text" name="position" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Basic Salary" data-ar="الراتب الأساسي">Basic Salary</label>
<input type="number" step="0.001" name="salary" class="form-control" value="0.000">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Biometric ID" data-ar="معرف البصمة">Biometric ID</label>
<input type="text" name="biometric_id" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Joining Date" data-ar="تاريخ الانضمام">Joining Date</label>
<input type="date" name="joining_date" class="form-control" value="<?= date('Y-m-d') ?>">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_hr_employee" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Role Group Modal -->
<div class="modal fade" id="addRoleGroupModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add Role Group" data-ar="إضافة مجموعة أدوار">Add Role Group</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Group Name" data-ar="اسم المجموعة">Group Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<label class="form-label fw-semibold mb-0" data-en="Permissions" data-ar="الصلاحيات">Permissions</label>
<div class="d-flex gap-2">
<button type="button" class="btn btn-xs btn-outline-primary py-0 px-2 small select-all-btn" data-modal="#addRoleGroupModal">Select All</button>
<button type="button" class="btn btn-xs btn-outline-secondary py-0 px-2 small deselect-all-btn" data-modal="#addRoleGroupModal">Deselect All</button>
</div>
</div>
<div class="mb-3 p-2 bg-light rounded d-flex justify-content-between align-items-center flex-wrap gap-2">
<span class="small fw-bold me-2">Global Actions:</span>
<div class="d-flex gap-3">
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="view" id="addSelectAllView">
<label class="form-check-label small" for="addSelectAllView" data-en="View" data-ar="عرض">View</label>
</div>
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="add" id="addSelectAllAdd">
<label class="form-check-label small" for="addSelectAllAdd" data-en="Add" data-ar="إضافة">Add</label>
</div>
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="edit" id="addSelectAllEdit">
<label class="form-check-label small" for="addSelectAllEdit" data-en="Edit" data-ar="تعديل" data-en="Edit" data-ar="تعديل">Edit</label>
</div>
<div class="form-check">
<input class="form-check-input select-all-action" type="checkbox" data-action="delete" id="addSelectAllDelete">
<label class="form-check-label small" for="addSelectAllDelete" data-en="Delete" data-ar="حذف" data-en="Delete" data-ar="حذف">Delete</label>
</div>
</div>
</div>
<div class="row overflow-auto pe-2" style="max-height: 500px;">
<?php
foreach ($permission_groups as $group_name => $modules): ?>
<div class="permission-group-container col-12 mb-4">
<div class="mt-3 mb-2 bg-secondary bg-opacity-10 p-2 d-flex justify-content-between align-items-center rounded border-start border-primary border-3">
<span class="fw-bold text-uppercase small text-primary"><?= $group_name ?></span>
<div class="form-check mb-0">
<input class="form-check-input select-all-group" type="checkbox" id="add_group_<?= strtolower(str_replace(' ', '_', $group_name)) ?>">
<label class="form-check-label small fw-bold" for="add_group_<?= strtolower(str_replace(' ', '_', $group_name)) ?>">Group All</label>
</div>
</div>
<div class="row g-3">
<?php foreach ($modules as $m => $label): ?>
<div class="col-md-6 mb-2 border-bottom pb-2">
<div class="small fw-bold mb-2 text-dark border-start border-2 ps-2 border-info"><?= $label ?></div>
<div class="d-flex gap-3 flex-wrap ps-2">
<?php foreach (['view', 'add', 'edit', 'delete'] as $a):
$p = $m . '_' . $a;
?>
<div class="form-check">
<input class="form-check-input perm-check" type="checkbox" name="permissions[]" value="<?= $p ?>" data-action="<?= $a ?>" id="add_perm_<?= $p ?>">
<label class="form-check-label small" for="add_perm_<?= $p ?>"><?= ucfirst($a) ?></label>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_role_group" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add User Modal -->
<div class="modal fade" id="addUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add New User" data-ar="إضافة مستخدم جديد">Add New User</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Username" data-ar="اسم المستخدم">Username</label>
<input type="text" name="username" class="form-control" required autocomplete="username">
</div>
<div class="mb-3">
<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">
</div>
<div class="mb-3">
<label class="form-label" data-en="Role Group" data-ar="مجموعة الأدوار">Role Group</label>
<select name="group_id" class="form-select">
<option value="">--- Select Group ---</option>
<?php foreach (($data['role_groups'] ?? []) as $g): ?>
<option value="<?= $g['id'] ?>"><?= htmlspecialchars($g['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="mb-3">
<label class="form-label" data-en="Assigned Outlets" data-ar="الفروع المخصصة">Assigned Outlets</label>
<select name="outlet_ids[]" class="form-select" multiple size="4">
<option value="">--- Global (Admin only) ---</option>
<?php foreach (($data['outlets'] ?? []) as $o): ?>
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
<?php endforeach; ?>
</select>
<div class="form-text small" data-en="Hold Ctrl/Cmd to select multiple outlets." data-ar="اضغط على Ctrl/Cmd لتحديد فروع متعددة.">Hold Ctrl/Cmd to select multiple outlets.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_user" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Customer Modal -->
<div class="modal fade" id="addCustomerModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">
<?php if ($page === 'suppliers'): ?>
<span data-en="Add New Supplier" data-ar="إضافة مورد جديد">Add New Supplier</span>
<?php else: ?>
<span data-en="Add New Customer" data-ar="إضافة عميل جديد">Add New Customer</span>
<?php endif; ?>
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="type" value="<?= $page === 'suppliers' ? 'supplier' : 'customer' ?>">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Name" data-ar="الاسم">Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label" data-en="Email" data-ar="البريد الإلكتروني">Email</label>
<input type="email" name="email" class="form-control">
</div>
<div class="mb-3">
<label class="form-label" data-en="Phone" data-ar="الهاتف">Phone</label>
<input type="text" name="phone" class="form-control">
</div>
<div class="mb-3">
<label class="form-label" data-en="Tax ID / VAT No" data-ar="الرقم الضريبي">Tax ID / VAT No</label>
<input type="text" name="tax_id" class="form-control">
</div>
<div class="mb-3">
<label class="form-label" data-en="Initial Balance" data-ar="الرصيد الافتتاحي">Initial Balance</label>
<input type="number" step="0.001" name="balance" class="form-control" value="0.000">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_customer" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Item Modal -->
<div class="modal fade" id="addItemModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add New Item" data-ar="إضافة صنف جديد">Add New Item</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="addItemNameEn" class="form-control" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="addItemNameAr" data-target="addItemNameEn" data-to="en">
<i class="bi bi-translate"></i> EN
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="addItemNameAr" class="form-control" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="addItemNameEn" data-target="addItemNameAr" data-to="ar">
<i class="bi bi-translate"></i> AR
</button>
</div>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select">
<option value="">---</option>
<?php foreach ($data['categories'] ?? [] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name_en']) ?> / <?= htmlspecialchars($c['name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Unit" data-ar="الوحدة">Unit</label>
<select name="unit_id" class="form-select">
<option value="">---</option>
<?php foreach ($data['units'] ?? [] as $u): ?>
<option value="<?= $u['id'] ?>"><?= htmlspecialchars($u['short_name_en']) ?> / <?= htmlspecialchars($u['short_name_ar']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label" data-en="Supplier" data-ar="المورد">Supplier</label>
<select name="supplier_id" class="form-select">
<option value="">---</option>
<?php foreach ($data['suppliers'] ?? [] as $s): ?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($s['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="SKU / Barcode" data-ar="الباركود">SKU / Barcode</label>
<div class="input-group">
<input type="text" name="sku" id="skuInput" class="form-control">
<button class="btn btn-outline-secondary" type="button" id="suggestSkuBtn" data-en="Suggest" data-ar="اقتراح">Suggest</button>
</div>
</div>
<div class="col-md-3">
<label class="form-label" data-en="Sale Price" data-ar="سعر البيع">Sale Price</label>
<input type="number" step="0.001" name="sale_price" class="form-control" value="0.000">
</div>
<div class="col-md-3">
<label class="form-label" data-en="Purchase Price" data-ar="سعر الشراء">Purchase Price</label>
<input type="number" step="0.001" name="purchase_price" class="form-control" value="0.000">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Initial Stock" data-ar="المخزون الحالي">Initial Stock</label>
<input type="number" step="0.001" name="stock_quantity" class="form-control" value="0.000">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Min Stock Level" data-ar="الحد الأدنى للمخزون">Min Stock Level</label>
<input type="number" step="0.001" name="min_stock_level" class="form-control" value="0.000">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Item Picture" data-ar="صورة الصنف">Item Picture</label>
<input type="file" name="image" class="form-control" accept="image/*">
</div>
<div class="col-md-4">
<label class="form-label" data-en="VAT Rate (%)" data-ar="ضريبة القيمة المضافة (%)">VAT Rate (%)</label>
<input type="number" step="0.01" name="vat_rate" class="form-control" value="0">
</div>
<div class="col-md-8">
<div class="form-check form-switch mt-4">
<input class="form-check-input" type="checkbox" id="hasExpiryToggle">
<label class="form-check-label" for="hasExpiryToggle" data-en="Has Expiry Date?" data-ar="هل له تاريخ انتهاء؟">Has Expiry Date?</label>
</div>
</div>
<div class="col-md-6" id="expiryDateContainer" style="display: none;">
<label class="form-label" data-en="Expiry Date" data-ar="تاريخ الانتهاء">Expiry Date</label>
<input type="date" name="expiry_date" class="form-control">
</div>
<div class="col-12 mt-3">
<hr>
<h6 data-en="Promotion Details" data-ar="تفاصيل العرض">Promotion Details</h6>
</div>
<div class="col-md-4">
<div class="form-check form-switch mt-4">
<input class="form-check-input" type="checkbox" name="is_promotion" id="isPromotionToggle">
<label class="form-check-label" for="isPromotionToggle" data-en="On Promotion?" data-ar="في العرض؟">On Promotion?</label>
</div>
</div>
<div class="col-md-8">
<div class="row g-3" id="promotionFieldsContainer" style="display: none;">
<div class="col-md-4">
<label class="form-label" data-en="Start Date" data-ar="تاريخ البدء">Start Date</label>
<input type="date" name="promotion_start" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label" data-en="End Date" data-ar="تاريخ الانتهاء">End Date</label>
<input type="date" name="promotion_end" class="form-control">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Percent (%)" data-ar="النسبة (%)">Percent (%)</label>
<input type="number" step="0.01" name="promotion_percent" class="form-control" value="0.00">
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_item" class="btn btn-primary" data-en="Save Item" data-ar="حفظ الصنف">Save Item</button>
</div>
</form>
</div>
</div>
</div>
<!-- Import Items Modal -->
<div class="modal fade" id="importItemsModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Import Items from Excel (CSV)" data-ar="استيراد الأصناف من اكسل (CSV)">Import Items from Excel (CSV)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="alert alert-info py-2">
<div class="mb-2">
<small data-en="Please upload an Excel (.xlsx) or CSV file with the following columns: SKU, English Name, Arabic Name, Sale Price, Cost Price." data-ar="يرجى رفع ملف إكسل (.xlsx) أو CSV بالأعمدة التالية: الباركود، الاسم الإنجليزي، الاسم العربي، سعر البيع، سعر التكلفة.">
Please upload an Excel (.xlsx) or CSV file with the following columns: SKU, English Name, Arabic Name, Sale Price, Cost Price.
</small>
</div>
<a href="index.php?action=download_items_template" class="btn btn-sm btn-primary">
<i class="bi bi-download"></i> <span data-en="Download Template" data-ar="تحميل القالب">Download Template</span>
</a>
</div>
<div class="mb-3">
<label class="form-label" data-en="Choose File" data-ar="اختر الملف">Choose File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv, .xlsx" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="import_items" class="btn btn-success" data-en="Import Now" data-ar="استيراد الآن">Import Now</button>
</div>
</form>
</div>
</div>
</div>
<!-- Import Customers Modal -->
<div class="modal fade" id="importCustomersModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Import Customers from Excel (CSV)" data-ar="استيراد العملاء من اكسل (CSV)">Import Customers from Excel (CSV)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="alert alert-info py-2">
<small data-en="Please upload an Excel (.xlsx) or CSV file with the following columns: Name, Email, Phone, Tax ID, Balance." data-ar="يرجى رفع ملف إكسل (.xlsx) أو CSV بالأعمدة التالية: الاسم، البريد الإلكتروني، الهاتف، الرقم الضريبي، الرصيد.">
Please upload an Excel (.xlsx) or CSV file with the following columns: Name, Email, Phone, Tax ID, Balance.
</small>
</div>
<div class="mb-3">
<label class="form-label" data-en="Choose File" data-ar="اختر الملف">Choose File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv, .xlsx" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="import_customers" class="btn btn-success" data-en="Import Now" data-ar="استيراد الآن">Import Now</button>
</div>
</form>
</div>
</div>
</div>
<!-- Import Suppliers Modal -->
<div class="modal fade" id="importSuppliersModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Import Suppliers from Excel (CSV)" data-ar="استيراد الموردين من اكسل (CSV)">Import Suppliers from Excel (CSV)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="alert alert-info py-2">
<small data-en="Please upload an Excel (.xlsx) or CSV file with the following columns: Name, Email, Phone, Tax ID, Balance." data-ar="يرجى رفع ملف إكسل (.xlsx) أو CSV بالأعمدة التالية: الاسم، البريد الإلكتروني، الهاتف، الرقم الضريبي، الرصيد.">
Please upload an Excel (.xlsx) or CSV file with the following columns: Name, Email, Phone, Tax ID, Balance.
</small>
</div>
<div class="mb-3">
<label class="form-label" data-en="Choose File" data-ar="اختر الملف">Choose File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv, .xlsx" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="import_suppliers" class="btn btn-success" data-en="Import Now" data-ar="استيراد الآن">Import Now</button>
</div>
</form>
</div>
</div>
</div>
<!-- Import Categories Modal -->
<div class="modal fade" id="importCategoriesModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Import Categories from Excel (CSV)" data-ar="استيراد الفئات من اكسل (CSV)">Import Categories from Excel (CSV)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="alert alert-info py-2">
<small data-en="Please upload an Excel (.xlsx) or CSV file with the following columns: Name (EN), Name (AR)." data-ar="يرجى رفع ملف إكسل (.xlsx) أو CSV بالأعمدة التالية: الاسم (إنجليزي)، الاسم (عربي).">
Please upload an Excel (.xlsx) or CSV file with the following columns: Name (EN), Name (AR).
</small>
</div>
<div class="mb-3">
<label class="form-label" data-en="Choose File" data-ar="اختر الملف">Choose File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv, .xlsx" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="import_categories" class="btn btn-success" data-en="Import Now" data-ar="استيراد الآن">Import Now</button>
</div>
</form>
</div>
</div>
</div>
<!-- Import Units Modal -->
<div class="modal fade" id="importUnitsModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Import Units from Excel (CSV)" data-ar="استيراد الوحدات من اكسل (CSV)">Import Units from Excel (CSV)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="alert alert-info py-2">
<small data-en="Please upload an Excel (.xlsx) or CSV file with the following columns: Name (EN), Name (AR), Short (EN), Short (AR)." data-ar="يرجى رفع ملف إكسل (.xlsx) أو CSV بالأعمدة التالية: الاسم (إنجليزي)، الاسم (عربي)، الاختصار (إنجليزي)، الاختصار (عربي).">
Please upload an Excel (.xlsx) or CSV file with the following columns: Name (EN), Name (AR), Short (EN), Short (AR).
</small>
</div>
<div class="mb-3">
<label class="form-label" data-en="Choose File" data-ar="اختر الملف">Choose File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv, .xlsx" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="import_units" class="btn btn-success" data-en="Import Now" data-ar="استيراد الآن">Import Now</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Category Modal -->
<div class="modal fade" id="addCategoryModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add Category" data-ar="إضافة فئة">Add Category</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="mb-3">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="addCatNameEn" class="form-control" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="addCatNameAr" data-target="addCatNameEn" data-to="en">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="mb-3">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="addCatNameAr" class="form-control" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="addCatNameEn" data-target="addCatNameAr" data-to="ar">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_category" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Unit Modal -->
<div class="modal fade" id="addUnitModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add Unit" data-ar="إضافة وحدة">Add Unit</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</label>
<div class="input-group">
<input type="text" name="name_en" id="addUnitNameEn" class="form-control" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="addUnitNameAr" data-target="addUnitNameEn" data-to="en">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Short Name (EN)" data-ar="الاختصار (إنجليزي)">Short (EN)</label>
<input type="text" name="short_en" class="form-control" required placeholder="e.g. Kg">
</div>
<div class="col-md-6">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label>
<div class="input-group">
<input type="text" name="name_ar" id="addUnitNameAr" class="form-control" required>
<button class="btn btn-outline-secondary btn-translate" type="button" data-source="addUnitNameEn" data-target="addUnitNameAr" data-to="ar">
<i class="bi bi-translate"></i>
</button>
</div>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Short Name (AR)" data-ar="الاختصار (عربي)">Short (AR)</label>
<input type="text" name="short_ar" class="form-control" required placeholder="مثلاً: كجم">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_unit" class="btn btn-primary" data-en="Save" data-ar="حفظ">Save</button>
</div>
</form>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log("DOM Content Loaded - Accounting System");
try {
// Initialize Select2 for all searchable dropdowns
$('.select2').each(function() {
$(this).select2({
width: '100%',
dropdownParent: $(this).closest('.modal').length ? $(this).closest('.modal') : $(document.body)
});
});
const hasExpiryToggle = document.getElementById('hasExpiryToggle');
const expiryDateContainer = document.getElementById('expiryDateContainer');
const suggestSkuBtn = document.getElementById('suggestSkuBtn');
const skuInput = document.getElementById('skuInput');
if (suggestSkuBtn && skuInput) {
suggestSkuBtn.addEventListener('click', function() {
const sku = Math.floor(100000000000 + Math.random() * 900000000000).toString();
skuInput.value = sku;
});
skuInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
console.log("Barcode scan detected in SKU field, preventing form submission");
}
});
}
// Toggle Expiry Date visibility
if (hasExpiryToggle && expiryDateContainer) {
hasExpiryToggle.addEventListener('change', function() {
expiryDateContainer.style.display = this.checked ? 'block' : 'none';
});
}
const isPromotionToggle = document.getElementById('isPromotionToggle');
const promotionFieldsContainer = document.getElementById('promotionFieldsContainer');
if (isPromotionToggle && promotionFieldsContainer) {
isPromotionToggle.addEventListener('change', function() {
promotionFieldsContainer.style.display = this.checked ? 'flex' : 'none';
});
}
// Translation Logic
document.querySelectorAll('.btn-translate').forEach(btn => {
btn.addEventListener('click', function() {
const sourceId = this.getAttribute('data-source');
const targetId = this.getAttribute('data-target');
const targetLang = this.getAttribute('data-to');
const sourceText = document.getElementById(sourceId).value;
if (!sourceText) {
alert(targetLang === 'ar' ? 'يرجى إدخال النص أولاً' : 'Please enter text first');
return;
}
const originalHtml = this.innerHTML;
this.disabled = true;
this.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
const formData = new FormData();
formData.append('action', 'translate');
formData.append('text', sourceText);
formData.append('target', targetLang);
fetch('index.php', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
if (data.success) {
document.getElementById(targetId).value = data.translated;
} else {
alert('Translation error: ' + (data.error || 'Unknown error'));
}
})
.catch(err => {
console.error(err);
alert('Connection error');
})
.finally(() => {
this.disabled = false;
this.innerHTML = originalHtml;
});
});
});
document.querySelectorAll('.isPromotionToggleEdit').forEach(toggle => {
toggle.addEventListener('change', function() {
const id = this.getAttribute('data-id');
const container = document.getElementById('promotionFieldsContainerEdit' + id);
if (container) {
container.style.display = this.checked ? 'flex' : 'none';
}
});
});
// Status change logic for Paid Amount field
const togglePaidAmount = (statusId, containerId) => {
const statusEl = document.getElementById(statusId);
const containerEl = document.getElementById(containerId);
if (statusEl && containerEl) {
statusEl.addEventListener('change', function() {
if (this.value === 'partially_paid') {
containerEl.style.display = 'block';
} else {
containerEl.style.display = 'none';
}
});
}
};
togglePaidAmount('add_status', 'addPaidAmountContainer');
togglePaidAmount('edit_status', 'editPaidAmountContainer');
// Pay Invoice Logic
document.querySelectorAll('.pay-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.getAttribute('data-id');
const total = parseFloat(this.getAttribute('data-total'));
const paid = parseFloat(this.getAttribute('data-paid') || 0);
const remaining = total - paid;
document.getElementById('pay_invoice_id').value = id;
document.getElementById('pay_invoice_total').value = total.toFixed(3);
document.getElementById('pay_remaining_amount').value = remaining.toFixed(3);
document.getElementById('pay_amount').value = remaining.toFixed(3);
document.getElementById('pay_amount').max = remaining.toFixed(3);
});
});
// Show receipt modal if needed
<?php if (isset($_SESSION['trigger_receipt_modal'])):
$rid = (int)$_SESSION['show_receipt_id'];
unset($_SESSION['trigger_receipt_modal']);
?>
if (typeof showReceipt === 'function') {
showReceipt(<?= $rid ?>);
} else {
setTimeout(() => { if (typeof showReceipt === 'function') showReceipt(<?= $rid ?>); }, 500);
}
<?php endif; ?>
function showReceipt(paymentId) {
fetch(`index.php?action=get_payment_details&payment_id=${paymentId}`)
.then(res => res.json())
.then(data => {
if (!data) return;
document.getElementById('receiptNo').textContent = 'RCP-' + data.id.toString().padStart(5, '0');
document.getElementById('receiptDate').textContent = data.payment_date;
document.getElementById('receiptCustomer').textContent = data.customer_name || '---';
document.getElementById('receiptInvNo').textContent = (data.inv_type === 'purchase' ? 'PUR-' : 'INV-') + data.inv_id.toString().padStart(5, '0');
document.getElementById('receiptMethod').textContent = data.payment_method;
document.getElementById('receiptAmount').textContent = parseFloat(data.amount).toFixed(3);
document.getElementById('receiptAmountWords').textContent = data.amount_words;
const rcn = document.getElementById('receiptCompanyName');
if(rcn) rcn.textContent = data.outlet_name || "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const rca = document.getElementById('receiptCompanyAddress');
if(rca) {
rca.textContent = data.outlet_address || (typeof companySettings !== 'undefined' ? companySettings.company_address : '') || '';
rca.style.whiteSpace = 'pre-line';
}
// Update labels for Purchase vs Sale
const partyLabel = document.getElementById('receiptPartyLabel');
const againstLabel = document.getElementById('receiptAgainstLabel');
const receiptTitle = document.querySelector('#receiptModal .modal-title');
const receiptH4 = document.querySelector('.receipt-container h4.letter-spacing-2');
if (data.inv_type === 'purchase') {
partyLabel.textContent = 'Paid To';
partyLabel.setAttribute('data-en', 'Paid To');
partyLabel.setAttribute('data-ar', 'صرف إلى');
againstLabel.textContent = 'Against Purchase';
againstLabel.setAttribute('data-en', 'Against Purchase');
againstLabel.setAttribute('data-ar', 'مقابل شراء');
receiptTitle.textContent = 'Payment Voucher';
receiptTitle.setAttribute('data-en', 'Payment Voucher');
receiptTitle.setAttribute('data-ar', 'سند صرف');
receiptH4.textContent = 'PAYMENT VOUCHER';
receiptH4.setAttribute('data-en', 'PAYMENT VOUCHER');
receiptH4.setAttribute('data-ar', 'سند صرف');
} else {
partyLabel.textContent = 'Received From';
partyLabel.setAttribute('data-en', 'Received From');
partyLabel.setAttribute('data-ar', 'وصلنا من');
againstLabel.textContent = 'Against Invoice';
againstLabel.setAttribute('data-en', 'Against Invoice');
againstLabel.setAttribute('data-ar', 'مقابل فاتورة');
receiptTitle.textContent = 'Payment Receipt';
receiptTitle.setAttribute('data-en', 'Payment Receipt');
receiptTitle.setAttribute('data-ar', 'سند قبض');
receiptH4.textContent = 'PAYMENT RECEIPT';
receiptH4.setAttribute('data-en', 'PAYMENT RECEIPT');
receiptH4.setAttribute('data-ar', 'سند قبض');
}
const notesContainer = document.getElementById('receiptNotesContainer');
if (data.notes) {
document.getElementById('receiptNotes').textContent = data.notes;
notesContainer.style.display = 'block';
} else {
notesContainer.style.display = 'none';
}
const receiptModal = new bootstrap.Modal(document.getElementById('receiptModal'));
receiptModal.show();
});
};
document.querySelectorAll('.view-payments-btn').forEach(btn => {
btn.addEventListener('click', function() {
const invoiceId = this.getAttribute('data-id');
const tbody = document.getElementById('paymentsTableBody');
tbody.innerHTML = '<tr><td colspan="5" class="text-center" data-en="Loading..." data-ar="جاري التحميل...">Loading...</td></tr>';
fetch(`index.php?action=get_payments&invoice_id=${invoiceId}`)
.then(res => res.json())
.then(data => {
tbody.innerHTML = '';
if (data.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center" data-en="No payments found." data-ar="لا توجد مدفوعات.">No payments found.</td></tr>';
return;
}
data.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>RCP-${p.id.toString().padStart(5, '0')}</td>
<td>${p.payment_date}</td>
<td>${p.payment_method}</td>
<td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>
<td class="text-end">
<button class="btn btn-sm btn-outline-primary" onclick="showReceipt(${p.id})">
<i class="bi bi-printer"></i>
</button>
</td>
`;
tbody.appendChild(tr);
});
});
});
});
window.printReceipt = function() {
const content = document.getElementById('printableReceipt').innerHTML;
const originalContent = document.body.innerHTML;
document.body.innerHTML = content;
window.print();
document.body.innerHTML = originalContent;
window.location.reload();
};
// Handle Expiry toggle in Edit Modals
document.querySelectorAll('.hasExpiryToggleEdit').forEach(toggle => {
toggle.addEventListener('change', function() {
const container = this.closest('.row').querySelector('.expiryDateContainerEdit');
if (container) {
container.style.display = this.checked ? 'block' : 'none';
if (!this.checked) {
container.querySelector('input').value = '';
}
}
});
});
// Invoice Form Logic
const initInvoiceForm = (searchInputId, suggestionsId, tableBodyId, grandTotalId, subtotalId, totalVatId) => {
const searchInput = document.getElementById(searchInputId);
const suggestions = document.getElementById(suggestionsId);
const tableBody = document.getElementById(tableBodyId);
const grandTotalEl = document.getElementById(grandTotalId);
const subtotalEl = document.getElementById(subtotalId);
const totalVatEl = document.getElementById(totalVatId);
if (!searchInput || !tableBody) return;
let timeout = null;
searchInput.addEventListener('input', function() {
clearTimeout(timeout);
const q = this.value.trim();
if (q.length < 2) {
suggestions.style.display = 'none';
return;
}
timeout = setTimeout(() => {
fetch(`index.php?action=search_items&q=${encodeURIComponent(q)}`)
.then(res => res.ok ? res.json() : [])
.then(data => {
suggestions.innerHTML = '';
if (data.length > 0) {
data.forEach(item => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'list-group-item list-group-item-action';
btn.innerHTML = `
<div class="d-flex justify-content-between">
<span><strong>${item.sku}</strong> - ${item.name_en} / ${item.name_ar}</span>
<span class="text-muted small">Stock: ${item.stock_quantity}</span>
</div>
`;
btn.onclick = () => addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl);
suggestions.appendChild(btn);
});
suggestions.style.display = 'block';
} else {
suggestions.style.display = 'none';
}
});
}, 300);
});
// Close suggestions when clicking outside
document.addEventListener('click', function(e) {
if (!searchInput.contains(e.target) && !suggestions.contains(e.target)) {
suggestions.style.display = 'none';
}
});
};
function addItemToTable(item, tableBody, searchInput, suggestions, grandTotalEl, subtotalEl, totalVatEl, customData = null) {
if (suggestions) suggestions.style.display = 'none';
if (searchInput) searchInput.value = '';
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
const currentStock = parseFloat(item.stock_quantity) || 0;
if (invoiceType === 'sale' && !allowZeroStock && !customData) {
const existingInTable = Array.from(tableBody.querySelectorAll('.item-row')).find(row => row.querySelector('.item-id-input').value == item.id);
let currentQtyInTable = 0;
if (existingInTable) {
currentQtyInTable = parseFloat(existingInTable.querySelector('.item-qty').value) || 0;
}
if (currentQtyInTable + 1 > currentStock) {
alert('Insufficient stock! Available: ' + currentStock);
return;
}
}
const existingRow = Array.from(tableBody.querySelectorAll('.item-id-input')).find(input => input.value == item.id);
if (existingRow && !customData) {
const row = existingRow.closest('tr');
const qtyInput = row.querySelector('.item-qty');
qtyInput.value = parseFloat(qtyInput.value) + 1;
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
return;
}
const row = document.createElement('tr');
row.className = 'item-row';
const price = customData ? customData.unit_price : (invoiceType === 'sale' ? item.sale_price : item.purchase_price);
const qty = customData ? customData.quantity : 1;
const vatRate = item.vat_rate || 0;
row.innerHTML = `
<td>
<input type="hidden" name="item_ids[]" class="item-id-input" value="${item.id}">
<input type="hidden" class="item-row-stock" value="${item.stock_quantity}">
<input type="hidden" class="item-vat-rate" value="${vatRate}">
<div><strong>${item.name_en}</strong></div>
<div class="small text-muted">${item.name_ar} (${item.sku})</div>
</td>
<td><input type="number" step="0.001" name="quantities[]" class="form-control item-qty" value="${qty}" required></td>
<td><input type="number" step="0.001" name="prices[]" class="form-control item-price" value="${price}" required></td>
<td><input type="text" class="form-control bg-light" value="${parseFloat(vatRate || 0).toFixed(2)}%" readonly></td>
<td><input type="number" step="0.001" class="form-control item-total" value="${(qty * price).toFixed(3)}" readonly></td>
<td><button type="button" class="btn btn-outline-danger btn-sm remove-row"><i class="bi bi-trash"></i></button></td>
`;
tableBody.appendChild(row);
attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl);
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
}
function recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl) {
let subtotal = 0;
let totalVat = 0;
tableBody.querySelectorAll('.item-row').forEach(row => {
const qty = parseFloat(row.querySelector('.item-qty').value) || 0;
const price = parseFloat(row.querySelector('.item-price').value) || 0;
const vatRate = parseFloat(row.querySelector('.item-vat-rate').value) || 0;
const total = qty * price;
const vatAmount = total * (vatRate / 100);
row.querySelector('.item-total').value = total.toFixed(3);
subtotal += total;
totalVat += vatAmount;
});
const grandTotal = subtotal + totalVat;
if (subtotalEl) subtotalEl.textContent = 'OMR ' + subtotal.toFixed(3);
if (totalVatEl) totalVatEl.textContent = 'OMR ' + totalVat.toFixed(2);
if (grandTotalEl) grandTotalEl.textContent = 'OMR ' + grandTotal.toFixed(3);
}
function attachRowListeners(row, tableBody, grandTotalEl, subtotalEl, totalVatEl) {
row.querySelector('.item-qty').addEventListener('input', function() {
const allowZeroStock = (typeof companySettings !== 'undefined' && String(companySettings.allow_zero_stock_sell) === '1');
if (invoiceType === 'sale' && !allowZeroStock) {
const stock = parseFloat(row.querySelector('.item-row-stock').value) || 0;
const qty = parseFloat(this.value) || 0;
if (qty > stock) {
alert('Insufficient stock! Available: ' + stock);
this.value = stock;
}
}
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
row.querySelector('.item-price').addEventListener('input', () => recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl));
row.querySelector('.remove-row').addEventListener('click', function() {
row.remove();
recalculate(tableBody, grandTotalEl, subtotalEl, totalVatEl);
});
}
const invoiceType = '<?= in_array($page, ["sales", "quotations"]) ? "sale" : (in_array($page, ["purchases", "lpos"]) ? "purchase" : "") ?>';
initInvoiceForm('productSearchInput', 'searchSuggestions', 'invoiceItemsTableBody', 'grandTotal', 'subtotal', 'totalVat');
initInvoiceForm('editProductSearchInput', 'editSearchSuggestions', 'editInvoiceItemsTableBody', 'edit_grandTotal', 'edit_subtotal', 'edit_totalVat');
// LPO Form Logic
initInvoiceForm('lpoProductSearchInput', 'lpoSearchSuggestions', 'lpoItemsTableBody', 'lpo_grand_display', 'lpo_subtotal_display', 'lpo_vat_display');
initInvoiceForm('editLpoProductSearchInput', 'editLpoSearchSuggestions', 'editLpoItemsTableBody', 'edit_lpo_grand_display', 'edit_lpo_subtotal_display', 'edit_lpo_vat_display');
document.querySelectorAll('.edit-lpo-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_lpo_id').value = data.id;
const supplierSelect = document.getElementById('edit_lpo_supplier_id');
supplierSelect.value = data.supplier_id;
if (window.jQuery && $(supplierSelect).data('select2')) {
$(supplierSelect).trigger('change');
}
document.getElementById('edit_lpo_date').value = data.lpo_date;
document.getElementById('edit_lpo_delivery_date').value = data.delivery_date || '';
document.getElementById('edit_lpo_status').value = data.status || 'pending';
document.getElementById('edit_lpo_terms').value = data.terms_conditions || '';
const tableBody = document.getElementById('editLpoItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_lpo_grand_display'),
document.getElementById('edit_lpo_subtotal_display'),
document.getElementById('edit_lpo_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
document.querySelectorAll('.view-lpo-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
window.viewAndPrintLPO(data);
});
});
window.viewAndPrintLPO = function(data) {
const modal = new bootstrap.Modal(document.getElementById('viewLpoModal'));
const content = document.getElementById('lpoDetailsContent');
const logoUrl = companySettings.company_logo || '';
const companyHeader = `
<div class="row align-items-center mb-4">
<div class="col-6">
${logoUrl ? `<img src="${logoUrl}" alt="Logo" style="max-height: 80px;" class="mb-3">` : ''}
<h4 class="fw-bold mb-0">${data.outlet_name || companySettings.company_name || 'Your Company'}</h4>
<p class="text-muted mb-0 small">
${data.outlet_address ? data.outlet_address.replace(/\n/g, '<br>') : (companySettings.company_address || '').replace(/\n/g, '<br>')}<br>
Phone: ${data.outlet_phone || companySettings.company_phone || ''} | Email: ${companySettings.company_email || ''}
${companySettings.tax_number ? `<br>TRN: ${companySettings.tax_number}` : ''}
</p>
</div>
<div class="col-6 text-end">
<h2 class="text-primary fw-bold mb-1">LOCAL PURCHASE ORDER</h2>
<p class="h5 mb-0 text-muted">LPO-${data.id.toString().padStart(5, '0')}</p>
</div>
</div>
`;
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small class="text-muted">${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-center">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
<td class="text-end">${parseFloat(item.total_amount).toFixed(3)}</td>
</tr>
`;
});
content.innerHTML = `
${companyHeader}
<hr>
<div class="row mb-4">
<div class="col-6">
<h6 class="text-uppercase text-muted fw-bold mb-3" data-en="Supplier" data-ar="المورد">Supplier</h6>
<p class="h6 mb-1 fw-bold">${data.supplier_name}</p>
<p class="small text-muted mb-0">
${data.supplier_phone ? `Phone: ${data.supplier_phone}` : ''}
</p>
</div>
<div class="col-6 text-end">
<h6 class="text-uppercase text-muted fw-bold mb-3" data-en="Details" data-ar="تفاصيل">Details</h6>
<div class="d-flex justify-content-end mb-1">
<span class="text-muted me-2">Date:</span>
<span class="fw-bold">${data.lpo_date}</span>
</div>
<div class="d-flex justify-content-end mb-1">
<span class="text-muted me-2">Delivery:</span>
<span class="fw-bold">${data.delivery_date || '---'}</span>
</div>
<div class="d-flex justify-content-end">
<span class="text-muted me-2">Status:</span>
<span class="badge ${data.status === 'pending' ? 'bg-warning text-dark' : 'bg-success'}">${data.status.toUpperCase()}</span>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead class="table-dark">
<tr>
<th style="width: 5%">#</th>
<th style="width: 45%" data-en="Description" data-ar="الوصف">Description</th>
<th style="width: 10%" class="text-center" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%" class="text-end">Unit Price</th>
<th style="width: 10%" class="text-center">VAT</th>
<th style="width: 15%" class="text-end" data-en="Total" data-ar="الإجمالي">Total</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot class="table-light">
<tr>
<td colspan="5" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold">OMR ${parseFloat(data.total_amount).toFixed(3)}</td>
</tr>
<tr>
<td colspan="5" class="text-end fw-bold">VAT Amount</td>
<td class="text-end fw-bold">OMR ${parseFloat(data.vat_amount).toFixed(2)}</td>
</tr>
<tr class="table-primary">
<td colspan="5" class="text-end fw-bold h5">Grand Total</td>
<td class="text-end fw-bold h5">OMR ${parseFloat(data.total_with_vat).toFixed(3)}</td>
</tr>
</tfoot>
</table>
</div>
${data.terms_conditions ? `
<div class="card bg-light border-0 mt-4">
<div class="card-body p-3">
<h6 class="fw-bold mb-2 small text-uppercase text-muted">Terms & Conditions</h6>
<p class="small mb-0">${data.terms_conditions.replace(/\n/g, '<br>')}</p>
</div>
</div>` : ''}
<div class="row mt-5 pt-3">
<div class="col-4 text-center">
<div style="border-top: 1px solid #dee2e6; padding-top: 10px;">
<p class="small mb-0">Prepared By</p>
</div>
</div>
<div class="col-4"></div>
<div class="col-4 text-center">
<div style="border-top: 1px solid #dee2e6; padding-top: 10px;">
<p class="small mb-0">Authorized Signature</p>
</div>
</div>
</div>
`;
window.printLPO = function() {
const printWindow = window.open('', '_blank');
printWindow.document.write('<html><head><title>LPO-' + data.id + '</title>');
printWindow.document.write('<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap<?= $dir === 'rtl' ? '.rtl' : '' ?>.min.css" rel="stylesheet">');
printWindow.document.write('<style>body { padding: 40px; } @media print { .no-print { display: none; } }</style>');
printWindow.document.write('</head><body>');
printWindow.document.write(content.innerHTML);
printWindow.document.write('</body></html>');
printWindow.document.close();
setTimeout(() => {
printWindow.print();
printWindow.close();
}, 500);
};
modal.show();
};
// Quotation Form Logic
initInvoiceForm('quotProductSearchInput', 'quotSearchSuggestions', 'quotItemsTableBody', 'quot_grand_display', 'quot_subtotal_display', 'quot_vat_display');
initInvoiceForm('editQuotProductSearchInput', 'editQuotSearchSuggestions', 'editQuotItemsTableBody', 'edit_quot_grand_display', 'edit_quot_subtotal_display', 'edit_quot_vat_display');
document.querySelectorAll('.edit-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_quotation_id').value = data.id;
document.getElementById('edit_quot_customer_id').value = data.customer_id;
document.getElementById('edit_quot_date').value = data.quotation_date;
document.getElementById('edit_quot_valid').value = data.valid_until || '';
document.getElementById('edit_quot_status').value = data.status || 'pending';
const tableBody = document.getElementById('editQuotItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0
}, tableBody, null, null,
document.getElementById('edit_quot_grand_display'),
document.getElementById('edit_quot_subtotal_display'),
document.getElementById('edit_quot_vat_display'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
document.querySelectorAll('.convert-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
if (confirm('Convert this quotation to an invoice? This will reduce stock.')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${this.dataset.id}">`;
document.body.appendChild(f);
f.submit();
}
});
});
// View Quotation Logic
window.viewAndPrintQuotation = function(data, autoPrint = false) {
const modal = new bootstrap.Modal(document.getElementById('viewQuotationModal'));
const content = document.getElementById('quotationPrintableArea');
let itemsHtml = '';
data.items.forEach((item, index) => {
itemsHtml += `
<tr>
<td>${index + 1}</td>
<td>${item.name_en}<br><small>${item.name_ar}</small></td>
<td class="text-center">${item.quantity}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-center">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
</tr>
`;
});
// Company Logo and Header Construction
const logoUrl = companySettings.company_logo || '';
const logoImg = logoUrl ? `<img src="${logoUrl}" alt="Logo" class="invoice-logo mb-3">` : '';
const companyName = data.outlet_name || companySettings.company_name || 'Accounting System';
const companyAddress = data.outlet_address ? data.outlet_address.replace(/\n/g, '<br>') : (companySettings.company_address || '').replace(/\n/g, '<br>');
const companyVat = companySettings.vat_number ? `<p class="text-muted small mb-0">VAT: ${companySettings.vat_number}</p>` : '';
const companyPhone = data.outlet_phone ? `<p class="text-muted small mb-0">Tel: ${data.outlet_phone}</p>` : (companySettings.company_phone ? `<p class="text-muted small mb-0">Tel: ${companySettings.company_phone}</p>` : '');
// Quotation Header Construction
const quotDate = data.quotation_date;
const quotValid = data.valid_until || 'N/A';
const quotNo = 'QUO-' + data.id.toString().padStart(5, '0');
const customerName = data.customer_name || 'Walk-in Customer';
const statusBadge = `<span class="badge ${data.status === 'converted' ? 'bg-success' : 'bg-secondary'}">${data.status.toUpperCase()}</span>`;
content.innerHTML = `
<div class="p-5">
<div class="invoice-header mb-4">
<div class="row align-items-center">
<div class="col-6">
${logoImg}
<h3 class="mb-1 fw-bold">${companyName}</h3>
<p class="text-muted small mb-0">${companyAddress}</p>
${companyVat}
${companyPhone}
</div>
<div class="col-6 text-end">
<h1 class="invoice-title fw-bold mb-0 text-uppercase">Quotation / عرض سعر</h1>
<div class="mt-2">${statusBadge}</div>
<div class="mt-3">
<p class="mb-0 fs-5">No / رقم: <strong class="text-primary">${quotNo}</strong></p>
<p class="mb-0">Date / التاريخ: <span class="fw-bold">${quotDate}</span></p>
<p class="mb-0">Valid Until / صالح لغاية: <span class="fw-bold">${quotValid}</span></p>
</div>
</div>
</div>
</div>
<div class="row mb-4 g-3">
<div class="col-6">
<div class="invoice-info-card">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1">To / إلى</p>
<h5 class="mb-1 fw-bold">${customerName}</h5>
</div>
</div>
</div>
<table class="table table-bordered table-formal">
<thead class="bg-dark text-white">
<tr>
<th>#</th>
<th>Item Description / وصف الصنف</th>
<th class="text-center">Qty / الكمية</th>
<th class="text-end">Unit Price / سعر الوحدة</th>
<th class="text-center">VAT / الضريبة</th>
<th class="text-end">Total / الإجمالي</th>
</tr>
</thead>
<tbody>${itemsHtml}</tbody>
<tfoot>
<tr>
<th colspan="5" class="text-end">Subtotal / المجموع الفرعي</th>
<td class="text-end fw-bold">${parseFloat(data.total_amount).toFixed(3)}</td>
</tr>
<tr>
<th colspan="5" class="text-end">VAT Amount / مبلغ الضريبة</th>
<td class="text-end fw-bold">${parseFloat(data.vat_amount).toFixed(2)}</td>
</tr>
<tr class="table-primary">
<th colspan="5" class="text-end h5">Grand Total (OMR) / المجموع الكلي (رع)</th>
<td class="text-end h5 fw-bold">${parseFloat(data.total_with_vat).toFixed(3)}</td>
</tr>
</tfoot>
</table>
<div class="mt-5 pt-3 border-top">
<div class="row">
<div class="col-6">
<p class="small text-muted text-uppercase fw-bold">Terms & Conditions / الشروط والأحكام:</p>
<ul class="small text-muted">
<li>Quotation is valid until the date mentioned above. / عرض السعر صالح لغاية التاريخ المذكور أعلاه.</li>
<li>Prices are inclusive of VAT where applicable. / الأسعار تشمل ضريبة القيمة المضافة حيثما ينطبق ذلك.</li>
</ul>
</div>
<div class="col-6 text-end pt-4">
<div class="border-top d-inline-block px-5">Authorized Signature / التوقيع المعتمد</div>
</div>
</div>
</div>
<div class="mt-4 text-center">
<p class="text-muted x-small mb-0">Generated by / تم إنشاؤه بواسطة ${companyName}</p>
</div>
</div>
`;
const actionButtons = document.getElementById('quotationActionButtons');
actionButtons.innerHTML = '';
if (data.status === 'pending') {
const convertBtn = document.createElement('button');
convertBtn.className = 'btn btn-success me-2';
convertBtn.innerHTML = '<i class="bi bi-receipt"></i> <span data-en="Convert to Invoice" data-ar="تحويل إلى فاتورة">Convert to Invoice</span>';
convertBtn.onclick = function() {
if (confirm('Convert this quotation to an invoice?')) {
const f = document.createElement('form');
f.method = 'POST';
f.innerHTML = `<input type="hidden" name="convert_to_invoice" value="1"><input type="hidden" name="quotation_id" value="${data.id}">`;
document.body.appendChild(f);
f.submit();
}
};
actionButtons.appendChild(convertBtn);
const editBtn = document.createElement('button');
editBtn.className = 'btn btn-primary';
editBtn.innerHTML = '<i class="bi bi-pencil-square"></i> <span data-en="Edit" data-ar="تعديل">Edit</span>';
editBtn.onclick = function() {
const editModal = new bootstrap.Modal(document.getElementById('editQuotationModal'));
modal.hide();
const originalEditBtn = document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id},']`) ||
document.querySelector(`.edit-quotation-btn[data-json*='"id":${data.id}']`);
if (originalEditBtn) originalEditBtn.click();
};
actionButtons.appendChild(editBtn);
}
modal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 500);
}
};
document.querySelectorAll('.view-quotation-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
window.viewAndPrintQuotation(data, false);
});
});
// Edit Invoice Logic
document.querySelectorAll('.edit-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const data = JSON.parse(this.dataset.json);
document.getElementById('edit_invoice_id').value = data.id;
document.getElementById('edit_customer_id').value = data.customer_id;
document.getElementById('edit_invoice_date').value = data.invoice_date;
document.getElementById('edit_due_date').value = data.due_date || '';
document.getElementById('edit_payment_type').value = data.payment_type || 'cash';
document.getElementById('edit_status').value = data.status || 'unpaid';
document.getElementById('edit_paid_amount').value = parseFloat(data.paid_amount || 0).toFixed(3);
if (data.status === 'partially_paid') {
document.getElementById('editPaidAmountContainer').style.display = 'block';
} else {
document.getElementById('editPaidAmountContainer').style.display = 'none';
}
const tableBody = document.getElementById('editInvoiceItemsTableBody');
tableBody.innerHTML = '';
data.items.forEach(item => {
// We need more data than what's in invoice_items (like SKU and names, but we have them from the join in PHP)
// The dataset-json already contains name_en, name_ar etc because of the PHP logic at line 1093
const itemMeta = {
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '', // Optional, or fetch if needed
vat_rate: 0 // Will be handled if we have it in the join
};
// Fetch current item details to get VAT rate if possible, or use stored if available
// For simplicity, let's assume we want to use the item's current VAT rate or store it.
// Looking at the join at line 1093, it doesn't fetch vat_rate. Let's fix that in PHP too.
addItemToTable({
id: item.item_id,
name_en: item.name_en,
name_ar: item.name_ar,
sku: '',
vat_rate: item.vat_rate || 0 // We'll add this to PHP join
}, tableBody, null, null,
document.getElementById('edit_grandTotal'),
document.getElementById('edit_subtotal'),
document.getElementById('edit_totalVat'),
{ quantity: item.quantity, unit_price: item.unit_price });
});
});
});
// View and Print Invoice Logic
document.addEventListener('click', function(e) {
if (e.target.closest('.view-invoice-btn')) {
const btn = e.target.closest('.view-invoice-btn');
const data = JSON.parse(btn.dataset.json);
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(data, false);
}
}
if (e.target.closest('.print-a4-btn')) {
const btn = e.target.closest('.print-a4-btn');
const data = JSON.parse(btn.dataset.json);
if (window.viewAndPrintA4Invoice) {
window.viewAndPrintA4Invoice(data, true);
}
}
});
// Return Logic (General for Sales and Purchase)
const setupReturnLogic = (selectId, containerId, tbodyId, totalDisplayId, submitBtnId, type = "sale") => {
const select = document.getElementById(selectId);
if (!select) return;
const calculateTotal = function() {
let total = 0;
document.querySelectorAll('#' + tbodyId + ' tr').forEach(row => {
const qtyInput = row.querySelector('.return-qty-input');
if (!qtyInput) return;
const qty = parseFloat(qtyInput.value) || 0;
const price = parseFloat(qtyInput.dataset.price) || 0;
const lineTotal = qty * price;
const lineTotalDisplay = row.querySelector('.line-total');
if (lineTotalDisplay) {
lineTotalDisplay.innerText = lineTotal.toFixed(3);
}
total += lineTotal;
});
const totalDisplay = document.getElementById(totalDisplayId);
if (totalDisplay) {
totalDisplay.innerText = 'OMR ' + total.toFixed(3);
}
const submitBtn = document.getElementById(submitBtnId);
if (submitBtn) {
submitBtn.disabled = total <= 0;
}
};
const handleInvoiceChange = async function() {
const invoiceId = select.value;
const container = document.getElementById(containerId);
const tbody = document.getElementById(tbodyId);
const submitBtn = document.getElementById(submitBtnId);
if (!invoiceId) {
if (container) container.style.display = 'none';
if (submitBtn) submitBtn.disabled = true;
return;
}
if (tbody) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center"><div class="spinner-border spinner-border-sm text-primary"></div> <span data-en="Loading items..." data-ar="جاري تحميل الأصناف...">Loading items...</span></td></tr>';
}
if (container) container.style.display = 'block';
try {
const resp = await fetch(`index.php?action=get_invoice_items&invoice_id=${invoiceId}&type=${type}`);
const items = await resp.json();
if (tbody) {
tbody.innerHTML = '';
if (items.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted" data-en="No items found for this invoice." data-ar="لا توجد أصناف لهذه الفاتورة.">No items found for this invoice.</td></tr>';
} else {
let html = '';
items.forEach(item => {
html += `
<tr>
<td>${item.name_en}<br><small class="text-muted">${item.sku}</small></td>
<td>${parseFloat(item.quantity).toFixed(2)}</td>
<td>
<input type="number" name="quantities[]" class="form-control form-control-sm return-qty-input"
step="0.01" min="0" max="${item.quantity}" value="0"
data-price="${item.unit_price}">
<input type="hidden" name="item_ids[]" value="${item.item_id}">
<input type="hidden" name="prices[]" value="${item.unit_price}">
</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end line-total">0.000</td>
</tr>
`;
});
tbody.innerHTML = html;
}
}
if (submitBtn) submitBtn.disabled = true;
const qtyInputs = tbody.querySelectorAll('.return-qty-input');
qtyInputs.forEach(input => {
['input', 'change', 'keyup'].forEach(evt => input.addEventListener(evt, calculateTotal));
});
calculateTotal();
} catch (e) {
console.error(e);
if (window.Swal) Swal.fire('Error', 'Failed to fetch invoice items', 'error');
}
};
select.addEventListener('change', handleInvoiceChange);
if (window.jQuery && jQuery.fn.select2) {
$(select).on('select2:select change', handleInvoiceChange);
}
};
setupReturnLogic('return_invoice_select', 'return_items_container', 'return_items_tbody', 'return_total_display', 'submit_return_btn', 'sale');
setupReturnLogic('purchase_return_invoice_select', 'purchase_return_items_container', 'purchase_return_items_tbody', 'purchase_return_total_display', 'purchase_submit_return_btn', 'purchase');
// Return Invoice Button from Sales/Purchases list
document.querySelectorAll('.return-invoice-btn').forEach(btn => {
btn.addEventListener('click', function() {
const invoiceId = this.dataset.id;
const targetModal = this.dataset.bsTarget;
const selectId = targetModal === '#addSalesReturnModal' ? 'return_invoice_select' : 'purchase_return_invoice_select';
const select = document.getElementById(selectId);
if (select) {
$(select).val(invoiceId).trigger('change');
}
});
});
// View Return Logic
document.querySelectorAll('.view-return-btn').forEach(btn => {
btn.addEventListener('click', async function() {
const returnId = this.dataset.id;
const type = '<?= $page === "purchase_returns" ? "purchase" : "sale" ?>';
const modal = new bootstrap.Modal(document.getElementById('viewReturnDetailsModal'));
try {
const resp = await fetch(`index.php?action=get_return_details&return_id=${returnId}&type=${type}`);
const data = await resp.json();
if (data) {
document.getElementById('view_return_no').innerText = (type === 'purchase' ? 'PRET-' : 'SRET-') + String(data.id).padStart(5, '0');
document.getElementById('view_return_party').innerText = data.party_name;
document.getElementById('view_return_date').innerText = data.return_date;
const refId = data.purchase_id || data.invoice_id;
const refPrefix = type === 'purchase' ? 'PUR-' : 'INV-';
document.getElementById('view_return_invoice').innerText = refPrefix + String(refId).padStart(5, '0');
document.getElementById('view_return_total').innerText = 'OMR ' + parseFloat(data.total_amount).toFixed(3);
document.getElementById('view_return_notes').innerText = data.notes || 'No notes';
let itemsHtml = '';
data.items.forEach(item => {
itemsHtml += `
<tr>
<td>${item.name_en}<br><small class="text-muted">${item.sku}</small></td>
<td class="text-center">${parseFloat(item.quantity).toFixed(2)}</td>
<td class="text-end">${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.total_price).toFixed(3)}</td>
</tr>
`;
});
document.getElementById('view_return_items_tbody').innerHTML = itemsHtml;
modal.show();
}
} catch (e) {
console.error(e);
Swal.fire('Error', 'Failed to fetch return details', 'error');
}
});
});
} catch (e) { console.error("JS Error in DOMContentLoaded:", e); }
});
</script>
<!-- View Return Details Modal -->
<div class="modal fade" id="viewReturnDetailsModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content text-start border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Return Details <span id="view_return_no"></span></h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-4">
<div class="col-6">
<label class="text-muted small d-block">Party Name</label>
<div id="view_return_party" class="fw-bold"></div>
</div>
<div class="col-3">
<label class="text-muted small d-block">Return Date</label>
<div id="view_return_date"></div>
</div>
<div class="col-3">
<label class="text-muted small d-block">Against Invoice</label>
<div id="view_return_invoice"></div>
</div>
</div>
<h6>Returned Items</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="bg-light">
<tr>
<th>Item</th>
<th class="text-center">Returned Qty</th>
<th class="text-end">Unit Price</th>
<th class="text-end">Total Price</th>
</tr>
</thead>
<tbody id="view_return_items_tbody"></tbody>
<tfoot>
<tr>
<th colspan="3" class="text-end">Total Amount:</th>
<th class="text-end fw-bold" id="view_return_total"></th>
</tr>
</tfoot>
</table>
</div>
<div class="mt-3">
<label class="text-muted small d-block">Notes</label>
<div id="view_return_notes" class="p-2 bg-light rounded"></div>
</div>
</div>
</div>
</div>
</div>
<?php if ($page === 'sales_returns' || $page === 'purchase_returns' || $page === 'sales' || $page === 'purchases'): ?>
<div class="modal fade" id="addPurchaseReturnModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content text-start border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Process Purchase Return</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="row g-3 mb-4 text-start">
<div class="col-md-6">
<label class="form-label" data-en="Select Invoice" data-ar="اختر الفاتورة">Select Invoice</label>
<select name="invoice_id" id="purchase_return_invoice_select" class="form-select select2" required>
<option value="">Choose Invoice...</option>
<?php if (!empty($data['purchase_invoices'])): ?>
<?php foreach ($data['purchase_invoices'] as $inv): ?>
<option value="<?= $inv['id'] ?>">PUR-<?= str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?> (<?= $inv['invoice_date'] ?>) - OMR <?= number_format((float)$inv['total_with_vat'], 3) ?></option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Return Date</label>
<input type="date" name="return_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
</div>
<div id="purchase_return_items_container" style="display:none;" class="text-start">
<h6>Items for Return</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="bg-light">
<tr>
<th>Item</th>
<th>Purchased Qty</th>
<th>Return Qty</th>
<th>Price</th>
<th data-en="Total" data-ar="الإجمالي">Total</th>
</tr>
</thead>
<tbody id="purchase_return_items_tbody"></tbody>
<tfoot>
<tr>
<th colspan="4" class="text-end">Total Return Amount:</th>
<th class="text-end" id="purchase_return_total_display"><?= __('currency') ?> 0.000</th>
</tr>
</tfoot>
</table>
</div>
<div class="mb-3">
<label class="form-label">Notes / Reason for Return</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_purchase_return" class="btn btn-danger" id="purchase_submit_return_btn" disabled>Process Return</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Sales Return Modal -->
<div class="modal fade" id="addSalesReturnModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content text-start border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Process Sales Return</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="row g-3 mb-4 text-start">
<div class="col-md-6">
<label class="form-label" data-en="Select Invoice" data-ar="اختر الفاتورة">Select Invoice</label>
<select name="invoice_id" id="return_invoice_select" class="form-select select2" required>
<option value="">Choose Invoice...</option>
<?php if (!empty($data['sales_invoices'])): ?>
<?php foreach ($data['sales_invoices'] as $inv): ?>
<option value="<?= $inv['id'] ?>">INV-<?= str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT) ?> (<?= $inv['invoice_date'] ?>) - OMR <?= number_format((float)$inv['total_with_vat'], 3) ?></option>
<?php endforeach; ?>
<?php endif; ?>
</select>
</div>
<div class="col-md-6">
<label class="form-label">Return Date</label>
<input type="date" name="return_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
</div>
<div id="return_items_container" style="display:none;" class="text-start">
<h6>Items for Return</h6>
<div class="table-responsive">
<table class="table table-sm table-bordered">
<thead class="bg-light">
<tr>
<th>Item</th>
<th>Sold Qty</th>
<th>Return Qty</th>
<th>Price</th>
<th data-en="Total" data-ar="الإجمالي">Total</th>
</tr>
</thead>
<tbody id="return_items_tbody"></tbody>
<tfoot>
<tr>
<th colspan="4" class="text-end">Total Return Amount:</th>
<th class="text-end" id="return_total_display"><?= __('currency') ?> 0.000</th>
</tr>
</tfoot>
</table>
</div>
<div class="mb-3">
<label class="form-label">Notes / Reason for Return</label>
<textarea name="notes" class="form-control" rows="2"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_sales_return" class="btn btn-danger" id="submit_return_btn" disabled>Process Return</button>
</div>
</form>
</div>
</div>
</div>
<?php endif; ?>
<!-- Add Account Modal -->
<div class="modal fade" id="addAccountModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white" data-en="Add New Account" data-ar="إضافة حساب جديد">Add New Account</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="row g-3">
<div class="col-md-4">
<label class="form-label" data-en="Code" data-ar="الكود">Code</label>
<input type="text" name="code" class="form-control" required placeholder="e.g. 1101">
</div>
<div class="col-md-8">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">Name (EN)</label>
<input type="text" name="name_en" class="form-control" required>
</div>
<div class="col-md-12">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">Name (AR)</label>
<input type="text" name="name_ar" class="form-control" required>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Type" data-ar="النوع">Type</label>
<select name="type" class="form-select" required>
<option value="asset">Asset</option>
<option value="liability">Liability</option>
<option value="equity">Equity</option>
<option value="revenue">Revenue</option>
<option value="expense">Expense</option>
</select>
</div>
<div class="col-md-6">
<label class="form-label" data-en="Parent Account" data-ar="الحساب الأب">Parent Account</label>
<select name="parent_id" class="form-select">
<option value="">--- None ---</option>
<?php foreach ($data['accounts'] as $acc): ?>
<option value="<?= $acc['id'] ?>"><?= $acc['code'] ?> - <?= htmlspecialchars($acc['name_en']) ?></option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_account" class="btn btn-primary">Save Account</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Manual Journal Modal -->
<div class="modal fade" id="addManualJournalModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-success text-white">
<h5 class="modal-title text-white" data-en="New Manual Journal Entry" data-ar="قيد يومية يدوي جديد">New Manual Journal Entry</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label" data-en="Entry Date" data-ar="تاريخ القيد">Entry Date</label>
<input type="date" name="entry_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-8">
<label class="form-label" data-en="Description" data-ar="الوصف">Description</label>
<input type="text" name="description" class="form-control" required placeholder="e.g. Opening Balance or Adjustment">
</div>
<div class="col-md-4">
<label class="form-label" data-en="Reference" data-ar="المرجع">Reference</label>
<input type="text" name="reference" class="form-control" placeholder="e.g. JV-001">
</div>
</div>
<h6>Journal Details</h6>
<div class="table-responsive">
<table class="table table-bordered">
<thead class="bg-light">
<tr>
<th style="width: 50%;">Account</th>
<th style="width: 20%;">Debit</th>
<th style="width: 20%;">Credit</th>
<th style="width: 10%;"></th>
</tr>
</thead>
<tbody id="manualJournalBody">
<tr class="journal-row">
<td>
<select name="codes[]" class="form-select select2-journal" required>
<option value="">--- Select Account ---</option>
<?php foreach ($data['accounts'] as $acc): ?>
<option value="<?= $acc['code'] ?>"><?= $acc['code'] ?> - <?= htmlspecialchars($acc['name_en']) ?></option>
<?php endforeach; ?>
</select>
</td>
<td><input type="number" step="0.001" name="debits[]" class="form-control journal-debit" value="0.000"></td>
<td><input type="number" step="0.001" name="credits[]" class="form-control journal-credit" value="0.000"></td>
<td></td>
</tr>
<tr class="journal-row">
<td>
<select name="codes[]" class="form-select select2-journal" required>
<option value="">--- Select Account ---</option>
<?php foreach ($data['accounts'] as $acc): ?>
<option value="<?= $acc['code'] ?>"><?= $acc['code'] ?> - <?= htmlspecialchars($acc['name_en']) ?></option>
<?php endforeach; ?>
</select>
</td>
<td><input type="number" step="0.001" name="debits[]" class="form-control journal-debit" value="0.000"></td>
<td><input type="number" step="0.001" name="credits[]" class="form-control journal-credit" value="0.000"></td>
<td></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<button type="button" class="btn btn-outline-primary btn-sm" onclick="addJournalRow()">
<i class="bi bi-plus-lg"></i> Add Row
</button>
</td>
</tr>
<tr class="table-light fw-bold">
<td class="text-end" data-en="Total" data-ar="الإجمالي">Total</td>
<td id="totalDebitDisplay">0.000</td>
<td id="totalCreditDisplay">0.000</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
<div id="journalBalanceWarning" class="alert alert-danger py-2 mt-2" style="display:none;">
<small><i class="bi bi-exclamation-triangle"></i> Journal is not balanced! Difference: <span id="journalDifference">0.000</span></small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_manual_journal" id="saveJournalBtn" class="btn btn-primary" data-en="Save Entry" data-ar="حفظ القيد" disabled>Save Entry</button>
</div>
</form>
</div>
</div>
</div>
<script>
function addJournalRow() {
const tbody = document.getElementById('manualJournalBody');
const row = document.createElement('tr');
row.className = 'journal-row';
row.innerHTML = `
<td>
<select name="codes[]" class="form-select select2-journal" required>
<option value="">--- Select Account ---</option>
<?php foreach ($data['accounts'] as $acc): ?>
<option value="<?= $acc['code'] ?>"><?= $acc['code'] ?> - <?= htmlspecialchars($acc['name_en']) ?></option>
<?php endforeach; ?>
</select>
</td>
<td><input type="number" step="0.001" name="debits[]" class="form-control journal-debit" value="0.000"></td>
<td><input type="number" step="0.001" name="credits[]" class="form-control journal-credit" value="0.000"></td>
<td class="text-center">
<button type="button" class="btn btn-link text-danger p-0" onclick="this.closest('tr').remove(); calculateJournalBalance();">
<i class="bi bi-trash"></i>
</button>
</td>
`;
tbody.appendChild(row);
// Init Select2 for new row
$(row).find('.select2-journal').select2({
width: '100%',
dropdownParent: $('#addManualJournalModal')
});
attachJournalListeners(row);
}
function attachJournalListeners(row) {
row.querySelectorAll('input').forEach(input => {
input.addEventListener('input', calculateJournalBalance);
});
}
function calculateJournalBalance() {
let totalDebit = 0;
let totalCredit = 0;
document.querySelectorAll('.journal-row').forEach(row => {
totalDebit += parseFloat(row.querySelector('.journal-debit').value) || 0;
totalCredit += parseFloat(row.querySelector('.journal-credit').value) || 0;
});
document.getElementById('totalDebitDisplay').innerText = totalDebit.toFixed(3);
document.getElementById('totalCreditDisplay').innerText = totalCredit.toFixed(3);
const diff = Math.abs(totalDebit - totalCredit);
const isBalanced = diff < 0.001 && (totalDebit > 0 || totalCredit > 0);
document.getElementById('saveJournalBtn').disabled = !isBalanced;
const warning = document.getElementById('journalBalanceWarning');
if (diff >= 0.001) {
warning.style.display = 'block';
document.getElementById('journalDifference').innerText = diff.toFixed(3);
} else {
warning.style.display = 'none';
}
}
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.journal-row').forEach(attachJournalListeners);
$('.select2-journal').select2({
width: '100%',
dropdownParent: $('#addManualJournalModal')
});
});
</script>
<!-- View Invoice Modal -->
<!-- Add Invoice Modal -->
<div class="modal fade" id="addInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title text-white" data-en="Create New Tax Invoice" data-ar="إنشاء فاتورة ضريبية جديدة">Create New Tax Invoice</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="type" value="<?= $page === 'sales' ? 'sale' : 'purchase' ?>">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label fw-bold" data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></label>
<select name="customer_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="invoice_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Due Date" data-ar="تاريخ الاستحقاق">Due Date</label>
<input type="date" name="due_date" class="form-control">
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Payment Type" data-ar="طريقة الدفع">Payment Type</label>
<select name="payment_type" class="form-select">
<option value="cash">Cash</option>
<option value="card">Credit Card</option>
<option value="bank_transfer">Bank Transfer</option>
<option value="credit">Credit</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="add_status" class="form-select">
<option value="unpaid">Unpaid</option>
<option value="partially_paid">Partially Paid</option>
<option value="paid">Paid</option>
</select>
</div>
<div class="col-md-3" id="addPaidAmountContainer" style="display: none;">
<label class="form-label fw-bold" data-en="Paid Amount" data-ar="المبلغ المدفوع">Paid Amount</label>
<input type="number" step="0.001" name="paid_amount" class="form-control" value="0.000">
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="productSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="searchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="invoiceItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="subtotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="totalVat"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="grandTotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_invoice" class="btn btn-primary" data-en="Create Invoice" data-ar="إنشاء الفاتورة">Create Invoice</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Invoice Modal -->
<div class="modal fade" id="editInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title text-white" data-en="Edit Invoice" data-ar="تعديل الفاتورة">Edit Invoice</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="invoice_id" id="edit_invoice_id">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label fw-bold" data-en="<?= $page === 'sales' ? 'Customer' : 'Supplier' ?>" data-ar="<?= $page === 'sales' ? 'العميل' : 'المورد' ?>"><?= $page === 'sales' ? 'Customer' : 'Supplier' ?></label>
<select name="customer_id" id="edit_customer_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="invoice_date" id="edit_invoice_date" class="form-control" required>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Due Date" data-ar="تاريخ الاستحقاق">Due Date</label>
<input type="date" name="due_date" id="edit_due_date" class="form-control">
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Payment Type" data-ar="طريقة الدفع">Payment Type</label>
<select name="payment_type" id="edit_payment_type" class="form-select">
<option value="cash">Cash</option>
<option value="card">Credit Card</option>
<option value="bank_transfer">Bank Transfer</option>
<option value="credit">Credit</option>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="edit_status" class="form-select">
<option value="unpaid">Unpaid</option>
<option value="partially_paid">Partially Paid</option>
<option value="paid">Paid</option>
</select>
</div>
<div class="col-md-3" id="editPaidAmountContainer" style="display: none;">
<label class="form-label fw-bold" data-en="Paid Amount" data-ar="المبلغ المدفوع">Paid Amount</label>
<input type="number" step="0.001" name="paid_amount" id="edit_paid_amount" class="form-control">
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="editProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="editSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="editInvoiceItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_subtotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_totalVat"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_grandTotal"><?= __('currency') ?> 0.000</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_invoice" class="btn btn-primary" data-en="Update Invoice" data-ar="تحديث الفاتورة">Update Invoice</button>
</div>
</form>
</div>
</div>
</div>
<!-- Add Quotation Modal -->
<!-- Add LPO Modal -->
<div class="modal fade" id="addLpoModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title text-white" data-en="Create New LPO" data-ar="إنشاء أمر شراء جديد">Create New LPO</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Supplier" data-ar="المورد">Supplier</label>
<select name="supplier_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['suppliers'] as $s): ?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($s['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="lpo_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Delivery Date" data-ar="تاريخ التسليم">Delivery Date</label>
<input type="date" name="delivery_date" class="form-control">
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Terms & Conditions" data-ar="الشروط والأحكام">Terms & Conditions</label>
<textarea name="terms_conditions" class="form-control" rows="1"></textarea>
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="lpoProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="lpoSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="lpoItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="lpo_subtotal_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="lpo_vat_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="lpo_grand_display"><?= __('currency') ?> 0.000</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_lpo" class="btn btn-primary" data-en="Create LPO" data-ar="إنشاء أمر الشراء">Create LPO</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit LPO Modal -->
<div class="modal fade" id="editLpoModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title text-white" data-en="Edit LPO" data-ar="تعديل أمر الشراء">Edit LPO</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="lpo_id" id="edit_lpo_id">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Supplier" data-ar="المورد">Supplier</label>
<select name="supplier_id" id="edit_lpo_supplier_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['suppliers'] as $s): ?>
<option value="<?= $s['id'] ?>"><?= htmlspecialchars($s['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="lpo_date" id="edit_lpo_date" class="form-control" required>
</div>
<div class="col-md-2">
<label class="form-label fw-bold" data-en="Delivery Date" data-ar="تاريخ التسليم">Delivery Date</label>
<input type="date" name="delivery_date" id="edit_lpo_delivery_date" class="form-control">
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="edit_lpo_status" class="form-select">
<option value="pending">Pending</option>
<option value="converted">Converted</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
<div class="col-md-12">
<label class="form-label fw-bold" data-en="Terms" data-ar="الشروط">Terms</label>
<textarea name="terms_conditions" id="edit_lpo_terms" class="form-control" rows="1"></textarea>
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="editLpoProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="editLpoSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="editLpoItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_lpo_subtotal_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_lpo_vat_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_lpo_grand_display"><?= __('currency') ?> 0.000</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_lpo" class="btn btn-primary" data-en="Update LPO" data-ar="تحديث أمر الشراء">Update LPO</button>
</div>
</form>
</div>
</div>
</div>
<!-- View LPO Modal -->
<div class="modal fade" id="viewLpoModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-info text-white">
<h5 class="modal-title text-white">LPO Details</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-4" id="lpoDetailsContent">
<!-- Loaded via JS -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
<button type="button" class="btn btn-primary" onclick="window.printLPO()">Print LPO</button>
<button type="button" class="btn btn-danger" onclick="downloadPDF('lpoDetailsContent', 'LPO')"><i class="bi bi-file-earmark-pdf me-2"></i>PDF</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="addQuotationModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title text-white" data-en="Create New Quotation" data-ar="إنشاء عرض سعر جديد">Create New Quotation</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-4">
<label class="form-label fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
<select name="customer_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-4">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="quotation_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="col-md-4">
<label class="form-label fw-bold" data-en="Valid Until" data-ar="صالح حتى">Valid Until</label>
<input type="date" name="valid_until" class="form-control">
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="quotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="quotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="quotItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="quot_subtotal_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="quot_vat_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="quot_grand_display"><?= __('currency') ?> 0.000</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="add_quotation" class="btn btn-primary" data-en="Create Quotation" data-ar="إنشاء عرض السعر">Create Quotation</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Quotation Modal -->
<div class="modal fade" id="editQuotationModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary text-white">
<h5 class="modal-title text-white" data-en="Edit Quotation" data-ar="تعديل عرض السعر">Edit Quotation</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="quotation_id" id="edit_quotation_id">
<div class="modal-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Customer" data-ar="العميل">Customer</label>
<select name="customer_id" id="edit_quot_customer_id" class="form-select select2" required>
<option value="">---</option>
<?php foreach ($data['customers_list'] as $c): ?>
<option value="<?= $c['id'] ?>"><?= htmlspecialchars($c['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Date" data-ar="التاريخ">Date</label>
<input type="date" name="quotation_date" id="edit_quot_date" class="form-control" required>
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Valid Until" data-ar="صالح حتى">Valid Until</label>
<input type="date" name="valid_until" id="edit_quot_valid" class="form-control">
</div>
<div class="col-md-3">
<label class="form-label fw-bold" data-en="Status" data-ar="الحالة">Status</label>
<select name="status" id="edit_quot_status" class="form-select">
<option value="pending">Pending</option>
<option value="converted">Converted</option>
<option value="expired">Expired</option>
<option value="cancelled">Cancelled</option>
</select>
</div>
</div>
<div class="card mb-4">
<div class="card-body bg-light">
<label class="form-label fw-bold" data-en="Search Items" data-ar="بحث عن أصناف">Search Items</label>
<div class="position-relative">
<input type="text" id="editQuotProductSearchInput" class="form-control" placeholder="Search by name or SKU..." autocomplete="off">
<div id="editQuotSearchSuggestions" class="list-group position-absolute w-100 shadow-sm" style="display: none; z-index: 1000;"></div>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-bordered align-middle">
<thead class="bg-light">
<tr>
<th style="width: 40%;" data-en="Item Details" data-ar="تفاصيل الصنف">Item Details</th>
<th style="width: 15%;" data-en="Qty" data-ar="الكمية">Qty</th>
<th style="width: 15%;" data-en="Unit Price" data-ar="سعر الوحدة">Unit Price</th>
<th style="width: 10%;" data-en="VAT" data-ar="الضريبة">VAT</th>
<th style="width: 15%;" data-en="Total" data-ar="الإجمالي">Total</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody id="editQuotItemsTableBody"></tbody>
<tfoot>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Subtotal" data-ar="الإجمالي الفرعي">Subtotal</td>
<td class="text-end fw-bold" id="edit_quot_subtotal_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr>
<td colspan="4" class="text-end fw-bold" data-en="Total VAT" data-ar="إجمالي الضريبة">Total VAT</td>
<td class="text-end fw-bold" id="edit_quot_vat_display"><?= __('currency') ?> 0.000</td>
</tr>
<tr class="table-primary">
<td colspan="4" class="text-end fw-bold h5" data-en="Grand Total" data-ar="الإجمالي النهائي">Grand Total</td>
<td class="text-end fw-bold h5" id="edit_quot_grand_display"><?= __('currency') ?> 0.000</td>
</tr>
</tfoot>
</table>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_quotation" class="btn btn-primary" data-en="Update Quotation" data-ar="تحديث عرض السعر">Update Quotation</button>
</div>
</form>
</div>
</div>
</div>
<!-- View Quotation Modal -->
<div class="modal fade" id="viewQuotationModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-info text-white d-print-none">
<h5 class="modal-title text-white" data-en="View Quotation" data-ar="عرض سعر">View Quotation</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="quotationPrintableArea">
<!-- Dynamic content -->
</div>
<div class="modal-footer d-print-none">
<div id="quotationActionButtons" class="me-auto"></div>
<button type="button" class="btn btn-secondary" onclick="window.print()"><i class="bi bi-printer"></i data-en="Print" data-ar="طباعة">Print</button>
<button type="button" class="btn btn-danger" onclick="downloadPDF('quotationPrintableArea', 'Quotation')"><i class="bi bi-file-earmark-pdf me-2"></i>PDF</button>
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
</div>
</div>
</div>
</div>
<style>
@media print {
.no-print, .sidebar, .topbar, .btn, .modal-header, .modal-footer, .d-print-none,
.modal-backdrop { display: none !important; }
body { background: white !important; margin: 0 !important; padding: 0 !important; overflow: visible !important; }
.main-content { margin: 0 !important; padding: 0 !important; background: white !important; }
/* Hide all modals by default */
.modal { display: none !important; }
/* Show ONLY the active modal */
.modal.show {
position: absolute !important;
left: 0 !important;
top: 0 !important;
margin: 0 !important;
padding: 0 !important;
overflow: visible !important;
display: block !important;
visibility: visible !important;
background: white !important;
width: 100% !important;
}
.modal.show .modal-dialog { max-width: 100% !important; width: 100% !important; margin: 0 !important; padding: 0 !important; }
.modal.show .modal-content { border: none !important; box-shadow: none !important; background: white !important; }
.modal.show .modal-body { padding: 0 !important; margin: 0 !important; background: white !important; }
@page {
size: auto;
margin: 5mm;
}
.invoice-printable-container {
padding: 10px !important;
}
.mt-4 { margin-top: 1rem !important; }
.mt-5 { margin-top: 1.5rem !important; }
.mb-4 { margin-bottom: 1rem !important; }
.p-5 { padding: 1.5rem !important; }
.table-bordered th, .table-bordered td { border: 1px solid #dee2e6 !important; }
.bg-light { background-color: #f8f9fa !important; -webkit-print-color-adjust: exact; }
.text-primary { color: #0d6efd !important; -webkit-print-color-adjust: exact; }
.badge { border: 1px solid #000; color: #000 !important; }
/* Ensure the modal is the only thing visible ONLY when a modal is open */
body.modal-open:not(.printing-receipt) { visibility: hidden !important; }
body.modal-open:not(.printing-receipt) .modal.show {
visibility: visible !important;
display: block !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
}
body.modal-open:not(.printing-receipt) .modal.show * { visibility: visible !important; }
/* Old rules that caused blank pages for nested modals */
/* body.modal-open:not(.printing-receipt) > *:not(.modal):not(.swal2-container) { display: none !important; } */
/* body.modal-open:not(.printing-receipt) .main-content { display: none !important; } */
/* POS Receipt printing specific */
body.printing-receipt .modal { display: none !important; }
body.printing-receipt .modal-backdrop { display: none !important; }
body.printing-receipt #posPrintArea {
display: block !important;
visibility: visible !important;
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
z-index: 9999 !important;
background: white !important;
}
body.printing-receipt #posPrintArea * {
visibility: visible !important;
}
}
.invoice-logo { max-height: 80px; width: auto; }
.invoice-header { border-bottom: 2px solid #333; padding-bottom: 20px; }
.invoice-title { font-size: 2.5rem; color: #333; letter-spacing: 2px; }
.invoice-info-card { background: #f8f9fa; border-radius: 8px; padding: 15px; height: 100%; }
.table-formal thead th { background: #333; color: #fff; border: none; text-transform: uppercase; font-size: 0.85rem; }
</style>
<div class="modal fade" id="viewInvoiceModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div class="modal-header d-print-none">
<h5 class="modal-title text-white" data-en="View Invoice" data-ar="عرض الفاتورة">View Invoice</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="invoicePrintableArea">
<div class="p-4 invoice-printable-container">
<div class="invoice-header mb-4">
<div class="row align-items-center">
<div class="col-6">
<?php
$logo = $data['settings']['company_logo'] ?? $_SERVER['PROJECT_IMAGE_URL'] ?? '';
if ($logo):
?>
<img src="<?= htmlspecialchars($logo) ?>" alt="Logo" class="invoice-logo mb-3">
<?php endif; ?>
<h3 class="mb-1 fw-bold" id="printCompanyName"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0" id="printCompanyAddress"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<p class="text-muted small mb-0">VAT: <?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?></p>
<p class="text-muted small mb-0" id="printCompanyPhoneContainer" style="<?= empty($data['settings']['company_phone']) ? 'display:none;' : '' ?>">Tel: <span id="printCompanyPhone"><?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?></span></p>
</div>
<div class="col-6 text-end">
<h1 class="invoice-title fw-bold mb-0 text-uppercase">Tax Invoice / فاتورة ضريبية</h1>
<div class="mt-2"><span id="invoiceTypeLabel" class="badge"></span></div>
<div class="mt-3">
<p class="mb-0 fs-5">Invoice No / رقم الفاتورة: <strong id="invNumber" class="text-primary"></strong></p>
<p class="mb-0">Date / التاريخ: <span id="invDate" class="fw-bold"></span></p>
<p class="mb-0 small">Status / الحالة: <span id="invoiceStatusLabel"></span></p>
</div>
</div>
</div>
</div>
<div class="row mb-4 g-3">
<div class="col-6">
<div class="invoice-info-card">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1" id="invPartyLabel">Bill To / فاتورة إلى</p>
<h5 class="mb-1 fw-bold"><span id="invCustomerName"></span></h5>
<p class="small text-muted mb-0" id="invCustomerTaxIdContainer">VAT / الضريبة: <span id="invCustomerTaxId"></span></p>
<p class="small text-muted mb-0" id="invCustomerPhoneContainer">Phone / الهاتف: <span id="invCustomerPhone"></span></p>
</div>
</div>
<div class="col-6">
<div class="invoice-info-card text-end">
<p class="text-muted small text-uppercase fw-bold mb-2 border-bottom pb-1">Payment Details / تفاصيل الدفع</p>
<p class="mb-1">Method / الطريقة: <strong id="invPaymentType"></strong></p>
<p class="mb-0 small text-muted">Currency / العملة: <strong>OMR / ريال عماني</strong></p>
</div>
</div>
</div>
<table class="table table-bordered table-formal">
<thead>
<tr>
<th>Item Description / وصف الصنف</th>
<th class="text-center" style="width: 80px;">Qty / الكمية</th>
<th class="text-end" style="width: 120px;">Unit Price / سعر الوحدة</th>
<th class="text-end" style="width: 100px;">VAT % / الضريبة %</th>
<th class="text-end" style="width: 150px;">Total / الإجمالي</th>
</tr>
</thead>
<tbody id="invItemsBody"></tbody>
</table>
<div class="row mt-4">
<div class="col-6">
<div class="p-3 bg-light rounded" style="min-height: 80px;">
<p class="text-muted small text-uppercase fw-bold mb-1">Amount in Words / المبلغ بالحروف</p>
<p id="invAmountInWords" class="small fw-bold mb-0"></p>
</div>
<div class="mt-3">
<p class="text-muted small text-uppercase fw-bold mb-1">Terms & Conditions / الشروط والأحكام</p>
<ul class="small text-muted ps-3 mb-0">
<li>Goods once sold will not be taken back or exchanged.</li>
<li>Payment is due within the agreed credit period.</li>
</ul>
</div>
</div>
<div class="col-6">
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)</span>
<span id="invSubtotal" class="fw-bold text-nowrap"></span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-muted">VAT Amount / مبلغ الضريبة</span>
<span id="invVatAmount" class="fw-bold text-nowrap"></span>
</div>
<div class="d-flex justify-content-between mb-3 border-top pt-2">
<span class="h4 fw-bold">Grand Total / المجموع الكلي</span>
<span id="invGrandTotal" class="h4 fw-bold text-primary text-nowrap"></span>
</div>
<div id="invPaymentsSection" class="mt-4 border-top pt-3">
<p class="text-muted small text-uppercase fw-bold mb-2">Payment Tracking / تتبع الدفع</p>
<table class="table table-sm table-bordered small mb-3">
<thead class="table-light">
<tr>
<th>Date / التاريخ</th>
<th>Method / الطريقة</th>
<th class="text-end">Amount / المبلغ</th>
</tr>
</thead>
<tbody id="invPaymentsBody"></tbody>
</table>
<div class="bg-light p-3 rounded">
<div class="d-flex justify-content-between small mb-1">
<span>Paid Amount / المبلغ المدفوع</span>
<span id="invPaidInfo" class="text-success fw-bold"></span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>Balance Due / الرصيد المتبقي</span>
<span id="invBalanceInfo" class="text-danger"></span>
</div>
</div>
</div>
</div>
</div>
<div class="mt-4 pt-3 border-top text-center">
<div class="row">
<div class="col-4">
<div class="border-top pt-2 mx-auto" style="width: 150px;">
<p class="small text-muted mb-0">Customer Signature / توقيع العميل</p>
</div>
</div>
<div class="col-4">
<div id="invQrCode" class="d-flex justify-content-center"></div>
</div>
<div class="col-4">
<div class="border-top pt-2 mx-auto" style="width: 150px;">
<p class="small text-muted mb-0">Authorized Signatory / التوقيع المعتمد</p>
</div>
</div>
</div>
<p class="text-muted x-small mt-3 mb-0">Generated by <?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?> | Visit us at <?= $_SERVER['HTTP_HOST'] ?></p>
</div>
</div>
</div>
<div class="modal-footer d-print-none">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
<button type="button" class="btn btn-primary" onclick="window.print()"><i class="bi bi-printer me-2"></i>Print Invoice</button>
<button type="button" class="btn btn-danger" onclick="downloadPDF('invoicePrintableArea', 'Invoice')"><i class="bi bi-file-earmark-pdf me-2"></i>PDF</button>
</div>
</div>
</div>
</div>
<!-- Pay Invoice Modal -->
<div class="modal fade" id="payInvoiceModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-success text-white">
<h5 class="modal-title text-white" data-en="Record Payment" data-ar="تسجيل دفعة">Record Payment</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="invoice_id" id="pay_invoice_id">
<div class="mb-2">
<label class="form-label smaller fw-bold mb-1" data-en="Total Amount" data-ar="المبلغ الإجمالي">Total Amount</label>
<input type="text" id="pay_invoice_total" class="form-control form-control-sm" readonly>
</div>
<div class="mb-2">
<label class="form-label smaller fw-bold mb-1" data-en="Remaining Amount" data-ar="المبلغ المتبقي">Remaining Amount</label>
<input type="text" id="pay_remaining_amount" class="form-control form-control-sm" readonly>
</div>
<div class="mb-2">
<label class="form-label smaller fw-bold mb-1" data-en="Amount to Pay" data-ar="المبلغ المراد دفعه">Amount to Pay</label>
<input type="number" step="0.001" name="amount" id="pay_amount" class="form-control form-control-sm" required>
</div>
<div class="mb-2">
<label class="form-label smaller fw-bold mb-1" data-en="Payment Date" data-ar="تاريخ الدفع">Payment Date</label>
<input type="date" name="payment_date" class="form-control form-control-sm" value="<?= date('Y-m-d') ?>" required>
</div>
<div class="mb-2">
<label class="form-label smaller fw-bold mb-1" data-en="Payment Method" data-ar="طريقة الدفع">Payment Method</label>
<select name="payment_method" class="form-select form-select-sm select2" required>
<option value="Cash">Cash</option>
<option value="Card">Credit Card</option>
<option value="Bank Transfer">Bank Transfer</option>
</select>
</div>
<div class="mb-2">
<label class="form-label smaller fw-bold mb-1" data-en="Notes" data-ar="ملاحظات">Notes</label>
<textarea name="notes" class="form-control form-control-sm" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="record_payment" class="btn btn-success" data-en="Save Payment" data-ar="حفظ الدفعة">Save Payment</button>
</div>
</form>
</div>
</div>
</div>
<!-- Payment Receipt Modal -->
<div class="modal fade" id="receiptModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header border-0 pb-0 d-print-none">
<h5 class="modal-title text-white">Payment Receipt</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body p-0" id="printableReceipt">
<div class="receipt-container p-4">
<div class="text-center mb-4">
<?php
$logo = $data['settings']['company_logo'] ?? '';
if ($logo): ?>
<img src="<?= htmlspecialchars($logo) ?>" alt="Logo" class="invoice-logo mb-3">
<?php endif; ?>
<h3 class="mb-1 fw-bold" id="receiptCompanyName"><?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?></h3>
<p class="text-muted small mb-0" id="receiptCompanyAddress"><?= nl2br(htmlspecialchars($data['settings']['company_address'] ?? '')) ?></p>
<hr class="my-4">
<h4 class="letter-spacing-2 fw-bold text-uppercase">Payment Receipt / سند قبض</h4>
</div>
<div class="row mb-4">
<div class="col-6">
<p class="mb-1 text-muted small text-uppercase fw-bold">Receipt No / رقم السند</p>
<p class="fw-bold h5 text-primary" id="receiptNo"></p>
</div>
<div class="col-6 text-end">
<p class="mb-1 text-muted small text-uppercase fw-bold">Date / التاريخ</p>
<p class="fw-bold" id="receiptDate"></p>
</div>
</div>
<div class="mb-4 p-3 bg-light rounded">
<div class="row mb-2">
<div class="col-4 text-muted small text-uppercase fw-bold" id="receiptPartyLabel">Received From / استلمنا من</div>
<div class="col-8 fw-bold" id="receiptCustomer"></div>
</div>
<div class="row mb-2">
<div class="col-4 text-muted small text-uppercase fw-bold" id="receiptAgainstLabel">Against Invoice / مقابل فاتورة</div>
<div class="col-8 fw-bold" id="receiptInvNo"></div>
</div>
<div class="row mb-2">
<div class="col-4 text-muted small text-uppercase fw-bold">Payment Method / طريقة الدفع</div>
<div class="col-8 fw-bold" id="receiptMethod"></div>
</div>
</div>
<div class="text-center my-4 py-4 border-top border-bottom">
<p class="mb-1 text-muted small text-uppercase fw-bold">Amount Paid / المبلغ المدفوع</p>
<h3 class="fw-bold text-primary mb-1" id="receiptAmount"></h3>
<p class="text-muted small font-italic" id="receiptAmountWords"></p>
</div>
<div id="receiptNotesContainer" class="mb-4" style="display: none;">
<p class="mb-1 text-muted small text-uppercase fw-bold">Notes / ملاحظات</p>
<p class="small p-2 bg-light rounded" id="receiptNotes"></p>
</div>
<div class="row mt-5 pt-4">
<div class="col-6">
<div class="border-top pt-2 text-center small text-muted">Receiver's Signature / توقيع المستلم</div>
</div>
<div class="col-6">
<div class="border-top pt-2 text-center small text-muted">Authorized Signatory / التوقيع المعتمد</div>
</div>
</div>
</div>
</div>
<div class="modal-footer border-0 d-print-none">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
<button type="button" class="btn btn-primary" onclick="window.printReceipt()"><i class="bi bi-printer me-2"></i>Print Receipt</button>
</div>
</div>
</div>
</div>
<style>
#posPaymentModal .modal-body { padding: 0.75rem; font-size: 0.8rem; }
#posPaymentModal .amount-due-box { background: #f8f9fa; border-radius: 12px; padding: 10px 0; border: 1px solid #eee; }
#posPaymentModal .amount-due-box .label { font-size: 0.65rem; text-transform: uppercase; font-weight: 700; color: #64748b; }
#posPaymentModal .amount-due-box .value { font-size: 1rem; font-weight: 800; color: #1e293b; }
#posPaymentModal .payment-methods-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
#posPaymentModal .payment-method-btn {
padding: 6px 4px; border-radius: 10px; border: 2px solid #f1f5f9; cursor: pointer;
text-align: center; transition: all 0.2s; background: white;
}
#posPaymentModal .payment-method-btn.active { border-color: #3b82f6; background: #eff6ff; color: #1d4ed8; }
#posPaymentModal .payment-method-btn i { font-size: 1rem; display: block; margin-bottom: 2px; }
#posPaymentModal .payment-method-btn span { font-size: 0.65rem; }
#posPaymentModal .quick-pay-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 4px; }
#posPaymentModal .quick-pay-btn {
padding: 5px; border-radius: 8px; border: 1px solid #e2e8f0; background: white;
font-weight: bold; text-align: center; cursor: pointer; font-size: 0.75rem;
}
#posPaymentModal .payment-line {
display: flex; justify-content: space-between; align-items: center;
padding: 6px 10px; background: #f1f5f9; border-radius: 8px; margin-bottom: 4px;
}
#posPaymentModal .payment-line .method { font-weight: 600; color: #475569; font-size: 0.75rem; }
#posPaymentModal .form-control { font-size: 0.85rem; padding: 0.35rem 0.6rem; }
#posPaymentModal .btn-primary { padding: 0.35rem 0.8rem; font-size: 0.85rem; }
#posPaymentModal .modal-header { padding: 0.75rem 1rem 0.25rem; }
#posPaymentModal .modal-footer { padding: 0.25rem 1rem 0.75rem; }
</style>
<!-- POS Payment Modal -->
<div class="modal fade" id="posPaymentModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content border-0">
<div class="modal-header border-0 pb-0">
<h5 class="modal-title fw-bold">Payment</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="mb-2 p-2 border rounded bg-light shadow-sm">
<div class="d-flex justify-content-between align-items-center">
<div>
<span class="small text-muted d-block" data-en="Customer" data-ar="العميل">Customer</span>
<span class="h6 fw-bold m-0 text-primary" id="paymentCustomerName">Walk-in Customer</span>
</div>
<i class="bi bi-person-circle fs-3 text-secondary"></i>
</div>
<div id="creditCustomerSection" class="mt-2 pt-2 border-top" style="display:none;">
<label class="form-label smaller fw-bold mb-1">Select Credit Customer</label>
<select id="paymentCreditCustomer" class="form-select form-select-sm select2" onchange="cart.syncCustomer(this.value)">
<option value="">--- Select Customer ---</option>
<?php foreach ($customers as $c): ?>
<option value="<?= $c['id'] ?>" data-search="<?= htmlspecialchars(strtolower($c['name'] . ' ' . ($c['phone'] ?? ''))) ?>"><?= htmlspecialchars($c['name']) ?> (<?= htmlspecialchars($c['phone'] ?? '') ?>)</option>
<?php endforeach; ?>
</select>
</div>
</div>
<div class="amount-due-box mb-2">
<div class="d-flex justify-content-between px-3">
<div class="text-start">
<div class="label">Amount Due</div>
<div class="value" id="paymentAmountDue">0.000</div>
</div>
<div class="text-end">
<div class="label text-danger">Remaining</div>
<div class="value text-danger" id="paymentRemaining">0.000</div>
</div>
</div>
</div>
<div id="paymentList" class="mb-2">
<!-- Added payments will appear here -->
</div>
<div class="mb-2 p-2 border rounded bg-light">
<label class="form-label small fw-bold mb-1">Add Payment Method</label>
<div class="payment-methods-grid mb-2">
<div class="payment-method-btn active" data-method="cash" onclick="cart.selectMethod('cash', this)">
<i class="bi bi-cash-stack"></i>
<span class="small fw-bold">Cash</span>
</div>
<div class="payment-method-btn" data-method="card" onclick="cart.selectMethod('card', this)">
<i class="bi bi-credit-card"></i>
<span class="small fw-bold">Credit Card</span>
</div>
<div class="payment-method-btn" data-method="credit" onclick="cart.selectMethod('credit', this)">
<i class="bi bi-person-badge"></i>
<span class="small fw-bold">Credit</span>
</div>
<div class="payment-method-btn" data-method="transfer" onclick="cart.selectMethod('transfer', this)">
<i class="bi bi-bank"></i>
<span class="small fw-bold">Bank Transfer</span>
</div>
</div>
<div class="row g-2 align-items-end">
<div class="col">
<label class="form-label smaller fw-bold mb-1" data-en="Amount" data-ar="المبلغ">Amount</label>
<div class="input-group">
<input type="number" step="0.001" id="partialAmount" class="form-control" placeholder="0.000" oninput="cart.updateRemaining()">
</div>
</div>
<div class="col-auto">
<button type="button" class="btn btn-primary" onclick="cart.addPaymentLine()">
<i class="bi bi-plus-lg"></i data-en="Add" data-ar="إضافة">ADD</button>
</div>
</div>
<div class="quick-pay-grid mt-2">
<div class="quick-pay-btn" onclick="cart.fillPartial(1)">1</div>
<div class="quick-pay-btn" onclick="cart.fillPartial(5)">5</div>
<div class="quick-pay-btn" onclick="cart.fillPartial(10)">10</div>
<div class="quick-pay-btn" onclick="cart.fillPartial(20)">20</div>
<div class="quick-pay-btn" onclick="cart.fillPartial(50)">50</div>
</div>
</div>
<div id="cashPaymentSection" style="display: none;">
<div class="d-flex justify-content-between align-items-center p-3 bg-primary-subtle rounded border border-primary-subtle">
<span class="fw-bold">Total Tendered (Cash)</span>
<span class="h6 m-0 fw-bold text-primary" id="changeDue">0.000</span>
</div>
<div class="small text-muted mt-1">* Change is calculated based on cash payments only.</div>
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="button" class="btn btn-primary px-4" id="confirmPaymentBtn" onclick="cart.completeOrder()">
PAY & COMPLETE
</button>
</div>
</div>
</div>
</div>
<!-- POS Receipt Modal -->
<div class="modal fade" id="posReceiptModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content border-0">
<div class="modal-header border-0 pb-0">
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body pt-0">
<div id="posReceiptContent">
<!-- Receipt content will be generated here -->
</div>
</div>
<div class="modal-footer border-0">
<button type="button" class="btn btn-primary w-100" onclick="printPosReceipt()">
<i class="bi bi-printer me-2"></i>PRINT RECEIPT
</button>
</div>
</div>
</div>
</div>
<div id="posPrintArea" class="d-none d-print-block"></div>
<!-- Barcode Print Modal -->
<div class="modal fade" id="barcodePrintModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header bg-primary">
<h5 class="modal-title text-white">Print Barcode Label</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<div id="barcodeContainer" class="p-4 bg-white border mb-3 mx-auto" style="width: fit-content;">
<div id="barcodeLabelName" class="fw-bold small mb-1"></div>
<svg id="barcodeSvg"></svg>
<div id="barcodeLabelPrice" class="fw-bold mt-1"></div>
</div>
<div class="mb-3">
<label class="form-label small">Number of Labels</label>
<input type="number" id="barcodeQty" class="form-control form-control-sm mx-auto" value="1" min="1" style="width: 80px;">
</div>
<div class="row mb-3 mx-auto" style="max-width: 200px;">
<div class="col-6">
<label class="form-label small">Width (mm)</label>
<input type="number" id="barcodeWidth" class="form-control form-control-sm" value="40" min="10">
</div>
<div class="col-6">
<label class="form-label small">Height (mm)</label>
<input type="number" id="barcodeHeight" class="form-control form-control-sm" value="25" min="10">
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
<button type="button" class="btn btn-primary" onclick="executeBarcodePrint()"><i class="bi bi-printer me-2"></i>Print Now</button>
</div>
</div>
</div>
</div>
<!-- Avery Labels Modal -->
<div class="modal fade" id="averyLabelsModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content border-0 shadow">
<div class="modal-header d-print-none">
<h5 class="modal-title text-white">Avery Barcode Labels (A4)</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="row mb-4 d-print-none">
<div class="col-md-4">
<label class="form-label small">Label Layout</label>
<select id="averyLayout" class="form-select form-select-sm" onchange="updateAveryPreview()">
<option value="3x7">3 x 7 (21 Labels per sheet)</option>
<option value="3x8">3 x 8 (24 Labels per sheet)</option>
<option value="4x10">4 x 10 (40 Labels per sheet)</option>
<option value="L7651">L7651 (5 x 13 - 65 Labels)</option>
<option value="L4736">L4736 (2 x 7 - 14 Labels)</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label small">Copies (Set All)</label>
<input type="number" id="averyCopies" class="form-control form-control-sm" value="1" min="1" oninput="updateAllItemQuantities()" onchange="updateAllItemQuantities()">
</div>
<div class="col-md-4 d-flex align-items-end">
<button class="btn btn-primary btn-sm w-100" onclick="window.print()"><i class="bi bi-printer me-2"></i>Print A4 Sheet</button>
</div>
</div>
<div class="row mb-3 d-print-none">
<div class="col-12">
<label class="form-label small fw-bold" data-en="Quantities per Item" data-ar="الكميات لكل صنف">Quantities per Item</label>
<div id="averyItemQuantities" class="border rounded p-2 bg-light" style="max-height: 150px; overflow-y: auto;">
<small class="text-muted">Select items to adjust quantities.</small>
</div>
</div>
</div>
<div id="averyPrintArea" class="avery-container">
<!-- Labels will be generated here -->
</div>
</div>
</div>
</div>
</div>
<footer class="main-footer d-print-none">
<div class="text-center py-3 border-top mt-5">
<div class="text-muted small">
Powered By <strong>Accounting</strong> &bull; omanapp.cloud &bull; aalabry@gmail.com &bull; Whatsapp: +968 99359472
</div>
</div>
</footer>
</div>
<style>
/* Avery Label Printing */
.avery-container {
background: white;
width: 210mm; /* A4 Width */
min-height: 297mm; /* A4 Height */
padding: 10mm 5mm;
margin: 0 auto;
display: grid;
gap: 2mm;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}
.avery-layout-3x7 { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 38mm; }
.avery-layout-3x8 { grid-template-columns: repeat(3, 1fr); grid-auto-rows: 34mm; }
.avery-layout-4x10 { grid-template-columns: repeat(4, 1fr); grid-auto-rows: 27mm; }
.avery-layout-L7651 { grid-template-columns: repeat(5, 1fr); grid-auto-rows: 21mm; }
.avery-layout-L4736 { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 38mm; }
.avery-layout-L7651 .avery-label { padding: 2mm; }
.avery-layout-L7651 .avery-label svg { height: 25px; }
.avery-layout-L7651 .avery-label div { font-size: 8px !important; }
.avery-label {
border: 1px dashed #eee;
padding: 5mm;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
overflow: hidden;
background: white;
}
.avery-label svg {
max-width: 100%;
height: auto;
}
@media print {
body.printing-avery .sidebar,
body.printing-avery .topbar,
body.printing-avery .modal-header,
body.printing-avery .d-print-none,
body.printing-avery .modal-backdrop {
display: none !important;
}
body.printing-avery .modal {
position: absolute !important;
left: 0 !important;
top: 0 !important;
width: 100% !important;
display: block !important;
visibility: visible !important;
background: white !important;
}
body.printing-avery .modal-dialog {
max-width: 100% !important;
width: 100% !important;
margin: 0 !important;
padding: 0 !important;
}
body.printing-avery .modal-content {
border: none !important;
}
body.printing-avery .avery-label {
border: none !important;
}
.avery-container {
margin: 0 !important;
padding: 10mm 5mm !important;
box-shadow: none !important;
border: none !important;
}
}
</style>
<script>
// Avery Label Logic
const selectAllItems = document.getElementById('selectAllItems');
const bulkBarcodeBtn = document.getElementById('bulkBarcodeBtn');
if (selectAllItems) {
selectAllItems.addEventListener('change', function() {
document.querySelectorAll('.item-checkbox').forEach(cb => {
cb.checked = this.checked;
});
toggleBulkBtn();
});
}
document.addEventListener('change', function(e) {
if (e.target.classList.contains('item-checkbox')) {
toggleBulkBtn();
}
});
function toggleBulkBtn() {
const checked = document.querySelectorAll('.item-checkbox:checked').length;
if (bulkBarcodeBtn) {
bulkBarcodeBtn.style.display = checked > 0 ? 'inline-block' : 'none';
}
}
window.openAveryModal = function() {
const modal = new bootstrap.Modal(document.getElementById('averyLabelsModal'));
const checkedItems = document.querySelectorAll('.item-checkbox:checked');
const container = document.getElementById('averyItemQuantities');
const defaultCopies = parseInt(document.getElementById('averyCopies').value) || 1;
if (container) {
container.innerHTML = '';
if (checkedItems.length === 0) {
container.innerHTML = '<small class="text-muted" data-en="No items selected." data-ar="لم يتم تحديد أي صنف.">No items selected.</small>';
} else {
const table = document.createElement('table');
table.className = 'table table-sm table-borderless mb-0';
const tbody = document.createElement('tbody');
checkedItems.forEach(cb => {
const sku = cb.dataset.sku;
const nameEn = cb.dataset.nameEn || '';
const nameAr = cb.dataset.nameAr || '';
const name = nameEn + (nameAr ? ' - ' + nameAr : '');
const id = cb.dataset.id;
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="align-middle" style="width: 70%; font-size: 0.9em; max-width: 200px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${name} <span class="text-muted">(${sku})</span></td>
<td class="align-middle" style="width: 30%;">
<input type="number" class="form-control form-control-sm item-qty-input"
data-id="${id}" value="${defaultCopies}" min="0" onchange="updateAveryPreview()" onkeyup="updateAveryPreview()">
</td>
`;
tbody.appendChild(tr);
});
table.appendChild(tbody);
container.appendChild(table);
}
}
modal.show();
updateAveryPreview();
};
window.updateAllItemQuantities = function() {
const globalQty = document.getElementById('averyCopies').value;
const itemInputs = document.querySelectorAll('.item-qty-input');
itemInputs.forEach(input => {
input.value = globalQty;
});
updateAveryPreview();
};
window.updateAveryPreview = function() {
const layout = document.getElementById('averyLayout').value;
const container = document.getElementById('averyPrintArea');
const checkedItems = document.querySelectorAll('.item-checkbox:checked');
container.className = 'avery-container avery-layout-' + layout;
container.innerHTML = '';
checkedItems.forEach(cb => {
const sku = cb.dataset.sku;
const nameEn = cb.dataset.nameEn || '';
const nameAr = cb.dataset.nameAr || '';
const price = cb.dataset.price;
const id = cb.dataset.id;
// Find specific quantity input
const qtyInput = document.querySelector(`.item-qty-input[data-id="${id}"]`);
let copies = 1;
if (qtyInput) {
copies = parseInt(qtyInput.value) || 0;
} else {
copies = parseInt(document.getElementById('averyCopies').value) || 1;
}
for (let i = 0; i < copies; i++) {
const label = document.createElement('div');
label.className = 'avery-label';
const uniqueId = Math.random().toString(36).substr(2, 9);
const svgId = `bc-${sku}-${uniqueId}`;
label.innerHTML = `
<div style="font-size: 9px; font-weight: bold; margin-bottom: 1px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;">${nameEn}</div>
<div style="font-size: 8px; font-weight: bold; margin-bottom: 2px; line-height: 1.1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%;">${nameAr}</div>
<svg id="${svgId}"></svg>
<div style="font-size: 11px; font-weight: bold; margin-top: 2px;">OMR ${price}</div>
`;
container.appendChild(label);
setTimeout(() => {
if (document.getElementById(svgId)) {
const bcHeight = layout === 'L7651' ? 20 : 35;
JsBarcode(`#${svgId}`, sku, {
format: "CODE128",
width: layout === 'L7651' ? 1.0 : 1.2,
height: bcHeight,
displayValue: true,
fontSize: layout === 'L7651' ? 8 : 10,
margin: 0
});
}
}, 0);
}
});
};
window.addEventListener('beforeprint', () => {
if (document.getElementById('averyLabelsModal') && document.getElementById('averyLabelsModal').classList.contains('show')) {
document.body.classList.add('printing-avery');
}
});
window.addEventListener('afterprint', () => {
document.body.classList.remove('printing-avery');
});
</script>
<script>
window.currentPrintData = {};
window.printItemBarcode = function(sku, nameEn, nameAr, price) {
if (!sku) {
Swal.fire('Error', 'This item has no SKU/Barcode assigned.', 'error');
return;
}
window.currentPrintData = { sku, nameEn, nameAr, price };
document.getElementById('barcodeLabelName').innerHTML = `<div class="text-truncate" style="max-width:100%;">${nameEn}</div><div class="text-truncate" style="max-width:100%; font-size:0.85em;">${nameAr}</div>`;
document.getElementById('barcodeLabelPrice').textContent = 'OMR ' + price;
JsBarcode("#barcodeSvg", sku, {
format: "CODE128",
lineColor: "#000",
width: 2,
height: 50,
displayValue: true
});
const modal = new bootstrap.Modal(document.getElementById('barcodePrintModal'));
modal.show();
};
window.executeBarcodePrint = function() {
const qty = parseInt(document.getElementById('barcodeQty').value) || 1;
const width = parseInt(document.getElementById('barcodeWidth').value) || 40;
const height = parseInt(document.getElementById('barcodeHeight').value) || 25;
// Get content
const nameEn = window.currentPrintData.nameEn || '';
const nameAr = window.currentPrintData.nameAr || '';
const price = document.getElementById('barcodeLabelPrice').innerText;
const svg = document.getElementById('barcodeSvg').outerHTML;
// Create a hidden iframe
const iframe = document.createElement('iframe');
iframe.style.position = 'absolute';
iframe.style.width = '0px';
iframe.style.height = '0px';
iframe.style.border = 'none';
document.body.appendChild(iframe);
const doc = iframe.contentWindow.document;
let labelsHtml = '';
for (let i = 0; i < qty; i++) {
labelsHtml += `
<div class="label-container">
<div class="label-name-en">${nameEn}</div>
<div class="label-name-ar">${nameAr}</div>
${svg}
<div class="label-price">${price}</div>
</div>
`;
}
doc.open();
doc.write(`
<html>
<head>
<style>
@page { size: ${width}mm ${height}mm; margin: 0; }
body { margin: 0; padding: 0; font-family: sans-serif; }
.label-container {
width: ${width}mm;
height: ${height}mm;
page-break-after: always;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
overflow: hidden;
box-sizing: border-box;
padding: 1mm;
}
.label-container:last-child { page-break-after: avoid; }
.label-name-en { font-size: 9px; font-weight: bold; margin-bottom: 1px; line-height: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.label-name-ar { font-size: 8px; font-weight: bold; margin-bottom: 2px; line-height: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.label-price { font-size: 12px; font-weight: bold; margin-top: 2px; }
svg { max-width: 100%; height: auto; max-height: 70%; display: block; }
</style>
</head>
<body>
${labelsHtml}
</body>
</html>
`);
doc.close();
iframe.contentWindow.focus();
setTimeout(() => {
iframe.contentWindow.print();
setTimeout(() => {
document.body.removeChild(iframe);
}, 2000);
}, 500);
};
window.viewAndPrintA4Invoice = function(data, autoPrint = true) {
if (!data) return;
// Reuse view logic
document.getElementById('printCompanyName').textContent = data.outlet_name || "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
document.getElementById('printCompanyAddress').textContent = data.outlet_address || (typeof companySettings !== 'undefined' ? companySettings.company_address : '') || '';
document.getElementById('printCompanyAddress').style.whiteSpace = 'pre-line';
const cPhone = data.outlet_phone || "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
if (cPhone) {
document.getElementById('printCompanyPhone').textContent = cPhone;
document.getElementById('printCompanyPhoneContainer').style.display = 'block';
} else {
document.getElementById('printCompanyPhoneContainer').style.display = 'none';
}
document.getElementById('invNumber').textContent = 'INV-' + data.id.toString().padStart(5, '0');
document.getElementById('invDate').textContent = data.invoice_date;
document.getElementById('invPaymentType').textContent = data.payment_type ? data.payment_type.toUpperCase() : 'CASH';
document.getElementById('invCustomerName').textContent = data.customer_name || '---';
const phoneEl = document.getElementById('invCustomerPhone');
const phoneContainer = document.getElementById('invCustomerPhoneContainer');
if (data.customer_phone) {
phoneEl.textContent = data.customer_phone;
phoneContainer.style.display = 'block';
} else {
phoneContainer.style.display = 'none';
}
const taxIdEl = document.getElementById('invCustomerTaxId');
const taxIdContainer = document.getElementById('invCustomerTaxIdContainer');
if (data.customer_tax_id) {
taxIdEl.textContent = data.customer_tax_id;
taxIdContainer.style.display = 'block';
} else {
taxIdContainer.style.display = 'none';
}
document.getElementById('invAmountInWords').textContent = data.total_in_words || '';
document.getElementById('invPartyLabel').textContent = data.type === 'sale' ? 'Bill To / فاتورة إلى' : 'Bill From / فاتورة من';
document.getElementById('invPartyLabel').setAttribute('data-en', data.type === 'sale' ? 'Bill To' : 'Bill From');
document.getElementById('invPartyLabel').setAttribute('data-ar', data.type === 'sale' ? 'فاتورة إلى' : 'فاتورة من');
document.getElementById('invoiceTypeLabel').textContent = data.type;
document.getElementById('invoiceTypeLabel').className = 'badge text-uppercase ' + (data.type === 'sale' ? 'bg-success' : 'bg-warning');
const statusLabel = document.getElementById('invoiceStatusLabel');
let statusClass = 'bg-secondary';
let statusEn = data.status ? (data.status.charAt(0).toUpperCase() + data.status.slice(1)) : '---';
if (data.status === 'paid') statusClass = 'bg-success';
else if (data.status === 'unpaid') statusClass = 'bg-danger';
else if (data.status === 'partially_paid') {
statusClass = 'bg-warning text-dark';
statusEn = 'Partially Paid';
}
statusLabel.textContent = statusEn;
statusLabel.className = 'badge text-uppercase ' + statusClass;
const body = document.getElementById('invItemsBody');
body.innerHTML = '';
if (data.items) {
data.items.forEach(item => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${item.name_en} / ${item.name_ar}</td>
<td class="text-center">${item.quantity}</td>
<td class="text-end"><small><?= __('currency') ?></small> ${parseFloat(item.unit_price).toFixed(3)}</td>
<td class="text-end">${parseFloat(item.vat_rate || 0).toFixed(2)}%</td>
<td class="text-end"><small><?= __('currency') ?></small> ${parseFloat(item.total_price).toFixed(3)}</td>
`;
body.appendChild(tr);
});
}
const vatVal = parseFloat(data.vat_amount || 0);
const totalVal = parseFloat(data.total_amount || 0);
const grandTotalValue = (parseFloat(data.total_with_vat) || (totalVal + vatVal));
if (document.getElementById('invSubtotal')) document.getElementById('invSubtotal').innerHTML = '<small><?= __('currency') ?></small> ' + (grandTotalValue - vatVal).toFixed(3);
if (document.getElementById('invVatAmount')) document.getElementById('invVatAmount').innerHTML = '<small><?= __('currency') ?></small> ' + vatVal.toFixed(2);
if (document.getElementById('invGrandTotal')) document.getElementById('invGrandTotal').innerHTML = '<small><?= __('currency') ?></small> ' + grandTotalValue.toFixed(3);
if (document.getElementById('invPaidInfo')) document.getElementById('invPaidInfo').innerHTML = '<small><?= __('currency') ?></small> ' + parseFloat(data.paid_amount || 0).toFixed(3);
const balance = grandTotalValue - parseFloat(data.paid_amount || 0);
if (document.getElementById('invBalanceInfo')) document.getElementById('invBalanceInfo').innerHTML = '<small><?= __('currency') ?></small> ' + balance.toFixed(3);
// Generate QR Code for Zakat, Tax and Customs Authority (ZATCA) style or simple formal
const companyName = data.outlet_name || <?= json_encode($data['settings']['company_name'] ?? 'Accounting System') ?>;
const vatNo = <?= json_encode($data['settings']['vat_number'] ?? '') ?>;
const qrData = `Seller: ${companyName}\nVAT: ${vatNo}\nInvoice: INV-${data.id.toString().padStart(5, '0')}\nDate: ${data.invoice_date}\nTotal: ${grandTotalValue.toFixed(3)}`;
const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=100x100&data=${encodeURIComponent(qrData)}`;
if (document.getElementById('invQrCode')) {
document.getElementById('invQrCode').innerHTML = `<img src="${qrUrl}" alt="QR Code" style="width: 100px; height: 100px;" class="border p-1 bg-white">`;
}
const viewModal = bootstrap.Modal.getOrCreateInstance(document.getElementById('viewInvoiceModal'));
viewModal.show();
if (autoPrint) {
setTimeout(() => { window.print(); }, 1000);
}
fetch(`index.php?action=get_payments&invoice_id=${data.id}`)
.then(res => res.json())
.then(payments => {
const paymentsBody = document.getElementById('invPaymentsBody');
const paymentsSection = document.getElementById('invPaymentsSection');
if (paymentsBody) paymentsBody.innerHTML = '';
if (payments && payments.length > 0) {
if (paymentsBody) {
payments.forEach(p => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${p.payment_date}</td><td>${p.payment_method}</td><td class="text-end fw-bold">OMR ${parseFloat(p.amount).toFixed(3)}</td>`;
paymentsBody.appendChild(tr);
});
}
if (paymentsSection) paymentsSection.style.display = 'block';
} else {
if (paymentsSection) paymentsSection.style.display = 'none';
}
}).catch(err => console.error('Error fetching payments:', err));
};
window.printPosReceiptFromInvoice = function(inv) {
const container = document.getElementById('posReceiptContent');
const itemsHtml = inv.items.map(item => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 0);
const vatAmount = itemTotal * (vatRate / (100 + vatRate));
return `
<tr>
<td>${item.name_en} / ${item.name_ar}<br><small>${item.quantity} x ${parseFloat(item.unit_price).toFixed(3)}</small></td>
<td style="text-align: right; vertical-align: bottom;">${vatAmount.toFixed(2)}</td>
<td style="text-align: right; vertical-align: bottom;">${itemTotal.toFixed(3)}</td>
</tr>
`;
}).join('');
const totalVat = inv.items.reduce((sum, item) => {
const itemTotal = item.unit_price * item.quantity;
const vatRate = parseFloat(item.vat_rate !== undefined && item.vat_rate !== null ? item.vat_rate : 0);
return sum + (itemTotal * (vatRate / (100 + vatRate)));
}, 0);
const subtotal = inv.items.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0);
const companyName = inv.outlet_name || "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const companyPhone = inv.outlet_phone || "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
const companyAddress = inv.outlet_address ? inv.outlet_address.replace(/\n/g, '<br>') : (typeof companySettings !== 'undefined' ? (companySettings.company_address || '').replace(/\n/g, '<br>') : '');
const companyVat = "<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>";
const companyLogo = "<?= htmlspecialchars($data['settings']['company_logo'] ?? '') ?>";
container.innerHTML = `
<div class="thermal-receipt">
<div class="center">
${companyLogo ? `<img src="${companyLogo}" alt="Logo" style="max-height: 60px; width: auto; margin-bottom: 10px; display: block; margin-left: auto; margin-right: auto;">` : ''}
<h5 class="mb-0 fw-bold">${companyName}</h5>
${companyAddress ? `<div>${companyAddress}</div>` : ''}
${companyPhone ? `<div>Tel: ${companyPhone}</div>` : ''}
${companyVat ? `<div>VAT: ${companyVat}</div>` : ''}
<div class="separator"></div>
<h6 class="fw-bold">TAX INVOICE / فاتورة ضريبية</h6>
<div>Inv / رقم: INV-${inv.id.toString().padStart(5, '0')}</div>
<div>Date / التاريخ: ${inv.invoice_date}</div>
<div class="separator"></div>
</div>
<div>
<strong>Customer / العميل:</strong> ${inv.customer_name || 'Walk-in / عميل عابر'}
</div>
<div class="separator"></div>
<table>
<thead>
<tr>
<th>ITEM / الصنف</th>
<th style="text-align: right;">VAT / الضريبة</th>
<th style="text-align: right;">TOTAL / الإجمالي</th>
</tr>
</thead>
<tbody>
${itemsHtml}
</tbody>
</table>
<div class="separator"></div>
<div class="d-flex justify-content-between small">
<span>Subtotal (Excl. VAT) / المجموع الفرعي (دون الضريبة)</span>
<span><?= __('currency') ?> ${(subtotal - totalVat).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>VAT / الضريبة</span>
<span><?= __('currency') ?> ${totalVat.toFixed(2)}</span>
</div>
<div class="total-row d-flex justify-content-between">
<span>TOTAL (Incl. VAT) / الإجمالي (شامل الضريبة)</span>
<span><?= __('currency') ?> ${subtotal.toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small">
<span>PAID / المدفوع</span>
<span><?= __('currency') ?> ${parseFloat(inv.paid_amount).toFixed(3)}</span>
</div>
<div class="d-flex justify-content-between small fw-bold">
<span>BALANCE / الرصيد</span>
<span><?= __('currency') ?> ${(subtotal - inv.paid_amount).toFixed(3)}</span>
</div>
<div class="separator"></div>
<div class="center small">
<p>Thank You for your business! / شكراً لتعاملكم معنا!</p>
</div>
</div>
`;
const posModal = new bootstrap.Modal(document.getElementById('posReceiptModal'));
posModal.show();
};
function printPosReceipt() {
const content = document.getElementById('posReceiptContent').innerHTML;
const printArea = document.getElementById('posPrintArea');
printArea.innerHTML = `<div class="thermal-receipt thermal-receipt-print">${content}</div>`;
document.body.classList.add('printing-receipt');
window.print();
document.body.classList.remove('printing-receipt');
location.reload();
}
window.downloadPDF = function(elementId, filenamePrefix) {
const element = document.getElementById(elementId);
if (!element) return;
// Hide elements with 'd-print-none' during PDF generation
const hiddenElements = element.querySelectorAll('.d-print-none');
hiddenElements.forEach(el => el.style.display = 'none');
const opt = {
margin: [10, 10, 10, 10],
filename: filenamePrefix + '_' + new Date().getTime() + '.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true },
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
};
html2pdf().set(opt).from(element).save().then(() => {
// Restore hidden elements
hiddenElements.forEach(el => el.style.display = '');
});
};
// --- Language Apply Script ---
function applyLanguage(node) {
const docLang = document.documentElement.lang || 'ar';
const targetAttr = docLang === 'ar' ? 'data-ar' : 'data-en';
let elements = [];
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute(targetAttr)) {
elements.push(node);
}
node.querySelectorAll('[' + targetAttr + ']').forEach(el => elements.push(el));
}
elements.forEach(el => {
const text = el.getAttribute(targetAttr);
if (!text) return;
if (el.hasAttribute('placeholder')) {
el.setAttribute('placeholder', text);
}
if (el.hasAttribute('title')) {
el.setAttribute('title', text);
}
let textNodes = [];
el.childNodes.forEach(child => {
if (child.nodeType === Node.TEXT_NODE && child.nodeValue.trim() !== '') {
textNodes.push(child);
}
});
if (textNodes.length > 0) {
textNodes[0].nodeValue = text;
for (let i = 1; i < textNodes.length; i++) {
textNodes[i].nodeValue = '';
}
} else if (el.children.length === 1 && ['STRONG', 'B', 'SPAN', 'SMALL'].includes(el.children[0].tagName)) {
el.children[0].textContent = text;
} else if (el.children.length === 0) {
el.textContent = text;
}
});
}
document.addEventListener('DOMContentLoaded', function() {
applyLanguage(document.body);
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
applyLanguage(node);
}
});
});
});
observer.observe(document.body, { childList: true, subtree: true });
});
// -----------------------------
<?php if ($page === 'dashboard' && can('dashboard_view')): ?>
const monthlyData = <?= json_encode($data['monthly_sales'] ?? []) ?>;
const yearlyData = <?= json_encode($data['yearly_sales'] ?? []) ?>;
const cashFlowData = <?= json_encode($data['cash_flow'] ?? []) ?>;
const ctx = document.getElementById('salesChart').getContext('2d');
let gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, 'rgba(13, 110, 253, 0.4)');
gradient.addColorStop(1, 'rgba(13, 110, 253, 0)');
let salesChart = new Chart(ctx, {
type: 'line',
data: {
labels: monthlyData.map(d => d.label),
datasets: [{
label: '<?= $lang === "ar" ? "المبيعات" : "Sales" ?>',
data: monthlyData.map(d => d.total),
borderColor: '#0d6efd',
backgroundColor: gradient,
borderWidth: 3,
pointBackgroundColor: '#ffffff',
pointBorderColor: '#0d6efd',
pointBorderWidth: 2,
pointRadius: 4,
pointHoverRadius: 6,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
titleFont: { size: 14, family: "'Segoe UI', Roboto, sans-serif" },
bodyFont: { size: 14, family: "'Segoe UI', Roboto, sans-serif" },
padding: 10,
cornerRadius: 8,
displayColors: false,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) { label += ': '; }
if (context.parsed.y !== null) { label += new Intl.NumberFormat().format(context.parsed.y) + ' OMR'; }
return label;
}
}
}
},
scales: {
x: { grid: { display: false, drawBorder: false } },
y: {
beginAtZero: true,
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
border: { dash: [5, 5] },
ticks: {
callback: function(value) { return 'OMR ' + value.toFixed(3); }
}
}
},
interaction: { mode: 'index', intersect: false }
}
});
document.getElementById('btnMonthly').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnYearly').classList.remove('active');
salesChart.data.labels = monthlyData.map(d => d.label);
salesChart.data.datasets[0].data = monthlyData.map(d => d.total);
salesChart.update();
});
document.getElementById('btnYearly').addEventListener('click', function() {
this.classList.add('active');
document.getElementById('btnMonthly').classList.remove('active');
salesChart.data.labels = yearlyData.map(d => d.label);
salesChart.data.datasets[0].data = yearlyData.map(d => d.total);
salesChart.update();
});
if (cashFlowData.length > 0) {
const ctxCf = document.getElementById('cashFlowChart').getContext('2d');
new Chart(ctxCf, {
type: 'bar',
data: {
labels: cashFlowData.map(d => d.label),
datasets: [
{
label: '<?= $lang === "ar" ? "الدخل" : "Income" ?>',
data: cashFlowData.map(d => d.income),
backgroundColor: '#198754',
borderRadius: 4,
barPercentage: 0.6,
categoryPercentage: 0.8
},
{
label: '<?= $lang === "ar" ? "المصروفات" : "Expenses" ?>',
data: cashFlowData.map(d => d.expense),
backgroundColor: '#dc3545',
borderRadius: 4,
barPercentage: 0.6,
categoryPercentage: 0.8
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'top',
labels: { usePointStyle: true, boxWidth: 8, font: { family: "'Segoe UI', Roboto, sans-serif" } }
},
tooltip: {
backgroundColor: 'rgba(0,0,0,0.8)',
padding: 10,
cornerRadius: 8,
callbacks: {
label: function(context) {
let label = context.dataset.label || '';
if (label) { label += ': '; }
if (context.parsed.y !== null) { label += new Intl.NumberFormat().format(context.parsed.y) + ' OMR'; }
return label;
}
}
}
},
scales: {
x: { grid: { display: false, drawBorder: false } },
y: {
beginAtZero: true,
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
border: { dash: [5, 5] },
ticks: {
callback: function(value) { return 'OMR ' + value.toFixed(3); }
}
}
},
interaction: { mode: 'index', intersect: false }
}
});
}
<?php endif; ?>
</script>
</body>
</html>