38471-vm/index.php
2026-05-03 04:30:55 +00:00

12027 lines
690 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("0;0660;" . $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 (!function_exists('app_file_debug_logging_enabled')) {
function app_file_debug_logging_enabled(): bool {
static $enabled = null;
if ($enabled !== null) {
return $enabled;
}
$candidates = [
getenv('APP_FILE_DEBUG_LOGS'),
$_ENV['APP_FILE_DEBUG_LOGS'] ?? null,
$_SERVER['APP_FILE_DEBUG_LOGS'] ?? null,
];
foreach ($candidates as $candidate) {
if ($candidate === false || $candidate === null) {
continue;
}
$value = strtolower(trim((string)$candidate));
if ($value === '') {
continue;
}
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
$enabled = true;
return true;
}
if (in_array($value, ['0', 'false', 'no', 'off'], true)) {
$enabled = false;
return false;
}
}
$enabled = false;
return false;
}
}
if (!function_exists('app_debug_file_log')) {
function app_debug_file_log(string $filename, string $message): void {
if (!app_file_debug_logging_enabled()) {
return;
}
$path = __DIR__ . '/' . ltrim($filename, '/');
@file_put_contents($path, rtrim($message, "\r\n") . PHP_EOL, FILE_APPEND);
}
}
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') {
app_debug_file_log('post_debug.log', date('Y-m-d H:i:s') . " - POST: " . json_encode($_POST));
}
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/SimpleXLSX.php';
require_once __DIR__ . '/includes/stock_helper.php';
require_once __DIR__ . '/db/BackupService.php';
// Helper for current outlet
if (!function_exists('current_outlet_id')) {
function current_outlet_id() {
if (session_status() === PHP_SESSION_NONE) session_start();
return (int)($_SESSION['outlet_id'] ?? 1);
}
}
if (!function_exists('db_table_exists')) {
function db_table_exists(string $tableName): bool {
static $cache = [];
$normalized = strtolower($tableName);
if (array_key_exists($normalized, $cache)) {
return $cache[$normalized];
}
try {
$stmt = db()->prepare("SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? LIMIT 1");
$stmt->execute([$tableName]);
$cache[$normalized] = (bool)$stmt->fetchColumn();
} catch (Throwable $e) {
$cache[$normalized] = false;
}
return $cache[$normalized];
}
}
if (!function_exists('db_column_exists')) {
function db_column_exists(string $tableName, string $columnName): bool {
static $cache = [];
$cacheKey = strtolower($tableName . '.' . $columnName);
if (array_key_exists($cacheKey, $cache)) {
return $cache[$cacheKey];
}
try {
$stmt = db()->prepare("SELECT 1 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = ? LIMIT 1");
$stmt->execute([$tableName, $columnName]);
$cache[$cacheKey] = (bool)$stmt->fetchColumn();
} catch (Throwable $e) {
$cache[$cacheKey] = false;
}
return $cache[$cacheKey];
}
}
if (!function_exists('db_first_existing_column')) {
function db_first_existing_column(string $tableName, array $columnNames): ?string {
foreach ($columnNames as $columnName) {
if (db_column_exists($tableName, (string)$columnName)) {
return (string)$columnName;
}
}
return null;
}
}
if (!function_exists('entity_tax_column')) {
function entity_tax_column(string $tableName): ?string {
return db_first_existing_column($tableName, ['tax_id', 'tax_number', 'vat_number', 'tax_no', 'vat_no', 'trn']);
}
}
if (!function_exists('db_insert_sql_for_existing_columns')) {
function db_insert_sql_for_existing_columns(string $tableName, array $columnValueMap): array {
$columns = [];
$values = [];
foreach ($columnValueMap as $columnName => $value) {
if (!db_column_exists($tableName, (string)$columnName)) {
continue;
}
$columns[] = (string)$columnName;
$values[] = $value;
}
if (empty($columns)) {
throw new RuntimeException("No compatible insert columns found for {$tableName}.");
}
$quotedColumns = '`' . implode('`, `', $columns) . '`';
$placeholders = implode(', ', array_fill(0, count($columns), '?'));
return ["INSERT INTO `{$tableName}` ({$quotedColumns}) VALUES ({$placeholders})", $values];
}
}
if (!function_exists('sales_return_reference_column')) {
function sales_return_reference_column(): string {
return db_first_existing_column('sales_returns', ['invoice_id', 'sale_id']) ?? 'invoice_id';
}
}
if (!function_exists('purchase_return_reference_column')) {
function purchase_return_reference_column(): string {
return db_first_existing_column('purchase_returns', ['invoice_id', 'purchase_id']) ?? 'invoice_id';
}
}
if (!function_exists('line_item_vat_amount')) {
function line_item_vat_amount(PDO $db, array $item): float {
if (isset($item['vat_amount']) && $item['vat_amount'] !== '' && $item['vat_amount'] !== null) {
return (float)$item['vat_amount'];
}
$vatRate = 0.0;
if (isset($item['vat_rate']) && $item['vat_rate'] !== '' && $item['vat_rate'] !== null) {
$vatRate = (float)$item['vat_rate'];
} elseif (!empty($item['item_id'])) {
$stmtVat = $db->prepare("SELECT vat_rate FROM stock_items WHERE id = ?");
$stmtVat->execute([(int)$item['item_id']]);
$vatRate = (float)$stmtVat->fetchColumn();
}
$lineTotal = isset($item['total_price']) && $item['total_price'] !== null
? (float)$item['total_price']
: ((float)($item['quantity'] ?? 0) * (float)($item['unit_price'] ?? 0));
return $lineTotal * ($vatRate / 100);
}
}
if (!function_exists('runtime_debug_can_render_details')) {
function runtime_debug_can_render_details(): bool {
if (PHP_SAPI === 'cli') {
return true;
}
$roleName = (string)($_SESSION['user_role_name'] ?? '');
if (strcasecmp($roleName, 'Administrator') === 0 || (int)($_SESSION['user_id'] ?? 0) === 1) {
return true;
}
$remoteAddress = $_SERVER['REMOTE_ADDR'] ?? '';
return in_array($remoteAddress, ['127.0.0.1', '::1'], true);
}
}
if (!function_exists('runtime_debug_infer_schema_hint')) {
function runtime_debug_infer_schema_hint(Throwable $throwable): ?string {
$message = $throwable->getMessage();
if (str_contains($message, 'Call to undefined method DatabaseInstaller::ensureCurrentSchema()')) {
return 'Outdated installer class: re-sync includes/DatabaseInstaller.php with the latest project version.';
}
if (preg_match("/Table '[^']+\.([^']+)' doesn't exist/i", $message, $matches)) {
return 'Missing table: ' . $matches[1];
}
if (preg_match("/Unknown column '([^']+)'/i", $message, $matches)) {
return 'Missing column: ' . $matches[1];
}
if (preg_match('/Base table or view not found: [0-9]+ ([a-zA-Z0-9_]+)/i', $message, $matches)) {
return 'Missing table or view: ' . $matches[1];
}
return null;
}
}
if (!function_exists('runtime_debug_log')) {
function runtime_debug_log(Throwable $throwable): void {
$parts = [
date('Y-m-d H:i:s'),
'[' . get_class($throwable) . ']',
$throwable->getMessage(),
'file=' . $throwable->getFile() . ':' . $throwable->getLine(),
'page=' . ($_GET['page'] ?? 'dashboard'),
'uri=' . ($_SERVER['REQUEST_URI'] ?? 'cli'),
'user_id=' . (string)($_SESSION['user_id'] ?? 0),
];
$hint = runtime_debug_infer_schema_hint($throwable);
if ($hint !== null) {
$parts[] = 'hint=' . $hint;
}
if ($throwable instanceof PDOException && isset($throwable->errorInfo) && is_array($throwable->errorInfo)) {
$parts[] = 'error_info=' . json_encode($throwable->errorInfo, JSON_UNESCAPED_UNICODE);
}
$trace = explode("
", $throwable->getTraceAsString());
if ($trace !== []) {
$parts[] = 'trace=' . implode(' | ', array_slice($trace, 0, 5));
}
@file_put_contents(__DIR__ . '/runtime_debug.log', implode(' || ', $parts) . PHP_EOL, FILE_APPEND);
}
}
if (!function_exists('runtime_debug_render_exception')) {
function runtime_debug_render_exception(Throwable $throwable): void {
runtime_debug_log($throwable);
$showDetails = runtime_debug_can_render_details();
while (ob_get_level() > 0) {
@ob_end_clean();
}
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: text/html; charset=UTF-8');
header('X-Robots-Tag: noindex, nofollow');
}
$hint = runtime_debug_infer_schema_hint($throwable);
$requestUri = (string)($_SERVER['REQUEST_URI'] ?? 'cli');
$page = (string)($_GET['page'] ?? 'dashboard');
$tracePreview = array_slice(explode("
", $throwable->getTraceAsString()), 0, 8);
$traceText = implode("
", $tracePreview);
$title = $showDetails ? 'Application Debug' : 'Application Error';
$summary = $showDetails
? 'The request failed. The details below should help identify the missing table or column.'
: 'An unexpected error occurred while loading this page.';
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title><?= htmlspecialchars($title) ?></title>
<style>
body { margin: 0; font-family: Inter, Arial, sans-serif; background: #f6f8fb; color: #1f2937; }
.wrap { max-width: 900px; margin: 48px auto; padding: 0 20px; }
.card { background: #fff; border-radius: 18px; box-shadow: 0 18px 60px rgba(15, 23, 42, 0.08); padding: 28px; }
h1 { margin: 0 0 10px; font-size: 28px; }
p { color: #4b5563; line-height: 1.6; }
.badge { display: inline-block; padding: 6px 10px; border-radius: 999px; background: #fee2e2; color: #991b1b; font-weight: 600; font-size: 13px; }
.grid { display: grid; grid-template-columns: 180px 1fr; gap: 12px 16px; margin-top: 22px; }
.label { font-weight: 700; color: #111827; }
.value { word-break: break-word; }
.hint { margin-top: 18px; padding: 14px 16px; border-radius: 14px; background: #fff7ed; color: #9a3412; border: 1px solid #fed7aa; }
pre { margin: 18px 0 0; padding: 16px; background: #0f172a; color: #e2e8f0; border-radius: 14px; overflow: auto; font-size: 12px; line-height: 1.5; }
.actions { margin-top: 22px; display: flex; gap: 12px; flex-wrap: wrap; }
.btn { display: inline-block; text-decoration: none; border-radius: 12px; padding: 10px 14px; font-weight: 600; }
.btn-primary { background: #2563eb; color: #fff; }
.btn-secondary { background: #e5e7eb; color: #111827; }
.note { margin-top: 20px; font-size: 14px; color: #6b7280; }
</style>
</head>
<body>
<main class="wrap">
<section class="card">
<span class="badge">HTTP 500</span>
<h1><?= htmlspecialchars($title) ?></h1>
<p><?= htmlspecialchars($summary) ?></p>
<?php if ($showDetails): ?>
<div class="grid">
<div class="label">Exception</div>
<div class="value"><?= htmlspecialchars(get_class($throwable)) ?></div>
<div class="label">Message</div>
<div class="value"><?= htmlspecialchars($throwable->getMessage()) ?></div>
<div class="label">File</div>
<div class="value"><?= htmlspecialchars($throwable->getFile()) ?></div>
<div class="label">Line</div>
<div class="value"><?= (int)$throwable->getLine() ?></div>
<div class="label">Page</div>
<div class="value"><?= htmlspecialchars($page) ?></div>
<div class="label">Request URI</div>
<div class="value"><?= htmlspecialchars($requestUri) ?></div>
</div>
<?php if ($hint !== null): ?>
<div class="hint"><strong>Schema hint:</strong> <?= htmlspecialchars($hint) ?></div>
<?php endif; ?>
<?php if ($traceText !== ''): ?>
<pre><?= htmlspecialchars($traceText) ?></pre>
<?php endif; ?>
<div class="actions">
<a class="btn btn-primary" href="schema_debug.php">Open schema debug report</a>
<a class="btn btn-secondary" href="index.php">Retry dashboard</a>
</div>
<p class="note">A copy of this failure was written to <code>runtime_debug.log</code>.</p>
<?php else: ?>
<div class="actions">
<a class="btn btn-secondary" href="index.php">Back to home</a>
</div>
<?php endif; ?>
</section>
</main>
</body>
</html>
<?php
exit;
}
}
if (!defined('APP_RUNTIME_DEBUG_HANDLER_REGISTERED')) {
define('APP_RUNTIME_DEBUG_HANDLER_REGISTERED', true);
set_exception_handler(static function (Throwable $throwable): void {
runtime_debug_render_exception($throwable);
});
}
// Handle Outlet Switch
if (isset($_GET['action']) && $_GET['action'] === 'switch_outlet' && isset($_GET['id'])) {
$target_id = (int)$_GET['id'];
$allowed_outlets = $_SESSION['user_outlets'] ?? [1];
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
if ($target_id === -1) {
$_SESSION['outlet_id'] = -1;
} elseif ($is_admin || in_array($target_id, $allowed_outlets)) {
if (!db_table_exists('outlets')) {
$_SESSION['outlet_id'] = $target_id;
} else {
$stmt = db()->prepare("SELECT id FROM outlets WHERE id = ? AND status = 'active'");
$stmt->execute([$target_id]);
if ($stmt->fetchColumn()) {
$_SESSION['outlet_id'] = $target_id;
}
}
}
header("Location: index.php");
exit;
}
// Timezone Setup
try {
$tz_stmt = db()->prepare("SELECT value FROM settings WHERE `key` = 'timezone'");
$tz_stmt->execute();
$app_tz = $tz_stmt->fetchColumn();
if ($app_tz && in_array($app_tz, DateTimeZone::listIdentifiers())) {
date_default_timezone_set($app_tz);
}
} catch (Exception $e) {
// Ignore if DB not ready
}
require_once 'includes/DatabaseInstaller.php';
// Auto-install database if not installed, then ensure pending migrations are applied.
try {
if (!DatabaseInstaller::isInstalled()) {
DatabaseInstaller::install();
} elseif (method_exists('DatabaseInstaller', 'ensureCurrentSchema')) {
DatabaseInstaller::ensureCurrentSchema();
} else {
error_log('Skipping DatabaseInstaller::ensureCurrentSchema() because the loaded DatabaseInstaller class does not define it.');
}
} catch (Throwable $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';
if (!function_exists('localized_option_label')) {
function localized_option_label(array $row, ?string $forceLang = null, string $fallback = '---'): string
{
global $lang;
$targetLang = $forceLang ?? $lang;
$keys = $targetLang === 'ar'
? ['name_ar', 'short_name_ar', 'name_en', 'short_name_en']
: ['name_en', 'short_name_en', 'name_ar', 'short_name_ar'];
foreach ($keys as $key) {
$value = trim((string)($row[$key] ?? ''));
if ($value !== '') {
return $value;
}
}
return $fallback;
}
}
// 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"><?= htmlspecialchars((string)$error) ?></div>
<?php endif; ?>
<?php if ($success): ?>
<div class="alert alert-success small"><?= htmlspecialchars((string)$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 'includes/accounting_helper.php';
// Helper to check permissions
function can(string $permission): bool {
if (($_SESSION["user_id"] ?? 0) == 1) return true;
if (strcasecmp($_SESSION["user_role_name"] ?? "", "Administrator") === 0) return true;
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') || !db_table_exists('purchases')) return [];
$db = db();
$hasSupplierJoin = db_table_exists('suppliers') && db_column_exists('purchases', 'supplier_id');
$dueDateExpression = db_column_exists('purchases', 'due_date') ? 'p.due_date' : 'NULL';
$statusPredicate = db_column_exists('purchases', 'status') ? "WHERE p.status != 'paid'" : 'WHERE 1=1';
$dueDatePredicate = db_column_exists('purchases', 'due_date')
? ' AND p.due_date IS NOT NULL AND p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)'
: '';
$totalExpression = db_column_exists('purchases', 'total_with_vat')
? 'p.total_with_vat'
: (db_column_exists('purchases', 'total_amount') ? 'COALESCE(p.total_amount, 0)' : '0');
$supplierExpression = $hasSupplierJoin ? 's.name' : 'NULL';
$joinClause = $hasSupplierJoin ? ' LEFT JOIN suppliers s ON p.supplier_id = s.id' : '';
$orderBy = db_column_exists('purchases', 'due_date') ? ' ORDER BY p.due_date ASC' : ' ORDER BY p.id DESC';
$sql = "SELECT p.id, {$dueDateExpression} AS due_date, {$totalExpression} AS total_with_vat, {$supplierExpression} AS supplier_name"
. ' FROM purchases p'
. $joinClause
. ' '
. $statusPredicate
. $dueDatePredicate
. $orderBy;
$stmt = $db->query($sql);
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 = '';
$login_username = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
$user = trim((string)($_POST['username'] ?? ''));
$login_username = $user;
$pass = (string)($_POST['password'] ?? '');
try {
$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, (string)($u['password'] ?? ''))) {
$_SESSION['user_id'] = $u['id'];
$_SESSION['username'] = $u['username'];
$_SESSION['user_role_name'] = $u['role_name'];
$permissions = [];
if (db_table_exists('role_permissions')) {
$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'] ?? null;
$_SESSION['theme'] = $u['theme'] ?? 'default';
$user_outlets = [];
if (db_table_exists('user_outlets')) {
$outletStmt = db()->prepare("SELECT outlet_id FROM user_outlets WHERE user_id = ?");
$outletStmt->execute([$u['id']]);
$user_outlets = $outletStmt->fetchAll(PDO::FETCH_COLUMN) ?: [];
}
if (empty($user_outlets)) {
if (($u['role_name'] ?? '') === 'Administrator' && db_table_exists('outlets')) {
$allOutlets = db()->query("SELECT id FROM outlets WHERE status = 'active'")->fetchAll(PDO::FETCH_COLUMN);
$user_outlets = $allOutlets ?: [1];
} else {
$user_outlets = [1];
}
}
$user_outlets = array_values(array_unique(array_map('intval', $user_outlets)));
if ($user_outlets === []) {
$user_outlets = [1];
}
$_SESSION['user_outlets'] = $user_outlets;
$_SESSION['outlet_id'] = $user_outlets[0];
header("Location: index.php");
exit;
}
$login_error = "Invalid username or password";
$reason = (!$u) ? "User not found or inactive" : "Password mismatch";
app_debug_file_log('login_debug.log', date('Y-m-d H:i:s') . " - Failed login for '$user'. Reason: $reason");
} catch (Throwable $e) {
app_debug_file_log('login_debug.log', date('Y-m-d H:i:s') . " - Login exception for '$user': " . $e->getMessage());
$login_error = $lang === 'ar'
? 'تعذر تسجيل الدخول مؤقتًا. يرجى تحديث الصفحة والمحاولة مرة أخرى.'
: 'Sign-in is temporarily unavailable. Please refresh the page and try again.';
}
}
// 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 = trim((string)($_GET['q'] ?? ''));
app_debug_file_log('search_debug.log', date('Y-m-d H:i:s') . " - search_items call: q=" . $q);
if ($q === '') {
echo json_encode([]);
exit;
}
$searchTerm = "%$q%";
$startsWith = $q . '%';
$oid = current_outlet_id();
$sql = "SELECT i.*, i.stock_quantity FROM stock_items i WHERE ";
$params = [];
if ($oid > 0) {
$sql .= "i.outlet_id = ? AND ";
$params[] = $oid;
}
$sql .= "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?) ";
$sql .= "ORDER BY CASE WHEN i.sku = ? THEN 0 WHEN i.sku LIKE ? THEN 1 WHEN i.name_en LIKE ? THEN 2 WHEN i.name_ar LIKE ? THEN 3 ELSE 4 END, i.name_en ASC LIMIT 15";
$params[] = $searchTerm;
$params[] = $searchTerm;
$params[] = $searchTerm;
$params[] = $q;
$params[] = $startsWith;
$params[] = $startsWith;
$params[] = $startsWith;
$stmt = db()->prepare($sql);
$stmt->execute($params);
echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC));
exit;
}
if ($action === 'pos_search_items') {
$q = $_GET['q'] ?? '';
$searchTerm = "%$q%";
$oid = current_outlet_id();
$sql = "SELECT * FROM stock_items WHERE outlet_id = ? AND (name_en LIKE ? OR name_ar LIKE ? OR sku LIKE ?) ORDER BY name_en ASC LIMIT 100";
$products_raw = db()->prepare($sql);
$products_raw->execute([$oid, $searchTerm, $searchTerm, $searchTerm]);
while($p = $products_raw->fetch(PDO::FETCH_ASSOC)) {
$p['original_price'] = (float)$p['sale_price'];
$p['sale_price'] = getPromotionalPrice($p);
// Render Card HTML
?>
<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" data-en="<?= htmlspecialchars($p['name_en']) ?>" data-ar="<?= htmlspecialchars($p['name_ar']) ?>">
<?php if(!empty($p['name_ar'])): ?>
<div><?= htmlspecialchars($p['name_ar']) ?></div>
<div class="small text-secondary" style="font-size: 0.75rem; line-height: 1.1;"><?= htmlspecialchars($p['name_en']) ?></div>
<?php else: ?>
<div><?= htmlspecialchars($p['name_en']) ?></div>
<?php endif; ?>
</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
}
exit;
}
if ($action === 'pos_get_item_by_sku') {
header('Content-Type: application/json');
$sku = trim((string)($_GET['sku'] ?? ''));
if ($sku === '') { echo json_encode(null); exit; }
$weightBarcode = parseWeightBarcode($sku);
$lookupSku = $weightBarcode['item_code'] ?? $sku;
$oid = current_outlet_id();
$stmt = db()->prepare("SELECT * FROM stock_items WHERE sku = ? AND outlet_id = ? LIMIT 1");
$stmt->execute([$lookupSku, $oid]);
$p = $stmt->fetch(PDO::FETCH_ASSOC);
if ($p) {
$p['original_price'] = (float)$p['sale_price'];
$p['sale_price'] = getPromotionalPrice($p);
$p['price'] = (float)$p['sale_price'];
$p['nameEn'] = $p['name_en'];
$p['nameAr'] = $p['name_ar'];
$p['vatRate'] = $p['vat_rate'];
if ($weightBarcode) {
$qty = 0.0;
if ($weightBarcode['mode'] === 'price') {
if ((float)$p['sale_price'] <= 0) {
echo json_encode(['error' => 'This item cannot use price-based scale barcodes because its sale price is zero.']);
exit;
}
$qty = round(((float)$weightBarcode['value']) / (float)$p['sale_price'], 3);
} else {
$qty = round((float)$weightBarcode['value'], 3);
}
if ($qty <= 0) {
echo json_encode(['error' => 'The weighing scale barcode value is invalid.']);
exit;
}
$p['qty'] = $qty;
$p['is_scale_barcode'] = true;
$p['scale_barcode_mode'] = $weightBarcode['mode'];
$p['scale_barcode_value'] = (float)$weightBarcode['value'];
$p['scanned_barcode'] = $sku;
}
echo json_encode($p);
} else {
echo json_encode(null);
}
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, c.name as customer_name, o.name as outlet_name
FROM payments p
JOIN invoices i ON p.invoice_id = i.id
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']];
}
$outlet_id = current_outlet_id();
if ($outlet_id == -1) $outlet_id = 1; // Default to main branch if All Outlets selected
$stmt = $db->prepare("INSERT INTO invoices (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, outlet_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'paid', ?, 1, ?, ?, ?, ?)");
$stmt->execute([$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'], $outlet_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]);
update_stock($item['id'], -$item['qty'], $outlet_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();
echo json_encode(['success' => true, 'invoice_id' => $transaction_id, 'transaction_no' => $transaction_no]);
} 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') {
$purchaseReturnReferenceColumn = purchase_return_reference_column();
$stmt = db()->prepare("SELECT pr.*, pr.`{$purchaseReturnReferenceColumn}` AS purchase_id, 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 {
$salesReturnReferenceColumn = sales_return_reference_column();
$stmt = db()->prepare("SELECT sr.*, sr.`{$salesReturnReferenceColumn}` AS invoice_id, 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__ . '/includes/translation_helper.php';
$text = trim((string) ($_POST['text'] ?? ''));
$target = strtolower(trim((string) ($_POST['target'] ?? ''))) === 'en' ? 'en' : 'ar';
if ($text === '') {
echo json_encode(['success' => false, 'error' => 'No text provided'], JSON_UNESCAPED_UNICODE);
exit;
}
$translationResult = app_translate_text($text, $target);
if (!empty($translationResult['success']) && !empty($translationResult['translated'])) {
echo json_encode([
'success' => true,
'translated' => $translationResult['translated'],
'provider' => $translationResult['provider'] ?? 'unknown',
], JSON_UNESCAPED_UNICODE);
} else {
$errorMessage = (string) ($translationResult['error'] ?? 'Translation failed');
error_log("Translation failed for text '$text': " . json_encode($translationResult, JSON_UNESCAPED_UNICODE));
echo json_encode(['success' => false, 'error' => $errorMessage], JSON_UNESCAPED_UNICODE);
}
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">
<meta name="description" content="<?= htmlspecialchars($lang === 'ar' ? 'بوابة الدخول إلى لوحة الإدارة والمخزون والمبيعات والمحاسبة.' : 'Secure sign-in for the inventory, sales, POS, and accounting admin panel.') ?>">
<meta name="robots" content="noindex, nofollow">
<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>
:root {
color-scheme: light;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 1.5rem;
background:
radial-gradient(circle at top left, rgba(56, 189, 248, 0.18), transparent 30%),
radial-gradient(circle at bottom right, rgba(34, 197, 94, 0.16), transparent 24%),
linear-gradient(135deg, #f8fafc 0%, #eef2ff 55%, #f8fafc 100%);
font-family: 'Inter', sans-serif;
color: #0f172a;
}
.login-shell {
width: 100%;
max-width: 1040px;
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(360px, 420px);
gap: 1.5rem;
align-items: stretch;
}
.login-panel,
.login-card {
border: none;
border-radius: 28px;
box-shadow: 0 24px 60px rgba(15, 23, 42, 0.12);
overflow: hidden;
}
.login-panel {
position: relative;
padding: 2.25rem;
background: linear-gradient(145deg, #0f172a 0%, #1d4ed8 50%, #38bdf8 100%);
color: #e2e8f0;
display: flex;
flex-direction: column;
justify-content: space-between;
min-height: 600px;
}
.login-panel::after {
content: '';
position: absolute;
inset: auto -12% -18% auto;
width: 240px;
height: 240px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.12);
filter: blur(12px);
}
.login-kicker {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.45rem 0.85rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.14);
color: #f8fafc;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.login-panel h1 {
font-size: clamp(2.2rem, 3vw, 3.3rem);
line-height: 1.05;
margin: 1.25rem 0 1rem;
}
.login-panel p {
max-width: 34rem;
color: rgba(226, 232, 240, 0.82);
font-size: 1rem;
line-height: 1.7;
}
.login-feature-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 0.85rem;
margin-top: 2rem;
}
.login-feature {
padding: 1rem;
border-radius: 1.1rem;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.12);
}
.login-feature span {
display: block;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.7;
margin-bottom: 0.5rem;
}
.login-feature strong {
display: block;
font-size: 1.05rem;
color: #fff;
}
.login-panel-footer {
display: flex;
flex-wrap: wrap;
gap: 0.65rem;
margin-top: 2rem;
}
.login-panel-pill {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.55rem 0.85rem;
border-radius: 999px;
background: rgba(15, 23, 42, 0.2);
border: 1px solid rgba(255, 255, 255, 0.16);
color: #f8fafc;
font-size: 0.82rem;
font-weight: 600;
}
.login-card {
background: rgba(255, 255, 255, 0.92);
backdrop-filter: blur(18px);
padding: 1.75rem;
align-self: center;
}
.login-card__header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.login-language-btn {
border-radius: 999px;
padding-inline: 0.9rem;
font-weight: 600;
}
.login-brand {
display: grid;
gap: 0.85rem;
}
.login-brand__icon {
width: 64px;
height: 64px;
display: grid;
place-items: center;
border-radius: 1.4rem;
background: linear-gradient(135deg, rgba(29, 78, 216, 0.12), rgba(56, 189, 248, 0.18));
color: #1d4ed8;
font-size: 1.8rem;
}
.login-brand h2 {
margin: 0;
font-size: 1.7rem;
font-weight: 800;
color: #0f172a;
}
.login-brand p {
margin: 0;
color: #64748b;
line-height: 1.65;
}
.login-form label {
font-weight: 700;
color: #0f172a;
margin-bottom: 0.55rem;
}
.login-form .form-control {
border-radius: 16px;
padding: 0.9rem 1rem;
border: 1px solid rgba(148, 163, 184, 0.35);
box-shadow: none;
}
.login-form .form-control:focus {
border-color: #38bdf8;
box-shadow: 0 0 0 4px rgba(56, 189, 248, 0.14);
}
.password-group {
position: relative;
}
.password-toggle {
border-radius: 0 16px 16px 0;
border: 1px solid rgba(148, 163, 184, 0.35);
border-left: none;
background: #fff;
min-width: 54px;
}
[dir="rtl"] .password-toggle {
border-radius: 16px 0 0 16px;
border-left: 1px solid rgba(148, 163, 184, 0.35);
border-right: none;
}
.password-group .form-control {
border-radius: 16px 0 0 16px;
}
[dir="rtl"] .password-group .form-control {
border-radius: 0 16px 16px 0;
}
.login-note {
border-radius: 18px;
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
border: 1px solid rgba(148, 163, 184, 0.18);
padding: 0.95rem 1rem;
color: #475569;
font-size: 0.92rem;
}
.btn-primary {
border-radius: 16px;
padding: 0.95rem 1rem;
font-weight: 700;
background: linear-gradient(135deg, #1d4ed8, #38bdf8);
border: none;
box-shadow: 0 14px 28px rgba(29, 78, 216, 0.24);
}
.btn-primary:hover,
.btn-primary:focus {
background: linear-gradient(135deg, #1e40af, #0ea5e9);
}
.caps-lock-note {
display: none;
color: #92400e;
font-size: 0.82rem;
margin-top: 0.55rem;
}
.caps-lock-note.is-visible {
display: block;
}
.login-meta {
display: flex;
flex-wrap: wrap;
gap: 0.6rem;
margin-top: 1.2rem;
}
.login-meta span {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.45rem 0.75rem;
border-radius: 999px;
background: #eff6ff;
color: #1e40af;
font-size: 0.8rem;
font-weight: 600;
}
.login-footer {
margin-top: 1.4rem;
font-size: 0.86rem;
color: #64748b;
}
[dir="rtl"] {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
@media (max-width: 991.98px) {
body {
padding: 1rem;
}
.login-shell {
grid-template-columns: 1fr;
max-width: 480px;
}
.login-panel {
display: none;
}
.login-card {
padding: 1.35rem;
}
}
</style>
</head>
<body>
<main class="login-shell">
<section class="login-panel d-none d-lg-flex" aria-hidden="true">
<div>
<span class="login-kicker">
<i class="bi bi-stars"></i>
<?= $lang === 'ar' ? 'لوحة تشغيل موحدة' : 'Unified operations workspace' ?>
</span>
<h1><?= $lang === 'ar' ? 'قم بإدارة المخزون والمبيعات والمحاسبة من مكان واحد.' : 'Run inventory, sales, POS, and accounting from one clean workspace.' ?></h1>
<p><?= $lang === 'ar' ? 'واجهة إدارية سريعة مع تتبع الأصناف والفواتير والعملاء والموردين والتقارير اليومية — مصممة لتقليل الخطوات وتسريع العمل.' : 'A faster admin experience for items, invoices, suppliers, customers, and daily reporting—designed to keep routine operations focused and efficient.' ?></p>
<div class="login-feature-grid">
<div class="login-feature">
<span><?= $lang === 'ar' ? 'المخزون' : 'Inventory' ?></span>
<strong><?= $lang === 'ar' ? 'أصناف ومخزون حي' : 'Live items & stock' ?></strong>
</div>
<div class="login-feature">
<span><?= $lang === 'ar' ? 'المبيعات' : 'Sales' ?></span>
<strong><?= $lang === 'ar' ? 'فواتير ودفع ونقطة بيع' : 'Invoices, payments & POS' ?></strong>
</div>
<div class="login-feature">
<span><?= $lang === 'ar' ? 'المحاسبة' : 'Accounting' ?></span>
<strong><?= $lang === 'ar' ? 'تقارير ورقابة يومية' : 'Daily reporting & control' ?></strong>
</div>
</div>
</div>
<div class="login-panel-footer">
<span class="login-panel-pill"><i class="bi bi-lightning-charge"></i> <?= $lang === 'ar' ? 'سريع' : 'Fast' ?></span>
<span class="login-panel-pill"><i class="bi bi-shield-check"></i> <?= $lang === 'ar' ? 'آمن' : 'Secure' ?></span>
<span class="login-panel-pill"><i class="bi bi-bar-chart"></i> <?= $lang === 'ar' ? 'واضح' : 'Insightful' ?></span>
</div>
</section>
<section class="login-card">
<div class="login-card__header">
<div class="login-brand">
<div class="login-brand__icon">
<i class="bi bi-shield-lock"></i>
</div>
<div>
<h2><?= __('welcome_back') ?></h2>
<p><?= $lang === 'ar' ? 'سجّل الدخول للوصول إلى لوحة الإدارة ومتابعة العمليات اليومية.' : 'Sign in to continue to your admin workspace and daily operations.' ?></p>
</div>
</div>
<a href="?lang=<?= $lang === 'ar' ? 'en' : 'ar' ?>" class="btn btn-sm btn-light border login-language-btn">
<?= $lang === 'ar' ? 'English' : 'العربية' ?>
</a>
</div>
<?php if ($login_error): ?>
<div class="alert alert-danger small py-2 mb-4" role="alert" aria-live="polite"><?= htmlspecialchars($login_error) ?></div>
<?php endif; ?>
<form method="POST" class="login-form">
<div class="mb-3">
<label class="form-label" for="loginUsername"><?= __('username') ?></label>
<input
type="text"
id="loginUsername"
name="username"
class="form-control"
placeholder="<?= $lang === 'ar' ? 'اسم المستخدم' : 'Username' ?>"
value="<?= htmlspecialchars($login_username) ?>"
required
autofocus
autocomplete="username"
autocapitalize="none"
spellcheck="false"
>
</div>
<div class="mb-2">
<label class="form-label" for="loginPassword"><?= __('password') ?></label>
<div class="input-group password-group">
<input
type="password"
id="loginPassword"
name="password"
class="form-control"
placeholder="<?= $lang === 'ar' ? 'كلمة المرور' : 'Password' ?>"
required
autocomplete="current-password"
>
<button
type="button"
class="btn btn-light password-toggle"
id="togglePasswordButton"
aria-label="<?= htmlspecialchars($lang === 'ar' ? 'إظهار أو إخفاء كلمة المرور' : 'Show or hide password') ?>"
aria-controls="loginPassword"
aria-pressed="false"
>
<i class="bi bi-eye"></i>
</button>
</div>
<div id="capsLockNotice" class="caps-lock-note">
<i class="bi bi-exclamation-circle me-1"></i>
<?= $lang === 'ar' ? 'زر Caps Lock مفعّل.' : 'Caps Lock is on.' ?>
</div>
</div>
<div class="login-note mb-4">
<?= $lang === 'ar'
? 'إذا فشل تسجيل الدخول، يتم الاحتفاظ باسم المستخدم لتجربة أسرع في المحاولة التالية.'
: 'Your username stays filled in after a failed sign-in so retrying is faster.' ?>
</div>
<button type="submit" name="login" class="btn btn-primary w-100">
<?= __('sign_in') ?>
</button>
<div class="login-meta">
<span><i class="bi bi-grid-1x2"></i> <?= $lang === 'ar' ? 'لوحة إدارية' : 'Admin panel' ?></span>
<span><i class="bi bi-receipt"></i> <?= $lang === 'ar' ? 'فواتير ونقطة بيع' : 'Invoices & POS' ?></span>
<span><i class="bi bi-clipboard-data"></i> <?= $lang === 'ar' ? 'تقارير وتشغيل' : 'Reports & ops' ?></span>
</div>
<div class="login-footer">
<?= $lang === 'ar' ? 'نصيحة: بدّل اللغة من الزر العلوي قبل تسجيل الدخول إذا كنت تفضل العربية أو الإنجليزية.' : 'Tip: switch language from the top button before signing in if you prefer Arabic or English.' ?>
</div>
</form>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
(function () {
const passwordInput = document.getElementById('loginPassword');
const toggleButton = document.getElementById('togglePasswordButton');
const capsLockNotice = document.getElementById('capsLockNotice');
if (!passwordInput || !toggleButton) {
return;
}
const updateCapsLock = function (event) {
if (!capsLockNotice || typeof event.getModifierState !== 'function') {
return;
}
const isOn = event.getModifierState('CapsLock');
capsLockNotice.classList.toggle('is-visible', isOn);
};
toggleButton.addEventListener('click', function () {
const nextType = passwordInput.type === 'password' ? 'text' : 'password';
passwordInput.type = nextType;
this.setAttribute('aria-pressed', nextType === 'text' ? 'true' : 'false');
this.innerHTML = nextType === 'text'
? '<i class="bi bi-eye-slash"></i>'
: '<i class="bi bi-eye"></i>';
passwordInput.focus();
if (typeof passwordInput.setSelectionRange === 'function') {
const end = passwordInput.value.length;
passwordInput.setSelectionRange(end, end);
}
});
passwordInput.addEventListener('keydown', updateCapsLock);
passwordInput.addEventListener('keyup', updateCapsLock);
passwordInput.addEventListener('blur', function () {
if (capsLockNotice) {
capsLockNotice.classList.remove('is-visible');
}
});
}());
</script>
</body>
</html>
<?php
exit;
}
// Handle POST Requests
$message = $_SESSION['message'] ?? '';
unset($_SESSION['message']);
function getSettingValue(string $key, ?string $default = null): ?string {
static $cache = [];
if (array_key_exists($key, $cache)) return $cache[$key];
try {
$stmt = db()->prepare("SELECT value FROM settings WHERE `key` = ? LIMIT 1");
$stmt->execute([$key]);
$value = $stmt->fetchColumn();
} catch (Throwable $e) {
$value = false;
}
if ($value === false || $value === null || $value === '') $value = $default;
$cache[$key] = $value;
return $value;
}
function getWeightBarcodeConfig(): array {
$prefixStart = (int)(getSettingValue('weight_barcode_prefix_start', '20') ?? '20');
$prefixEnd = (int)(getSettingValue('weight_barcode_prefix_end', '29') ?? '29');
if ($prefixStart < 20 || $prefixStart > 29) $prefixStart = 20;
if ($prefixEnd < 20 || $prefixEnd > 29) $prefixEnd = 29;
if ($prefixStart > $prefixEnd) {
[$prefixStart, $prefixEnd] = [$prefixEnd, $prefixStart];
}
$mode = strtolower((string)(getSettingValue('weight_barcode_mode', 'weight') ?? 'weight'));
if (!in_array($mode, ['weight', 'price'], true)) $mode = 'weight';
return [
'prefix_start' => $prefixStart,
'prefix_end' => $prefixEnd,
'mode' => $mode,
'value_divisor' => 1000,
];
}
function isWeightBarcode(string $barcode): bool {
$barcode = trim($barcode);
if (!preg_match('/^\d{13}$/', $barcode)) return false;
$config = getWeightBarcodeConfig();
$prefix = (int)substr($barcode, 0, 2);
return $prefix >= $config['prefix_start'] && $prefix <= $config['prefix_end'];
}
function parseWeightBarcode(string $barcode): ?array {
$barcode = trim($barcode);
if (!isWeightBarcode($barcode)) return null;
$config = getWeightBarcodeConfig();
$rawValue = (int)substr($barcode, 7, 5);
return [
'full_barcode' => $barcode,
'prefix' => substr($barcode, 0, 2),
'item_code' => substr($barcode, 2, 5),
'raw_value' => $rawValue,
'value' => $rawValue / (float)$config['value_divisor'],
'mode' => $config['mode'],
'check_digit' => substr($barcode, 12, 1),
];
}
function validateItemSkuBarcode(string $sku): ?string {
$sku = trim($sku);
if ($sku === '') return null;
if (!isWeightBarcode($sku)) return null;
$config = getWeightBarcodeConfig();
return "This barcode is reserved for weighing scale scans. 13-digit barcodes starting with {$config['prefix_start']}-{$config['prefix_end']} cannot be saved as item barcodes; please save the 5-digit scale item code instead.";
}
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 = trim((string)($_POST['sku'] ?? ''));
if ($sku_error = validateItemSkuBarcode($sku)) {
redirectWithMessage($sku_error, 'index.php?page=items');
}
$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;
}
$current_oid = current_outlet_id();
$stmt = db()->prepare("INSERT INTO stock_items (outlet_id, 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([$current_oid, $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]);
$new_item_id = db()->lastInsertId();
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 = trim((string)($_POST['sku'] ?? ''));
if ($sku_error = validateItemSkuBarcode($sku)) {
redirectWithMessage($sku_error, 'index.php?page=items');
}
$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);
// Update stock_items
$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'])) {
$current_oid = current_outlet_id();
db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, ?)")
->execute([$_POST['name_en'] ?? '', $_POST['name_ar'] ?? '', $current_oid]);
redirectWithMessage("Category added!");
}
if (isset($_POST['add_unit'])) {
$current_oid = current_outlet_id();
$unitNameEn = trim((string)($_POST['name_en'] ?? ''));
$unitNameAr = trim((string)($_POST['name_ar'] ?? ''));
if ($unitNameAr === '') $unitNameAr = $unitNameEn;
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, ?)")
->execute([$unitNameEn, $unitNameAr, $unitNameEn, $unitNameAr, $current_oid]);
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'])) {
$unitNameEn = trim((string)($_POST['name_en'] ?? ''));
$unitNameAr = trim((string)($_POST['name_ar'] ?? ''));
if ($unitNameAr === '') $unitNameAr = $unitNameEn;
db()->prepare("UPDATE stock_units SET name_en = ?, name_ar = ?, short_name_en = ?, short_name_ar = ? WHERE id = ?")->execute([$unitNameEn, $unitNameAr, $unitNameEn, $unitNameAr, (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';
$taxColumn = entity_tax_column($table);
$columns = ['name', 'email', 'phone'];
$placeholders = ['?', '?', '?'];
$params = [$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? ''];
if ($taxColumn !== null) {
$columns[] = $taxColumn;
$placeholders[] = '?';
$params[] = $_POST['tax_id'] ?? '';
}
$columns[] = 'balance';
$placeholders[] = '?';
$params[] = (float)($_POST['balance'] ?? 0);
if ($table === 'customers') {
$columns[] = 'loyalty_points';
$placeholders[] = '?';
$params[] = 0;
} else {
$columns[] = 'outlet_id';
$placeholders[] = '?';
$params[] = current_outlet_id();
}
$sql = "INSERT INTO $table (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
db()->prepare($sql)->execute($params);
redirectWithMessage("Entity added!");
}
if (isset($_POST['edit_customer'])) {
$table = ($_POST['type'] ?? '') === 'supplier' ? 'suppliers' : 'customers';
$taxColumn = entity_tax_column($table);
$assignments = ['name = ?', 'email = ?', 'phone = ?'];
$params = [$_POST['name'] ?? '', $_POST['email'] ?? '', $_POST['phone'] ?? ''];
if ($taxColumn !== null) {
$assignments[] = $taxColumn . ' = ?';
$params[] = $_POST['tax_id'] ?? '';
}
$assignments[] = 'balance = ?';
$params[] = (float)($_POST['balance'] ?? 0);
$params[] = (int)$_POST['id'];
$sql = "UPDATE $table SET " . implode(', ', $assignments) . " WHERE id = ?";
db()->prepare($sql)->execute($params);
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!");
}
// Sales/Purchases save/update handlers
require_once __DIR__ . '/pages/sales_purchases_save_logic.php';
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;
[$quotationInsertSql, $quotationInsertValues] = db_insert_sql_for_existing_columns('quotations', [
'customer_id' => $cust_id,
'quotation_date' => $quot_date,
'valid_until' => $valid_until,
'status' => $status,
'total_amount' => $total_subtotal,
'vat_amount' => $total_vat,
'total_with_vat' => $total_with_vat,
'outlet_id' => current_outlet_id(),
]);
$stmt = $db->prepare($quotationInsertSql);
$stmt->execute($quotationInsertValues);
$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;
[$lpoInsertSql, $lpoInsertValues] = db_insert_sql_for_existing_columns('lpos', [
'supplier_id' => $supp_id,
'lpo_date' => $lpo_date,
'delivery_date' => $delivery_date,
'status' => 'pending',
'total_amount' => $total_subtotal,
'vat_amount' => $total_vat,
'total_with_vat' => $total_with_vat,
'terms_conditions' => $terms,
'outlet_id' => current_outlet_id(),
]);
$stmt = $db->prepare($lpoInsertSql);
$stmt->execute($lpoInsertValues);
$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 (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
$stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat'], current_outlet_id()]);
$inv_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($qItems as $item) {
$lineVatAmount = line_item_vat_amount($db, $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'], $lineVatAmount, $item['total_price']]);
// Update stock
update_stock($item['item_id'], -$item['quantity']);
$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 (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
$stmtPur->execute([$lpo['supplier_id'], $pur_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat'], current_outlet_id()]);
$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
update_stock($item['item_id'], $item['quantity']);
$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 (category_id, amount, expense_date, reference_no, description) VALUES (?, ?, ?, ?, ?)")->execute([(int)$_POST['category_id'], $amt, $date, $_POST['reference_no'] ?? '', $desc]);
recordExpenseJournal(db()->lastInsertId(), $amt, $date, $desc);
redirectWithMessage("Expense recorded!", "index.php?page=expenses");
}
# --- Unified Import Logic (Excel & CSV) ---
# --- Unified Import Logic (Excel & CSV) ---
if (isset($_POST['import_items'])) {
error_log("Import items triggered.");
$count = 0;
$skipped = 0;
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$rows = [];
if ( $xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath) ) {
$rows = $xlsx->rows();
} else {
$handle = fopen($tmpPath, "r");
$firstLine = fgets($handle); rewind($handle);
$sep = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ',';
$bom = fread($handle, 3); if ($bom !== "") rewind($handle);
while (($data = fgetcsv($handle, 0, $sep)) !== FALSE) $rows[] = $data;
fclose($handle);
}
if (isset($rows[0][0]) && stripos($rows[0][0], 'sku') !== false) array_shift($rows);
$current_oid = current_outlet_id();
foreach ($rows as $row) {
if (empty($row[0])) continue;
$sku = trim((string)$row[0]);
if ($sku_error = validateItemSkuBarcode($sku)) {
$skipped++;
continue;
}
$name_en = trim((string)($row[1] ?? ''));
$name_ar = trim((string)($row[2] ?? ''));
$sale_price = (float)($row[3] ?? 0);
$purchase_price = (float)($row[4] ?? 0);
$qty = (float)($row[5] ?? 0);
$vat_rate = (float)($row[6] ?? 0);
$check = db()->prepare("SELECT id FROM stock_items WHERE sku = ? AND outlet_id = ?");
$check->execute([$sku, $current_oid]);
$exists = $check->fetch(PDO::FETCH_ASSOC);
if ($exists) {
$item_id = $exists['id'];
// Update Item (including stock_quantity)
db()->prepare("UPDATE stock_items SET name_en = ?, name_ar = ?, sale_price = ?, purchase_price = ?, vat_rate = ?, stock_quantity = ? WHERE id = ?")
->execute([$name_en, $name_ar, $sale_price, $purchase_price, $vat_rate, $qty, $item_id]);
} else {
// Insert Item
db()->prepare("INSERT INTO stock_items (outlet_id, sku, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
->execute([$current_oid, $sku, $name_en, $name_ar, $sale_price, $purchase_price, $qty, $vat_rate]);
}
$count++;
}
$weightConfig = getWeightBarcodeConfig();
$summary = "Import items completed! $count processed.";
if ($skipped > 0) {
$summary .= " $skipped skipped because 13-digit barcodes starting with {$weightConfig['prefix_start']}-{$weightConfig['prefix_end']} are reserved for weighing scale barcodes.";
}
redirectWithMessage($summary, "index.php?page=items");
}
}
if (isset($_POST['import_customers']) || isset($_POST['import_suppliers'])) {
$type = isset($_POST['import_customers']) ? 'customers' : 'suppliers';
$table = $type;
error_log("Import $type triggered.");
$count = 0;
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$rows = [];
if ( $xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath) ) {
$rows = $xlsx->rows();
} else {
$handle = fopen($tmpPath, "r");
$firstLine = fgets($handle); rewind($handle);
$sep = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ',';
$bom = fread($handle, 3); if ($bom !== "") rewind($handle);
while (($data = fgetcsv($handle, 0, $sep)) !== FALSE) $rows[] = $data;
fclose($handle);
}
if (isset($rows[0][0]) && (stripos($rows[0][0], 'name') !== false || stripos($rows[0][0], 'id') !== false)) array_shift($rows);
$taxColumn = entity_tax_column($table);
$importColumns = ['name', 'email', 'phone'];
$importValues = ['?', '?', '?'];
if ($taxColumn !== null) {
$importColumns[] = $taxColumn;
$importValues[] = '?';
}
$importColumns[] = 'created_at';
$importValues[] = 'NOW()';
if ($table === 'suppliers') {
$importColumns[] = 'outlet_id';
$importValues[] = '?';
}
$importSql = "INSERT INTO $table (" . implode(', ', $importColumns) . ") VALUES (" . implode(', ', $importValues) . ")";
$importStmt = db()->prepare($importSql);
foreach ($rows as $row) {
if (empty($row[0])) continue;
$name = trim((string)$row[0]);
if (!$name) continue;
$email = trim((string)($row[1] ?? ''));
$phone = trim((string)($row[2] ?? ''));
$tax_id = trim((string)($row[3] ?? ''));
$importParams = [$name, $email, $phone];
if ($taxColumn !== null) {
$importParams[] = $tax_id;
}
if ($table === 'suppliers') {
$importParams[] = current_outlet_id();
}
$importStmt->execute($importParams);
$count++;
}
redirectWithMessage("Import $type completed! $count processed.", "index.php?page=$type");
}
}
if (isset($_POST['import_categories'])) {
$count = 0;
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$rows = [];
if ( $xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath) ) { $rows = $xlsx->rows(); }
else {
$handle = fopen($tmpPath, "r");
while (($data = fgetcsv($handle)) !== FALSE) $rows[] = $data;
fclose($handle);
}
if (isset($rows[0][0]) && stripos($rows[0][0], 'name') !== false) array_shift($rows);
$current_oid = current_outlet_id();
foreach ($rows as $row) {
if (empty($row[0])) continue;
$name_en = trim((string)$row[0]);
$name_ar = trim((string)($row[1] ?? $name_en));
db()->prepare("INSERT INTO stock_categories (name_en, name_ar, outlet_id) VALUES (?, ?, ?)")
->execute([$name_en, $name_ar, $current_oid]);
$count++;
}
redirectWithMessage("Import categories completed! $count processed.", "index.php?page=categories");
}
}
if (isset($_POST['import_units'])) {
$count = 0;
if (isset($_FILES['excel_file']) && $_FILES['excel_file']['error'] === 0) {
$tmpPath = $_FILES['excel_file']['tmp_name'];
$rows = [];
if ( $xlsx = \Shuchkin\SimpleXLSX::parse($tmpPath) ) { $rows = $xlsx->rows(); }
else {
$handle = fopen($tmpPath, "r");
$firstLine = fgets($handle); rewind($handle);
$sep = (substr_count($firstLine, ';') > substr_count($firstLine, ',')) ? ';' : ',';
$bom = fread($handle, 3); if ($bom !== "") rewind($handle);
while (($data = fgetcsv($handle, 0, $sep)) !== FALSE) $rows[] = $data;
fclose($handle);
}
if (isset($rows[0][0]) && (stripos($rows[0][0], 'name') !== false || stripos($rows[0][0], 'id') !== false)) array_shift($rows);
foreach ($rows as $row) {
if (empty($row[0])) continue;
$name_en = trim((string)$row[0]);
$name_ar = trim((string)($row[1] ?? ''));
if ($name_ar === '') $name_ar = $name_en;
db()->prepare("INSERT INTO stock_units (name_en, name_ar, short_name_en, short_name_ar, outlet_id) VALUES (?, ?, ?, ?, ?)")
->execute([$name_en, $name_ar, $name_en, $name_ar, current_outlet_id()]);
$count++;
}
redirectWithMessage("Import units completed! $count processed.", "index.php?page=units");
}
}
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_en = trim((string)($_POST['name_en'] ?? ''));
$name_ar = trim((string)($_POST['name_ar'] ?? ''));
if ($name_en === '' && $name_ar === '') {
redirectWithMessage("Please enter a payment method name.", "index.php?page=payment_methods");
}
if ($name_en === '') {
$name_en = $name_ar;
}
if ($name_ar === '') {
$name_ar = $name_en;
}
if (db_column_exists('payment_methods', 'name_en') || db_column_exists('payment_methods', 'name_ar')) {
$columns = [];
$placeholders = [];
$values = [];
if (db_column_exists('payment_methods', 'name_en')) {
$columns[] = 'name_en';
$placeholders[] = '?';
$values[] = $name_en;
}
if (db_column_exists('payment_methods', 'name_ar')) {
$columns[] = 'name_ar';
$placeholders[] = '?';
$values[] = $name_ar;
}
db()->prepare("INSERT INTO payment_methods (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")")->execute($values);
} elseif (db_column_exists('payment_methods', 'name')) {
db()->prepare("INSERT INTO payment_methods (`name`) VALUES (?)")->execute([$name_en]);
} else {
throw new RuntimeException('payment_methods table is missing a usable name column.');
}
redirectWithMessage("Payment method added!", "index.php?page=payment_methods");
}
if (isset($_POST['edit_payment_method'])) {
$id = (int)($_POST['id'] ?? 0);
$name_en = trim((string)($_POST['name_en'] ?? ''));
$name_ar = trim((string)($_POST['name_ar'] ?? ''));
if ($id <= 0) {
redirectWithMessage("Invalid payment method.", "index.php?page=payment_methods");
}
if ($name_en === '' && $name_ar === '') {
redirectWithMessage("Please enter a payment method name.", "index.php?page=payment_methods");
}
if ($name_en === '') {
$name_en = $name_ar;
}
if ($name_ar === '') {
$name_ar = $name_en;
}
if (db_column_exists('payment_methods', 'name_en') || db_column_exists('payment_methods', 'name_ar')) {
$sets = [];
$values = [];
if (db_column_exists('payment_methods', 'name_en')) {
$sets[] = 'name_en = ?';
$values[] = $name_en;
}
if (db_column_exists('payment_methods', 'name_ar')) {
$sets[] = 'name_ar = ?';
$values[] = $name_ar;
}
$values[] = $id;
db()->prepare("UPDATE payment_methods SET " . implode(', ', $sets) . " WHERE id = ?")->execute($values);
} elseif (db_column_exists('payment_methods', 'name')) {
db()->prepare("UPDATE payment_methods SET `name` = ? WHERE id = ?")->execute([$name_en, $id]);
} else {
throw new RuntimeException('payment_methods table is missing a usable name column.');
}
redirectWithMessage("Payment method updated!", "index.php?page=payment_methods");
}
if (isset($_POST['delete_payment_method'])) {
$id = (int)($_POST['id'] ?? 0);
if ($id > 0) {
db()->prepare("DELETE FROM payment_methods WHERE id = ?")->execute([$id]);
}
redirectWithMessage("Payment method deleted!", "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;
[$lpoInsertSql, $lpoInsertValues] = db_insert_sql_for_existing_columns('lpos', [
'supplier_id' => $supp_id,
'lpo_date' => $lpo_date,
'delivery_date' => $delivery_date,
'status' => 'pending',
'total_amount' => $total_subtotal,
'vat_amount' => $total_vat,
'total_with_vat' => $total_with_vat,
'terms_conditions' => $terms,
'outlet_id' => current_outlet_id(),
]);
$stmt = $db->prepare($lpoInsertSql);
$stmt->execute($lpoInsertValues);
$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 (customer_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
$stmtInv->execute([$quot['customer_id'], $inv_date, $quot['total_amount'], $quot['vat_amount'], $quot['total_with_vat'], current_outlet_id()]);
$inv_id = $db->lastInsertId();
$items_for_journal = [];
foreach ($qItems as $item) {
$lineVatAmount = line_item_vat_amount($db, $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'], $lineVatAmount, $item['total_price']]);
// Update stock
update_stock($item['item_id'], -$item['quantity']);
$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 (supplier_id, invoice_date, status, payment_type, total_amount, vat_amount, total_with_vat, paid_amount, outlet_id) VALUES (?, ?, 'unpaid', 'credit', ?, ?, ?, 0, ?)");
$stmtPur->execute([$lpo['supplier_id'], $inv_date, $lpo['total_amount'], $lpo['vat_amount'], $lpo['total_with_vat'], current_outlet_id()]);
$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
update_stock($item['item_id'], $item['quantity']);
$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 (category_id, amount, expense_date, reference_no, description) VALUES (?, ?, ?, ?, ?)")->execute([(int)$_POST['category_id'], $amt, $date, $_POST['reference_no'] ?? '', $desc]);
recordExpenseJournal(db()->lastInsertId(), $amt, $date, $desc);
$message = "Expense recorded!";
}
// --- 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.");
}
$payrollColumns = ['employee_id', 'payroll_month', 'payroll_year', 'basic_salary', 'bonus', 'deductions', 'net_salary'];
$payrollPlaceholders = array_fill(0, count($payrollColumns), '?');
$payrollValues = [$emp_id, $month, $year, $basic, $bonus, $deduct, $net];
if (db_column_exists('hr_payroll', 'notes')) {
$payrollColumns[] = 'notes';
$payrollPlaceholders[] = '?';
$payrollValues[] = $notes;
}
$stmt = $db->prepare("INSERT INTO hr_payroll (" . implode(', ', $payrollColumns) . ") VALUES (" . implode(', ', $payrollPlaceholders) . ")");
$stmt->execute($payrollValues);
$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
$salesReturnReferenceColumn = sales_return_reference_column();
[$salesReturnInsertSql, $salesReturnInsertValues] = db_insert_sql_for_existing_columns('sales_returns', [
$salesReturnReferenceColumn => $invoice_id,
'customer_id' => $customer_id,
'return_date' => $return_date,
'total_amount' => $total_return,
'notes' => $notes,
'outlet_id' => current_outlet_id(),
]);
$stmt = $db->prepare($salesReturnInsertSql);
$stmt->execute($salesReturnInsertValues);
$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]);
update_stock($item_id, $qty);
}
}
$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
$purchaseReturnReferenceColumn = purchase_return_reference_column();
[$purchaseReturnInsertSql, $purchaseReturnInsertValues] = db_insert_sql_for_existing_columns('purchase_returns', [
$purchaseReturnReferenceColumn => $invoice_id,
'supplier_id' => $supplier_id,
'return_date' => $return_date,
'total_amount' => $total_return,
'notes' => $notes,
'outlet_id' => current_outlet_id(),
]);
$stmt = $db->prepare($purchaseReturnInsertSql);
$stmt->execute($purchaseReturnInsertValues);
$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]);
update_stock($item_id, -$qty);
}
}
$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['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!";
}
}
// --- 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'])) {
$settings = $_POST['settings'];
$settings['weight_barcode_mode'] = in_array(($settings['weight_barcode_mode'] ?? 'weight'), ['weight', 'price'], true) ? $settings['weight_barcode_mode'] : 'weight';
$licenseAppName = trim((string)($settings['license_app_name'] ?? ''));
$settings['license_app_name'] = $licenseAppName !== '' ? substr($licenseAppName, 0, 190) : '';
$licenseAppSlug = trim((string)($settings['license_app_slug'] ?? ''));
$settings['license_app_slug'] = $licenseAppSlug !== '' ? LicenseService::sanitizeAppSlug($licenseAppSlug, true) : '';
$prefixStart = (int)($settings['weight_barcode_prefix_start'] ?? 20);
$prefixEnd = (int)($settings['weight_barcode_prefix_end'] ?? 29);
if ($prefixStart < 20 || $prefixStart > 29) $prefixStart = 20;
if ($prefixEnd < 20 || $prefixEnd > 29) $prefixEnd = 29;
if ($prefixStart > $prefixEnd) {
[$prefixStart, $prefixEnd] = [$prefixEnd, $prefixStart];
}
$settings['weight_barcode_prefix_start'] = (string)$prefixStart;
$settings['weight_barcode_prefix_end'] = (string)$prefixEnd;
foreach ($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;
}
}
// --- 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");
}
}
// Removed conflicting close_register handler
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'] ?? '';
$redirect_to = $_POST['redirect_to'] ?? 'dashboard';
// Calculate expected closing balance
$session = db()->prepare("SELECT opening_balance FROM register_sessions WHERE id = ?");
$session->execute([$session_id]);
$opening = (float)$session->fetchColumn();
// Unified calculation logic (POS Transactions + Invoices)
// Note: POS transactions are saved in invoices table with is_pos=1
$sales_sql = "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 LOWER(p.payment_method) = 'cash'";
$sales_stmt = db()->prepare($sales_sql);
$sales_stmt->execute([$session_id]);
$cash_sales = (float)$sales_stmt->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!";
if ($redirect_to === 'dashboard') {
// For POS users, we want dashboard
header("Location: index.php?page=dashboard");
} else {
// For Admin, we stay on sessions page
redirectWithMessage($message, "index.php?page=" . urlencode($redirect_to));
}
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'),
'register_sessions' => __('register_sessions')
],
'Settings' => [
'payment_methods' => __('payment_methods'),
'devices' => __('devices'),
'settings' => __('settings')
],
'Administration' => [
'role_groups' => __('role_groups'),
'users' => __('users'),
'cash_registers' => __('cash_registers'),
'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 = [];
$referenceSearchColumn = db_column_exists($table, 'transaction_no') ? 'transaction_no' : null;
if (!empty($_GET['search'])) {
$s = trim((string)$_GET['search']);
$clean_id = preg_replace('/[^0-9]/', '', $s);
$searchClauses = ["CAST(v.id AS CHAR) LIKE ?", "c.name LIKE ?"];
$searchParams = ["%$s%", "%$s%"];
if ($referenceSearchColumn !== null) {
$searchClauses[] = "v.$referenceSearchColumn LIKE ?";
$searchParams[] = "%$s%";
}
if ($clean_id !== '') {
$searchClauses[] = "v.id = ?";
$searchParams[] = $clean_id;
}
$where[] = '(' . implode(' OR ', $searchClauses) . ')';
$params = array_merge($params, $searchParams);
}
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';
$taxColumn = entity_tax_column($table);
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$taxSearch = $taxColumn !== null ? " OR $taxColumn LIKE ?" : '';
$where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ?$taxSearch)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
if ($taxColumn !== null) {
$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);
$taxSelect = $taxColumn !== null ? "$taxColumn AS tax_id" : "'' AS tax_id";
$stmt = db()->prepare("SELECT id, name, email, phone, $taxSelect, 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()->prepare("SELECT id, name_en, name_ar FROM stock_categories WHERE outlet_id = ? ORDER BY id DESC");
$stmt->execute([current_outlet_id()]);
$headers = ['ID', 'Name (EN)', 'Name (AR)'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'units') {
$stmt = db()->prepare("SELECT id, name_en, name_ar FROM stock_units WHERE outlet_id = ? ORDER BY id DESC");
$stmt->execute([current_outlet_id()]);
$headers = ['ID', 'Name (EN)', 'Name (AR)'];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) $rows[] = $row;
} elseif ($type === 'sales_returns') {
$salesReturnReferenceColumn = sales_return_reference_column();
$stmt = db()->query("SELECT sr.id, sr.`{$salesReturnReferenceColumn}` AS 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') {
$purchaseReturnReferenceColumn = purchase_return_reference_column();
$stmt = db()->query("SELECT pr.id, pr.`{$purchaseReturnReferenceColumn}` AS 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
$current_oid = current_outlet_id();
$stmt = db()->prepare("SELECT * FROM stock_categories WHERE outlet_id = ? ORDER BY name_en ASC");
$stmt->execute([$current_oid]);
$data['categories'] = $stmt->fetchAll();
$stmt = db()->prepare("SELECT * FROM stock_units WHERE outlet_id = ? ORDER BY name_en ASC");
$stmt->execute([$current_oid]);
$data['units'] = $stmt->fetchAll();
$stmt = db()->prepare("SELECT * FROM suppliers WHERE outlet_id = ? ORDER BY name ASC");
$stmt->execute([$current_oid]);
$data['suppliers'] = $stmt->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'];
}
// Fetch current outlet name
$oid = current_outlet_id();
if ($oid != -1) {
$stmt = db()->prepare("SELECT name FROM outlets WHERE id = ?");
$stmt->execute([$oid]);
$outlet_name = $stmt->fetchColumn();
if ($outlet_name) {
$data['settings']['current_outlet_name'] = $outlet_name;
}
}
$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 'suppliers':
$supplierTaxColumn = entity_tax_column('suppliers');
$where = ["outlet_id = " . current_outlet_id()];
$params = [];
if (!empty($_GET['search'])) {
$taxSearch = $supplierTaxColumn !== null ? " OR $supplierTaxColumn LIKE ?" : '';
$where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ?$taxSearch)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
if ($supplierTaxColumn !== null) {
$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':
$customerTaxColumn = entity_tax_column('customers');
$where = ["1=1"];
$params = [];
if (!empty($_GET['search'])) {
$taxSearch = $customerTaxColumn !== null ? " OR $customerTaxColumn LIKE ?" : '';
$where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ?$taxSearch)";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
$params[] = "%{$_GET['search']}%";
if ($customerTaxColumn !== null) {
$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':
app_debug_file_log('debug.log', date('Y-m-d H:i:s') . " - Items case hit");
$where = ["i.outlet_id = " . current_outlet_id()];
$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;
$oid = current_outlet_id();
$stmt = db()->prepare("SELECT i.*, i.stock_quantity, c.name_en as cat_en, c.name_ar as cat_ar, COALESCE(NULLIF(u.name_en, ''), u.short_name_en) as unit_en, COALESCE(NULLIF(u.name_ar, ''), u.short_name_ar, u.name_en, u.short_name_en) 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"];
$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
FROM quotations q
JOIN customers c ON q.customer_id = c.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"];
$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
FROM lpos q
JOIN suppliers s ON q.supplier_id = s.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 'outlets':
if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
if (isset($_POST['add_outlet'])) {
$name = trim($_POST['name'] ?? '');
$address = trim($_POST['address'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$status = $_POST['status'] ?? 'active';
if ($name) {
$stmt = db()->prepare("INSERT INTO outlets (name, address, phone, status, created_at) VALUES (?, ?, ?, ?, NOW())");
$stmt->execute([$name, $address, $phone, $status]);
}
} elseif (isset($_POST['edit_outlet'])) {
$id = (int)$_POST['id'];
$name = trim($_POST['name'] ?? '');
$address = trim($_POST['address'] ?? '');
$phone = trim($_POST['phone'] ?? '');
$status = $_POST['status'] ?? 'active';
if ($name && $id) {
$stmt = db()->prepare("UPDATE outlets SET name = ?, address = ?, phone = ?, status = ? WHERE id = ?");
$stmt->execute([$name, $address, $phone, $status, $id]);
}
} elseif (isset($_POST['delete_outlet'])) {
$id = (int)$_POST['id'];
if ($id !== 1) {
db()->prepare("DELETE FROM outlets WHERE id = ?")->execute([$id]);
}
}
header("Location: index.php?page=outlets");
exit;
}
$countStmt = db()->query("SELECT COUNT(*) FROM outlets");
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = ceil($total_records / $limit);
$data['current_page'] = $page_num;
$stmt = db()->prepare("SELECT * FROM outlets ORDER BY id ASC LIMIT $limit OFFSET $offset");
$stmt->execute();
$data['outlets'] = $stmt->fetchAll();
break;
case 'copy_outlet_data': require 'pages/copy_outlet_data_logic.php'; 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':
require 'pages/sales_purchases_logic.php';
break;
case 'sales_returns':
$salesReturnReferenceColumn = sales_return_reference_column();
$where = ["1=1"];
$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.`{$salesReturnReferenceColumn}` LIKE ? OR sr.id = ? OR sr.`{$salesReturnReferenceColumn}` = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
$params[] = $clean_id;
} else {
$where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.`{$salesReturnReferenceColumn}` LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT sr.*, sr.`{$salesReturnReferenceColumn}` AS invoice_id, 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.`{$salesReturnReferenceColumn}` = 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':
$purchaseReturnReferenceColumn = purchase_return_reference_column();
$where = ["1=1"];
$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.`{$purchaseReturnReferenceColumn}` LIKE ? OR pr.id = ? OR pr.`{$purchaseReturnReferenceColumn}` = ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = $clean_id;
$params[] = $clean_id;
} else {
$where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.`{$purchaseReturnReferenceColumn}` LIKE ?)";
$params[] = "%$s%";
$params[] = "%$s%";
$params[] = "%$s%";
}
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT pr.*, pr.`{$purchaseReturnReferenceColumn}` AS purchase_id, 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.`{$purchaseReturnReferenceColumn}` = 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':
$data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll();
$where = ["1=1"];
$params = [];
if (!empty($_GET['category_id'])) {
$where[] = "e.category_id = ?";
$params[] = $_GET['category_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = "e.expense_date >= ?";
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = "e.expense_date <= ?";
$params[] = $_GET['end_date'];
}
$whereSql = implode(" AND ", $where);
$stmt = db()->prepare("SELECT e.*, c.name_en as cat_en, c.name_ar as cat_ar
FROM expenses e
LEFT JOIN expense_categories c ON e.category_id = c.id
WHERE $whereSql
ORDER BY e.expense_date DESC, e.id DESC");
$stmt->execute($params);
$data['expenses'] = $stmt->fetchAll();
break;
case 'role_groups':
$data['role_groups'] = db()->query("SELECT * FROM role_groups ORDER BY name ASC")->fetchAll();
break;
case 'users':
require 'pages/users_logic.php';
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':
require 'pages/accounting_logic.php';
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':
$oid = current_outlet_id();
$stmt = db()->prepare("SELECT i.*, i.stock_quantity, 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.outlet_id = $oid AND 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')) {
$data['customers'] = db()->query("SELECT * FROM customers ORDER BY id DESC LIMIT 5")->fetchAll();
// Statistics with Outlet Filter
$current_oid = current_outlet_id();
$inv_cond = ($current_oid > 0) ? " WHERE outlet_id = $current_oid " : "";
$pos_cond = " WHERE status = 'completed' " . (($current_oid > 0) ? " AND outlet_id = $current_oid " : "");
$pay_inv_cond = ($current_oid > 0) ? " WHERE i.outlet_id = $current_oid " : "";
$pay_pos_cond = ($current_oid > 0) ? " WHERE t.outlet_id = $current_oid " : "";
$pur_cond = ($current_oid > 0) ? " WHERE outlet_id = $current_oid " : "";
$pay_pur_cond = ($current_oid > 0) ? " WHERE p.outlet_id = $current_oid " : "";
$low_stock_query = ($current_oid > 0)
? "SELECT COUNT(*) FROM stock_items WHERE outlet_id = $current_oid AND stock_quantity <= min_stock_level"
: "SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level";
$data['stats'] = [
'total_customers' => db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(),
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices $inv_cond")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions $pos_cond")->fetchColumn() ?: 0),
'total_received' => (db()->query("SELECT SUM(p.amount) FROM payments p JOIN invoices i ON p.invoice_id = i.id $pay_inv_cond")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(pp.amount) FROM pos_payments pp JOIN pos_transactions t ON pp.transaction_id = t.id $pay_pos_cond")->fetchColumn() ?: 0),
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases $pur_cond")->fetchColumn() ?: 0,
'total_paid' => db()->query("SELECT SUM(pp.amount) FROM purchase_payments pp JOIN purchases p ON pp.purchase_id = p.id $pay_pur_cond")->fetchColumn() ?: 0,
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
'low_stock_items_count' => db()->query($low_stock_query)->fetchColumn(),
];
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
// Sales Chart Data
$data['monthly_sales'] = db()->query("SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, SUM(total_with_vat) as total FROM invoices GROUP BY DATE_FORMAT(invoice_date, '%Y-%m') ORDER BY invoice_date ASC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC);
$data['yearly_sales'] = db()->query("SELECT YEAR(invoice_date) as label, SUM(total_with_vat) as total FROM invoices GROUP BY label ORDER BY label ASC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
}
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><?= htmlspecialchars(__($page)) ?> - 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="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; }
}
.print-only { display: none; }
.units-page-card {
background: linear-gradient(180deg, rgba(255,255,255,0.98) 0%, rgba(247,249,252,0.95) 100%);
border: 1px solid rgba(15, 23, 42, 0.06);
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.08);
}
.units-shell {
display: grid;
gap: 1.5rem;
}
.units-hero {
position: relative;
overflow: hidden;
border-radius: 1.5rem;
padding: 1.75rem;
background: linear-gradient(135deg, #ffffff 0%, #eef7f2 42%, #fff5ea 100%);
border: 1px solid rgba(15, 23, 42, 0.08);
}
.units-hero::before,
.units-hero::after {
content: '';
position: absolute;
border-radius: 999px;
pointer-events: none;
}
.units-hero::before {
width: 18rem;
height: 18rem;
right: -6rem;
top: -8rem;
background: radial-gradient(circle, rgba(14, 165, 233, 0.18) 0%, rgba(14, 165, 233, 0) 72%);
}
.units-hero::after {
width: 14rem;
height: 14rem;
left: -4rem;
bottom: -7rem;
background: radial-gradient(circle, rgba(34, 197, 94, 0.18) 0%, rgba(34, 197, 94, 0) 72%);
}
.units-hero__copy,
.units-toolbar,
.units-stats {
position: relative;
z-index: 1;
}
.units-eyebrow {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.45rem 0.8rem;
margin-bottom: 0.85rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(15, 23, 42, 0.08);
color: #0f172a;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.units-toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
}
.units-toolbar .btn {
border-radius: 999px;
padding: 0.75rem 1rem;
font-weight: 600;
box-shadow: 0 10px 20px rgba(15, 23, 42, 0.08);
}
.units-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
margin-top: 1.5rem;
}
.units-stat {
padding: 1rem 1.15rem;
border-radius: 1.25rem;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(15, 23, 42, 0.08);
backdrop-filter: blur(8px);
box-shadow: 0 10px 30px rgba(15, 23, 42, 0.05);
}
.units-stat__label {
display: block;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #64748b;
margin-bottom: 0.35rem;
}
.units-stat strong {
display: block;
font-size: 1.5rem;
line-height: 1;
color: #0f172a;
}
.units-stat small {
display: block;
margin-top: 0.45rem;
color: #64748b;
}
.units-table-card {
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 1.25rem;
background: #fff;
overflow: hidden;
}
.units-table-card__header {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
justify-content: space-between;
padding: 1.25rem 1.5rem;
border-bottom: 1px solid rgba(15, 23, 42, 0.06);
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
}
.units-helper-pill {
display: inline-flex;
align-items: center;
gap: 0.45rem;
padding: 0.55rem 0.85rem;
border-radius: 999px;
background: #f1f5f9;
color: #0f172a;
font-size: 0.8rem;
font-weight: 600;
}
.units-table thead th {
font-size: 0.78rem;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #64748b;
border-bottom-color: rgba(15, 23, 42, 0.06);
padding: 1rem 1.25rem;
}
.units-table tbody td {
padding: 1.15rem 1.25rem;
border-bottom-color: rgba(15, 23, 42, 0.06);
}
.units-table tbody tr:last-child td {
border-bottom: none;
}
.units-name-stack {
display: grid;
gap: 0.2rem;
}
.units-name-stack__primary {
font-weight: 700;
color: #0f172a;
}
.units-name-stack__secondary {
color: #64748b;
font-size: 0.95rem;
}
.units-short-stack {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.units-short-badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 3.25rem;
padding: 0.45rem 0.75rem;
border-radius: 0.8rem;
background: #ecfdf5;
color: #166534;
font-weight: 700;
font-size: 0.82rem;
border: 1px solid rgba(22, 101, 52, 0.1);
}
.units-short-badge--muted {
background: #fff7ed;
color: #9a3412;
border-color: rgba(154, 52, 18, 0.1);
}
.units-readiness {
display: flex;
flex-wrap: wrap;
gap: 0.45rem;
}
.units-status-badge {
font-weight: 600;
padding: 0.45rem 0.7rem;
border-radius: 999px;
}
.units-status-badge.is-ready {
background: #dcfce7;
color: #166534;
}
.units-status-badge.is-pending {
background: #fee2e2;
color: #b91c1c;
}
.units-actions {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.units-actions .btn {
border-radius: 0.85rem;
padding: 0.45rem 0.75rem;
}
.units-empty-state {
max-width: 24rem;
margin: 0 auto;
display: grid;
gap: 0.75rem;
justify-items: center;
}
.units-empty-state__icon {
width: 4rem;
height: 4rem;
display: grid;
place-items: center;
border-radius: 1.2rem;
background: linear-gradient(135deg, #e0f2fe, #dcfce7);
color: #0f172a;
font-size: 1.5rem;
}
.unit-modal .modal-header {
padding: 1.5rem 1.5rem 0;
}
.units-modal-kicker {
display: inline-flex;
padding: 0.35rem 0.7rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.08em;
background: #e0f2fe;
color: #0c4a6e;
margin-bottom: 0.75rem;
}
.unit-form-shell {
display: grid;
gap: 1rem;
}
.unit-form-section {
border: 1px solid rgba(15, 23, 42, 0.08);
border-radius: 1.2rem;
padding: 1rem;
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
}
.unit-form-section__header {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.unit-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem;
}
.unit-field label {
font-weight: 600;
color: #0f172a;
}
.unit-field .form-text {
color: #64748b;
font-size: 0.82rem;
}
.unit-preview-card {
border-radius: 1rem;
padding: 1rem;
background: linear-gradient(135deg, #0f172a, #1e293b);
color: #f8fafc;
}
.unit-preview-card__label {
display: block;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.08em;
opacity: 0.72;
margin-bottom: 0.65rem;
}
.unit-preview-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.unit-preview-chip {
display: inline-flex;
align-items: center;
padding: 0.45rem 0.7rem;
border-radius: 999px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(6px);
}
.unit-preview-chip.is-muted {
background: rgba(255, 255, 255, 0.2);
}
.unit-import-note {
border-radius: 1rem;
padding: 1rem;
background: linear-gradient(135deg, #eff6ff 0%, #fefce8 100%);
border: 1px solid rgba(148, 163, 184, 0.18);
}
.unit-import-note ul {
padding-left: 1.25rem;
margin-bottom: 0;
color: #475569;
}
[dir="rtl"] .unit-import-note ul {
padding-left: 0;
padding-right: 1.25rem;
}
@media (max-width: 991.98px) {
.units-stats {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 767.98px) {
.units-hero {
padding: 1.25rem;
}
.units-stats,
.unit-form-grid {
grid-template-columns: 1fr;
}
.units-table-card__header {
padding: 1rem;
}
.units-table thead th,
.units-table tbody td {
padding: 0.95rem 0.85rem;
}
.unit-modal .modal-header {
padding: 1.25rem 1.25rem 0;
}
}
[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 text-center">
<div class="text-white fw-bold"><i class="fas fa-balance-scale me-2"></i> Meezan Accounting System</div>
<div class="text-white-50 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' && !isset($_GET['view']) ? 'active' : '' ?>">
<i class="fas fa-book-open"></i> <span><?= __('accounting') ?></span>
</a>
<a href="index.php?page=accounting&view=coa" class="nav-link <?= isset($_GET['view']) && $_GET['view'] === 'coa' ? 'active' : '' ?>">
<i class="fas fa-sitemap"></i> <span data-en="Accounts" data-ar="الحسابات">Accounts</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', 'loyalty_history', 'register_sessions']) ? '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', 'register_sessions', 'register_sessions']) ? '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; ?>
<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>
</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-gear"></i> <span><?= __('settings') ?></span>
</a>
</div>
<?php endif; ?>
<!-- Administrations Section -->
<?php if (can('users_view')): ?>
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['role_groups', 'users', 'cash_registers', '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', 'scale_devices', 'backups', 'customer_display_settings', 'outlets']) ? 'show' : '' ?>" id="admin-collapse">
<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=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>
<a href="index.php?page=outlets" class="nav-link <?= $page === 'outlets' ? 'active' : '' ?>">
<i class="fas fa-shop"></i> <span>Manage Outlets</span>
</a>
</div>
<?php endif; ?>
<!-- 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; ?>
<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="dropdown d-flex align-items-center">
<!-- Outlet Switcher -->
<?php
$user_outlets_list = $_SESSION['user_outlets'] ?? [1];
$is_admin = ($_SESSION['user_role_name'] ?? '') === 'Administrator';
if (count($user_outlets_list) > 1 || $is_admin):
$current_oid = current_outlet_id();
$current_oname = $current_oid === -1 ? (__('All Outlets') ?: 'All Outlets') : (db()->query("SELECT name FROM outlets WHERE id = $current_oid")->fetchColumn() ?: 'Outlet ' . $current_oid);
?>
<div class="dropdown d-inline-block me-3">
<button class="btn btn-outline-secondary dropdown-toggle btn-sm" type="button" data-bs-toggle="dropdown">
<i class="fas fa-store me-1"></i> <?= htmlspecialchars($current_oname) ?>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item <?= $current_oid == -1 ? 'active' : '' ?>" href="index.php?action=switch_outlet&id=-1"><?= __('All Outlets') ?: 'All Outlets' ?></a></li>
<li><hr class="dropdown-divider"></li>
<?php
$availOutlets = $user_outlets_list;
if ($is_admin) {
$availOutlets = db()->query("SELECT id FROM outlets WHERE status='active'")->fetchAll(PDO::FETCH_COLUMN);
}
foreach($availOutlets as $oid):
$oname = db()->query("SELECT name FROM outlets WHERE id=$oid")->fetchColumn();
?>
<li>
<a class="dropdown-item <?= $oid == $current_oid ? 'active' : '' ?>" href="index.php?action=switch_outlet&id=<?= $oid ?>">
<?= htmlspecialchars($oname) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<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>
<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-12">
<div class="card p-4">
<div class="d-flex justify-content-between align-items-center mb-4">
<h5 class="m-0" data-en="Sales Performance" data-ar="أداء المبيعات">Sales Performance</h5>
<div class="btn-group btn-group-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;">
<canvas id="salesChart"></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 === '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">
<h5 class="modal-title">
<span data-en="Edit <?= $currTitle['en'] ?>" data-ar="تعديل <?= $currTitle['ar'] ?>">Edit <?= $currTitle['en'] ?></span>
</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title">Edit Category</h5>
<button type="button" class="btn-close" 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 flex-column flex-lg-row justify-content-between align-items-lg-center gap-3 mb-4">
<div>
<h5 class="m-0"><span data-en="Units" data-ar="الوحدات">Units</span> (<?= count($data['units'] ?? []) ?>)</h5>
<p class="text-muted small mb-0" data-en="Manage the English and Arabic unit names used across items, invoices, and reports." data-ar="قم بإدارة أسماء الوحدات بالإنجليزية والعربية المستخدمة في الأصناف والفواتير والتقارير.">Manage the English and Arabic unit names used across items, invoices, and reports.</p>
</div>
<?php if (can('units_add')): ?>
<div class="d-flex flex-wrap gap-2">
<a href="index.php?page=export&type=units&format=excel" class="btn btn-outline-success">
<i class="bi bi-download"></i> <span data-en="Export" data-ar="تصدير">Export</span>
</a>
<button class="btn btn-outline-success" data-bs-toggle="modal" data-bs-target="#importUnitsModal">
<i class="bi bi-file-earmark-arrow-up"></i> <span data-en="Import" data-ar="استيراد">Import</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>
</div>
<?php endif; ?>
</div>
<div class="table-responsive border rounded-3 bg-white">
<table class="table align-middle units-table mb-0">
<thead>
<tr>
<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 if (empty($data['units'])): ?>
<tr>
<td colspan="3" class="text-center py-5">
<div class="units-empty-state">
<div class="units-empty-state__icon"><i class="bi bi-rulers"></i></div>
<h6 class="mb-0" data-en="No units yet" data-ar="لا توجد وحدات بعد">No units yet</h6>
<p class="text-muted mb-0" data-en="Add your first unit so items can reuse the same labels in invoices and reports." data-ar="أضف أول وحدة حتى تستخدم الأصناف نفس التسميات في الفواتير والتقارير.">Add your first unit so items can reuse the same labels in invoices and reports.</p>
<?php if (can('units_add')): ?>
<button class="btn btn-primary mt-2" data-bs-toggle="modal" data-bs-target="#addUnitModal">
<i class="bi bi-plus-lg"></i> <span data-en="Create first unit" data-ar="إنشاء أول وحدة">Create first unit</span>
</button>
<?php endif; ?>
</div>
</td>
</tr>
<?php else: ?>
<?php foreach ($data['units'] as $u): ?>
<?php
$unitNameEn = trim((string)($u['name_en'] ?? ''));
$unitNameAr = trim((string)($u['name_ar'] ?? ''));
?>
<tr>
<td><?= htmlspecialchars($unitNameEn !== '' ? $unitNameEn : '---') ?></td>
<td><span lang="ar" dir="rtl" class="d-inline-block"><?= htmlspecialchars($unitNameAr !== '' ? $unitNameAr : '---') ?></span></td>
<td class="text-end">
<div class="units-actions justify-content-end">
<?php if (can('units_edit')): ?>
<button class="btn btn-outline-primary btn-sm" 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 btn-sm" title="Delete"><i class="bi bi-trash"></i></button>
</form>
<?php endif; ?>
</div>
<div class="modal fade" id="editUnitModal<?= $u['id'] ?>" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow-lg text-start unit-modal">
<div class="modal-header">
<div>
<span class="units-modal-kicker" data-en="Refine labels" data-ar="تحسين التسميات">Refine labels</span>
<h5 class="modal-title" data-en="Edit Unit" data-ar="تعديل الوحدة">Edit Unit</h5>
<p class="text-muted small mb-0" data-en="Update the English and Arabic unit names used across stock items and invoices." data-ar="حدّث أسماء الوحدة بالإنجليزية والعربية المستخدمة في الأصناف والفواتير.">Update the English and Arabic unit names used across stock items and invoices.</p>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $u['id'] ?>">
<div class="modal-body pt-4">
<div class="unit-form-shell">
<section class="unit-form-section">
<div class="unit-form-section__header">
<div>
<h6 class="mb-1" data-en="Display names" data-ar="الأسماء الكاملة">Display names</h6>
<p class="text-muted small mb-0" data-en="Shown in item forms, reports, and detailed views." data-ar="تظهر في نماذج الأصناف والتقارير والعروض التفصيلية.">Shown in item forms, reports, and detailed views.</p>
</div>
<span class="units-helper-pill" data-en="Bilingual" data-ar="ثنائي اللغة">Bilingual</span>
</div>
<div class="unit-form-grid">
<div class="unit-field">
<label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">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($unitNameEn) ?>" 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 class="form-text" data-en="Example: Kilogram" data-ar="مثال: Kilogram">Example: Kilogram</div>
</div>
<div class="unit-field">
<label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">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($unitNameAr) ?>" 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 class="form-text" data-en="Example: كيلوغرام" data-ar="مثال: كيلوغرام">Example: كيلوغرام</div>
</div>
</div>
</section>
<section class="unit-form-section">
<div class="unit-form-section__header">
<div>
<h6 class="mb-1" data-en="Name preview" data-ar="معاينة الأسماء">Name preview</h6>
<p class="text-muted small mb-0" data-en="This is how the unit name will appear across the app." data-ar="هكذا سيظهر اسم الوحدة في جميع أجزاء التطبيق.">This is how the unit name will appear across the app.</p>
</div>
<span class="units-helper-pill" data-en="Live label" data-ar="التسمية النهائية">Live label</span>
</div>
<div class="unit-preview-card mt-0">
<span class="unit-preview-card__label" data-en="Current preview" data-ar="المعاينة الحالية">Current preview</span>
<div class="unit-preview-row">
<span class="unit-preview-chip"><?= htmlspecialchars($unitNameEn !== '' ? $unitNameEn : 'Name (EN)') ?></span>
<span class="unit-preview-chip"><?= htmlspecialchars($unitNameAr !== '' ? $unitNameAr : 'الاسم') ?></span>
</div>
</div>
</section>
</div>
</div>
<div class="modal-footer pt-0 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_unit" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</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-ar="<?= htmlspecialchars($item['name_ar']) ?>" data-name-en="<?= htmlspecialchars($item['name_en']) ?>" data-name="<?= htmlspecialchars($item['name_en'] . ' - ' . $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_ar']) ?>', '<?= htmlspecialchars($item['name_en']) ?>', '<?= 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">
<h5 class="modal-title"><?= htmlspecialchars((string)($item['name_en'] ?? '')) ?></h5>
<button type="button" class="btn-close" 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_ar']) ?>', '<?= htmlspecialchars($item['name_en']) ?>', '<?= 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">
<h5 class="modal-title" data-en="Edit Item" data-ar="تعديل صنف">Edit Item</h5>
<button type="button" class="btn-close" 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="form-grid-3">
<div class="col-md-4"><label class="form-label" data-en="Name (EN)" data-ar="الاسم (إنجليزي)">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-4"><label class="form-label" data-en="Name (AR)" data-ar="الاسم (عربي)">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-4"><label class="form-label" data-en="SKU / Barcode" data-ar="الباركود">SKU / Barcode</label><input type="text" name="sku" class="form-control" value="<?= htmlspecialchars((string)($item['sku'] ?? '')) ?>"><div class="form-text" data-en="Reserved 13-digit scale barcodes cannot be saved here. Use the 5-digit scale item code for weighing products." data-ar="لا يمكن حفظ باركود الميزان الكامل المكوّن من 13 رقمًا هنا. استخدم كود الصنف المكوّن من 5 أرقام لأصناف الميزان.">Reserved 13-digit scale barcodes cannot be saved here. Use the 5-digit scale item code for weighing products.</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="" data-en="---" data-ar="---">---</option><?php foreach ($data['categories'] ?? [] as $c): ?><option value="<?= $c['id'] ?>" <?= $c['id'] == $item['category_id'] ? 'selected' : '' ?> data-en="<?= htmlspecialchars(localized_option_label($c, 'en')) ?>" data-ar="<?= htmlspecialchars(localized_option_label($c, 'ar')) ?>"><?= htmlspecialchars(localized_option_label($c)) ?></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="" data-en="---" data-ar="---">---</option><?php foreach ($data['units'] ?? [] as $u): ?><option value="<?= $u['id'] ?>" <?= $u['id'] == $item['unit_id'] ? 'selected' : '' ?> data-en="<?= htmlspecialchars(localized_option_label($u, 'en')) ?>" data-ar="<?= htmlspecialchars(localized_option_label($u, 'ar')) ?>"><?= htmlspecialchars(localized_option_label($u)) ?></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'] ?>" <?= $s['id'] == $item['supplier_id'] ? 'selected' : '' ?>><?= htmlspecialchars($s['name']) ?></option><?php endforeach; ?></select></div>
<div class="col-md-4"><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="<?= (float)$item['sale_price'] ?>"></div>
<div class="col-md-4"><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="<?= (float)$item['purchase_price'] ?>"></div>
<div class="col-md-4"><label class="form-label" data-en="Stock Qty" data-ar="كمية المخزون">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-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="<?= (float)$item['min_stock_level'] ?>"></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="<?= number_format((float)$item['vat_rate'], 2, '.', '') ?>"></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-12 full-width"><hr><h6 data-en="Promotion Details" data-ar="تفاصيل العرض">Promotion Details</h6></div>
<div class="col-md-12 full-width"><div class="form-check form-switch mt-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" for="isPromotionToggleEdit<?= $item['id'] ?>" data-en="On Promotion?" data-ar="في العرض؟">On Promotion?</label></div></div>
<div class="col-12 full-width promotionFieldsContainerEdit" id="promotionFieldsContainerEdit<?= $item['id'] ?>" style="display: <?= $item['is_promotion'] ? 'block' : 'none' ?>;"><div class="form-grid-3"><div><label class="form-label" data-en="Start Date" data-ar="تاريخ البداية">Start Date</label><input type="date" name="promotion_start" class="form-control" value="<?= $item['promotion_start'] ?>"></div><div><label class="form-label" data-en="End Date" data-ar="تاريخ النهاية">End Date</label><input type="date" name="promotion_end" class="form-control" value="<?= $item['promotion_end'] ?>"></div><div><label class="form-label" data-en="Percent (%)" data-ar="النسبة (%)">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">
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
<button type="submit" name="edit_item" class="btn btn-primary" data-en="Update Item" data-ar="تحديث الصنف">Update Item</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((float)$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';
$oid = current_outlet_id();
$sql = "SELECT * FROM stock_items WHERE outlet_id = $oid ORDER BY name_en ASC LIMIT 100";
$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" data-en="<?= htmlspecialchars($p['name_en']) ?>" data-ar="<?= htmlspecialchars($p['name_ar']) ?>">
<?php if(!empty($p['name_ar'])): ?>
<div><?= htmlspecialchars($p['name_ar']) ?></div>
<div class="small text-secondary" style="font-size: 0.75rem; line-height: 1.1;"><?= htmlspecialchars($p['name_en']) ?></div>
<?php else: ?>
<div><?= htmlspecialchars($p['name_en']) ?></div>
<?php endif; ?>
</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: (function(){ let n = ''; if(i.nameAr) n += '<div>'+i.nameAr+'</div>'; if(i.nameEn) n += '<div>'+i.nameEn+'</div>'; return n || '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 addQty = Math.max(parseFloat(product.qty) || 1, 0.001);
const unitPrice = (product.price !== undefined && product.price !== null) ? (parseFloat(product.price) || 0) : (parseFloat(product.sale_price) || 0);
const normalizedProduct = {...product, price: unitPrice};
const existing = this.items.find(item => item.id === product.id);
if (existing) {
if (!allowZeroStock && (existing.qty + addQty) > currentStock) {
Swal.fire('Error', 'Insufficient stock!', 'error');
return;
}
existing.qty = Number((existing.qty + addQty).toFixed(3));
existing.price = unitPrice;
} else {
if (!allowZeroStock && currentStock < addQty) {
Swal.fire('Error', 'Insufficient stock!', 'error');
return;
}
this.items.push({...normalizedProduct, qty: Number(addQty.toFixed(3))});
}
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();
},
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;
let nameHtml = '';
if (item.nameAr) nameHtml += `<div>${item.nameAr}</div>`;
if (item.nameEn) nameHtml += `<div>${item.nameEn}</div>`;
if (!nameHtml) nameHtml = '<div>' + (item.nameAr || item.nameEn || 'Unknown Item') + '</div>';
return `
<div class="cart-item">
<div class="flex-grow-1">
<div class="small">${nameHtml}</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>
</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);
} 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) {
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 = "<?= htmlspecialchars($data['settings']['company_name'] ?? 'Accounting System') ?>";
const outletName = "<?= htmlspecialchars($data['settings']['current_outlet_name'] ?? '') ?>";
const companyPhone = "<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>";
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>
${outletName ? `<div class="fw-bold text-uppercase">${outletName}</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();
}
};
// Event Delegation for clicking on cards
document.getElementById('productGrid').addEventListener('click', (e) => {
const card = e.target.closest('.product-card');
if (card) {
addToCartFromCard(card);
}
});
function addToCartFromCard(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);
}
let searchTimeout;
document.getElementById('productSearch').addEventListener('input', (e) => {
const q = e.target.value.trim();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const grid = document.getElementById('productGrid');
grid.style.opacity = '0.5';
fetch('index.php?action=pos_search_items&q=' + encodeURIComponent(q))
.then(response => response.text())
.then(html => {
grid.innerHTML = html;
grid.style.opacity = '1';
})
.catch(err => {
console.error(err);
grid.style.opacity = '1';
});
}, 300);
});
document.getElementById('barcodeInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const barcode = e.target.value.trim();
if (!barcode) return;
// Try finding in DOM first (current view)
const card = Array.from(document.querySelectorAll('.product-card')).find(c => c.dataset.sku === barcode);
if (card) {
addToCartFromCard(card);
e.target.value = '';
} else {
// Not found in current view, check server
fetch('index.php?action=pos_get_item_by_sku&sku=' + encodeURIComponent(barcode))
.then(response => response.json())
.then(product => {
if (product && product.error) {
Swal.fire({
toast: true, position: 'top-end', icon: 'error',
title: product.error, showConfirmButton: false, timer: 1800
});
e.target.select();
return;
}
if (product) {
cart.add(product);
e.target.value = '';
Swal.fire({
toast: true, position: 'top-end', icon: 'success',
title: (document.documentElement.lang === 'ar' ? 'تم إضافة: ' : 'Added: ') + (document.documentElement.lang === 'ar' ? (product.nameAr || product.nameEn) : (product.nameEn || product.nameAr)),
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();
}
});
}
}
});
// 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">
<h5 class="modal-title">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">
<h5 class="modal-title">Close Cash Register</h5>
<button type="button" class="btn-close" 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="redirect_to" value="dashboard">
<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="form-grid-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>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</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="form-grid-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>
<?= renderPagination($data['current_page'] ?? 1, $data['total_pages'] ?? 1) ?>
</div>
<?php elseif ($page === 'sales' || $page === 'purchases'): ?>
<?php require 'pages/sales_purchases_view.php'; ?>
<?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>
<?php elseif ($page === 'payment_methods'): ?>
<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 if (empty($data['payment_methods'])): ?>
<tr>
<td colspan="4" class="text-center text-muted py-4" data-en="No payment methods found yet. Add one to get started." data-ar="لا توجد طرق دفع حتى الآن. أضف طريقة دفع للبدء.">No payment methods found yet. Add one to get started.</td>
</tr>
<?php else: ?>
<?php foreach ($data['payment_methods'] as $pm): ?>
<?php
$paymentMethodId = (int)($pm['id'] ?? 0);
$paymentMethodNameEn = (string)($pm['name_en'] ?? $pm['name'] ?? '');
$paymentMethodNameAr = (string)($pm['name_ar'] ?? $pm['name_en'] ?? $pm['name'] ?? '');
?>
<tr>
<td><?= $paymentMethodId ?></td>
<td><?= htmlspecialchars($paymentMethodNameEn) ?></td>
<td><?= htmlspecialchars($paymentMethodNameAr) ?></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<?= $paymentMethodId ?>"><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="<?= $paymentMethodId ?>">
<button type="submit" name="delete_payment_method" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</div>
<div class="modal fade" id="editPaymentMethodModal<?= $paymentMethodId ?>" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow text-start">
<div class="modal-header">
<h5 class="modal-title" data-en="Edit Payment Method" data-ar="تعديل طريقة الدفع">Edit Payment Method</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $paymentMethodId ?>">
<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" value="<?= htmlspecialchars($paymentMethodNameEn) ?>" 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" value="<?= htmlspecialchars($paymentMethodNameAr) ?>" 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_payment_method" class="btn btn-primary" data-en="Update" data-ar="تحديث">Update</button>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</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">
<h5 class="modal-title" data-en="Add Payment Method" data-ar="إضافة طريقة دفع">Add Payment Method</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title">Edit Category</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title">Add Category</h5>
<button type="button" class="btn-close" 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'): ?>
<?php require 'pages/accounting_view.php'; ?>
<?php elseif ($page === 'expenses'): ?>
<?php $expenseCategories = $data['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="Expenses List" data-ar="قائمة المصروفات">Expenses List</h5>
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addExpenseModal" <?= empty($expenseCategories) ? 'disabled aria-disabled="true" title="Add an expense category first"' : '' ?>>
<i class="bi bi-plus-lg"></i> <span data-en="Add Expense" data-ar="إضافة مصروف">Add Expense</span>
</button>
</div>
<?php if (empty($expenseCategories)): ?>
<div class="alert alert-warning d-flex flex-column flex-md-row align-items-md-center justify-content-between gap-2" role="alert">
<div>
<strong data-en="No expense categories found." data-ar="لا توجد فئات مصروفات.">No expense categories found.</strong>
<span data-en=" Add at least one category before creating or editing expenses." data-ar=" أضف فئة واحدة على الأقل قبل إنشاء أو تعديل المصروفات."> Add at least one category before creating or editing expenses.</span>
</div>
<a href="index.php?page=expense_categories" class="btn btn-sm btn-outline-dark" data-en="Manage Categories" data-ar="إدارة الفئات">Manage Categories</a>
</div>
<?php endif; ?>
<div class="bg-light p-3 rounded mb-4">
<form method="GET" class="form-grid-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 ($expenseCategories 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">
<h5 class="modal-title">Edit Expense</h5>
<button type="button" class="btn-close" 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 ($expenseCategories 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">
<h5 class="modal-title">Add Expense</h5>
<button type="button" class="btn-close" 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 ($expenseCategories 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="form-grid-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="form-grid-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="form-grid-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">
<h5 class="modal-title" data-en="Edit Department" data-ar="تعديل القسم">Edit Department</h5>
<button type="button" class="btn-close" 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((float)($e['salary'] ?? 0), 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">
<h5 class="modal-title" data-en="Edit Employee" data-ar="تعديل الموظف">Edit Employee</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<input type="hidden" name="id" value="<?= $e['id'] ?>">
<div class="modal-body">
<div class="form-grid-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">
<h5 class="modal-title" data-en="Mark Attendance" data-ar="تسجيل الحضور">Mark Attendance - <?= htmlspecialchars($e['name']) ?></h5>
<button type="button" class="btn-close" 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="form-grid-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">
<h5 class="modal-title" data-en="Biometric Integration Info" data-ar="معلومات تكامل البصمة">Biometric Integration Info</h5>
<button type="button" class="btn-close" 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((float)($p['basic_salary'] ?? 0), 3) ?></td>
<td class="text-success">+ OMR <?= number_format((float)($p['bonus'] ?? 0), 3) ?></td>
<td class="text-danger">- OMR <?= number_format((float)($p['deductions'] ?? 0), 3) ?></td>
<td class="fw-bold">OMR <?= number_format((float)($p['net_salary'] ?? 0), 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">
<h5 class="modal-title" data-en="Generate Payroll" data-ar="توليد الرواتب">Generate Payroll</h5>
<button type="button" class="btn-close" 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((float)($e['salary'] ?? 0), 3) ?>)</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-grid-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">
<h5 class="modal-title" data-en="Edit Device" data-ar="تعديل الجهاز">Edit Device</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title" data-en="Add Biometric Device" data-ar="إضافة جهاز بصمة">Add Biometric Device</h5>
<button type="button" class="btn-close" 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" 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" 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="form-grid-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 === "outlets" && ($_SESSION["user_role_name"] ?? "") === "Administrator"): ?>
<?php require "outlets_html.php"; ?>
<?php elseif ($page === 'copy_outlet_data'): require 'pages/copy_outlet_data_view.php'; ?><?php elseif ($page === 'settings'): ?>
<div class="row justify-content-center">
<div class="col-xl-10">
<div class="card border-0 shadow-sm rounded-4">
<div class="card-header bg-white py-3 border-bottom-0 d-flex justify-content-between align-items-center">
<div class="d-flex align-items-center gap-3">
<div class="rounded-circle bg-primary bg-opacity-10 p-3 text-primary">
<i class="bi bi-building-gear fs-4"></i>
</div>
<div>
<h5 class="m-0 fw-bold text-dark" data-en="Company Profile & Settings" data-ar="ملف الشركة والإعدادات">Company Profile & Settings</h5>
<p class="text-muted small mb-0" data-en="Manage your business identity and system preferences" data-ar="إدارة هوية عملك وتفضيلات النظام">Manage your business identity and system preferences</p>
</div>
</div>
<div>
<a href="index.php?page=copy_outlet_data" class="btn btn-outline-primary btn-sm">
<i class="bi bi-arrow-repeat me-1"></i> <span data-en="Sync Outlets" data-ar="مزامنة الفروع">Sync Outlets</span>
</a>
</div>
</div>
<div class="card-body p-4">
<?php
$licenseIdentity = LicenseService::getClientIdentity();
$licenseSourceLabels = [
'settings' => 'Saved in settings',
'environment' => 'Environment variable',
'derived' => 'Derived from app name',
'default' => 'Built-in fallback',
];
$savedLicenseAppName = trim((string)($data['settings']['license_app_name'] ?? ''));
$savedLicenseAppSlug = trim((string)($data['settings']['license_app_slug'] ?? ''));
$licenseAppNameInput = $savedLicenseAppName;
$licenseAppSlugInput = $savedLicenseAppSlug;
?>
<form method="POST" enctype="multipart/form-data">
<!-- Company Details Section -->
<div class="mb-5">
<h6 class="fw-bold text-primary mb-3 border-bottom pb-2">
<i class="bi bi-info-circle me-2"></i><span data-en="Company Details" data-ar="تفاصيل الشركة">Company Details</span>
</h6>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="Company Name" data-ar="اسم الشركة">Company Name</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-building"></i></span>
<input type="text" name="settings[company_name]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['company_name'] ?? '') ?>" placeholder="e.g. Tech Solutions LLC">
</div>
</div>
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="CTR No (Commercial Registration)" data-ar="رقم السجل التجاري">CTR No (Commercial Registration)</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-file-text"></i></span>
<input type="text" name="settings[ctr_no]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['ctr_no'] ?? '') ?>" placeholder="e.g. 1234567">
</div>
</div>
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="VAT Number" data-ar="الرقم الضريبي">VAT Number</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-receipt"></i></span>
<input type="text" name="settings[vat_number]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['vat_number'] ?? '') ?>" placeholder="e.g. OM123456789">
</div>
</div>
</div>
</div>
<!-- Contact Information Section -->
<div class="mb-5">
<h6 class="fw-bold text-primary mb-3 border-bottom pb-2">
<i class="bi bi-telephone me-2"></i><span data-en="Contact Information" data-ar="معلومات الاتصال">Contact Information</span>
</h6>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="Phone Number" data-ar="رقم الهاتف">Phone Number</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-phone"></i></span>
<input type="text" name="settings[company_phone]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['company_phone'] ?? '') ?>" placeholder="+968 9999 9999">
</div>
</div>
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="Email Address" data-ar="البريد الإلكتروني">Email Address</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-envelope"></i></span>
<input type="email" name="settings[company_email]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['company_email'] ?? '') ?>" placeholder="info@example.com">
</div>
</div>
<div class="col-md-12">
<label class="form-label text-muted small fw-semibold" data-en="Physical Address" data-ar="العنوان الفعلي">Physical Address</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-geo-alt"></i></span>
<textarea name="settings[company_address]" class="form-control border-start-0 ps-0" rows="2" placeholder="Street, Building, City..."><?= htmlspecialchars($data['settings']['company_address'] ?? '') ?></textarea>
</div>
</div>
</div>
</div>
<!-- System Configuration Section -->
<div class="mb-5">
<h6 class="fw-bold text-primary mb-3 border-bottom pb-2">
<i class="bi bi-sliders me-2"></i><span data-en="System Configuration" data-ar="تكوين النظام">System Configuration</span>
</h6>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="System Timezone" data-ar="المنطقة الزمنية للنظام">System Timezone</label>
<select name="settings[timezone]" class="form-select">
<?php
$tz_identifiers = DateTimeZone::listIdentifiers();
$current_tz = $data['settings']['timezone'] ?? date_default_timezone_get();
foreach ($tz_identifiers as $tz) {
$selected = ($tz === $current_tz) ? 'selected' : '';
echo "<option value=\"\"$tz\" $selected>$tz</option>";
}
?>
</select>
</div>
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="Stock Policy" data-ar="سياسة المخزون">Stock Policy</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="Prevent selling out of stock" data-ar="منع البيع عند نفاذ المخزون">Prevent selling out of stock</option>
<option value="1" <?= ($data['settings']['allow_zero_stock_sell'] ?? '1') === '1' ? 'selected' : '' ?> data-en="Allow selling out of stock" data-ar="السماح بالبيع عند نفاذ المخزون">Allow selling out of stock</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label text-muted small fw-semibold" data-en="Scale Barcode Mode" data-ar="وضع باركود الميزان">Scale Barcode Mode</label>
<select name="settings[weight_barcode_mode]" class="form-select">
<option value="weight" <?= ($data['settings']['weight_barcode_mode'] ?? 'weight') === 'weight' ? 'selected' : '' ?> data-en="Use embedded weight" data-ar="استخدام الوزن">Use embedded weight</option>
<option value="price" <?= ($data['settings']['weight_barcode_mode'] ?? '') === 'price' ? 'selected' : '' ?> data-en="Use embedded price" data-ar="استخدام السعر">Use embedded price</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label text-muted small fw-semibold" data-en="Scale Prefix From" data-ar="بادئة الميزان من">Scale Prefix From</label>
<input type="number" min="20" max="29" name="settings[weight_barcode_prefix_start]" class="form-control" value="<?= htmlspecialchars($data['settings']['weight_barcode_prefix_start'] ?? '20') ?>">
</div>
<div class="col-md-4">
<label class="form-label text-muted small fw-semibold" data-en="Scale Prefix To" data-ar="بادئة الميزان إلى">Scale Prefix To</label>
<input type="number" min="20" max="29" name="settings[weight_barcode_prefix_end]" class="form-control" value="<?= htmlspecialchars($data['settings']['weight_barcode_prefix_end'] ?? '29') ?>">
</div>
<div class="col-12">
<div class="form-text" data-en="13-digit scale barcode format: 2-digit prefix + 5-digit item code + 5-digit value + 1 check digit. Full 13-digit scale barcodes are reserved and cannot be saved on items or imported." data-ar="صيغة باركود الميزان 13 رقمًا: بادئة من رقمين + كود صنف من 5 أرقام + قيمة من 5 أرقام + رقم تحقق. الباركود الكامل 13 رقمًا محجوز ولا يمكن حفظه أو استيراده كصنف.">13-digit scale barcode format: 2-digit prefix + 5-digit item code + 5-digit value + 1 check digit. Full 13-digit scale barcodes are reserved and cannot be saved on items or imported.</div>
</div>
</div>
</div>
<!-- License Identity Section -->
<div class="mb-5">
<div class="d-flex justify-content-between align-items-start flex-wrap gap-3 mb-3 border-bottom pb-2">
<h6 class="fw-bold text-primary m-0">
<i class="bi bi-shield-check me-2"></i><span data-en="License App Identity" data-ar="هوية ترخيص التطبيق">License App Identity</span>
</h6>
<span class="badge rounded-pill text-bg-light border">
<span data-en="Name source" data-ar="مصدر الاسم">Name source</span>: <?= htmlspecialchars($licenseSourceLabels[$licenseIdentity['app_name_source']] ?? 'Built-in fallback') ?>
·
<span data-en="Slug source" data-ar="مصدر المعرّف">Slug source</span>: <?= htmlspecialchars($licenseSourceLabels[$licenseIdentity['app_slug_source']] ?? 'Built-in fallback') ?>
</span>
</div>
<div class="alert alert-info border-0 shadow-sm small mb-3">
<strong data-en="Use one stable identity per product." data-ar="استخدم هوية ثابتة لكل منتج.">Use one stable identity per product.</strong>
<span data-en="This is the product identity your central license manager sees. Change the slug only when you intentionally rename or split a product." data-ar="هذه هي هوية المنتج التي يراها مدير التراخيص المركزي. غيّر المعرّف فقط عندما تقصد إعادة تسمية المنتج أو تقسيمه.">This is the product identity your central license manager sees. Change the slug only when you intentionally rename or split a product.</span>
</div>
<div class="row g-3">
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="App Name for Licensing" data-ar="اسم التطبيق للترخيص">App Name for Licensing</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-window"></i></span>
<input type="text" id="license-app-name" name="settings[license_app_name]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($licenseAppNameInput) ?>" placeholder="<?= htmlspecialchars((string)$licenseIdentity['app_name']) ?>">
</div>
<div class="form-text" data-en="Leave blank only if you want to fall back to environment/default values." data-ar="اتركه فارغًا فقط إذا كنت تريد الرجوع إلى قيم البيئة أو القيم الافتراضية.">Leave blank only if you want to fall back to environment/default values.</div>
</div>
<div class="col-md-6">
<label class="form-label text-muted small fw-semibold" data-en="Stable App Slug" data-ar="المعرّف الثابت للتطبيق">Stable App Slug</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-tag"></i></span>
<input type="text" id="license-app-slug" name="settings[license_app_slug]" data-explicit="<?= $savedLicenseAppSlug !== '' ? '1' : '0' ?>" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($licenseAppSlugInput) ?>" placeholder="<?= htmlspecialchars((string)$licenseIdentity['app_slug']) ?>">
<button type="button" class="btn btn-outline-secondary" id="suggest-license-slug" data-en="Suggest" data-ar="اقتراح">Suggest</button>
</div>
<div class="form-text" data-en="Use one slug per product, not per customer or installation." data-ar="استخدم معرّفًا واحدًا لكل منتج، وليس لكل عميل أو تثبيت.">Use one slug per product, not per customer or installation.</div>
</div>
<div class="col-12">
<div class="form-text">
<span data-en="Current effective identity:" data-ar="هوية التطبيق الفعالة حاليًا:">Current effective identity:</span>
<code><?= htmlspecialchars((string)$licenseIdentity['app_name']) ?></code>
·
<code><?= htmlspecialchars((string)$licenseIdentity['app_slug']) ?></code>
</div>
</div>
</div>
</div>
<!-- Visual Identity Section -->
<div class="mb-5">
<h6 class="fw-bold text-primary mb-3 border-bottom pb-2">
<i class="bi bi-palette me-2"></i><span data-en="Visual Identity" data-ar="الهوية البصرية">Visual Identity</span>
</h6>
<div class="row g-4">
<div class="col-md-4">
<div class="card h-100 border-dashed bg-light text-center p-3">
<label class="form-label fw-semibold mb-2" data-en="Company Logo" data-ar="شعار الشركة">Company Logo</label>
<div class="mb-3 d-flex justify-content-center align-items-center" style="height: 100px;">
<?php if (!empty($data['settings']['company_logo'])): ?>
<img src="<?= htmlspecialchars($data['settings']['company_logo']) ?>?v=<?= time() ?>" alt="Logo" class="img-fluid" style="max-height: 80px;">
<?php else:
?>
<i class="bi bi-image text-muted fs-1"></i>
<?php endif; ?>
</div>
<input type="file" name="company_logo" class="form-control form-control-sm" accept="image/*">
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-dashed bg-light text-center p-3">
<label class="form-label fw-semibold mb-2" data-en="Website Favicon" data-ar="أيقونة الموقع">Website Favicon</label>
<div class="mb-3 d-flex justify-content-center align-items-center" style="height: 100px;">
<?php if (!empty($data['settings']['favicon'])): ?>
<img src="<?= htmlspecialchars($data['settings']['favicon']) ?>?v=<?= time() ?>" alt="Favicon" class="img-fluid" style="max-height: 32px;">
<?php else:
?>
<i class="bi bi-globe text-muted fs-1"></i>
<?php endif; ?>
</div>
<input type="file" name="favicon" class="form-control form-control-sm" accept="image/*">
</div>
</div>
<div class="col-md-4">
<div class="card h-100 border-dashed bg-light text-center p-3">
<label class="form-label fw-semibold mb-2" data-en="Manager Signature" data-ar="توقيع المدير">Manager Signature</label>
<div class="mb-3 d-flex justify-content-center align-items-center" style="height: 100px;">
<?php if (!empty($data['settings']['manager_signature'])): ?>
<img src="<?= htmlspecialchars($data['settings']['manager_signature']) ?>?v=<?= time() ?>" alt="Signature" class="img-fluid" style="max-height: 80px;">
<?php else:
?>
<i class="bi bi-pen text-muted fs-1"></i>
<?php endif; ?>
</div>
<input type="file" name="manager_signature" class="form-control form-control-sm" accept="image/*">
</div>
</div>
</div>
</div>
<!-- Loyalty Configuration Section -->
<div class="mb-5">
<h6 class="fw-bold text-primary mb-3 border-bottom pb-2">
<i class="bi bi-award me-2"></i><span data-en="Loyalty Program" data-ar="برنامج الولاء">Loyalty Program</span>
</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label text-muted small fw-semibold" data-en="Loyalty Status" data-ar="حالة الولاء">Loyalty Status</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="Active" data-ar="نشط">Active</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label text-muted small fw-semibold" data-en="Earning Rule (Points/1 OMR)" data-ar="قاعدة الكسب (نقاط/1 ريال)">Earning Rule (Points/1 OMR)</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-arrow-up-circle"></i></span>
<input type="number" step="0.01" name="settings[loyalty_points_per_unit]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['loyalty_points_per_unit'] ?? '1') ?>">
</div>
</div>
<div class="col-md-4">
<label class="form-label text-muted small fw-semibold" data-en="Redemption Rule (Points/1 OMR)" data-ar="قاعدة الاسترداد (نقاط/1 ريال)">Redemption Rule (Points/1 OMR)</label>
<div class="input-group">
<span class="input-group-text bg-light border-end-0"><i class="bi bi-arrow-down-circle"></i></span>
<input type="number" step="0.01" name="settings[loyalty_redeem_points_per_unit]" class="form-control border-start-0 ps-0" value="<?= htmlspecialchars($data['settings']['loyalty_redeem_points_per_unit'] ?? '100') ?>">
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end pt-3">
<button type="submit" name="update_settings" class="btn btn-primary btn-lg rounded-pill px-5 shadow-sm">
<i class="bi bi-check-lg me-2"></i> <span data-en="Save All Changes" data-ar="حفظ جميع التغييرات">Save All Changes</span>
</button>
</div>
</form>
<script>
document.addEventListener('DOMContentLoaded', function () {
var nameInput = document.getElementById('license-app-name');
var slugInput = document.getElementById('license-app-slug');
var suggestButton = document.getElementById('suggest-license-slug');
if (!nameInput || !slugInput || !suggestButton) {
return;
}
var slugWasExplicit = slugInput.dataset.explicit === '1';
var slugify = function (value) {
return String(value || '')
.toLowerCase()
.trim()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
};
nameInput.addEventListener('input', function () {
if (!slugWasExplicit || slugInput.value.trim() === '') {
slugInput.value = slugify(nameInput.value);
}
});
slugInput.addEventListener('input', function () {
slugWasExplicit = slugInput.value.trim() !== '';
slugInput.dataset.explicit = slugWasExplicit ? '1' : '0';
});
suggestButton.addEventListener('click', function () {
slugInput.value = slugify(nameInput.value);
slugWasExplicit = slugInput.value.trim() !== '';
slugInput.dataset.explicit = slugWasExplicit ? '1' : '0';
slugInput.focus();
});
});
</script>
</div>
</div>
</div>
</div>
<?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">
<h5 class="modal-title fw-bold" data-en="Edit Role Group" data-ar="تعديل مجموعة الأدوار">Edit Role Group</h5>
<button type="button" class="btn-close" 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>
<?php require 'pages/users_role_permissions_script.php'; ?>
<?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="form-grid-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'): ?>
<?php require 'pages/users_view.php'; ?>
<?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">
<h5 class="modal-title">Edit Register</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title">Add Cash Register</h5>
<button type="button" class="btn-close" 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 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']]);
$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((float)$c_total, 3) ?></td>
<td>OMR <?= number_format((float)$cd_total, 3) ?></td>
<td>OMR <?= number_format((float)$cr_total, 3) ?></td>
<td class="fw-bold">OMR <?= number_format((float)$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((float)$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" onclick="loadSessionReport(<?= $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 -->
<!-- Session Report Modal (Universal) -->
<div class="modal fade" id="universalSessionReportModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content border-0 shadow">
<div id="sessionReportContent">
<div class="modal-body text-center p-5">
<div class="spinner-border text-primary"></div>
</div>
</div>
</div>
</div>
</div>
<?php require 'pages/register_session_report_script.php'; ?>
<!-- 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">
<h5 class="modal-title">Open Register Session</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title">Close Register Session (#<?= $session['id'] ?>)</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<input type="hidden" name="session_id" value="<?= $session['id'] ?>">
<input type="hidden" name="redirect_to" value="register_sessions">
<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 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 GROUP BY payment_method");
$curBreakdown->execute([$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((float)$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((float)$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((float)$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((float)$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((float)$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((float)$total_all, 3) ?></span>
</div>
<div class="d-flex justify-content-between fw-bold text-success">
<span>Expected Cash:</span>
<span>OMR <?= number_format((float)$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
// Fetch from invoices
$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([$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['total_with_vat'], 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 = ['runtime_debug.log', 'error_log', 'login_debug.log', 'post_debug.log', 'search_debug.log', '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 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">
<h5 class="modal-title" data-en="Add HR Department" data-ar="إضافة قسم">Add HR Department</h5>
<button type="button" class="btn-close" 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">
<h5 class="modal-title" data-en="Add HR Employee" data-ar="إضافة موظف">Add HR Employee</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body">
<div class="form-grid-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">
<h5 class="modal-title" data-en="Add Role Group" data-ar="إضافة مجموعة أدوار">Add Role Group</h5>
<button type="button" class="btn-close" 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 -->
<!-- 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">
<h5 class="modal-title">
<?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" 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">
<h5 class="modal-title" data-en="Add New Item" data-ar="إضافة صنف جديد">Add New Item</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body">
<div class="form-grid-3">
<div class="col-md-4"><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-4"><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="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 class="form-text" data-en="Reserved 13-digit scale barcodes cannot be saved here. Use the 5-digit scale item code for weighing products." data-ar="لا يمكن حفظ باركود الميزان الكامل المكوّن من 13 رقمًا هنا. استخدم كود الصنف المكوّن من 5 أرقام لأصناف الميزان.">Reserved 13-digit scale barcodes cannot be saved here. Use the 5-digit scale item code for weighing products.</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="" data-en="---" data-ar="---">---</option><?php foreach ($data['categories'] ?? [] as $c): ?><option value="<?= $c['id'] ?>" data-en="<?= htmlspecialchars(localized_option_label($c, 'en')) ?>" data-ar="<?= htmlspecialchars(localized_option_label($c, 'ar')) ?>"><?= htmlspecialchars(localized_option_label($c)) ?></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="" data-en="---" data-ar="---">---</option><?php foreach ($data['units'] ?? [] as $u): ?><option value="<?= $u['id'] ?>" data-en="<?= htmlspecialchars(localized_option_label($u, 'en')) ?>" data-ar="<?= htmlspecialchars(localized_option_label($u, 'ar')) ?>"><?= htmlspecialchars(localized_option_label($u)) ?></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-4"><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-4"><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="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-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-12 full-width"><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-12 full-width" 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 full-width mt-3"><hr><h6 data-en="Promotion Details" data-ar="تفاصيل العرض">Promotion Details</h6></div>
<div class="col-md-12 full-width promotionFieldsContainer" id="promotionFieldsContainer" style="display: none;"><div class="form-grid-3"><div><label class="form-label" data-en="Start Date" data-ar="تاريخ البداية">Start Date</label><input type="date" name="promotion_start" class="form-control"></div><div><label class="form-label" data-en="End Date" data-ar="تاريخ النهاية">End Date</label><input type="date" name="promotion_end" class="form-control"></div><div><label class="form-label" data-en="Percent (%)" data-ar="النسبة (%)">Percent (%)</label><input type="number" step="0.01" name="promotion_percent" class="form-control"></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">
<h5 class="modal-title" data-en="Import Items from Excel (CSV)" data-ar="استيراد الأصناف من اكسل (CSV)">Import Items from Excel (CSV)</h5>
<button type="button" class="btn-close" 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 a CSV file with the following columns: SKU, English Name, Arabic Name, Sale Price, Cost Price. Reserved 13-digit scale barcodes will be skipped; save weighing products with their 5-digit item code instead." data-ar="يرجى رفع ملف CSV بالأعمدة التالية: الباركود، الاسم الإنجليزي، الاسم العربي، سعر البيع، سعر التكلفة. سيتم تجاهل باركود الميزان الكامل المكوّن من 13 رقمًا؛ احفظ أصناف الميزان باستخدام كود الصنف المكوّن من 5 أرقام.">
Please upload a CSV file with the following columns: SKU, English Name, Arabic Name, Sale Price, Cost Price. Reserved 13-digit scale barcodes will be skipped; save weighing products with their 5-digit item code instead.
</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 CSV File" data-ar="اختر ملف CSV">Choose CSV File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv" 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">
<h5 class="modal-title" data-en="Import Customers from Excel (CSV)" data-ar="استيراد العملاء من اكسل (CSV)">Import Customers from Excel (CSV)</h5>
<button type="button" class="btn-close" 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 a CSV file with the following columns: Name, Email, Phone, Tax ID, Balance." data-ar="يرجى رفع ملف CSV بالأعمدة التالية: الاسم، البريد الإلكتروني، الهاتف، الرقم الضريبي، الرصيد.">
Please upload a 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 CSV File" data-ar="اختر ملف CSV">Choose CSV File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv" 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">
<h5 class="modal-title" data-en="Import Suppliers from Excel (CSV)" data-ar="استيراد الموردين من اكسل (CSV)">Import Suppliers from Excel (CSV)</h5>
<button type="button" class="btn-close" 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 a CSV file with the following columns: Name, Email, Phone, Tax ID, Balance." data-ar="يرجى رفع ملف CSV بالأعمدة التالية: الاسم، البريد الإلكتروني، الهاتف، الرقم الضريبي، الرصيد.">
Please upload a 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 CSV File" data-ar="اختر ملف CSV">Choose CSV File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv" 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">
<h5 class="modal-title" data-en="Import Categories from Excel (CSV)" data-ar="استيراد الفئات من اكسل (CSV)">Import Categories from Excel (CSV)</h5>
<button type="button" class="btn-close" 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 a CSV file with the following columns: Name (EN), Name (AR)." data-ar="يرجى رفع ملف CSV بالأعمدة التالية: الاسم (إنجليزي)، الاسم (عربي).">
Please upload a CSV file with the following columns: Name (EN), Name (AR).
</small>
</div>
<div class="mb-3">
<label class="form-label" data-en="Choose CSV File" data-ar="اختر ملف CSV">Choose CSV File</label>
<input type="file" name="excel_file" class="form-control" accept=".csv" 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 modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow-lg text-start unit-modal">
<div class="modal-header">
<div>
<span class="units-modal-kicker" data-en="Bulk setup" data-ar="إعداد جماعي">Bulk setup</span>
<h5 class="modal-title" data-en="Import Units" data-ar="استيراد الوحدات">Import Units</h5>
<p class="text-muted small mb-0" data-en="Upload one file to create multiple units at once." data-ar="ارفع ملفاً واحداً لإنشاء عدة وحدات دفعة واحدة.">Upload one file to create multiple units at once.</p>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST" enctype="multipart/form-data">
<div class="modal-body pt-4">
<div class="unit-import-note mb-3">
<strong data-en="Supported columns" data-ar="الأعمدة المدعومة">Supported columns</strong>
<ul class="mt-2 small">
<li data-en="Name (EN), Name (AR)" data-ar="الاسم (إنجليزي)، الاسم (عربي)">Name (EN), Name (AR)</li>
<li data-en="CSV and XLSX files are accepted." data-ar="يتم قبول ملفات CSV و XLSX.">CSV and XLSX files are accepted.</li>
<li data-en="If Arabic is blank, the English name will be reused automatically." data-ar="إذا كان الاسم العربي فارغاً فسيتم استخدام الاسم الإنجليزي تلقائياً.">If Arabic is blank, the English name will be reused automatically.</li>
</ul>
</div>
<div class="unit-form-section">
<div class="unit-field">
<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 class="form-text" data-en="Tip: export current units first if you want a formatting reference." data-ar="نصيحة: صدّر الوحدات الحالية أولاً إذا كنت تريد مرجعاً للتنسيق.">Tip: export current units first if you want a formatting reference.</div>
</div>
</div>
</div>
<div class="modal-footer pt-0 border-0">
<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">
<h5 class="modal-title" data-en="Add Category" data-ar="إضافة فئة">Add Category</h5>
<button type="button" class="btn-close" 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 modal-lg modal-dialog-centered">
<div class="modal-content border-0 shadow-lg text-start unit-modal">
<div class="modal-header">
<div>
<span class="units-modal-kicker" data-en="Create unit" data-ar="إنشاء وحدة">Create unit</span>
<h5 class="modal-title" data-en="Add Unit" data-ar="إضافة وحدة">Add Unit</h5>
<p class="text-muted small mb-0" data-en="Use the full unit name in English and Arabic so stock screens stay consistent." data-ar="استخدم اسم الوحدة الكامل بالإنجليزية والعربية حتى تبقى شاشات المخزون متناسقة.">Use the full unit name in English and Arabic so stock screens stay consistent.</p>
</div>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form method="POST">
<div class="modal-body pt-4">
<div class="unit-form-shell">
<section class="unit-form-section">
<div class="unit-form-section__header">
<div>
<h6 class="mb-1" data-en="Display names" data-ar="الأسماء الكاملة">Display names</h6>
<p class="text-muted small mb-0" data-en="Shown in item forms, reports, and detailed views." data-ar="تظهر في نماذج الأصناف والتقارير والعروض التفصيلية.">Shown in item forms, reports, and detailed views.</p>
</div>
<span class="units-helper-pill" data-en="Bilingual" data-ar="ثنائي اللغة">Bilingual</span>
</div>
<div class="unit-form-grid">
<div class="unit-field">
<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 class="form-text" data-en="Example: Kilogram" data-ar="مثال: Kilogram">Example: Kilogram</div>
</div>
<div class="unit-field">
<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 class="form-text" data-en="Example: كيلوغرام" data-ar="مثال: كيلوغرام">Example: كيلوغرام</div>
</div>
</div>
</section>
<section class="unit-form-section">
<div class="unit-form-section__header">
<div>
<h6 class="mb-1" data-en="Name preview" data-ar="معاينة الأسماء">Name preview</h6>
<p class="text-muted small mb-0" data-en="This is how the unit name will appear across the app." data-ar="هكذا سيظهر اسم الوحدة في جميع أجزاء التطبيق.">This is how the unit name will appear across the app.</p>
</div>
<span class="units-helper-pill" data-en="Live label" data-ar="التسمية النهائية">Live label</span>
</div>
<div class="unit-preview-card mt-0">
<span class="unit-preview-card__label" data-en="Example preview" data-ar="معاينة مثال">Example preview</span>
<div class="unit-preview-row">
<span class="unit-preview-chip">Kilogram</span>
<span class="unit-preview-chip">كيلوغرام</span>
</div>
</div>
</section>
</div>
</div>
<div class="modal-footer pt-0 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_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';
}
});
});
<?php require 'pages/sales_purchases_payment_receipt_script.php'; ?>
// 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 = '';
}
}
});
});
<?php require 'pages/sales_purchases_invoice_form_helpers.php'; ?>
<?php require 'pages/lpo_quotation_script.php'; ?>
<?php require 'pages/sales_purchases_invoice_actions_script.php'; ?>
} catch (e) { console.error("JS Error in DOMContentLoaded:", e); }
});
</script>
<?php require 'pages/sales_purchases_modals.php'; ?>
<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" 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" 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">
<h5 class="modal-title">Print Barcode Label</h5>
<button type="button" class="btn-close" 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">Avery Barcode Labels (A4)</h5>
<button type="button" class="btn-close" 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>
<option value="L7431">L7431 (6 x 8 - 48 Labels)</option>
<option value="L4716">L4716 (6 x 8 - 48 Labels - Round)</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-L7431 { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 33mm; }
.avery-layout-L4716 { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 33mm; }
.avery-layout-L4716 .avery-label { border-radius: 50%; }
.avery-layout-L7431 .avery-label, .avery-layout-L4716 .avery-label { padding: 1mm; }
.avery-layout-L7431 .avery-label div, .avery-layout-L4716 .avery-label div { font-size: 8px !important; }
.avery-layout-L7431 .avery-label svg, .avery-layout-L4716 .avery-label svg { height: 20px; }
.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>
<?php require 'pages/avery_label_script.php'; ?>
<script>
<?php require 'pages/barcode_pos_script.php'; ?>
<?php require 'pages/language_dashboard_script.php'; ?>
</script>
</body>
</html>