38471-vm/index.php
2026-05-08 03:11:43 +00:00

12896 lines
718 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
$GLOBALS['app_runtime_debug_stage'] = 'boot:script_loaded';
$GLOBALS['app_runtime_debug_timeline'] = [
['time' => date('H:i:s'), 'stage' => 'boot:script_loaded'],
];
$GLOBALS['app_runtime_debug_rendered'] = false;
if (!function_exists('runtime_debug_boot_mark')) {
function runtime_debug_boot_mark(string $stage, array $context = []): void {
$GLOBALS['app_runtime_debug_stage'] = $stage;
$timeline = $GLOBALS['app_runtime_debug_timeline'] ?? [];
$entry = [
'time' => date('H:i:s'),
'stage' => $stage,
];
if ($context !== []) {
$entry['context'] = $context;
}
$timeline[] = $entry;
if (count($timeline) > 20) {
$timeline = array_slice($timeline, -20);
}
$GLOBALS['app_runtime_debug_timeline'] = $timeline;
}
}
if (!function_exists('runtime_debug_boot_force_details')) {
function runtime_debug_boot_force_details(): bool {
$candidates = [
$_GET['debug'] ?? null,
$_GET['app_debug'] ?? null,
getenv('APP_RUNTIME_DEBUG'),
$_ENV['APP_RUNTIME_DEBUG'] ?? null,
$_SERVER['APP_RUNTIME_DEBUG'] ?? null,
];
foreach ($candidates as $candidate) {
if ($candidate === false || $candidate === null) {
continue;
}
$value = strtolower(trim((string)$candidate));
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
return true;
}
}
return false;
}
}
if (!defined('APP_RUNTIME_DEBUG_BOOTSTRAP_SHUTDOWN_REGISTERED')) {
define('APP_RUNTIME_DEBUG_BOOTSTRAP_SHUTDOWN_REGISTERED', true);
register_shutdown_function(static function (): void {
if (!empty($GLOBALS['app_runtime_debug_rendered']) || defined('APP_RUNTIME_DEBUG_FATAL_HANDLER_REGISTERED')) {
return;
}
$error = error_get_last();
if (!is_array($error)) {
return;
}
$fatalTypes = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR];
if (!in_array((int)($error['type'] ?? 0), $fatalTypes, true)) {
return;
}
$stage = (string)($GLOBALS['app_runtime_debug_stage'] ?? 'unknown');
$timeline = $GLOBALS['app_runtime_debug_timeline'] ?? [];
$parts = [
date('Y-m-d H:i:s'),
'[FatalError]',
(string)($error['message'] ?? 'Fatal error'),
'file=' . (string)($error['file'] ?? 'unknown') . ':' . (int)($error['line'] ?? 0),
'page=' . (string)($_GET['page'] ?? 'dashboard'),
'uri=' . (string)($_SERVER['REQUEST_URI'] ?? 'cli'),
'stage=' . $stage,
];
if ($timeline !== []) {
$parts[] = 'timeline=' . json_encode(array_slice($timeline, -8), JSON_UNESCAPED_UNICODE);
}
@file_put_contents(__DIR__ . '/runtime_debug.log', implode(' || ', $parts) . PHP_EOL, FILE_APPEND);
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: text/html; charset=UTF-8');
header('X-Robots-Tag: noindex, nofollow');
}
$roleName = (string)($_SESSION['user_role_name'] ?? '');
$showDetails = runtime_debug_boot_force_details()
|| PHP_SAPI === 'cli'
|| strcasecmp($roleName, 'Administrator') === 0
|| (int)($_SESSION['user_id'] ?? 0) === 1;
$message = (string)($error['message'] ?? 'Fatal error');
$safeMessage = $showDetails
? $message
: 'A fatal application error occurred before the page finished loading.';
$timelineText = json_encode(array_slice($timeline, -8), JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$GLOBALS['app_runtime_debug_rendered'] = true;
?>
<!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>Application Boot Error</title>
<style>
body { margin: 0; font-family: Inter, Arial, sans-serif; background: #f6f8fb; color: #1f2937; }
.wrap { max-width: 860px; 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 12px; font-size: 28px; }
.meta { margin: 12px 0; color: #374151; }
.meta strong { color: #111827; }
.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; }
</style>
</head>
<body>
<main class="wrap">
<section class="card">
<h1>Application Boot Error</h1>
<p>The request failed before the main page finished loading.</p>
<div class="meta"><strong>Stage:</strong> <?= htmlspecialchars($stage) ?></div>
<div class="meta"><strong>Message:</strong> <?= htmlspecialchars($safeMessage) ?></div>
<div class="hint">A copy of this failure was written to <code>runtime_debug.log</code>.</div>
<?php if ($showDetails && $timelineText !== false && $timelineText !== '[]'): ?>
<pre><?= htmlspecialchars((string)$timelineText) ?></pre>
<?php endif; ?>
</section>
</main>
</body>
</html>
<?php
exit;
});
}
runtime_debug_boot_mark('boot:session_setup');
// 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.");
}
runtime_debug_boot_mark('boot:extensions_ready');
// 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();
runtime_debug_boot_mark('boot:session_started');
$route_helpers_path = __DIR__ . '/includes/page_routes.php';
if (is_file($route_helpers_path)) {
require_once $route_helpers_path;
} else {
if (!function_exists('page_url')) {
function page_url(string $page = 'dashboard', array $params = []): string {
$query = $params;
$page = strtolower(trim($page));
if ($page !== '' && $page !== 'dashboard') {
$query = ['page' => $page] + $query;
}
$queryString = http_build_query($query);
return 'index.php' . ($queryString !== '' ? '?' . $queryString : '');
}
}
if (!function_exists('page_normalize_url')) {
function page_normalize_url(string $url): string {
$url = trim($url);
return $url !== '' ? $url : 'index.php';
}
}
if (!function_exists('page_request_is_ajax')) {
function page_request_is_ajax(): bool {
return strtolower((string)($_SERVER['HTTP_X_REQUESTED_WITH'] ?? '')) === 'xmlhttprequest';
}
}
if (!function_exists('page_redirect_legacy_url')) {
function page_redirect_legacy_url(): void {
// Route helper file is missing; keep serving the legacy index.php?page=... URLs.
}
}
if (!function_exists('set_route_page_context')) {
function set_route_page_context(string $page): void {
$_GET['page'] = $page;
$_REQUEST['page'] = $page;
}
}
runtime_debug_boot_mark('boot:route_helpers_fallback');
@file_put_contents(
__DIR__ . '/runtime_debug.log',
date('Y-m-d H:i:s') . " || [route_fallback] || Missing include={$route_helpers_path} || uri=" . ($_SERVER['REQUEST_URI'] ?? '') . PHP_EOL,
FILE_APPEND
);
}
if (!defined('APP_ROUTE_BOOTSTRAP')) {
page_redirect_legacy_url();
}
runtime_debug_boot_mark('boot:routes_ready');
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));
}
if (!function_exists('wablas_helper_missing_message')) {
function wablas_helper_missing_message(): string {
return 'Wablas integration is temporarily unavailable because includes/wablas_helper.php is missing from this deployment.';
}
}
if (!function_exists('register_wablas_helper_fallback')) {
function register_wablas_helper_fallback(string $missingPath): void {
$GLOBALS['app_wablas_helper_missing'] = $missingPath;
runtime_debug_boot_mark('boot:wablas_helper_fallback', [
'missing' => basename($missingPath),
]);
@file_put_contents(
__DIR__ . '/runtime_debug.log',
date('Y-m-d H:i:s') . " || [wablas_helper_fallback] || Missing include={$missingPath} || uri=" . ($_SERVER['REQUEST_URI'] ?? 'cli') . PHP_EOL,
FILE_APPEND
);
if (!function_exists('wablasHelperAvailable')) {
function wablasHelperAvailable(): bool {
return false;
}
}
if (!function_exists('wablasSettingValue')) {
function wablasSettingValue(string $key, ?string $default = null): ?string {
return $default;
}
}
if (!function_exists('wablasNumbersFromText')) {
function wablasNumbersFromText(string $rawNumbers, string $defaultCountryCode = ''): array {
$parts = preg_split('/[\s,;]+/', str_replace(["\r", "\n", "\t"], ' ', $rawNumbers)) ?: [];
$normalized = [];
$defaultCountryCode = preg_replace('/[^0-9]/', '', $defaultCountryCode);
foreach ($parts as $part) {
$candidate = trim((string)$part);
if ($candidate === '') {
continue;
}
$candidate = preg_replace('/[^0-9+]/', '', $candidate);
if ($candidate === '') {
continue;
}
if (str_starts_with($candidate, '00')) {
$candidate = '+' . substr($candidate, 2);
}
if ($candidate[0] !== '+' && $defaultCountryCode !== '') {
$candidate = '+' . $defaultCountryCode . ltrim($candidate, '0');
}
$normalized[] = $candidate;
}
return array_values(array_unique(array_filter($normalized)));
}
}
if (!function_exists('wablasNow')) {
function wablasNow(): DateTimeImmutable {
return new DateTimeImmutable('now', new DateTimeZone(date_default_timezone_get()));
}
}
if (!function_exists('wablasSendManualTest')) {
function wablasSendManualTest(array $numbers, string $message): array {
return [
'success' => false,
'error' => wablas_helper_missing_message(),
];
}
}
if (!function_exists('wablasQueueDailySummaryIfDue')) {
function wablasQueueDailySummaryIfDue(?DateTimeImmutable $now = null): array {
return [
'queued' => false,
'reason' => 'helper_missing',
'error' => wablas_helper_missing_message(),
];
}
}
if (!function_exists('wablasProcessDueDispatches')) {
function wablasProcessDueDispatches(int $limit = 20): array {
return [
'success' => false,
'checked' => 0,
'sent' => 0,
'failed' => 0,
'skipped' => 0,
'messages' => [wablas_helper_missing_message()],
];
}
}
if (!function_exists('wablasQueueInvoiceNotification')) {
function wablasQueueInvoiceNotification(int $invoiceId): array {
return [
'success' => false,
'notice' => ' WhatsApp notification was skipped because includes/wablas_helper.php is missing from this deployment.',
];
}
}
if (!function_exists('wablasDispatchLogTableReady')) {
function wablasDispatchLogTableReady(): bool {
return false;
}
}
if (!function_exists('wablasFetchRecentDispatchLogs')) {
function wablasFetchRecentDispatchLogs(int $limit = 25): array {
return [];
}
}
if (!function_exists('wablasDispatchEventLabel')) {
function wablasDispatchEventLabel(string $eventType): string {
return ucfirst(str_replace('_', ' ', $eventType));
}
}
if (!function_exists('wablasDispatchStatusMeta')) {
function wablasDispatchStatusMeta(string $status): array {
$status = strtolower(trim($status));
if ($status === '') {
$status = 'pending';
}
$meta = [
'label' => ucfirst($status),
'class' => 'text-bg-light border',
];
if ($status === 'sent') {
$meta['class'] = 'bg-success bg-opacity-10 text-success';
} elseif ($status === 'failed') {
$meta['class'] = 'bg-danger bg-opacity-10 text-danger';
} elseif ($status === 'pending') {
$meta['class'] = 'bg-warning bg-opacity-10 text-warning';
}
return $meta;
}
}
if (!function_exists('wablasJsonDecode')) {
function wablasJsonDecode(?string $json): array {
$decoded = json_decode((string)$json, true);
return is_array($decoded) ? $decoded : [];
}
}
}
}
runtime_debug_boot_mark('boot:loading_core_dependencies');
require_once __DIR__ . '/db/config.php';
require_once __DIR__ . '/includes/SimpleXLSX.php';
require_once __DIR__ . '/includes/quantity_helper.php';
require_once __DIR__ . '/includes/stock_helper.php';
$wablasHelperPath = __DIR__ . '/includes/wablas_helper.php';
if (is_file($wablasHelperPath)) {
require_once $wablasHelperPath;
} else {
register_wablas_helper_fallback($wablasHelperPath);
}
require_once __DIR__ . '/db/BackupService.php';
runtime_debug_boot_mark('boot:core_dependencies_loaded');
// 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('ensure_payment_methods_schema_ready')) {
function ensure_payment_methods_schema_ready(): void {
static $loaded = false;
if ($loaded) {
return;
}
require_once __DIR__ . '/db/migrations/20260502_zzz_payment_methods_schema_sync.php';
$loaded = true;
}
}
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('current_outlet_name')) {
function current_outlet_name(): string {
static $cache = [];
$oid = current_outlet_id();
if ($oid === -1 || $oid === 0) {
return __('All Outlets') ?: 'All Outlets';
}
if (array_key_exists($oid, $cache)) {
return $cache[$oid];
}
if (!db_table_exists('outlets')) {
$cache[$oid] = 'Outlet ' . $oid;
return $cache[$oid];
}
try {
$stmt = db()->prepare("SELECT name FROM outlets WHERE id = ? LIMIT 1");
$stmt->execute([$oid]);
$cache[$oid] = (string)($stmt->fetchColumn() ?: ('Outlet ' . $oid));
} catch (Throwable $e) {
$cache[$oid] = 'Outlet ' . $oid;
}
return $cache[$oid];
}
}
if (!function_exists('outlet_scope_sql')) {
function outlet_scope_sql(string $tableName, string $qualifiedColumn = 'outlet_id', bool $includeLegacyNull = true): array {
if (!db_column_exists($tableName, 'outlet_id')) {
return ['sql' => '1=1', 'params' => []];
}
$oid = current_outlet_id();
if ($oid === -1) {
return ['sql' => '1=1', 'params' => []];
}
$sql = $includeLegacyNull
? "({$qualifiedColumn} = ? OR {$qualifiedColumn} IS NULL)"
: "{$qualifiedColumn} = ?";
return ['sql' => $sql, 'params' => [$oid]];
}
}
if (!function_exists('dashboard_sales_series')) {
function dashboard_sales_series(string $period = 'month', int $limit = 12): array {
$period = strtolower($period) === 'year' ? 'year' : 'month';
$limit = max(1, (int)$limit);
$sources = [];
$params = [];
$db = db();
if (db_table_exists('invoices')) {
$invoiceDateColumn = db_first_existing_column('invoices', ['invoice_date', 'created_at']);
$invoiceTotalExpression = db_column_exists('invoices', 'total_with_vat')
? 'COALESCE(total_with_vat, 0)'
: (db_column_exists('invoices', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
if ($invoiceDateColumn !== null) {
$invoiceScope = outlet_scope_sql('invoices', 'outlet_id');
if ($period === 'year') {
$sources[] = "SELECT YEAR(`{$invoiceDateColumn}`) AS period_key, CAST(YEAR(`{$invoiceDateColumn}`) AS CHAR) AS label, {$invoiceTotalExpression} AS total FROM invoices WHERE `{$invoiceDateColumn}` IS NOT NULL AND {$invoiceScope['sql']}";
} else {
$sources[] = "SELECT DATE_FORMAT(`{$invoiceDateColumn}`, '%Y-%m') AS period_key, DATE_FORMAT(`{$invoiceDateColumn}`, '%b %Y') AS label, {$invoiceTotalExpression} AS total FROM invoices WHERE `{$invoiceDateColumn}` IS NOT NULL AND {$invoiceScope['sql']}";
}
$params = array_merge($params, $invoiceScope['params']);
}
}
if (db_table_exists('pos_transactions')) {
$posDateColumn = db_first_existing_column('pos_transactions', ['created_at', 'transaction_date', 'sale_date']);
$posTotalExpression = db_column_exists('pos_transactions', 'net_amount')
? 'COALESCE(net_amount, 0)'
: (db_column_exists('pos_transactions', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
if ($posDateColumn !== null) {
$posScope = outlet_scope_sql('pos_transactions', 'outlet_id');
$posStatusPredicate = db_column_exists('pos_transactions', 'status') ? "status = 'completed' AND " : '';
if ($period === 'year') {
$sources[] = "SELECT YEAR(`{$posDateColumn}`) AS period_key, CAST(YEAR(`{$posDateColumn}`) AS CHAR) AS label, {$posTotalExpression} AS total FROM pos_transactions WHERE {$posStatusPredicate}`{$posDateColumn}` IS NOT NULL AND {$posScope['sql']}";
} else {
$sources[] = "SELECT DATE_FORMAT(`{$posDateColumn}`, '%Y-%m') AS period_key, DATE_FORMAT(`{$posDateColumn}`, '%b %Y') AS label, {$posTotalExpression} AS total FROM pos_transactions WHERE {$posStatusPredicate}`{$posDateColumn}` IS NOT NULL AND {$posScope['sql']}";
}
$params = array_merge($params, $posScope['params']);
}
}
if ($sources === []) {
return [];
}
$sql = "SELECT period_key, label, SUM(total) AS total FROM (" . implode(' UNION ALL ', $sources) . ") dashboard_sales_rollup GROUP BY period_key, label ORDER BY period_key DESC LIMIT {$limit}";
$stmt = $db->prepare($sql);
$stmt->execute($params);
$rows = array_reverse($stmt->fetchAll(PDO::FETCH_ASSOC));
foreach ($rows as &$row) {
$row['label'] = (string)($row['label'] ?? '');
$row['total'] = (float)($row['total'] ?? 0);
}
unset($row);
return $rows;
}
}
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_mark')) {
function runtime_debug_mark(string $stage, array $context = []): void {
runtime_debug_boot_mark($stage, $context);
}
}
if (!function_exists('runtime_debug_current_stage')) {
function runtime_debug_current_stage(): string {
return (string)($GLOBALS['app_runtime_debug_stage'] ?? 'unknown');
}
}
if (!function_exists('runtime_debug_recent_timeline')) {
function runtime_debug_recent_timeline(int $limit = 8): array {
$timeline = $GLOBALS['app_runtime_debug_timeline'] ?? [];
if (!is_array($timeline)) {
return [];
}
return array_slice($timeline, -max(1, $limit));
}
}
if (!function_exists('runtime_debug_force_details_enabled')) {
function runtime_debug_force_details_enabled(): bool {
return runtime_debug_boot_force_details();
}
}
if (!function_exists('sales_purchases_load_logic')) {
function sales_purchases_load_logic(string $page, array &$data, $limit = 20, $page_num = 1): void {
runtime_debug_mark('logic:sales_purchases_inline', ['page' => $page]);
$incomingLimit = $limit ?? ($_GET['limit'] ?? 20);
$requestedLimit = is_numeric($incomingLimit) ? (int)$incomingLimit : 20;
if ($requestedLimit < 1) {
if (function_exists('runtime_debug_mark')) {
runtime_debug_mark('page:sales_purchases_limit_fallback', [
'page' => (string)$page,
'requested_limit' => isset($_GET['limit']) ? (string)$_GET['limit'] : 'unset',
'applied_limit' => 20,
]);
}
if (function_exists('app_debug_file_log')) {
app_debug_file_log(
'runtime_debug.log',
date('Y-m-d H:i:s') . " [sales_purchases_limit_fallback] page=" . (string)$page
. " requested_limit=" . (isset($_GET['limit']) ? (string)$_GET['limit'] : 'unset')
. " applied_limit=20"
);
}
}
$limit = min(500, max(5, $requestedLimit > 0 ? $requestedLimit : 20));
$_GET['limit'] = (string)$limit;
$_REQUEST['limit'] = (string)$limit;
$incomingPageNum = $page_num ?? ($_GET['p'] ?? 1);
$page_num = is_numeric($incomingPageNum) ? max(1, (int)$incomingPageNum) : 1;
$_GET['p'] = (string)$page_num;
$_REQUEST['p'] = (string)$page_num;
$offset = ($page_num - 1) * $limit;
$type = ($page === 'sales') ? 'sale' : 'purchase';
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers';
$where = ['1=1'];
$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_supplier_col = ?";
$params[] = $_GET['customer_id'];
}
if (!empty($_GET['start_date'])) {
$where[] = 'v.invoice_date >= ?';
$params[] = $_GET['start_date'];
}
if (!empty($_GET['end_date'])) {
$where[] = 'v.invoice_date <= ?';
$params[] = $_GET['end_date'];
}
$tableHasOutlet = db_column_exists($table, 'outlet_id');
$oid = current_outlet_id();
if ($tableHasOutlet && $oid !== -1) {
$where[] = '(v.outlet_id = ? OR v.outlet_id IS NULL)';
$params[] = $oid;
}
$whereSql = implode(' AND ', $where);
$countStmt = db()->prepare("SELECT COUNT(*) FROM $table v LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id WHERE $whereSql");
$countStmt->execute($params);
$total_records = (int)$countStmt->fetchColumn();
$data['total_pages'] = max(1, (int)ceil($total_records / max(1, (int)$limit)));
$data['current_page'] = $page_num;
$customerTaxColumn = entity_tax_column($cust_supplier_table);
$customerTaxSelect = $customerTaxColumn !== null ? "c.$customerTaxColumn" : "''";
$outletSelectSql = "'' AS outlet_name";
$outletJoinSql = '';
if ($tableHasOutlet && db_table_exists('outlets')) {
$outletSelectSql = 'o.name AS outlet_name';
$outletJoinSql = 'LEFT JOIN outlets o ON v.outlet_id = o.id';
}
$invoiceSql = implode(' ', [
"SELECT v.*, c.name as customer_name, $customerTaxSelect as customer_tax_id, c.phone as customer_phone, $outletSelectSql",
"FROM $table v",
"LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id",
$outletJoinSql,
"WHERE $whereSql",
"ORDER BY v.id DESC LIMIT $limit OFFSET $offset",
]);
$stmt = db()->prepare($invoiceSql);
$stmt->execute($params);
$data['invoices'] = $stmt->fetchAll();
$documentPrefix = ($type === 'purchase') ? 'PUR' : 'INV';
foreach ($data['invoices'] as &$inv) {
$inv['due_date'] = $inv['due_date'] ?? null;
$transactionNo = trim((string)($inv['transaction_no'] ?? ''));
$partyFallback = ($type === 'sale' && !empty($inv['is_pos'])) ? 'Walk-in Customer' : '---';
$normalizedPaymentType = strtolower(str_replace([' ', '-'], '_', (string)($inv['payment_type'] ?? 'cash')));
$paymentTypeLabel = 'Cash';
if ($normalizedPaymentType === 'bank_transfer') {
$paymentTypeLabel = 'Bank Transfer';
} elseif (in_array($normalizedPaymentType, ['card', 'credit_card'], true)) {
$paymentTypeLabel = 'Card';
} elseif ($normalizedPaymentType === 'credit') {
$paymentTypeLabel = 'Credit';
}
$inv['party_name'] = trim((string)($inv['customer_name'] ?? '')) !== '' ? (string)$inv['customer_name'] : $partyFallback;
$inv['document_no'] = ($type === 'sale' && $transactionNo !== '') ? $transactionNo : $documentPrefix . '-' . str_pad((string)$inv['id'], 5, '0', STR_PAD_LEFT);
$inv['type'] = $type;
$inv['payment_type'] = $normalizedPaymentType;
$inv['payment_type_label'] = $paymentTypeLabel;
$inv['total_with_vat'] = (float)($inv['total_with_vat'] ?? (($inv['total_amount'] ?? 0) + ($inv['vat_amount'] ?? 0)));
$inv['paid_amount'] = (float)($inv['paid_amount'] ?? 0);
$inv['balance_amount'] = max($inv['total_with_vat'] - $inv['paid_amount'], 0);
$inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']);
if ($type === 'sale') {
$item_stmt = db()->prepare('SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?');
$item_stmt->execute([$inv['id']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
} else {
$item_stmt = db()->prepare('SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?');
$item_stmt->execute([$inv['id']]);
$inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
unset($inv);
$items_list_raw = db()->query('SELECT i.id, i.name_en, i.name_ar, i.sale_price, i.purchase_price, i.stock_quantity, i.vat_rate, i.is_promotion, i.promotion_start, i.promotion_end, i.promotion_percent FROM stock_items i ORDER BY i.name_en ASC')->fetchAll(PDO::FETCH_ASSOC);
foreach ($items_list_raw as &$item) {
$item['sale_price'] = getPromotionalPrice($item);
}
unset($item);
$data['items_list'] = $items_list_raw;
$data['customers_list'] = db()->query("SELECT id, name FROM $cust_supplier_table ORDER BY name ASC")->fetchAll();
$oid = current_outlet_id();
$outlet_sql = ($oid !== -1) ? "WHERE outlet_id = $oid" : '';
if ($type === 'sale') {
$data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices $outlet_sql ORDER BY id DESC")->fetchAll();
} else {
$data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM purchases $outlet_sql ORDER BY id DESC")->fetchAll();
}
}
}
if (!function_exists('runtime_debug_require')) {
function runtime_debug_require(string $file, array $context = []): void {
runtime_debug_mark('require:' . basename($file), $context + ['file' => $file]);
foreach (array_keys($GLOBALS) as $globalName) {
if ($globalName === 'GLOBALS' || $globalName === 'file' || $globalName === 'context') {
continue;
}
if (!preg_match('/^[A-Za-z_\x80-\xff][A-Za-z0-9_\x80-\xff]*$/', $globalName)) {
continue;
}
${$globalName} =& $GLOBALS[$globalName];
}
require $file;
}
}
if (!function_exists('runtime_debug_extract_table_name')) {
function runtime_debug_extract_table_name(Throwable $throwable): ?string {
$message = $throwable->getMessage();
$patterns = [
"/Table '[^']+\.([^']+)' doesn't exist/i",
'/(?:INSERT\s+INTO|UPDATE|FROM|JOIN|DELETE\s+FROM|ALTER\s+TABLE)\s+`?([a-zA-Z0-9_]+)`?/i',
];
foreach ($patterns as $pattern) {
if (preg_match($pattern, $message, $matches)) {
return (string)$matches[1];
}
}
return null;
}
}
if (!function_exists('runtime_debug_extract_column_name')) {
function runtime_debug_extract_column_name(Throwable $throwable): ?string {
$message = $throwable->getMessage();
if (preg_match("/Unknown column '([^']+)'/i", $message, $matches)) {
return (string)$matches[1];
}
if (preg_match('/Column not found: [0-9]+ ([a-zA-Z0-9_]+)/i', $message, $matches)) {
return (string)$matches[1];
}
return null;
}
}
if (!function_exists('runtime_debug_find_related_migrations')) {
function runtime_debug_find_related_migrations(?string $tableName, ?string $columnName = null, int $limit = 6): array {
$directory = __DIR__ . '/db/migrations';
if (!is_dir($directory)) {
return [];
}
$needles = array_values(array_filter([
$tableName ? strtolower($tableName) : null,
$columnName ? strtolower($columnName) : null,
]));
if ($needles === []) {
return [];
}
$matches = [];
foreach (glob($directory . '/*.{sql,php}', GLOB_BRACE) ?: [] as $migrationPath) {
if (!is_readable($migrationPath)) {
continue;
}
$contents = @file_get_contents($migrationPath);
if ($contents === false) {
continue;
}
$haystack = strtolower($contents);
$matched = true;
foreach ($needles as $needle) {
if (!str_contains($haystack, $needle)) {
$matched = false;
break;
}
}
if ($matched) {
$matches[] = basename($migrationPath);
if (count($matches) >= $limit) {
break;
}
}
}
return $matches;
}
}
if (!function_exists('runtime_debug_schema_snapshot')) {
function runtime_debug_schema_snapshot(Throwable $throwable): array {
$tableName = runtime_debug_extract_table_name($throwable);
$columnName = runtime_debug_extract_column_name($throwable);
$snapshot = [
'table' => $tableName,
'column' => $columnName,
'table_exists' => null,
'columns' => [],
'database_error' => null,
'related_migrations' => runtime_debug_find_related_migrations($tableName, $columnName),
];
if ($tableName === null) {
return $snapshot;
}
try {
$pdo = db();
$snapshot['table_exists'] = db_table_exists($tableName);
if ($snapshot['table_exists']) {
$stmt = $pdo->prepare(
"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION"
);
$stmt->execute([$tableName]);
$snapshot['columns'] = array_map('strval', $stmt->fetchAll(PDO::FETCH_COLUMN) ?: []);
}
} catch (Throwable $e) {
$snapshot['database_error'] = $e->getMessage();
}
return $snapshot;
}
}
if (!function_exists('runtime_debug_is_fatal_error')) {
function runtime_debug_is_fatal_error($error): bool {
if (!is_array($error)) {
return false;
}
return in_array((int)($error['type'] ?? 0), [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR, E_RECOVERABLE_ERROR], true);
}
}
if (!function_exists('runtime_debug_can_render_details')) {
function runtime_debug_can_render_details(): bool {
if (runtime_debug_force_details_enabled()) {
return true;
}
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),
'stage=' . runtime_debug_current_stage(),
];
$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);
}
$timeline = runtime_debug_recent_timeline(10);
if ($timeline !== []) {
$parts[] = 'timeline=' . json_encode($timeline, JSON_UNESCAPED_UNICODE);
}
$trace = explode("\n", $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 {
if (!empty($GLOBALS['app_runtime_debug_rendered'])) {
if (!headers_sent()) {
http_response_code(500);
header('Content-Type: text/plain; charset=UTF-8');
}
echo 'Application Error';
exit;
}
$GLOBALS['app_runtime_debug_rendered'] = true;
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');
$currentStage = runtime_debug_current_stage();
$timeline = runtime_debug_recent_timeline(10);
$timelineText = json_encode($timeline, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
$schemaSnapshot = runtime_debug_schema_snapshot($throwable);
$tableExistsText = $schemaSnapshot['table_exists'] === null ? 'Unknown' : ($schemaSnapshot['table_exists'] ? 'Yes' : 'No');
$schemaColumnsText = !empty($schemaSnapshot['columns']) ? implode(', ', $schemaSnapshot['columns']) : '—';
$migrationText = !empty($schemaSnapshot['related_migrations']) ? implode(', ', $schemaSnapshot['related_migrations']) : '—';
$tracePreview = array_slice(explode("\n", $throwable->getTraceAsString()), 0, 8);
$traceText = implode("\n", $tracePreview);
$title = $showDetails ? 'Application Debug' : 'Application Error';
$summary = $showDetails
? 'The request failed. The details below should help identify the missing table, column, or view file.'
: 'An unexpected error occurred while loading this page. The diagnostic snapshot below may still point to what is missing.';
?>
<!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: 980px; 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; }
h2 { margin: 28px 0 10px; font-size: 18px; color: #111827; }
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 ($hint !== null): ?>
<div class="hint"><strong>Possible issue:</strong> <?= htmlspecialchars($hint) ?></div>
<?php endif; ?>
<div class="grid">
<div class="label">Last boot step</div>
<div class="value"><?= htmlspecialchars($currentStage) ?></div>
<div class="label">Page</div>
<div class="value"><?= htmlspecialchars($page) ?></div>
<div class="label">Request URI</div>
<div class="value"><?= htmlspecialchars($requestUri) ?></div>
<?php if (!empty($schemaSnapshot['table'])): ?>
<div class="label">Detected table</div>
<div class="value"><?= htmlspecialchars((string)$schemaSnapshot['table']) ?></div>
<div class="label">Table exists</div>
<div class="value"><?= htmlspecialchars($tableExistsText) ?></div>
<?php endif; ?>
<?php if (!empty($schemaSnapshot['column'])): ?>
<div class="label">Detected column</div>
<div class="value"><?= htmlspecialchars((string)$schemaSnapshot['column']) ?></div>
<?php endif; ?>
<?php if (!empty($schemaSnapshot['columns'])): ?>
<div class="label">Existing columns</div>
<div class="value"><?= htmlspecialchars($schemaColumnsText) ?></div>
<?php endif; ?>
<?php if (!empty($schemaSnapshot['related_migrations'])): ?>
<div class="label">Likely migration</div>
<div class="value"><?= htmlspecialchars($migrationText) ?></div>
<?php endif; ?>
<?php if (!empty($schemaSnapshot['database_error'])): ?>
<div class="label">DB snapshot error</div>
<div class="value"><?= htmlspecialchars((string)$schemaSnapshot['database_error']) ?></div>
<?php endif; ?>
</div>
<?php if ($showDetails): ?>
<h2>Full exception</h2>
<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>
<?php if ($traceText !== ''): ?>
<pre><?= htmlspecialchars($traceText) ?></pre>
<?php endif; ?>
<?php endif; ?>
<?php if ($timelineText !== false && $timelineText !== '[]'): ?>
<h2>Boot timeline</h2>
<pre><?= htmlspecialchars((string)$timelineText) ?></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>. For private troubleshooting you can also add <code>?debug=1</code> to the URL to force expanded details.</p>
</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);
});
}
if (!defined('APP_RUNTIME_DEBUG_FATAL_HANDLER_REGISTERED')) {
define('APP_RUNTIME_DEBUG_FATAL_HANDLER_REGISTERED', true);
register_shutdown_function(static function (): void {
if (!empty($GLOBALS['app_runtime_debug_rendered'])) {
return;
}
$error = error_get_last();
if (!runtime_debug_is_fatal_error($error)) {
return;
}
$throwable = new ErrorException(
(string)($error['message'] ?? 'Fatal error'),
0,
(int)($error['type'] ?? E_ERROR),
(string)($error['file'] ?? __FILE__),
(int)($error['line'] ?? 0)
);
runtime_debug_render_exception($throwable);
});
}
runtime_debug_mark('boot:runtime_debug_ready');
// 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
}
runtime_debug_mark('boot:loading_database_installer');
require_once 'includes/DatabaseInstaller.php';
runtime_debug_mark('boot:database_installer_loaded');
// Auto-install database if not installed, then ensure pending migrations are applied.
try {
if (!DatabaseInstaller::isInstalled()) {
runtime_debug_mark('boot:database_installing');
DatabaseInstaller::install();
} elseif (method_exists('DatabaseInstaller', 'ensureCurrentSchema')) {
runtime_debug_mark('boot:database_schema_sync');
DatabaseInstaller::ensureCurrentSchema();
} else {
error_log('Skipping DatabaseInstaller::ensureCurrentSchema() because the loaded DatabaseInstaller class does not define it.');
}
runtime_debug_mark('boot:database_ready');
} catch (Throwable $e) {
runtime_debug_mark('boot:database_installer_failed');
runtime_debug_render_exception($e);
}
runtime_debug_mark('boot:loading_license_dependencies');
require_once 'lib/LicenseService.php';
require_once 'includes/lang.php';
runtime_debug_mark('boot:license_dependencies_loaded');
// 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();
runtime_debug_mark('boot:license_validated');
} catch (PDOException $e) {
runtime_debug_mark('boot:license_validation_failed', ['reason' => 'pdo']);
runtime_debug_render_exception($e);
} catch (Exception $e) {
runtime_debug_mark('boot:license_validation_failed', ['reason' => 'application']);
runtime_debug_render_exception($e);
}
$page = $_GET['page'] ?? 'dashboard';
runtime_debug_mark('page:selected', ['page' => (string)$page, 'phase' => 'activation_gate']);
if (!$can_access && $page !== 'activate') {
header("Location: " . page_url("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="<?= htmlspecialchars(page_url('activate', ['lang' => "en"])) ?>">English</a></li>
<li><a class="dropdown-menu" href="<?= htmlspecialchars(page_url('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';
$totalExpression = db_column_exists('purchases', 'total_with_vat')
? 'COALESCE(p.total_with_vat, 0)'
: (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';
$where = [];
$params = [];
if (db_column_exists('purchases', 'status')) {
$where[] = "p.status != 'paid'";
}
if (db_column_exists('purchases', 'due_date')) {
$where[] = 'p.due_date IS NOT NULL';
$where[] = 'p.due_date <= DATE_ADD(CURDATE(), INTERVAL 7 DAY)';
}
$outletScope = outlet_scope_sql('purchases', 'p.outlet_id');
if ($outletScope['sql'] !== '1=1') {
$where[] = $outletScope['sql'];
$params = array_merge($params, $outletScope['params']);
}
$whereSql = $where === [] ? '1=1' : implode(' AND ', $where);
$sql = "SELECT p.id, {$dueDateExpression} AS due_date, {$totalExpression} AS total_with_vat, {$supplierExpression} AS supplier_name"
. ' FROM purchases p'
. $joinClause
. ' WHERE '
. $whereSql
. $orderBy;
$stmt = $db->prepare($sql);
$stmt->execute($params);
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']) ? min(500, max(5, (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' => ($lang === 'ar' ? 'رمز الخصم غير صالح أو منتهي الصلاحية' : '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-purchase-price="<?= (float)$p['purchase_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>" data-stock-quantity="<?= format_quantity($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"><?= format_quantity($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 = normalize_quantity(((float)$weightBarcode['value']) / (float)$p['sale_price']);
} else {
$qty = normalize_quantity((float)$weightBarcode['value']);
}
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'] ?? ($lang === 'ar' ? 'طلب غير مسمى' : '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);
if (!is_array($items)) {
$items = [];
}
foreach ($items as $itemIndex => $item) {
$items[$itemIndex]['qty'] = normalize_quantity($item['qty'] ?? 0);
}
$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 = max(0, (float)($_POST['discount_amount'] ?? 0));
$manual_discount_amount = max(0, (float)($_POST['manual_discount_amount'] ?? 0));
$manualDiscountEnabled = getSettingValue('manual_discount_enabled', '0') === '1';
if (!$manualDiscountEnabled) {
$manual_discount_amount = 0.0;
if ($discount_code_id === null) {
$discount_amount = 0.0;
}
}
if ($manual_discount_amount > ($discount_amount + 0.0005)) {
throw new Exception(($lang ?? 'en') === 'ar' ? 'بيانات الخصم اليدوي غير صالحة.' : 'Invalid manual discount data.');
}
if ($manual_discount_amount > 0) {
$discountMetrics = calculateManualDiscountProfitMetrics($items, true);
$maxManualDiscount = min(max(0, $total_amount), max(0, (float)($discountMetrics['max_discount'] ?? 0)));
if ($manual_discount_amount > ($maxManualDiscount + 0.0005)) {
throw new Exception(manualDiscountLimitMessage($discountMetrics, $manual_discount_amount));
}
}
if ($discount_amount > $total_amount) {
$discount_amount = max(0, $total_amount);
}
$loyalty_redeemed = max(0, (float)($_POST['loyalty_redeemed'] ?? 0));
$loyalty_redeemed = min($loyalty_redeemed, max(0, $total_amount - $discount_amount));
$net_amount = max(0, $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_details') {
header('Content-Type: application/json');
$invoice_id = (int)($_GET['invoice_id'] ?? 0);
$type = (($_GET['type'] ?? 'sale') === 'purchase') ? 'purchase' : 'sale';
if ($invoice_id < 1) {
echo json_encode(['success' => false, 'error' => 'Invalid invoice id']);
exit;
}
$table = ($type === 'purchase') ? 'purchases' : 'invoices';
$itemTable = ($type === 'purchase') ? 'purchase_items' : 'invoice_items';
$fkColumn = ($type === 'purchase') ? 'purchase_id' : 'invoice_id';
$partyColumn = ($type === 'purchase') ? 'supplier_id' : 'customer_id';
$partyTable = ($type === 'purchase') ? 'suppliers' : 'customers';
$partyAlias = ($type === 'purchase') ? 'supplier_name' : 'customer_name';
$where = ['v.id = ?'];
$params = [$invoice_id];
if (db_column_exists($table, 'outlet_id')) {
$oid = current_outlet_id();
if ($oid !== -1) {
$where[] = '(v.outlet_id = ? OR v.outlet_id IS NULL)';
$params[] = $oid;
}
}
$stmt = db()->prepare("SELECT v.*, c.name AS {$partyAlias}, c.phone AS party_phone FROM $table v LEFT JOIN $partyTable c ON v.$partyColumn = c.id WHERE " . implode(' AND ', $where) . " LIMIT 1");
$stmt->execute($params);
$invoice = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$invoice) {
echo json_encode(['success' => false, 'error' => 'Invoice not found']);
exit;
}
$partyName = trim((string)($invoice[$partyAlias] ?? ''));
if ($partyName === '' && $type === 'sale' && !empty($invoice['is_pos'])) {
$partyName = 'Walk-in Customer';
}
$invoice['type'] = $type;
$invoice['party_name'] = $partyName !== '' ? $partyName : '---';
$invoice['paid_amount'] = (float)($invoice['paid_amount'] ?? 0);
$stmtItems = db()->prepare("SELECT li.*, i.name_en, i.name_ar, i.sku, i.vat_rate, i.stock_quantity, i.purchase_price FROM $itemTable li LEFT JOIN stock_items i ON li.item_id = i.id WHERE li.$fkColumn = ?");
$stmtItems->execute([$invoice_id]);
$invoice['items'] = $stmtItems->fetchAll(PDO::FETCH_ASSOC);
echo json_encode($invoice, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE);
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, i.vat_rate, i.stock_quantity
FROM purchase_items pi
LEFT 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, i.vat_rate, i.stock_quantity
FROM invoice_items ii
LEFT 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 getManualDiscountProfitLimitPercent(): float {
$rawValue = getSettingValue('manual_discount_profit_limit_percent', '5');
$percent = is_numeric($rawValue) ? (float)$rawValue : 5.0;
if ($percent < 0) {
$percent = 0.0;
}
if ($percent > 100) {
$percent = 100.0;
}
return $percent;
}
function calculateManualDiscountProfitMetrics(array $lines, bool $pricesIncludeVat = false): array {
$percent = getManualDiscountProfitLimitPercent();
$normalizedLines = [];
$itemIds = [];
foreach ($lines as $line) {
$itemId = (int)($line['item_id'] ?? $line['id'] ?? 0);
$qty = normalize_quantity($line['qty'] ?? $line['quantity'] ?? 0);
$unitPrice = (float)($line['unit_price'] ?? $line['price'] ?? 0);
$vatRate = isset($line['vat_rate']) ? (float)$line['vat_rate'] : 0.0;
if ($itemId <= 0 || $qty <= 0) {
continue;
}
$normalizedLines[] = [
'item_id' => $itemId,
'qty' => $qty,
'unit_price' => $unitPrice,
'vat_rate' => $vatRate,
];
$itemIds[$itemId] = true;
}
if ($normalizedLines === [] || $itemIds === []) {
return [
'percent' => $percent,
'profit_amount' => 0.0,
'max_discount' => 0.0,
];
}
$ids = array_keys($itemIds);
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$purchasePrices = [];
try {
$stmt = db()->prepare("SELECT id, purchase_price FROM stock_items WHERE id IN ($placeholders)");
$stmt->execute($ids);
foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
$purchasePrices[(int)$row['id']] = (float)($row['purchase_price'] ?? 0);
}
} catch (Throwable $e) {
$purchasePrices = [];
}
$profitAmount = 0.0;
foreach ($normalizedLines as $line) {
$purchasePrice = $purchasePrices[(int)$line['item_id']] ?? 0.0;
$effectiveUnitPrice = (float)$line['unit_price'];
$vatRate = (float)($line['vat_rate'] ?? 0.0);
if ($pricesIncludeVat && $vatRate > 0) {
$effectiveUnitPrice = $effectiveUnitPrice / (1 + ($vatRate / 100));
}
$profitAmount += ($effectiveUnitPrice - $purchasePrice) * (float)$line['qty'];
}
$profitAmount = max(0, $profitAmount);
$maxDiscount = max(0, $profitAmount * ($percent / 100));
return [
'percent' => $percent,
'profit_amount' => $profitAmount,
'max_discount' => $maxDiscount,
];
}
function manualDiscountLimitMessage(array $metrics, ?float $requestedAmount = null): string {
$langCode = $_SESSION['lang'] ?? 'en';
$currency = function_exists('__') ? __('currency') : 'OMR';
$allowed = number_format(max(0, (float)($metrics['max_discount'] ?? 0)), 3, '.', '');
$profit = number_format(max(0, (float)($metrics['profit_amount'] ?? 0)), 3, '.', '');
$percent = rtrim(rtrim(number_format(max(0, (float)($metrics['percent'] ?? 0)), 3, '.', ''), '0'), '.');
if ($percent === '') {
$percent = '0';
}
$requested = $requestedAmount !== null ? number_format(max(0, $requestedAmount), 3, '.', '') : null;
if ($langCode === 'ar') {
$message = "الخصم اليدوي المسموح هو {$currency} {$allowed} فقط ({$percent}% من ربح الفاتورة {$currency} {$profit}).";
if ($requested !== null) {
$message = "الخصم اليدوي {$currency} {$requested} يتجاوز الحد المسموح. " . $message;
}
return $message;
}
$message = "The maximum allowed manual discount is {$currency} {$allowed} ({$percent}% of invoice profit {$currency} {$profit}).";
if ($requested !== null) {
$message = "Manual discount {$currency} {$requested} exceeds the allowed limit. " . $message;
}
return $message;
}
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'] ?? 'dashboard.php';
}
$url = page_normalize_url((string)$url);
$_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 = normalize_quantity($_POST['stock_quantity'] ?? 0);
$min_stock_level = normalize_quantity($_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 = normalize_quantity($_POST['stock_quantity'] ?? 0);
$min_stock_level = normalize_quantity($_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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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");
}
// Sales/Purchases secondary handlers
require_once __DIR__ . '/pages/sales_purchases_handlers.php';
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 = normalize_quantity($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'])) {
ensure_payment_methods_schema_ready();
$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'])) {
ensure_payment_methods_schema_ready();
$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'])) {
ensure_payment_methods_schema_ready();
$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_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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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 = normalize_quantity($qtys[$i] ?? 0);
$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['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 += normalize_quantity($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 = normalize_quantity($quantities[$i] ?? 0);
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 += normalize_quantity($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 = normalize_quantity($quantities[$i] ?? 0);
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");
}
}
runtime_debug_require('pages/settings_save_logic.php', ['phase' => 'save_logic', 'page' => (string)$page]);
// --- 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: " . page_url("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: " . page_url("dashboard"));
} else {
// For Admin, we stay on sessions page
redirectWithMessage($message, page_url((string)$redirect_to));
}
exit;
}
// Routing & Data Fetching
$page = $_GET['page'] ?? 'dashboard';
runtime_debug_mark('page:selected', ['page' => (string)$page, 'phase' => 'data']);
// 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' => [],
'dashboard_scope_label' => '',
'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,
'total_paid' => 0,
'total_payable' => 0,
'total_customers' => 0,
'total_items' => 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 = ["i.outlet_id = ?"];
$params = [current_outlet_id()];
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']}%";
}
if (!empty($_GET['category_id'])) {
$where[] = "i.category_id = ?";
$params[] = (int)$_GET['category_id'];
}
if (!empty($_GET['supplier_id'])) {
$where[] = "i.supplier_id = ?";
$params[] = (int)$_GET['supplier_id'];
}
$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
LEFT JOIN suppliers s ON i.supplier_id = s.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;
}
runtime_debug_mark('page:shared_data_loading', ['page' => (string)$page]);
// 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"]) ? min(500, 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;
runtime_debug_mark('page:data_switch_loading', ['page' => (string)$page]);
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 = ?"];
$params = [current_outlet_id()];
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']}%";
}
if (!empty($_GET['category_id'])) {
$where[] = "i.category_id = ?";
$params[] = (int)$_GET['category_id'];
}
if (!empty($_GET['supplier_id'])) {
$where[] = "i.supplier_id = ?";
$params[] = (int)$_GET['supplier_id'];
}
$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':
ensure_payment_methods_schema_ready();
$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: " . page_url("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': runtime_debug_require('pages/copy_outlet_data_logic.php', ['phase' => 'logic', 'page' => (string)$page]); 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':
$salesRequestedLimit = isset($_GET['limit']) ? (int)$_GET['limit'] : (isset($limit) ? (int)$limit : 20);
$salesSafeLimit = min(500, max(5, $salesRequestedLimit > 0 ? $salesRequestedLimit : 20));
if ($salesRequestedLimit !== $salesSafeLimit) {
runtime_debug_mark('page:sales_purchases_limit_normalized', [
'page' => (string)$page,
'requested_limit' => (string)$salesRequestedLimit,
'applied_limit' => (string)$salesSafeLimit,
]);
if (function_exists('app_debug_file_log')) {
app_debug_file_log(
'runtime_debug.log',
date('Y-m-d H:i:s') . " [sales_purchases_limit_normalized] page=" . (string)$page
. " requested_limit=" . (string)$salesRequestedLimit
. " applied_limit=" . (string)$salesSafeLimit
);
}
}
$limit = $salesSafeLimit;
$page_num = isset($_GET['p']) ? max(1, (int)$_GET['p']) : (isset($page_num) ? max(1, (int)$page_num) : 1);
$offset = ($page_num - 1) * $limit;
$_GET['limit'] = (string)$limit;
$_REQUEST['limit'] = (string)$limit;
$_GET['p'] = (string)$page_num;
$_REQUEST['p'] = (string)$page_num;
runtime_debug_mark('require:sales_purchases_logic.php', ['phase' => 'logic', 'page' => (string)$page, 'mode' => 'inline']);
sales_purchases_load_logic((string)$page, $data, $limit, $page_num);
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':
runtime_debug_require('pages/users_logic.php', ['phase' => 'logic', 'page' => (string)$page]);
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':
runtime_debug_require('pages/accounting_logic.php', ['phase' => 'logic', 'page' => (string)$page]);
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')) {
$db = db();
$scalar = static function (string $sql, array $params = []) use ($db) {
$stmt = $db->prepare($sql);
$stmt->execute($params);
return $stmt->fetchColumn();
};
$data['dashboard_scope_label'] = current_outlet_name();
$customerScope = outlet_scope_sql('customers', 'outlet_id');
if (db_table_exists('customers')) {
$customerStmt = $db->prepare("SELECT * FROM customers WHERE {$customerScope['sql']} ORDER BY id DESC LIMIT 5");
$customerStmt->execute($customerScope['params']);
$data['customers'] = $customerStmt->fetchAll();
$data['stats']['total_customers'] = (int)($scalar("SELECT COUNT(*) FROM customers WHERE {$customerScope['sql']}", $customerScope['params']) ?: 0);
}
$itemScope = outlet_scope_sql('stock_items', 'outlet_id');
if (db_table_exists('stock_items')) {
$data['stats']['total_items'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE {$itemScope['sql']}", $itemScope['params']) ?: 0);
if (db_column_exists('stock_items', 'expiry_date')) {
$data['stats']['expired_items'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE() AND {$itemScope['sql']}", $itemScope['params']) ?: 0);
$data['stats']['near_expiry_items'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY) AND {$itemScope['sql']}", $itemScope['params']) ?: 0);
}
if (db_column_exists('stock_items', 'stock_quantity') && db_column_exists('stock_items', 'min_stock_level')) {
$data['stats']['low_stock_items_count'] = (int)($scalar("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level AND {$itemScope['sql']}", $itemScope['params']) ?: 0);
}
}
if (db_table_exists('invoices')) {
$invoiceScope = outlet_scope_sql('invoices', 'outlet_id');
$invoiceTotalExpression = db_column_exists('invoices', 'total_with_vat') ? 'COALESCE(total_with_vat, 0)' : (db_column_exists('invoices', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
$data['stats']['total_sales'] += (float)($scalar("SELECT COALESCE(SUM({$invoiceTotalExpression}), 0) FROM invoices WHERE {$invoiceScope['sql']}", $invoiceScope['params']) ?: 0);
}
if (db_table_exists('pos_transactions')) {
$posScope = outlet_scope_sql('pos_transactions', 'outlet_id');
$posTotalExpression = db_column_exists('pos_transactions', 'net_amount') ? 'COALESCE(net_amount, 0)' : (db_column_exists('pos_transactions', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
$posWhere = [];
$posParams = [];
if (db_column_exists('pos_transactions', 'status')) {
$posWhere[] = 'status = ?';
$posParams[] = 'completed';
}
if ($posScope['sql'] !== '1=1') {
$posWhere[] = $posScope['sql'];
$posParams = array_merge($posParams, $posScope['params']);
}
$posWhereSql = $posWhere === [] ? '1=1' : implode(' AND ', $posWhere);
$data['stats']['total_sales'] += (float)($scalar("SELECT COALESCE(SUM({$posTotalExpression}), 0) FROM pos_transactions WHERE {$posWhereSql}", $posParams) ?: 0);
}
if (db_table_exists('payments') && db_table_exists('invoices') && db_column_exists('payments', 'invoice_id')) {
$paymentInvoiceScope = outlet_scope_sql('invoices', 'i.outlet_id');
$paymentInvoiceWhere = $paymentInvoiceScope['sql'] === '1=1' ? '1=1' : $paymentInvoiceScope['sql'];
$data['stats']['total_received'] += (float)($scalar("SELECT COALESCE(SUM(p.amount), 0) FROM payments p JOIN invoices i ON p.invoice_id = i.id WHERE {$paymentInvoiceWhere}", $paymentInvoiceScope['params']) ?: 0);
}
if (db_table_exists('pos_payments') && db_table_exists('pos_transactions') && db_column_exists('pos_payments', 'transaction_id')) {
$paymentPosScope = outlet_scope_sql('pos_transactions', 't.outlet_id');
$paymentPosWhere = [];
$paymentPosParams = [];
if (db_column_exists('pos_transactions', 'status')) {
$paymentPosWhere[] = 't.status = ?';
$paymentPosParams[] = 'completed';
}
if ($paymentPosScope['sql'] !== '1=1') {
$paymentPosWhere[] = $paymentPosScope['sql'];
$paymentPosParams = array_merge($paymentPosParams, $paymentPosScope['params']);
}
$paymentPosWhereSql = $paymentPosWhere === [] ? '1=1' : implode(' AND ', $paymentPosWhere);
$data['stats']['total_received'] += (float)($scalar("SELECT COALESCE(SUM(pp.amount), 0) FROM pos_payments pp JOIN pos_transactions t ON pp.transaction_id = t.id WHERE {$paymentPosWhereSql}", $paymentPosParams) ?: 0);
}
if (db_table_exists('purchases')) {
$purchaseScope = outlet_scope_sql('purchases', 'outlet_id');
$purchaseTotalExpression = db_column_exists('purchases', 'total_with_vat') ? 'COALESCE(total_with_vat, 0)' : (db_column_exists('purchases', 'total_amount') ? 'COALESCE(total_amount, 0)' : '0');
$data['stats']['total_purchases'] = (float)($scalar("SELECT COALESCE(SUM({$purchaseTotalExpression}), 0) FROM purchases WHERE {$purchaseScope['sql']}", $purchaseScope['params']) ?: 0);
}
if (db_table_exists('purchase_payments') && db_table_exists('purchases') && db_column_exists('purchase_payments', 'purchase_id')) {
$purchasePaymentScope = outlet_scope_sql('purchases', 'p.outlet_id');
$purchasePaymentWhere = $purchasePaymentScope['sql'] === '1=1' ? '1=1' : $purchasePaymentScope['sql'];
$data['stats']['total_paid'] = (float)($scalar("SELECT COALESCE(SUM(pp.amount), 0) FROM purchase_payments pp JOIN purchases p ON pp.purchase_id = p.id WHERE {$purchasePaymentWhere}", $purchasePaymentScope['params']) ?: 0);
}
$data['stats']['total_receivable'] = max((float)$data['stats']['total_sales'] - (float)$data['stats']['total_received'], 0);
$data['stats']['total_payable'] = max((float)$data['stats']['total_purchases'] - (float)$data['stats']['total_paid'], 0);
$data['monthly_sales'] = dashboard_sales_series('month', 12);
$data['yearly_sales'] = dashboard_sales_series('year', 5);
}
break;
}
runtime_debug_mark('page:data_loaded', ['page' => (string)$page]);
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
runtime_debug_mark('page:rendering', ['page' => (string)$page]);
?>
<!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;
}
.items-list-heading {
font-size: calc(1.25rem + 1px);
font-weight: 800;
color: #1d4ed8;
letter-spacing: 0.01em;
}
.items-list-table thead th {
font-size: 0.82rem;
font-weight: 800;
color: #1d4ed8;
letter-spacing: 0.05em;
text-transform: uppercase;
background: rgba(29, 78, 216, 0.08);
border-bottom-color: rgba(29, 78, 216, 0.18);
}
.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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('hr_departments')) ?>" class="nav-link <?= $page === 'hr_departments' ? 'active' : '' ?>">
<i class="fas fa-building-user"></i> <span><?= __('departments') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('hr_employees')) ?>" class="nav-link <?= $page === 'hr_employees' ? 'active' : '' ?>">
<i class="fas fa-user-badge"></i> <span><?= __('employees') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('hr_attendance')) ?>" class="nav-link <?= $page === 'hr_attendance' ? 'active' : '' ?>">
<i class="fas fa-user-check"></i> <span><?= __('attendance') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('devices')) ?>" class="nav-link <?= isset($_GET['page']) && $_GET['page'] === 'devices' ? 'active' : '' ?>">
<i class="fas fa-id-card"></i> <span><?= __('devices') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('role_groups')) ?>" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
<i class="fas fa-user-shield"></i> <span><?= __('role_groups') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('users')) ?>" class="nav-link <?= $page === 'users' ? 'active' : '' ?>">
<i class="fas fa-users-gear"></i> <span><?= __('users') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('cash_registers')) ?>" class="nav-link <?= $page === 'cash_registers' ? 'active' : '' ?>">
<i class="fas fa-cash-register"></i> <span><?= __('cash_registers') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('scale_devices')) ?>" class="nav-link <?= $page === 'scale_devices' ? 'active' : '' ?>">
<i class="fas fa-microchip"></i> <span><?= __('scale_devices') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('customer_display_settings')) ?>" class="nav-link <?= $page === 'customer_display_settings' ? 'active' : '' ?>">
<i class="fas fa-desktop"></i> <span><?= __('customer_display') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('backups')) ?>" class="nav-link <?= $page === 'backups' ? 'active' : '' ?>">
<i class="fas fa-database"></i> <span><?= __('backups') ?></span>
</a>
<a href="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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_outlet_name();
?>
<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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('purchases')) ?>" class="btn btn-primary btn-sm" data-en="View Purchases" data-ar="عرض المشتريات">View Purchases</a>
<?php endif; ?>
<a href="<?= htmlspecialchars(page_url('expiry_report')) ?>" class="btn btn-warning btn-sm" data-en="Expiry Report" data-ar="تقرير الانتهاء">Expiry Report</a>
<a href="<?= htmlspecialchars(page_url('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">
<?php $dashboardScopeLabel = (string)($data['dashboard_scope_label'] ?? current_outlet_name()); ?>
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-4">
<div>
<h5 class="m-0" data-en="Sales Performance" data-ar="أداء المبيعات">Sales Performance</h5>
<div class="small text-muted mt-1">
<i class="fas fa-store me-1"></i>
<span data-en="<?= htmlspecialchars('Scope: ' . $dashboardScopeLabel, ENT_QUOTES) ?>" data-ar="<?= htmlspecialchars('النطاق: ' . $dashboardScopeLabel, ENT_QUOTES) ?>"><?= $lang === 'ar' ? 'النطاق: ' : 'Scope: ' ?><?= htmlspecialchars($dashboardScopeLabel) ?></span>
</div>
</div>
<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 id="salesChartEmptyState" class="small text-muted mt-3 d-none" data-en="No completed sales recorded for this outlet yet." data-ar="لا توجد مبيعات مكتملة مسجلة لهذا الفرع حتى الآن.">No completed sales recorded for this outlet yet.</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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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 items-list-heading" 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 -->
<?php $hasItemFilters = !empty($_GET['search']) || !empty($_GET['category_id']) || !empty($_GET['supplier_id']); ?>
<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-lg-4 col-md-12">
<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-lg-3 col-md-6">
<label class="form-label small" data-en="Category" data-ar="الفئة">Category</label>
<select name="category_id" class="form-select">
<option value="" data-en="All Categories" data-ar="كل الفئات">All Categories</option>
<?php foreach ($data['categories'] ?? [] as $category): ?>
<option value="<?= $category['id'] ?>" <?= (string)($category['id'] ?? '') === (string)($_GET['category_id'] ?? '') ? 'selected' : '' ?> data-en="<?= htmlspecialchars(localized_option_label($category, 'en')) ?>" data-ar="<?= htmlspecialchars(localized_option_label($category, 'ar')) ?>"><?= htmlspecialchars(localized_option_label($category)) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-lg-3 col-md-6">
<label class="form-label small" data-en="Supplier" data-ar="المورد">Supplier</label>
<select name="supplier_id" class="form-select">
<option value="" data-en="All Suppliers" data-ar="كل الموردين">All Suppliers</option>
<?php foreach ($data['suppliers'] ?? [] as $supplier): ?>
<option value="<?= $supplier['id'] ?>" <?= (string)($supplier['id'] ?? '') === (string)($_GET['supplier_id'] ?? '') ? 'selected' : '' ?>><?= htmlspecialchars((string)($supplier['name'] ?? '')) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="col-lg-2 col-md-12 d-flex gap-1">
<button type="submit" class="btn btn-primary flex-grow-1">
<i class="bi bi-search"></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' => '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 ($hasItemFilters): ?>
<a href="<?= htmlspecialchars(page_url('items', ['limit' => (int)($_GET['limit'] ?? 20)])) ?>" class="btn btn-outline-secondary" title="Clear filters">
<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 items-list-table">
<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="Sale Price" data-ar="سعر البيع">Sale Price</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): ?>
<?php
$itemBarcodePrice = number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3);
?>
<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="<?= htmlspecialchars((string)$itemBarcodePrice, ENT_QUOTES, 'UTF-8') ?>"></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['sale_price'], 3) ?></strong>
<div class="small text-muted" data-en="Incl. VAT: <?= number_format((float)$itemBarcodePrice, 3) ?>" data-ar="شامل الضريبة: <?= number_format((float)$itemBarcodePrice, 3) ?>">Incl. VAT: <?= number_format((float)$itemBarcodePrice, 3) ?></div>
</div>
</td>
<td>
<div class="text-end">
<strong><?= format_quantity($item['stock_quantity']) ?></strong>
<div class="small text-muted">Min: <?= format_quantity($item['min_stock_level']) ?></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>
<?php
$itemBarcodePrice = number_format((float)$item['sale_price'] * (1 + (float)($item['vat_rate'] ?? 0) / 100), 3);
$itemSkuJs = htmlspecialchars(json_encode((string)($item['sku'] ?? ''), JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemNameArJs = htmlspecialchars(json_encode((string)($item['name_ar'] ?? ''), JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemNameEnJs = htmlspecialchars(json_encode((string)($item['name_en'] ?? ''), JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemBarcodePriceJs = htmlspecialchars(json_encode((string)$itemBarcodePrice, JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
$itemExpiryDateJs = htmlspecialchars(json_encode(!empty($item['expiry_date']) ? (string)$item['expiry_date'] : '', JSON_UNESCAPED_UNICODE), ENT_QUOTES, 'UTF-8');
?>
<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(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemBarcodePriceJs ?>)"><i class="bi bi-upc"></i></button>
<button class="btn btn-outline-secondary" title="Barcode + Dates" onclick="printItemBarcodeWithDates(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemBarcodePriceJs ?>, <?= $itemExpiryDateJs ?>)"><i class="bi bi-calendar-date"></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><?= format_quantity($item['stock_quantity']) ?></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(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemBarcodePriceJs ?>)"><i class="bi bi-printer"></i> Print Barcode</button>
<button class="btn btn-outline-secondary" onclick="printItemBarcodeWithDates(<?= $itemSkuJs ?>, <?= $itemNameArJs ?>, <?= $itemNameEnJs ?>, <?= $itemBarcodePriceJs ?>, <?= $itemExpiryDateJs ?>)"><i class="bi bi-calendar-date"></i> Barcode + Dates</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="<?= quantity_step() ?>" name="stock_quantity" class="form-control" value="<?= format_quantity($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="<?= quantity_step() ?>" name="min_stock_level" class="form-control" value="<?= format_quantity($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><?= format_quantity($item['stock_quantity']) ?></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><?= format_quantity($item['min_stock_level']) ?></td>
<td>
<span class="badge <?= (float)$item['stock_quantity'] <= 0 ? 'bg-danger' : 'bg-warning text-dark' ?>">
<?= format_quantity($item['stock_quantity']) ?>
</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="<?= $lang === 'ar' ? 'ابحث عن المنتجات بالاسم أو رمز الصنف...' : '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="<?= $lang === 'ar' ? 'امسح الباركود...' : '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="الطلبات المعلقة"><?= $lang === '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-purchase-price="<?= (float)$p['purchase_price'] ?>" data-sku="<?= htmlspecialchars($p['sku']) ?>" data-stock-quantity="<?= format_quantity($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"><?= format_quantity($p['stock_quantity']) ?> <?= $lang === 'ar' ? 'متبقٍ' : '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><?= $lang === 'ar' ? 'السلة' : '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="<?= $lang === 'ar' ? 'شاشة العميل' : 'Customer Display' ?>"><i class="bi bi-display me-1"></i> <?= $lang === 'ar' ? 'شاشة العميل' : 'Customer Screen' ?></button>
<?php if ($active_session): ?>
<button class="btn btn-sm btn-outline-dark" data-bs-toggle="modal" data-bs-target="#closeRegisterModal" title="<?= $lang === 'ar' ? 'إغلاق الخزينة' : 'Close Register' ?>"><i class="bi bi-x-circle me-1"></i><span data-en="Close" data-ar="إغلاق"><?= $lang === 'ar' ? 'إغلاق' : 'Close' ?></span></button>
<?php endif; ?>
<button class="btn btn-sm btn-outline-warning" onclick="cart.openHeldCartsModal()" title="<?= $lang === 'ar' ? 'الطلبات المعلقة' : 'Held List' ?>"><i class="bi bi-list-task"></i></button>
<button class="btn btn-sm btn-outline-secondary" onclick="cart.hold()" title="<?= $lang === 'ar' ? 'تعليق الطلب' : 'Hold Cart' ?>"><i class="bi bi-pause-circle"></i></button>
<button class="btn btn-sm btn-outline-danger" onclick="cart.clear()" title="<?= $lang === 'ar' ? 'مسح السلة' : '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="العميل"><?= $lang === '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"><?= $lang === 'ar' ? 'برونزي' : 'Bronze' ?></span>
<span class="fw-bold ms-1 text-primary"><span id="customerPoints">0</span> <?= $lang === 'ar' ? 'نقطة' : '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"><?= $lang === 'ar' ? 'استبدال' : '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"><?= $lang === 'ar' ? 'أنفق المزيد للوصول إلى الفضي' : 'Spend more to unlock Silver' ?></div>
</div>
</div>
<?php if (($data['settings']['manual_discount_enabled'] ?? '0') === '1'): ?>
<div class="mb-3">
<label class="small fw-bold mb-1" data-en="Manual Discount" data-ar="الخصم اليدوي"><?= $lang === 'ar' ? 'الخصم اليدوي' : 'Manual Discount' ?></label>
<div class="input-group input-group-sm">
<span class="input-group-text"><?= __('currency') ?></span>
<input type="number" id="manualDiscountAmount" class="form-control" value="0.000" min="0" step="0.001" oninput="cart.onManualDiscountChange()">
</div>
<div class="smaller text-muted mt-1" data-en="Fixed amount discount applied to the cart total." data-ar="يتم تطبيق خصم ثابت على إجمالي السلة."><?= $lang === 'ar' ? 'يتم تطبيق خصم ثابت على إجمالي السلة.' : 'Fixed amount discount applied to the cart total.' ?></div>
<div class="smaller text-muted mt-1" id="manualDiscountLimitInfo"></div>
</div>
<?php endif; ?>
<div>
<label class="small fw-bold mb-1" data-en="Discount Code" data-ar="رمز الخصم"><?= $lang === 'ar' ? 'رمز الخصم' : 'Discount Code' ?></label>
<div class="input-group input-group-sm">
<input type="text" id="discountCode" class="form-control" placeholder="<?= $lang === 'ar' ? 'الرمز' : 'Code' ?>" data-en="Code" data-ar="الرمز">
<button class="btn btn-outline-primary" type="button" onclick="cart.applyDiscount()" data-en="Apply" data-ar="تطبيق"><?= $lang === 'ar' ? 'تطبيق' : '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 data-en="Cart is empty" data-ar="السلة فارغة"><?= $lang === 'ar' ? 'السلة فارغة' : '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="المجموع (بدون الضريبة)"><?= $lang === '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="الضريبة"><?= $lang === '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="الإجمالي"><?= $lang === '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()" data-en="Place Order" data-ar="إتمام الطلب">
<?= $lang === 'ar' ? 'إتمام الطلب' : 'PLACE ORDER' ?>
</button>
</div>
</div>
</div>
<script>
const posLang = document.documentElement.lang || <?= json_encode($lang, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const posIsArabic = posLang === 'ar';
const posT = (en, ar) => (posIsArabic ? ar : en);
const posTierLabel = (tier) => {
switch (String(tier || '').toLowerCase()) {
case 'gold':
return posT('Gold', 'ذهبي');
case 'silver':
return posT('Silver', 'فضي');
default:
return posT('Bronze', 'برونزي');
}
};
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);
?>,
isManualDiscountEnabled() {
return String((typeof companySettings !== 'undefined' && companySettings && companySettings.manual_discount_enabled !== undefined) ? companySettings.manual_discount_enabled : '0') === '1';
},
getManualDiscountProfitLimitPercent() {
let value = (typeof companySettings !== 'undefined' && companySettings && companySettings.manual_discount_profit_limit_percent !== undefined)
? parseFloat(companySettings.manual_discount_profit_limit_percent)
: 5;
if (!Number.isFinite(value) || value < 0) value = 5;
return Math.min(100, Math.max(0, value));
},
getManualDiscountProfitMetrics() {
const percent = this.getManualDiscountProfitLimitPercent();
let profitAmount = 0;
(this.items || []).forEach(item => {
const qty = normalizeQuantity(item.qty);
const grossUnitPrice = parseFloat(item.price) || 0;
const vatRate = (item.vatRate !== undefined && item.vatRate !== null)
? (parseFloat(item.vatRate) || 0)
: (parseFloat(item.vat_rate) || 0);
const purchasePrice = (item.purchasePrice !== undefined && item.purchasePrice !== null)
? (parseFloat(item.purchasePrice) || 0)
: (parseFloat(item.purchase_price) || 0);
const netUnitPrice = vatRate > 0 ? (grossUnitPrice / (1 + (vatRate / 100))) : grossUnitPrice;
profitAmount += (netUnitPrice - purchasePrice) * qty;
});
profitAmount = Math.max(0, profitAmount);
return {
percent,
profitAmount,
maxDiscount: Math.max(0, profitAmount * (percent / 100))
};
},
updateManualDiscountLimitInfo(limitAmount = null, metrics = null) {
const info = document.getElementById('manualDiscountLimitInfo');
if (!info || !this.isManualDiscountEnabled()) return;
const activeMetrics = metrics || this.getManualDiscountProfitMetrics();
const safeLimit = Number.isFinite(limitAmount) ? Math.max(0, limitAmount) : Math.max(0, activeMetrics.maxDiscount);
const percentLabel = activeMetrics.percent.toFixed(3).replace(/\.0+$/, '').replace(/(\.\d*[1-9])0+$/, '$1');
info.textContent = posIsArabic
? `الحد الأقصى الآن: <?= __('currency') ?> ${safeLimit.toFixed(3)} (${percentLabel}% من ربح الفاتورة <?= __('currency') ?> ${activeMetrics.profitAmount.toFixed(3)})`
: `Max allowed now: <?= __('currency') ?> ${safeLimit.toFixed(3)} (${percentLabel}% of invoice profit <?= __('currency') ?> ${activeMetrics.profitAmount.toFixed(3)})`;
},
getCodeDiscountAmount(subtotal) {
if (!this.discount) return 0;
const rawDiscount = this.discount.type === 'percentage'
? subtotal * (parseFloat(this.discount.value) / 100)
: parseFloat(this.discount.value);
const safeDiscount = Number.isFinite(rawDiscount) ? rawDiscount : 0;
return Math.min(Math.max(0, safeDiscount), Math.max(0, subtotal));
},
getManualDiscountAmount(maxDiscount = null, syncInput = false) {
if (!this.isManualDiscountEnabled()) return 0;
const input = document.getElementById('manualDiscountAmount');
let value = input ? parseFloat(input.value) : 0;
if (!Number.isFinite(value) || value < 0) value = 0;
if (Number.isFinite(maxDiscount)) {
value = Math.min(value, Math.max(0, maxDiscount));
}
if (syncInput && input) {
input.value = value.toFixed(3);
}
return value;
},
calculateTotals(syncInput = false) {
const subtotal = this.items.reduce((sum, item) => sum + ((parseFloat(item.price) || 0) * normalizeQuantity(item.qty)), 0);
const totalVat = this.items.reduce((sum, item) => {
const price = parseFloat(item.price) || 0;
const qty = normalizeQuantity(item.qty);
const vatRate = (item.vatRate !== undefined && item.vatRate !== null) ? item.vatRate : 0;
return sum + (price * qty * (vatRate / (100 + vatRate)));
}, 0);
const codeDiscount = this.getCodeDiscountAmount(subtotal);
const profitMetrics = this.getManualDiscountProfitMetrics();
const manualDiscountLimit = Math.min(Math.max(0, subtotal - codeDiscount), Math.max(0, profitMetrics.maxDiscount));
const manualDiscount = this.getManualDiscountAmount(manualDiscountLimit, syncInput);
this.updateManualDiscountLimitInfo(manualDiscountLimit, profitMetrics);
const discountAmount = Math.min(subtotal, codeDiscount + manualDiscount);
const redeemSwitch = document.getElementById('redeemLoyalty');
const redeemRate = (this.loyaltySettings && this.loyaltySettings.redeemPointsPerUnit) ? this.loyaltySettings.redeemPointsPerUnit : 100;
const availableRedeemValue = (parseFloat(this.customerPoints) || 0) / redeemRate;
const loyaltyRedeemed = (redeemSwitch && redeemSwitch.checked)
? Math.min(Math.max(0, subtotal - discountAmount), availableRedeemValue)
: 0;
const total = Math.max(0, subtotal - discountAmount - loyaltyRedeemed);
return { subtotal, totalVat, codeDiscount, manualDiscount, discountAmount, loyaltyRedeemed, total, manualDiscountLimit, profitMetrics };
},
onManualDiscountChange() {
const manualDiscountInput = document.getElementById('manualDiscountAmount');
const manualValue = manualDiscountInput ? parseFloat(manualDiscountInput.value) : 0;
if (Number.isFinite(manualValue) && manualValue > 0 && this.discount) {
this.discount = null;
const discInput = document.getElementById('discountCode');
if (discInput) discInput.value = '';
const discInfo = document.getElementById('appliedDiscountInfo');
if (discInfo) discInfo.style.display = 'none';
}
this.calculateTotals(true);
this.render();
},
broadcast() {
try {
// Ensure items is an array
if (!Array.isArray(this.items)) this.items = [];
const shouldSyncManualDiscountInput = !(document.activeElement && document.activeElement.id === 'manualDiscountAmount');
const totals = this.calculateTotals(shouldSyncManualDiscountInput);
const subtotal = totals.subtotal;
const totalVat = totals.totalVat;
const discountAmount = totals.discountAmount;
const loyaltyRedeemed = totals.loyaltyRedeemed;
const total = totals.total;
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 || posT('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 = normalizeQuantity(product.stock_quantity);
const addQty = Math.max(normalizeQuantity((product.qty !== undefined && product.qty !== null) ? product.qty : 1), 0.01);
const unitPrice = (product.price !== undefined && product.price !== null) ? (parseFloat(product.price) || 0) : (parseFloat(product.sale_price) || 0);
const purchasePrice = (product.purchasePrice !== undefined && product.purchasePrice !== null)
? (parseFloat(product.purchasePrice) || 0)
: (parseFloat(product.purchase_price) || 0);
const normalizedProduct = {...product, price: unitPrice, purchasePrice};
const existing = this.items.find(item => item.id === product.id);
if (existing) {
if (!allowZeroStock && (existing.qty + addQty) > currentStock) {
Swal.fire(posT('Error', 'خطأ'), posT('Insufficient stock!', 'المخزون غير كافٍ!'), 'error');
return;
}
existing.qty = normalizeQuantity(existing.qty + addQty);
existing.price = unitPrice;
existing.purchasePrice = purchasePrice;
} else {
if (!allowZeroStock && currentStock < addQty) {
Swal.fire(posT('Error', 'خطأ'), posT('Insufficient stock!', 'المخزون غير كافٍ!'), 'error');
return;
}
this.items.push({...normalizedProduct, qty: normalizeQuantity(addQty)});
}
this.render();
// Add visual feedback
const displayName = posIsArabic ? (product.nameAr || product.nameEn) : (product.nameEn || product.nameAr);
Swal.fire({
toast: true,
position: 'top-end',
icon: 'success',
title: (posIsArabic ? 'تمت إضافة: ' : '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 = normalizeQuantity(item.stock_quantity);
if (delta > 0 && !allowZeroStock && (item.qty + delta) > currentStock) {
Swal.fire(posT('Error', 'خطأ'), posT('Insufficient stock!', 'المخزون غير كافٍ!'), 'error');
return;
}
item.qty = normalizeQuantity(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 manualDiscountInput = document.getElementById('manualDiscountAmount');
if (manualDiscountInput) manualDiscountInput.value = '0.000';
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 = posTierLabel(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 = posIsArabic ? `أنفق ${(500 - spent).toFixed(3)} <?= __('currency') ?> للوصول إلى الفضي (1.2x نقطة)` : `Spend ${(500 - spent).toFixed(3)} OMR more for Silver (1.2x points)`;
} else if (this.customerTier === 'silver') {
progress = ((spent - 500) / 1000) * 100;
nextTierInfo.innerText = posIsArabic ? `أنفق ${(1500 - spent).toFixed(3)} <?= __('currency') ?> للوصول إلى الذهبي (1.5x نقطة)` : `Spend ${(1500 - spent).toFixed(3)} OMR more for Gold (1.5x points)`;
} else {
progress = 100;
nextTierInfo.innerText = posT('You are a Gold member! (1.5x points)', 'أنت عضو ذهبي! (1.5x نقطة)');
}
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 manualDiscountInput = document.getElementById('manualDiscountAmount');
if (manualDiscountInput) manualDiscountInput.value = '0.000';
const info = document.getElementById('appliedDiscountInfo');
info.innerText = `${posT('Applied', 'تم تطبيق')}: ${this.discount.code} (${this.discount.type === 'percentage' ? this.discount.value + '%' : '<?= __('currency') ?> ' + parseFloat(this.discount.value).toFixed(3)})`;
info.style.display = 'block';
this.render();
} else {
Swal.fire(posT('Error', 'خطأ'), res.error, 'error');
}
} catch (err) { console.error(err); }
},
async hold() {
if (this.items.length === 0) return;
const { value: name } = await Swal.fire({
title: posT('Hold Cart', 'تعليق الطلب'),
input: 'text',
inputLabel: posT('Enter a name for this cart', 'أدخل اسمًا لهذا الطلب'),
inputValue: (posIsArabic ? 'طلب ' : 'Cart ') + new Date().toLocaleTimeString(),
showCancelButton: true,
confirmButtonText: posT('Save', 'حفظ'),
cancelButtonText: posT('Cancel', 'إلغاء')
});
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(posT('Held', 'تم التعليق'), posT('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(posT('Invalid server response', 'استجابة غير صالحة من الخادم'));
}
const lang = document.documentElement.lang || 'en';
const escapeHtml = (value) => String(value ?? '').replace(/[&<>"']/g, (char) => ({
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}[char] || char));
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 => {
const cartId = Number.parseInt(c.id, 10);
if (!Number.isFinite(cartId) || cartId <= 0) return;
const cartName = escapeHtml(c.cart_name || (lang === 'ar' ? 'طلب غير مسمى' : 'Untitled Cart'));
const customerName = escapeHtml(c.customer_name || (lang === 'ar' ? 'عميل عابر' : 'Walk-in'));
const createdAtDate = c.created_at ? new Date(c.created_at) : null;
const createdAtText = createdAtDate && !Number.isNaN(createdAtDate.getTime())
? createdAtDate.toLocaleString()
: (c.created_at || '');
html += `
<div class="list-group-item d-flex justify-content-between align-items-center gap-3 p-3 hover-bg-light border-start-0 border-end-0 held-cart-entry" role="button" tabindex="0" data-held-action="resume" data-held-cart-id="${cartId}" aria-label="${lang === 'ar' ? 'استرجاع الطلب المعلق' : 'Resume held cart'}" style="cursor:pointer;">
<div class="text-start flex-grow-1 pe-2" data-held-action="resume" data-held-cart-id="${cartId}">
<div class="fw-bold text-primary">${cartName}</div>
<div class="small text-muted">
<i class="bi bi-person me-1"></i>${customerName}
<span class="mx-2 text-silver">|</span>
<i class="bi bi-clock me-1"></i>${escapeHtml(createdAtText)}
</div>
</div>
<div class="btn-group flex-shrink-0" role="group">
<button type="button" class="btn btn-sm btn-primary" data-held-action="resume" data-held-cart-id="${cartId}">
<i class="bi bi-arrow-repeat me-1"></i><span data-en="Resume" data-ar="استرجاع">${lang === 'ar' ? 'استرجاع' : 'Resume'}</span>
</button>
<button type="button" class="btn btn-sm btn-outline-danger" data-held-action="delete" data-held-cart-id="${cartId}" aria-label="${lang === 'ar' ? 'حذف الطلب المعلق' : 'Delete held cart'}">
<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'
},
didOpen: (popup) => {
const triggerHeldCartAction = (action, id) => {
if (!Number.isFinite(id) || id <= 0) return;
if (action === 'delete') {
this.deleteHeld(id);
return;
}
this.resume(id);
};
popup.querySelectorAll('.held-cart-entry').forEach((row) => {
row.addEventListener('keydown', (event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
const id = Number.parseInt(row.getAttribute('data-held-cart-id') || '', 10);
triggerHeldCartAction('resume', id);
}
});
});
popup.addEventListener('click', (event) => {
const actionTarget = event.target.closest('[data-held-action]');
if (!actionTarget) return;
event.preventDefault();
event.stopPropagation();
const action = actionTarget.getAttribute('data-held-action');
const id = Number.parseInt(actionTarget.getAttribute('data-held-cart-id') || '', 10);
triggerHeldCartAction(action, id);
});
}
});
} catch (err) {
console.error(err);
Swal.fire(posT('Error', 'خطأ'), posT('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) || []).map(item => ({...item, qty: normalizeQuantity(item.qty)}));
document.getElementById('posCustomer').value = c.customer_id || '';
await this.onCustomerChange();
await this.deleteHeld(id, true);
Swal.close();
}
} catch (err) {
console.error(err);
Swal.fire(posT('Error', 'خطأ'), posT('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 = normalizeQuantity(item.qty);
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 || posT('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">${posT('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">${formatQuantity(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">${posT('VAT', 'الضريبة')}: ${itemVat.toFixed(2)}</div>
</div>
</div>
`;
}).join('');
const shouldSyncManualDiscountInput = !(document.activeElement && document.activeElement.id === 'manualDiscountAmount');
const totals = this.calculateTotals(shouldSyncManualDiscountInput);
const discountAmount = totals.discountAmount;
const loyaltyRedeemedValue = totals.loyaltyRedeemed;
const total = totals.total;
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">- ${posT('Discount', 'الخصم')}: <?= __('currency') ?> ${discountAmount.toFixed(3)}</div>`;
if (loyaltyRedeemedValue > 0) totalHtml += `<div class="smaller text-success">- ${posT('Loyalty', 'الولاء')}: <?= __('currency') ?> ${loyaltyRedeemedValue.toFixed(3)}</div>`;
const customerId = document.getElementById('posCustomer') ? document.getElementById('posCustomer').value : '';
if (customerId) {
totalHtml += `<div class="smaller text-info">+ ${posT('Earn', 'تكسب')}: ${pointsToEarn} ${posT('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 totals = this.calculateTotals(false);
const total = totals.total;
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() {
return this.calculateTotals(false).total;
},
getRemaining() {
const total = this.getGrandTotal();
const paid = this.payments.reduce((sum, p) => sum + p.amount, 0);
return total - paid;
},
setCompleteOrderButtonsDisabled(disabled) {
document.querySelectorAll('.pos-complete-order-btn').forEach(button => {
button.disabled = !!disabled;
});
},
renderPayments() {
const container = document.getElementById('paymentList');
const methodLabels = {
'cash': <?= json_encode($lang === 'ar' ? 'نقدًا' : 'Cash', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
'card': <?= json_encode($lang === 'ar' ? 'بطاقة ائتمان' : 'Credit Card', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
'credit': <?= json_encode($lang === 'ar' ? 'آجل' : 'Credit', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
'transfer': <?= json_encode($lang === 'ar' ? 'تحويل بنكي' : 'Bank Transfer', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
};
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');
this.setCompleteOrderButtonsDisabled(false);
} else {
display.classList.remove('text-success');
display.classList.add('text-danger');
this.setCompleteOrderButtonsDisabled(true);
}
},
async completeOrder(autoPrint = true) {
if (this.items.length === 0) {
Swal.fire(<?= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, <?= json_encode($lang === 'ar' ? 'السلة فارغة' : 'Cart is empty', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, '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(<?= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, <?= json_encode($lang === 'ar' ? 'الدفع غير مكتمل' : 'Payment is incomplete', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
return;
}
const customerId = document.getElementById('posCustomer').value;
if (this.payments.some(p => p.method === 'credit') && !customerId) {
Swal.fire(<?= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, <?= json_encode($lang === 'ar' ? 'الدفع الآجل مسموح للعملاء المسجلين فقط' : 'Credit payment is only allowed for registered customers', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
return;
}
const activeBtn = document.getElementById(autoPrint ? 'confirmPaymentPrintBtn' : 'confirmPaymentSaveBtn');
const actionButtons = Array.from(document.querySelectorAll('.pos-complete-order-btn'));
const originalButtonHtml = actionButtons.reduce((carry, button) => {
carry[button.id] = button.innerHTML;
return carry;
}, {});
actionButtons.forEach(button => {
button.disabled = true;
});
if (activeBtn) {
activeBtn.innerText = <?= json_encode($lang === 'ar' ? 'جارٍ المعالجة...' : 'PROCESSING...', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
}
const restoreActionButtons = () => {
actionButtons.forEach(button => {
if (Object.prototype.hasOwnProperty.call(originalButtonHtml, button.id)) {
button.innerHTML = originalButtonHtml[button.id];
}
button.disabled = false;
});
};
const savedTitle = <?= json_encode($lang === 'ar' ? 'تم الحفظ' : 'Saved', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const savedWithoutPrintText = <?= json_encode($lang === 'ar' ? 'تم حفظ الفاتورة بدون طباعة' : 'Invoice saved without printing', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const savedWithoutPrintPrefix = <?= json_encode($lang === 'ar' ? 'تم حفظ الفاتورة بدون طباعة. الرقم:' : 'Invoice saved without printing. No:', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const okText = <?= json_encode($lang === 'ar' ? 'حسنًا' : 'OK', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
const totals = this.calculateTotals(false);
const subtotal = totals.subtotal;
const totalVat = totals.totalVat;
const discountAmount = totals.discountAmount;
const manualDiscountAmount = totals.manualDiscount;
const loyaltyRedeemed = totals.loyaltyRedeemed;
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('manual_discount_amount', manualDiscountAmount);
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(<?= json_encode($lang === 'ar' ? 'أعاد الخادم استجابة غير صالحة' : 'Server returned an invalid response', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>);
}
if (result.success) {
const payModalEl = document.getElementById('posPaymentModal');
const payModal = payModalEl ? bootstrap.Modal.getInstance(payModalEl) : null;
if (payModal) payModal.hide();
if (autoPrint) {
this.showReceipt(result.invoice_id, discountAmount, loyaltyRedeemed, result.transaction_no, true);
} else {
this.clear();
Swal.fire({
icon: 'success',
title: savedTitle,
text: result.transaction_no ? `${savedWithoutPrintPrefix} ${result.transaction_no}` : savedWithoutPrintText,
confirmButtonText: okText
}).then(() => {
location.reload();
});
}
} else {
Swal.fire(<?= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, result.error, 'error');
restoreActionButtons();
}
} catch (err) {
console.error(err);
Swal.fire(<?= json_encode($lang === 'ar' ? 'خطأ' : 'Error', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, err.message || <?= json_encode($lang === 'ar' ? 'حدث خطأ ما' : 'Something went wrong', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>, 'error');
restoreActionButtons();
}
},
showReceipt(invId, discountAmount, loyaltyRedeemed, transactionNo, autoPrint = false) {
const container = document.getElementById('posReceiptContent');
const customerSelect = document.getElementById('posCustomer');
const customerName = (customerSelect && customerSelect.selectedIndex >= 0 && customerSelect.value !== '') ? customerSelect.options[customerSelect.selectedIndex].text : <?= json_encode(__('walk_in_customer'), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>;
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>${formatQuantity(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 receiptModalEl = document.getElementById('posReceiptModal');
const modal = bootstrap.Modal.getOrCreateInstance(receiptModalEl);
modal.show();
this.clear();
receiptModalEl.addEventListener('hidden.bs.modal', function () {
location.reload();
}, { once: true });
if (autoPrint) {
setTimeout(() => {
if (typeof window.printPosReceipt === 'function') {
window.printPosReceipt();
}
}, 250);
}
},
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),
purchasePrice: parseFloat(card.dataset.purchasePrice) || 0,
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: <?= json_encode($lang === 'ar' ? 'اختر العميل' : 'Select Customer', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>
});
$('#paymentCreditCustomer').select2({
width: '100%',
placeholder: <?= json_encode($lang === 'ar' ? 'اختر العميل' : 'Select Customer', JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
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" data-en="Open Cash Register" data-ar="فتح الخزينة"><?= $lang === 'ar' ? 'فتح الخزينة' : '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" data-en="Select Register" data-ar="اختر الخزينة"><?= $lang === 'ar' ? 'اختر الخزينة' : '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" data-en="Opening Balance" data-ar="الرصيد الافتتاحي"><?= $lang === 'ar' ? 'الرصيد الافتتاحي' : '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="<?= htmlspecialchars(page_url('dashboard')) ?>" class="btn btn-secondary" data-en="Cancel & Go to Dashboard" data-ar="إلغاء والعودة إلى لوحة التحكم"><?= $lang === 'ar' ? 'إلغاء والعودة إلى لوحة التحكم' : 'Cancel & Go to Dashboard' ?></a>
<button type="submit" class="btn btn-primary" data-en="Open Session" data-ar="فتح الجلسة"><?= $lang === 'ar' ? 'فتح الجلسة' : '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" data-en="Close Cash Register" data-ar="إغلاق الخزينة"><?= $lang === 'ar' ? 'إغلاق الخزينة' : '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" data-en="Cash in Hand (Counted)" data-ar="النقد في الخزينة (المعدود)"><?= $lang === 'ar' ? 'النقد في الخزينة (المعدود)' : '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" data-en="Notes" data-ar="ملاحظات"><?= $lang === 'ar' ? 'ملاحظات' : 'Notes' ?></label>
<textarea name="notes" class="form-control" rows="3" placeholder="<?= htmlspecialchars($lang === 'ar' ? 'أي فروقات أو ملاحظات...' : 'Any discrepancies or notes...', ENT_QUOTES) ?>"></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" data-en="Close Session" data-ar="إغلاق الجلسة"><?= $lang === 'ar' ? 'إغلاق الجلسة' : '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" action="<?= htmlspecialchars(page_url('quotations')) ?>" class="documents-filter">
<div class="documents-filter__field documents-filter__field--search">
<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="documents-filter__field documents-filter__field--party">
<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="documents-filter__field documents-filter__field--date">
<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="documents-filter__field documents-filter__field--date">
<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="documents-filter__actions">
<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>
<button type="submit" class="btn btn-primary btn-sm">
<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 dropdown-menu-end">
<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="<?= htmlspecialchars(page_url('quotations')) ?>" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</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-id="<?= $q['id'] ?>" 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; ?>
<form method="POST" class="d-inline js-swal-confirm-form" data-confirm-title="Delete this quotation?" data-confirm-text="This quotation will be permanently removed." data-confirm-button="Yes, delete it" data-cancel-button="Keep it">
<input type="hidden" name="delete_quotation" value="1">
<input type="hidden" name="id" value="<?= $q['id'] ?>">
<button type="submit" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</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" action="<?= htmlspecialchars(page_url('lpos')) ?>" class="documents-filter">
<div class="documents-filter__field documents-filter__field--search">
<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="documents-filter__field documents-filter__field--party">
<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="documents-filter__field documents-filter__field--date">
<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="documents-filter__field documents-filter__field--date">
<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="documents-filter__actions">
<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>
<button type="submit" class="btn btn-primary btn-sm">
<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 dropdown-menu-end">
<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="<?= htmlspecialchars(page_url('lpos')) ?>" class="btn btn-outline-secondary btn-sm">
<i class="bi bi-x-circle"></i> <span data-en="Clear" data-ar="مسح">Clear</span>
</a>
</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-id="<?= $q['id'] ?>" 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; ?>
<form method="POST" class="d-inline js-swal-confirm-form" data-confirm-title="Delete this LPO?" data-confirm-text="This LPO will be permanently removed." data-confirm-button="Yes, delete it" data-cancel-button="Keep it">
<input type="hidden" name="delete_lpo" value="1">
<input type="hidden" name="id" value="<?= $q['id'] ?>">
<button type="submit" class="btn btn-outline-danger" title="Delete"><i class="bi bi-trash"></i></button>
</form>
</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 runtime_debug_require('pages/sales_purchases_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?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 runtime_debug_require('pages/accounting_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?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="<?= htmlspecialchars(page_url('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'): runtime_debug_require('pages/copy_outlet_data_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?php elseif ($page === 'settings'): runtime_debug_require('pages/settings_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?php elseif ($page === 'role_groups'): runtime_debug_require('pages/role_groups_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?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 runtime_debug_require('pages/users_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?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="<?= htmlspecialchars(page_url('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="<?= htmlspecialchars(page_url('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 runtime_debug_require('pages/register_session_report_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<!-- 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'): ?>
<?php runtime_debug_require('pages/logs_view.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?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="<?= quantity_step() ?>" name="stock_quantity" class="form-control" value="0.00"></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="<?= quantity_step() ?>" name="min_stock_level" class="form-control" value="0.00"></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>
window.quantityPrecision = 2;
window.quantityStep = '0.01';
window.quantityFieldSelector = 'input[name="stock_quantity"], input[name="min_stock_level"], input[name="quantities[]"], input.item-qty';
window.normalizeQuantity = window.normalizeQuantity || function(value) {
const parsed = Number.parseFloat(value);
if (!Number.isFinite(parsed)) {
return 0;
}
return Number(parsed.toFixed(window.quantityPrecision));
};
window.formatQuantity = window.formatQuantity || function(value) {
return window.normalizeQuantity(value).toFixed(window.quantityPrecision);
};
window.trimQuantityInput = window.trimQuantityInput || function(input) {
if (!input) return '';
let value = String(input.value ?? '').replace(/[^0-9.]/g, '');
if (value === '') {
input.value = '';
return '';
}
const firstDot = value.indexOf('.');
if (firstDot !== -1) {
value = value.slice(0, firstDot + 1) + value.slice(firstDot + 1).replace(/\./g, '');
}
const parts = value.split('.');
if (parts.length === 2) {
value = parts[0] + '.' + parts[1].slice(0, window.quantityPrecision);
}
input.value = value;
return value;
};
window.isQuantityInput = window.isQuantityInput || function(input) {
return !!(input && typeof input.matches === 'function' && input.matches(window.quantityFieldSelector));
};
window.syncQuantityInputs = window.syncQuantityInputs || function(root = document) {
const scope = root && typeof root.querySelectorAll === 'function' ? root : document;
scope.querySelectorAll(window.quantityFieldSelector).forEach((input) => {
input.step = window.quantityStep;
input.setAttribute('inputmode', 'decimal');
});
};
document.addEventListener('DOMContentLoaded', function() {
console.log("DOM Content Loaded - Accounting System");
try {
if (typeof window.syncQuantityInputs === 'function') {
window.syncQuantityInputs();
}
if (!window.quantityInputListenersBound) {
document.addEventListener('input', function(event) {
if (window.isQuantityInput && window.isQuantityInput(event.target)) {
window.trimQuantityInput(event.target);
}
});
document.addEventListener('blur', function(event) {
if (window.isQuantityInput && window.isQuantityInput(event.target) && event.target.value !== '') {
event.target.value = window.formatQuantity(event.target.value);
}
}, true);
window.quantityInputListenersBound = true;
}
// 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';
}
});
});
// 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 if (in_array($page, ['sales', 'purchases', 'lpos', 'quotations'], true)): ?>
<?php runtime_debug_require('pages/sales_purchases_invoice_form_helpers.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<?php endif; ?>
<?php if ($page === 'lpos' || $page === 'quotations'): ?>
<?php runtime_debug_require('pages/lpo_quotation_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<?php endif; ?>
<?php if ($page === 'sales' || $page === 'purchases'): ?>
<?php runtime_debug_require('pages/sales_purchases_page_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<?php endif; ?>
} catch (e) { console.error("JS Error in DOMContentLoaded:", e); }
});
</script>
<?php if ($page === 'sales' || $page === 'purchases'): ?>
<?php runtime_debug_require('pages/sales_purchases_modals.php', ['phase' => 'view', 'page' => (string)$page]); ?>
<?php endif; ?>
<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; gap: 0.5rem; flex-wrap: wrap; }
#posPaymentModal .modal-footer .btn-light { min-width: 110px; }
#posPaymentModal .modal-footer .pos-complete-order-btn { flex: 1 1 180px; }
</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" data-en="Payment" data-ar="الدفع"><?= $lang === 'ar' ? 'الدفع' : 'Payment' ?></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="<?= $lang === 'ar' ? 'إغلاق' : '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="العميل"><?= $lang === '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" data-en="Select Credit Customer" data-ar="اختر عميل الآجل"><?= $lang === 'ar' ? 'اختر عميل الآجل' : 'Select Credit Customer' ?></label>
<select id="paymentCreditCustomer" class="form-select form-select-sm select2" onchange="cart.syncCustomer(this.value)">
<option value=""><?= $lang === 'ar' ? '--- اختر العميل ---' : '--- 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" data-en="Amount Due" data-ar="المبلغ المستحق"><?= $lang === 'ar' ? 'المبلغ المستحق' : 'Amount Due' ?></div>
<div class="value" id="paymentAmountDue">0.000</div>
</div>
<div class="text-end">
<div class="label text-danger" data-en="Remaining" data-ar="المتبقي"><?= $lang === 'ar' ? 'المتبقي' : '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" data-en="Add Payment Method" data-ar="إضافة طريقة دفع"><?= $lang === 'ar' ? 'إضافة طريقة دفع' : '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" data-en="Cash" data-ar="نقدًا"><?= $lang === 'ar' ? 'نقدًا' : '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" data-en="Credit Card" data-ar="بطاقة ائتمان"><?= $lang === 'ar' ? 'بطاقة ائتمان' : '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" data-en="Credit" data-ar="آجل"><?= $lang === 'ar' ? 'آجل' : '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" data-en="Bank Transfer" data-ar="تحويل بنكي"><?= $lang === 'ar' ? 'تحويل بنكي' : '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="المبلغ"><?= $lang === '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()" data-en="Add" data-ar="إضافة">
<i class="bi bi-plus-lg"></i><?= $lang === '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" data-en="Total Tendered (Cash)" data-ar="إجمالي المقبوض نقدًا"><?= $lang === 'ar' ? 'إجمالي المقبوض نقدًا' : '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" data-en="* Change is calculated based on cash payments only." data-ar="* يتم احتساب الباقي بناءً على الدفعات النقدية فقط."><?= $lang === 'ar' ? '* يتم احتساب الباقي بناءً على الدفعات النقدية فقط.' : '* 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="إلغاء"><?= $lang === 'ar' ? 'إلغاء' : 'Cancel' ?></button>
<button type="button" class="btn btn-outline-primary pos-complete-order-btn" id="confirmPaymentSaveBtn" onclick="cart.completeOrder(false)" data-en="Save Without Print" data-ar="حفظ بدون طباعة">
<?= $lang === 'ar' ? 'حفظ بدون طباعة' : 'SAVE WITHOUT PRINT' ?>
</button>
<button type="button" class="btn btn-primary pos-complete-order-btn" id="confirmPaymentPrintBtn" onclick="cart.completeOrder(true)" data-en="Save & Print" data-ar="حفظ وطباعة">
<?= $lang === 'ar' ? 'حفظ وطباعة' : 'SAVE & PRINT' ?>
</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="<?= $lang === 'ar' ? 'إغلاق' : '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()" data-en="Print Receipt" data-ar="طباعة الإيصال">
<i class="bi bi-printer me-2"></i><?= $lang === 'ar' ? 'طباعة الإيصال' : '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; max-width: 100%;">
<div id="barcodeLabelName" class="fw-bold small mb-1"></div>
<svg id="barcodeSvg" style="max-width: 100%; height: auto;"></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-2 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="50" 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="30" min="10">
</div>
</div>
<div class="form-text text-center mb-1">For best scanner reliability, start at 50 × 30 mm or larger. Printed price includes VAT.</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>
<!-- Barcode + Dates Print Modal -->
<div class="modal fade" id="datedBarcodePrintModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content border-0 shadow">
<div class="modal-header">
<h5 class="modal-title">Print Barcode + Dates Label</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body text-center">
<div id="datedBarcodeContainer" class="p-3 bg-white border mb-3 mx-auto" style="width: fit-content; max-width: 100%;">
<div id="datedBarcodeLabelName" class="fw-bold small mb-1"></div>
<svg id="datedBarcodeSvg" style="max-width: 100%; height: auto;"></svg>
<div id="datedBarcodeLabelDates" class="small mt-2 text-center mx-auto" style="width: 100%; max-width: 220px; direction: ltr;"></div>
<div id="datedBarcodeLabelPrice" class="fw-bold small mt-2" style="display: none;"></div>
</div>
<div class="row g-2 mb-3 text-start">
<div class="col-6">
<label class="form-label small" data-en="Production Date" data-ar="تاريخ الإنتاج">Production Date</label>
<input type="date" id="datedBarcodeProductionDate" class="form-control form-control-sm">
</div>
<div class="col-6">
<label class="form-label small" data-en="Expiry Date" data-ar="تاريخ الانتهاء">Expiry Date</label>
<input type="date" id="datedBarcodeExpiryDate" class="form-control form-control-sm">
</div>
</div>
<div class="mb-3">
<label class="form-label small">Number of Labels</label>
<input type="number" id="datedBarcodeQty" class="form-control form-control-sm mx-auto" value="1" min="1" style="width: 80px;">
</div>
<div class="row mb-2 mx-auto" style="max-width: 200px;">
<div class="col-6">
<label class="form-label small">Width (mm)</label>
<input type="number" id="datedBarcodeWidth" class="form-control form-control-sm" value="50" min="10">
</div>
<div class="col-6">
<label class="form-label small">Height (mm)</label>
<input type="number" id="datedBarcodeHeight" class="form-control form-control-sm" value="35" min="10">
</div>
</div>
<div class="form-text text-center mb-1">For barcode labels with P / E dates, 50 × 35 mm or larger is recommended. Printed price includes VAT.</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="executeDatedBarcodePrint()"><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 runtime_debug_require('pages/avery_label_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<script>
<?php runtime_debug_require('pages/barcode_pos_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
<?php runtime_debug_require('pages/language_dashboard_script.php', ['phase' => 'script', 'page' => (string)$page]); ?>
</script>
</body>
</html>