diff --git a/api/place_order.php b/api/place_order.php
index d12316f..7a56c3d 100644
--- a/api/place_order.php
+++ b/api/place_order.php
@@ -23,7 +23,8 @@ if ($name === '' || $phone === '' || $address === '') {
}
$items = $input['items'];
-$total = 0;
+$subtotal = 0;
+$totalVat = 0;
// Recalculate total for security
$db = db();
@@ -32,19 +33,28 @@ foreach ($items as $id => $item) {
$qty = (int)$item['qty'];
if ($qty <= 0) continue;
- // get price from DB
- $stmt = $db->prepare("SELECT sku, name, price FROM items WHERE id = ?");
+ // get price and vat from DB
+ $stmt = $db->prepare("SELECT sku, name, price, vat FROM items WHERE id = ?");
$stmt->execute([$id]);
$dbItem = $stmt->fetch();
if ($dbItem) {
$price = (float)$dbItem['price'];
- $total += ($price * $qty);
+ $vatPercent = (float)($dbItem['vat'] ?? 0);
+ $lineTotal = $price * $qty;
+ $itemVat = $lineTotal * ($vatPercent / 100);
+
+ $subtotal += $lineTotal;
+ $totalVat += $itemVat;
+
$processedItems[] = [
'id' => $id,
'sku' => $dbItem['sku'],
'name' => $dbItem['name'],
'price' => $price,
- 'qty' => $qty
+ 'vat' => $vatPercent,
+ 'vat_amount' => $itemVat,
+ 'qty' => $qty,
+ 'line_total' => $lineTotal
];
}
}
@@ -54,24 +64,29 @@ if (empty($processedItems)) {
exit;
}
+$totalAmount = $subtotal + $totalVat;
+
try {
- $stmt = $db->prepare("INSERT INTO online_orders (customer_name, customer_phone, customer_address, items_json, total_amount) VALUES (?, ?, ?, ?, ?)");
+ $stmt = $db->prepare("INSERT INTO online_orders (customer_name, customer_phone, customer_address, items_json, subtotal, vat_amount, total_amount) VALUES (?, ?, ?, ?, ?, ?, ?)");
$stmt->execute([
$name,
$phone,
$address,
json_encode($processedItems, JSON_UNESCAPED_UNICODE),
- $total
+ $subtotal,
+ $totalVat,
+ $totalAmount
]);
// Optional: send telegram notification if configured
try {
- // require_once __DIR__ . '/telegram_webhook.php'; // wait, it might not be a function but a script. Let's just do simple notification
$orderId = $db->lastInsertId();
$msg = "🛒 *New Online Order #{$orderId}*\n\n";
$msg .= "👤 {$name}\n📞 {$phone}\n📍 {$address}\n\n";
- $msg .= "💰 Total: " . currency($total) . "\n";
- // To send, we'd need to call telegram api directly if token is set.
+ $msg .= "💰 Subtotal: " . currency($subtotal) . "\n";
+ $msg .= "🧾 VAT: " . currency($totalVat) . "\n";
+ $msg .= "💵 Total: " . currency($totalAmount) . "\n";
+
$botToken = getenv('TELEGRAM_BOT_TOKEN') ?: get_setting('telegram_bot_token');
$chatId = getenv('TELEGRAM_CHAT_ID') ?: get_setting('telegram_chat_id');
if ($botToken && $chatId) {
@@ -93,5 +108,5 @@ try {
echo json_encode(['success' => true]);
} catch (Exception $e) {
- echo json_encode(['success' => false, 'error' => 'Database error']);
-}
+ echo json_encode(['success' => false, 'error' => 'Database error: ' . $e->getMessage()]);
+}
\ No newline at end of file
diff --git a/api/settings.php b/api/settings.php
index 0559f09..9cd53bf 100644
--- a/api/settings.php
+++ b/api/settings.php
@@ -13,7 +13,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$pdo = db();
$keys = [
'company_name_ar', 'company_name_en', 'vat_percentage',
- 'company_vat_number', 'company_phone', 'company_email', 'company_address'
+ 'company_vat_number', 'company_phone', 'company_email', 'company_address',
+ 'smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_secure', 'mail_from', 'mail_from_name'
];
$stmt = $pdo->prepare("INSERT INTO settings (setting_key, setting_value) VALUES (?, ?) ON DUPLICATE KEY UPDATE setting_value = VALUES(setting_value)");
diff --git a/edit_online_order.php b/edit_online_order.php
new file mode 100644
index 0000000..b8c9aa1
--- /dev/null
+++ b/edit_online_order.php
@@ -0,0 +1,571 @@
+ 0) {
+ $stmt = db()->prepare('SELECT * FROM online_orders WHERE id = :id');
+ $stmt->execute([':id' => $editOrderId]);
+ $editOrder = $stmt->fetch();
+}
+if (!$editOrder) {
+ die(tr('الطلب غير موجود.', 'Order not found.'));
+}
+
+$pageTitle = tr('تعديل طلب', 'Edit Order') . ' #' . $editOrderId;
+$activeNav = 'online_orders';
+$error = '';
+$catalog = catalog();
+
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $customerName = trim((string) ($_POST['customer_name'] ?? ''));
+ $customerPhone = trim((string) ($_POST['customer_phone'] ?? ''));
+ $customerAddress = trim((string) ($_POST['customer_address'] ?? ''));
+ $saleStatus = trim((string) ($_POST['sale_status'] ?? 'pending'));
+ $cartJson = (string) ($_POST['cart_json'] ?? '[]');
+ $items = json_decode($cartJson, true);
+
+ if ($customerName === '' || $customerPhone === '' || $customerAddress === '') {
+ $error = tr('الرجاء تعبئة بيانات العميل الأساسية.', 'Please fill the main customer details.');
+ } elseif (!is_array($items) || $items === []) {
+ $error = tr('أضف صنفاً واحداً على الأقل إلى الطلب.', 'Add at least one item to the order.');
+ } else {
+ $normalized = [];
+ $subtotal = 0.0;
+ $totalVat = 0.0;
+ foreach ($items as $item) {
+ $sku = (string) ($item['sku'] ?? '');
+ $qty = (int) ($item['qty'] ?? 0);
+ if (!isset($catalog[$sku]) || $qty < 1) {
+ continue; // if sku doesn't exist in catalog or qty invalid
+ }
+ $product = $catalog[$sku];
+ $price = (float) $product['price'];
+ $lineTotal = $price * $qty;
+
+ $vatPercent = (float) ($product['vat'] ?? 0);
+ $itemVat = $lineTotal * ($vatPercent / 100);
+ $totalVat += $itemVat;
+
+ $normalized[] = [
+ 'id' => $product['id'] ?? 0,
+ 'sku' => $sku,
+ 'name' => current_lang() === 'ar' ? $product['name_ar'] : $product['name_en'],
+ 'name_ar' => $product['name_ar'],
+ 'name_en' => $product['name_en'],
+ 'qty' => $qty,
+ 'price' => $price,
+ 'line_total' => $lineTotal,
+ 'vat_percent' => $vatPercent,
+ 'vat_amount' => $itemVat
+ ];
+ $subtotal += $lineTotal;
+ }
+
+ if ($normalized === []) {
+ $error = tr('الطلب غير صالح بعد التحقق من الأصناف.', 'The order is invalid after product validation.');
+ } else {
+ $stmt = db()->prepare('UPDATE online_orders SET
+ customer_name = :customer_name,
+ customer_phone = :customer_phone,
+ customer_address = :customer_address,
+ items_json = :items_json,
+ subtotal = :subtotal,
+ vat_amount = :vat_amount,
+ total_amount = :total_amount,
+ status = :status
+ WHERE id = :id');
+ $stmt->execute([
+ ':customer_name' => $customerName,
+ ':customer_phone' => $customerPhone,
+ ':customer_address' => $customerAddress,
+ ':items_json' => json_encode($normalized, JSON_UNESCAPED_UNICODE),
+ ':subtotal' => $subtotal,
+ ':vat_amount' => $totalVat,
+ ':total_amount' => $subtotal + $totalVat,
+ ':status' => $saleStatus,
+ ':id' => $editOrderId,
+ ]);
+
+ set_flash('success', tr('تم تحديث الطلب بنجاح.', 'Order updated successfully.'));
+ redirect_to('online_orders.php');
+ }
+ }
+}
+
+require __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
+
+
+
= h($error) ?>
+
+
+
+
+
+
+
+
+
diff --git a/expense_categories.php b/expense_categories.php
new file mode 100644
index 0000000..8097ba9
--- /dev/null
+++ b/expense_categories.php
@@ -0,0 +1,222 @@
+prepare('INSERT INTO expense_categories (name_ar, name_en) VALUES (?, ?)');
+ $stmt->execute([$_POST['name_ar'], $_POST['name_en']]);
+ set_flash('success', tr('تمت إضافة التصنيف بنجاح', 'Category added successfully'));
+ redirect_to('expense_categories.php');
+ } elseif ($action === 'edit') {
+ $stmt = $pdo->prepare('UPDATE expense_categories SET name_ar = ?, name_en = ? WHERE id = ?');
+ $stmt->execute([$_POST['name_ar'], $_POST['name_en'], $_POST['id']]);
+ set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
+ redirect_to('expense_categories.php');
+ } elseif ($action === 'delete') {
+ // Check if there are expenses linked
+ $checkStmt = $pdo->prepare('SELECT COUNT(*) FROM expenses WHERE category_id = ?');
+ $checkStmt->execute([$_POST['id']]);
+ if ($checkStmt->fetchColumn() > 0) {
+ set_flash('danger', tr('لا يمكن حذف التصنيف لأنه مرتبط بمصروفات.', 'Cannot delete category because it is linked to expenses.'));
+ } else {
+ $stmt = $pdo->prepare('DELETE FROM expense_categories WHERE id = ?');
+ $stmt->execute([$_POST['id']]);
+ set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
+ }
+ redirect_to('expense_categories.php');
+ }
+}
+
+// Pagination & Search
+$page = max(1, (int)($_GET['p'] ?? 1));
+$limit = 10;
+$offset = ($page - 1) * $limit;
+$search = $_GET['q'] ?? '';
+
+$where = '1=1';
+$params = [];
+if ($search) {
+ $where .= ' AND (name_ar LIKE ? OR name_en LIKE ?)';
+ $params[] = "%$search%";
+ $params[] = "%$search%";
+}
+
+$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM expense_categories WHERE $where");
+$totalStmt->execute($params);
+$total = $totalStmt->fetchColumn();
+$totalPages = ceil($total / $limit);
+
+$queryStmt = $pdo->prepare("SELECT * FROM expense_categories WHERE $where ORDER BY id DESC LIMIT $limit OFFSET $offset");
+$queryStmt->execute($params);
+$items = $queryStmt->fetchAll();
+
+require __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
= h($pageTitle) ?>
+
= h(tr('إدارة تصنيفات المصروفات', 'Manage expense categories')) ?>
+
+
+ = h(tr('إضافة تصنيف', 'Add Category')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ID
+ = h(tr('الاسم (عربي)', 'Name (AR)')) ?>
+ = h(tr('الاسم (إنجليزي)', 'Name (EN)')) ?>
+ = h(tr('إجراءات', 'Actions')) ?>
+
+
+
+
+ = h(tr('لا توجد بيانات', 'No data found')) ?>
+
+
+
+ = h($item['id']) ?>
+ = h($item['name_ar']) ?>
+ = h($item['name_en']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/expenses.php b/expenses.php
new file mode 100644
index 0000000..be24b46
--- /dev/null
+++ b/expenses.php
@@ -0,0 +1,321 @@
+query("SELECT id, name_ar, name_en FROM expense_categories ORDER BY name_ar");
+$categories = $catStmt->fetchAll();
+
+$branchesStmt = $pdo->query("SELECT code, name_ar, name_en FROM branches ORDER BY name_ar");
+$branches = $branchesStmt->fetchAll();
+
+// Check if user is restricted to a branch
+$userBranch = $user['branch_code'] ?? '';
+$isOwner = $user['role'] === 'owner';
+
+// Handle Form Submission
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+ $action = $_POST['action'] ?? '';
+
+ if ($action === 'create' && has_permission('expenses', 'add')) {
+ $branch_code = $isOwner ? ($_POST['branch_code'] ?? null) : $userBranch;
+ $stmt = $pdo->prepare('INSERT INTO expenses (branch_code, category_id, amount, expense_date, description, created_by) VALUES (?, ?, ?, ?, ?, ?)');
+ $stmt->execute([
+ $branch_code === '' ? null : $branch_code,
+ $_POST['category_id'],
+ $_POST['amount'],
+ $_POST['expense_date'],
+ $_POST['description'] ?? '',
+ $user['id']
+ ]);
+ set_flash('success', tr('تمت إضافة المصروف بنجاح', 'Expense added successfully'));
+ redirect_to('expenses.php');
+ } elseif ($action === 'edit' && has_permission('expenses', 'edit')) {
+ $branch_code = $isOwner ? ($_POST['branch_code'] ?? null) : $userBranch;
+ $stmt = $pdo->prepare('UPDATE expenses SET branch_code = ?, category_id = ?, amount = ?, expense_date = ?, description = ? WHERE id = ?');
+ $stmt->execute([
+ $branch_code === '' ? null : $branch_code,
+ $_POST['category_id'],
+ $_POST['amount'],
+ $_POST['expense_date'],
+ $_POST['description'] ?? '',
+ $_POST['id']
+ ]);
+ set_flash('success', tr('تم التحديث بنجاح', 'Updated successfully'));
+ redirect_to('expenses.php');
+ } elseif ($action === 'delete' && has_permission('expenses', 'del')) {
+ $stmt = $pdo->prepare('DELETE FROM expenses WHERE id = ?');
+ $stmt->execute([$_POST['id']]);
+ set_flash('success', tr('تم الحذف بنجاح', 'Deleted successfully'));
+ redirect_to('expenses.php');
+ }
+}
+
+// Pagination & Search
+$page = max(1, (int)($_GET['p'] ?? 1));
+$limit = 10;
+$offset = ($page - 1) * $limit;
+$search = $_GET['q'] ?? '';
+
+$where = '1=1';
+$params = [];
+if ($search) {
+ $where .= ' AND (e.description LIKE ?)';
+ $params[] = "%$search%";
+}
+
+if (!$isOwner && $userBranch) {
+ $where .= ' AND (e.branch_code = ? OR e.branch_code IS NULL)';
+ $params[] = $userBranch;
+}
+
+$totalStmt = $pdo->prepare("SELECT COUNT(*) FROM expenses e WHERE $where");
+$totalStmt->execute($params);
+$total = $totalStmt->fetchColumn();
+$totalPages = ceil($total / $limit);
+
+$queryStmt = $pdo->prepare("
+ SELECT e.*,
+ c.name_ar as category_ar, c.name_en as category_en,
+ b.name_ar as branch_ar, b.name_en as branch_en,
+ u.name_ar as user_ar, u.name_en as user_en
+ FROM expenses e
+ LEFT JOIN expense_categories c ON e.category_id = c.id
+ LEFT JOIN branches b ON e.branch_code = b.code
+ LEFT JOIN users u ON e.created_by = u.id
+ WHERE $where
+ ORDER BY e.expense_date DESC, e.id DESC
+ LIMIT $limit OFFSET $offset
+");
+$queryStmt->execute($params);
+$items = $queryStmt->fetchAll();
+
+require __DIR__ . '/includes/header.php';
+?>
+
+
+
+
+
= h($pageTitle) ?>
+
= h(tr('إدارة المصروفات وتسجيلها', 'Manage and record expenses')) ?>
+
+
+
+ = h(tr('إضافة مصروف', 'Add Expense')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = h(tr('التاريخ', 'Date')) ?>
+ = h(tr('التصنيف', 'Category')) ?>
+ = h(tr('المبلغ', 'Amount')) ?>
+ = h(tr('الفرع', 'Branch')) ?>
+ = h(tr('الوصف', 'Description')) ?>
+ = h(tr('إجراءات', 'Actions')) ?>
+
+
+
+
+ = h(tr('لا توجد بيانات', 'No data found')) ?>
+
+
+
+ = h($item['expense_date']) ?>
+ = h(current_lang() == 'ar' ? $item['category_ar'] : $item['category_en']) ?>
+ = h(number_format($item['amount'], 2)) ?>
+ = $item['branch_code'] ? h(current_lang() == 'ar' ? $item['branch_ar'] : $item['branch_en']) : ''.h(tr('عام', 'General')).' ' ?>
+ = h($item['description']) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1): ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = h(tr('التاريخ', 'Date')) ?> *
+
+
+
+ = h(tr('التصنيف', 'Category')) ?> *
+
+ = h(tr('-- اختر التصنيف --', '-- Select Category --')) ?>
+
+ = h(current_lang() == 'ar' ? $cat['name_ar'] : $cat['name_en']) ?>
+
+
+
+
+ = h(tr('المبلغ', 'Amount')) ?> *
+
+
+
+
+ = h(tr('الفرع', 'Branch')) ?>
+
+ = h(tr('مصروف عام (بدون فرع)', 'General (No branch)')) ?>
+
+ = h(current_lang() == 'ar' ? $b['name_ar'] : $b['name_en']) ?>
+
+
+
+
+
+ = h(tr('الوصف', 'Description')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = h(tr('التاريخ', 'Date')) ?> *
+
+
+
+ = h(tr('التصنيف', 'Category')) ?> *
+
+ = h(tr('-- اختر التصنيف --', '-- Select Category --')) ?>
+
+ = h(current_lang() == 'ar' ? $cat['name_ar'] : $cat['name_en']) ?>
+
+
+
+
+ = h(tr('المبلغ', 'Amount')) ?> *
+
+
+
+
+ = h(tr('الفرع', 'Branch')) ?>
+
+ = h(tr('مصروف عام (بدون فرع)', 'General (No branch)')) ?>
+
+ = h(current_lang() == 'ar' ? $b['name_ar'] : $b['name_en']) ?>
+
+
+
+
+
+ = h(tr('الوصف', 'Description')) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/includes/app.php b/includes/app.php
index 84a6e83..bb69dbf 100644
--- a/includes/app.php
+++ b/includes/app.php
@@ -180,7 +180,7 @@ function require_auth(): array
return $user;
}
-function get_app_modules(): array { return ["pos" => ["name_ar" => "نقاط البيع", "name_en" => "POS", "actions" => ["show", "add"]], "normal_sale" => ["name_ar" => "بيع عادي", "name_en" => "Normal Sale", "actions" => ["show", "add"]], "sales" => ["name_ar" => "المبيعات", "name_en" => "Sales", "actions" => ["show", "edit", "del"]], "purchases" => ["name_ar" => "المشتريات", "name_en" => "Purchases", "actions" => ["show", "add", "edit", "del"]], "stock" => ["name_ar" => "المخزون", "name_en" => "Stock", "actions" => ["show", "add", "edit", "del"]], "reports" => ["name_ar" => "التقارير", "name_en" => "Reports", "actions" => ["show"]], "customers" => ["name_ar" => "العملاء", "name_en" => "Customers", "actions" => ["show", "add", "edit", "del"]], "suppliers" => ["name_ar" => "الموردين", "name_en" => "Suppliers", "actions" => ["show", "add", "edit", "del"]], "categories" => ["name_ar" => "التصنيفات", "name_en" => "Categories", "actions" => ["show", "add", "edit", "del"]], "units" => ["name_ar" => "الوحدات", "name_en" => "Units", "actions" => ["show", "add", "edit", "del"]], "users" => ["name_ar" => "المستخدمين", "name_en" => "Users", "actions" => ["show", "add", "edit", "del"]], "settings" => ["name_ar" => "الإعدادات", "name_en" => "Settings", "actions" => ["show", "edit"]]]; } function has_permission(string $m, string $a = "show"): bool { $u = current_user(); if (!$u) return false; if ($u["role"] === "owner") return true; $p = !empty($u["permissions"]) ? (is_array($u["permissions"]) ? $u["permissions"] : json_decode($u["permissions"], true)) : []; return !empty($p[$m][$a]); } function require_permission(string $m, string $a = "show"): array { $u = require_auth(); if (!has_permission($m, $a)) { set_flash("warning", tr("ليس لديك صلاحية.", "You do not have permission.")); redirect_to("index.php"); } return $u; }
+function get_app_modules(): array { return ["pos" => ["name_ar" => "نقاط البيع", "name_en" => "POS", "actions" => ["show", "add"]], "normal_sale" => ["name_ar" => "بيع عادي", "name_en" => "Normal Sale", "actions" => ["show", "add"]], "sales" => ["name_ar" => "المبيعات", "name_en" => "Sales", "actions" => ["show", "edit", "del"]], "purchases" => ["name_ar" => "المشتريات", "name_en" => "Purchases", "actions" => ["show", "add", "edit", "del"]], "stock" => ["name_ar" => "المخزون", "name_en" => "Stock", "actions" => ["show", "add", "edit", "del"]], "reports" => ["name_ar" => "التقارير", "name_en" => "Reports", "actions" => ["show"]], "customers" => ["name_ar" => "العملاء", "name_en" => "Customers", "actions" => ["show", "add", "edit", "del"]], "suppliers" => ["name_ar" => "الموردين", "name_en" => "Suppliers", "actions" => ["show", "add", "edit", "del"]], "categories" => ["name_ar" => "التصنيفات", "name_en" => "Categories", "actions" => ["show", "add", "edit", "del"]], "units" => ["name_ar" => "الوحدات", "name_en" => "Units", "actions" => ["show", "add", "edit", "del"]], "users" => ["name_ar" => "المستخدمين", "name_en" => "Users", "actions" => ["show", "add", "edit", "del"]], "settings" => ["name_ar" => "الإعدادات", "name_en" => "Settings", "actions" => ["show", "edit"]], "expense_categories" => ["name_ar" => "تصنيفات المصروفات", "name_en" => "Expense Categories", "actions" => ["show", "add", "edit", "del"]], "expenses" => ["name_ar" => "المصروفات", "name_en" => "Expenses", "actions" => ["show", "add", "edit", "del"]]]; } function has_permission(string $m, string $a = "show"): bool { $u = current_user(); if (!$u) return false; if ($u["role"] === "owner") return true; $p = !empty($u["permissions"]) ? (is_array($u["permissions"]) ? $u["permissions"] : json_decode($u["permissions"], true)) : []; return !empty($p[$m][$a]); } function require_permission(string $m, string $a = "show"): array { $u = require_auth(); if (!has_permission($m, $a)) { set_flash("warning", tr("ليس لديك صلاحية.", "You do not have permission.")); redirect_to("index.php"); } return $u; }
function require_roles(array $roles): array
{
$user = require_auth();
diff --git a/includes/footer_settings.php b/includes/footer_settings.php
index 6652257..4b25295 100644
--- a/includes/footer_settings.php
+++ b/includes/footer_settings.php
@@ -52,6 +52,42 @@
+
+
+
= h(tr("إعدادات البريد الإلكتروني (SMTP)", "SMTP Email Settings")) ?>
+
+
+ = h(tr("خادم SMTP (Host)", "SMTP Host")) ?>
+ ">
+
+
+ = h(tr("منفذ SMTP (Port)", "SMTP Port")) ?>
+ ">
+
+
+ = h(tr("مستخدم SMTP (User)", "SMTP User")) ?>
+ ">
+
+
+ = h(tr("كلمة مرور SMTP (Pass)", "SMTP Password")) ?>
+ ">
+
+
+ = h(tr("تشفير SMTP (Secure)", "SMTP Secure (tls/ssl)")) ?>
+
+ >TLS
+ >SSL
+ >None
+
+
+
+ = h(tr("البريد المرسل (From Email)", "From Email")) ?>
+ ">
+
+
+ = h(tr("اسم المرسل (From Name)", "From Name")) ?>
+ ">
+
+
+
+
+
+ = h(tr('المصروفات', 'Expenses')) ?>
+
+
+
+
+
+
= h(tr('الموردون', 'Suppliers')) ?>
diff --git a/mail/config.php b/mail/config.php
index c3bdb73..f7c11d0 100644
--- a/mail/config.php
+++ b/mail/config.php
@@ -1,21 +1,48 @@
config array for MailService.
+// Mail configuration sourced from DB settings with environment variables fallback.
function env_val(string $key, $default = null) {
$v = getenv($key);
return ($v === false || $v === null || $v === '') ? $default : $v;
}
-$transport = env_val('MAIL_TRANSPORT', 'smtp');
-$smtp_host = env_val('SMTP_HOST');
-$smtp_port = (int) env_val('SMTP_PORT', 587);
-$smtp_secure = env_val('SMTP_SECURE', 'tls'); // tls | ssl | null
-$smtp_user = env_val('SMTP_USER');
-$smtp_pass = env_val('SMTP_PASS');
+$db_settings = [];
+if (file_exists(__DIR__ . '/../db/config.php')) {
+ try {
+ require_once __DIR__ . '/../db/config.php';
+ if (function_exists('db')) {
+ $pdo = db();
+ if ($pdo) {
+ $stmt = $pdo->query("SELECT setting_key, setting_value FROM settings WHERE setting_key IN ('smtp_host', 'smtp_port', 'smtp_user', 'smtp_pass', 'smtp_secure', 'mail_from', 'mail_from_name')");
+ while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
+ $db_settings[$row['setting_key']] = $row['setting_value'];
+ }
+ }
+ }
+ } catch (\Throwable $e) {
+ // ignore DB errors during config loading
+ }
+}
-$from_email = env_val('MAIL_FROM', 'no-reply@localhost');
-$from_name = env_val('MAIL_FROM_NAME', 'App');
+// Function to get config value with fallback
+function get_cfg($db_settings, $key, $env_key, $default = null, $allow_empty = false) {
+ if (isset($db_settings[$key])) {
+ if ($allow_empty || $db_settings[$key] !== '') {
+ return $db_settings[$key];
+ }
+ }
+ return env_val($env_key, $default);
+}
+
+$transport = env_val('MAIL_TRANSPORT', 'smtp');
+$smtp_host = get_cfg($db_settings, 'smtp_host', 'SMTP_HOST');
+$smtp_port = (int) get_cfg($db_settings, 'smtp_port', 'SMTP_PORT', 587);
+$smtp_secure = get_cfg($db_settings, 'smtp_secure', 'SMTP_SECURE', 'tls', true);
+$smtp_user = get_cfg($db_settings, 'smtp_user', 'SMTP_USER');
+$smtp_pass = get_cfg($db_settings, 'smtp_pass', 'SMTP_PASS', null, true);
+
+$from_email = get_cfg($db_settings, 'mail_from', 'MAIL_FROM', 'no-reply@localhost');
+$from_name = get_cfg($db_settings, 'mail_from_name', 'MAIL_FROM_NAME', 'App');
$reply_to = env_val('MAIL_REPLY_TO');
$dkim_domain = env_val('DKIM_DOMAIN');
diff --git a/online_orders.php b/online_orders.php
index 498fd93..99481d5 100644
--- a/online_orders.php
+++ b/online_orders.php
@@ -15,21 +15,79 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
$stmt->execute([$status, $id]);
set_flash('success', tr('تم تحديث حالة الطلب', 'Order status updated'));
redirect_to('online_orders.php');
+ } elseif ($_POST['action'] === 'delete') {
+ $id = (int)$_POST['id'];
+ $stmt = $db->prepare("DELETE FROM online_orders WHERE id = ?");
+ $stmt->execute([$id]);
+ set_flash('success', tr('تم حذف الطلب بنجاح', 'Order deleted successfully'));
+ redirect_to('online_orders.php');
}
+
}
-$stmt = $db->query("SELECT * FROM online_orders ORDER BY created_at DESC");
+$search = $_GET['search'] ?? '';
+$date_from = $_GET['date_from'] ?? date('Y-m-d', strtotime('-30 days'));
+$date_to = $_GET['date_to'] ?? date('Y-m-d');
+
+$query = "SELECT * FROM online_orders WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?";
+$params = [$date_from, $date_to];
+
+if ($search !== '') {
+ $query .= " AND (customer_name LIKE ? OR customer_phone LIKE ?)";
+ $params[] = "%$search%";
+ $params[] = "%$search%";
+}
+
+$query .= " ORDER BY created_at DESC";
+$stmt = $db->prepare($query);
+$stmt->execute($params);
$orders = $stmt->fetchAll(PDO::FETCH_ASSOC);
require __DIR__ . '/includes/header.php';
?>
-
+
+
+
= h(tr('طلبات المتجر الإلكتروني', 'Online Store Orders')) ?>
+
+
= h(tr('تقرير طلبات المتجر', 'Online Orders Report')) ?>
+
= h($date_from) ?> - = h($date_to) ?>
+
+
+
+
+ = h(tr('بحث', 'Search')) ?>
+
+
+
+ = h(tr('من تاريخ', 'From Date')) ?>
+
+
+
+ = h(tr('إلى تاريخ', 'To Date')) ?>
+
+
+
+ = h(tr('بحث', 'Search')) ?>
+
+
+
+
@@ -42,14 +100,17 @@ require __DIR__ . '/includes/header.php';
= h(tr('العنوان', 'Address')) ?>
= h(tr('المبلغ', 'Amount')) ?>
= h(tr('الحالة', 'Status')) ?>
- = h(tr('إجراءات', 'Actions')) ?>
+ = h(tr('إجراءات', 'Actions')) ?>
- = h(tr('لا توجد طلبات بعد', 'No orders yet')) ?>
+ = h(tr('لا توجد طلبات', 'No orders found')) ?>
- ">= h($o['customer_address']) ?>
= h(currency($o['total_amount'])) ?>
= h($statusText) ?>
-
- $o["id"],
"name" => $o["customer_name"],
"phone" => $o["customer_phone"],
"address" => $o["customer_address"],
- "total" => $o["total_amount"],
+ "subtotal" => $o["subtotal"] ?? 0, "vat" => $o["vat_amount"] ?? 0, "total" => $o["total_amount"],
"items" => $items
- ], JSON_UNESCAPED_UNICODE) ?>)'>
- = h(tr('عرض', 'View')) ?>
+ ], JSON_UNESCAPED_UNICODE), ENT_QUOTES, "UTF-8") ?>)'>
+
+
- = h(tr('تغيير الحالة', 'Change Status')) ?>
+
= h(tr('قيد الانتظار', 'Pending')) ?>
@@ -89,17 +151,30 @@ require __DIR__ . '/includes/header.php';
= h(tr('مرفوض', 'Rejected')) ?>
+
+
+
+
+
+
+
+
+ = h(tr('إجمالي المبالغ:', 'Total Amounts:')) ?>
+ = h(currency($totalAmount)) ?>
+
+
+
-
-
+
+
+
+
+
- = h(tr('إجمالي المبيعات', 'Gross sales')) ?>
= h(currency((float) $report['gross'])) ?>
= h(tr('حسب نطاق صلاحية المستخدم الحالي', 'Scoped to the current viewer permissions')) ?>
- = h(tr('إجمالي الضريبة', 'Total VAT')) ?>
= h(currency((float) ($report['total_vat'] ?? 0))) ?>
= h(tr('مجموع ضريبة القيمة المضافة', 'Total Value Added Tax')) ?>
- = h(tr('عدد الفواتير', 'Invoices')) ?>
= h((string) $report['sales_count']) ?>
= h(tr('إجمالي الفواتير المسجلة', 'Total logged invoices')) ?>
- = h(tr('أفضل صنف', 'Top product')) ?>
= h($report['product_totals'] ? product_label((string) array_key_first($report['product_totals'])) : tr('لا يوجد', 'None yet')) ?>
= h(tr('الأكثر مبيعاً حتى الآن', 'Most sold item so far')) ?>
+
+
+
+
+
= h(tr('إجمالي المبيعات', 'Gross sales')) ?>
+
= h(currency((float) $report['gross'])) ?>
+
= h(tr('شامل جميع العمليات', 'Includes all transactions')) ?>
+
+
+
+
+
+
+
+
= h(tr('إجمالي الضريبة', 'Total VAT')) ?>
+
= h(currency((float) ($report['total_vat'] ?? 0))) ?>
+
= h(tr('مجموع ضريبة القيمة المضافة', 'Total Value Added Tax')) ?>
+
+
+
+
+
+
+
+
= h(tr('عدد الفواتير', 'Invoices')) ?>
+
= h((string) $report['sales_count']) ?>
+
= h(tr('إجمالي الفواتير المسجلة', 'Total logged invoices')) ?>
+
+
+
+
+
+
+
+
= h(tr('أفضل صنف', 'Top product')) ?>
+
= h($report['product_totals'] ? product_label((string) array_key_first($report['product_totals'])) : tr('لا يوجد', 'None yet')) ?>
+
= h(tr('الأكثر مبيعاً حتى الآن', 'Most sold item so far')) ?>
+
+
+
+
+
+
+
+
+
+
= h(tr('أداء المبيعات الشهري', 'Monthly Sales Performance')) ?>
+
+
= h(tr('لا توجد بيانات', 'No data')) ?>
+
+
+
+
+
+
+
+
-
= h(tr('المبيعات حسب الفرع', 'Sales by branch')) ?>
+
= h(tr('المبيعات حسب الفرع', 'Sales by branch')) ?>
= h(tr('لا توجد بيانات', 'No data')) ?> = h(tr('أضف عملية بيع أولاً لبدء التقارير.', 'Add a first sale to activate reports.')) ?>
-
- $amount): ?>
-
= h(branch_label((string) $branchCode)) ?> = h(currency((float) $amount)) ?>
+
+ $amount):
+ $percent = $report['gross'] > 0 ? ($amount / $report['gross']) * 100 : 0;
+ ?>
+
+
+
= h(branch_label((string) $branchCode)) ?>
+
+ = h(currency((float) $amount)) ?>
+ = round($percent, 1) ?>%
+
+
+
+
+
-
= h(tr('المبيعات حسب الدفع', 'Sales by payment')) ?>
+
= h(tr('المبيعات حسب الدفع', 'Sales by payment')) ?>
= h(tr('بانتظار البيانات', 'Waiting for data')) ?> = h(tr('عند تسجيل عمليات بيع ستظهر هنا طرق الدفع.', 'Payment mix will appear here once sales are logged.')) ?>
-
- $amount): ?>
-
= h(ucfirst((string) $payment)) ?> = h(currency((float) $amount)) ?>
+
+ 'success', 'card' => 'info', 'bank' => 'warning', 'mixed' => 'secondary'];
+ foreach ($report['payment_totals'] as $payment => $amount):
+ $percent = $report['gross'] > 0 ? ($amount / $report['gross']) * 100 : 0;
+ $bg = $colors[$payment] ?? 'primary';
+ ?>
+
+
+
= h(ucfirst((string) $payment)) ?>
+
+ = h(currency((float) $amount)) ?>
+ = round($percent, 1) ?>%
+
+
+
+
+
diff --git a/shop.php b/shop.php
index 06b680f..7c18f54 100644
--- a/shop.php
+++ b/shop.php
@@ -130,7 +130,7 @@ body { background-color: #f8f9fa; }
'id' => $item['id'],
'sku' => $item['sku'],
'name' => $item['name'],
- 'price' => $item['price']
+ 'price' => $item['price'], 'vat' => $item['vat'] ?? 0
]), ENT_QUOTES, 'UTF-8') ?>)">
= h(tr('إضافة للسلة', 'Add to Cart')) ?>
@@ -168,9 +168,21 @@ body { background-color: #f8f9fa; }
-
-
= h(tr('المجموع الإجمالي', 'Total Amount')) ?>
+
+
+
+ = h(tr("المجموع الفرعي", "Subtotal")) ?>
+ 0.00
+
+
+ = h(tr("الضريبة", "VAT")) ?>
+ 0.00
+
+
+
+
= h(tr("المجموع الإجمالي", "Total Amount")) ?>
0.00
+
= h(tr('بيانات العميل', 'Customer Details')) ?>
@@ -251,6 +263,7 @@ function renderCart() {
const list = document.getElementById('cartItemsList');
let html = '';
let total = 0;
+ let totalVat = 0;
if (Object.keys(cart).length === 0) {
html = '
= h(tr('السلة فارغة', 'Cart is empty')) ?>
';
@@ -259,6 +272,8 @@ function renderCart() {
for (let id in cart) {
const item = cart[id];
const subtotal = item.price * item.qty;
+ const itemVat = subtotal * ((item.vat || 0) / 100);
+ totalVat += itemVat;
total += subtotal;
html += `
@@ -278,7 +293,9 @@ function renderCart() {
}
list.innerHTML = html;
- document.getElementById('cartTotal').innerText = total.toFixed(2);
+ document.getElementById('cartSubtotal').innerText = total.toFixed(2);
+ document.getElementById('cartVat').innerText = totalVat.toFixed(2);
+ document.getElementById('cartTotal').innerText = (total + totalVat).toFixed(2);
}
function openCart() {