diff --git a/admin/company.php b/admin/company.php index 522f5f5..28a20e4 100644 --- a/admin/company.php +++ b/admin/company.php @@ -19,6 +19,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $vat_rate = $_POST['vat_rate'] ?? 0; $currency_symbol = $_POST['currency_symbol'] ?? '$'; $currency_decimals = $_POST['currency_decimals'] ?? 2; + $currency_position = $_POST['currency_position'] ?? 'before'; $ctr_number = $_POST['ctr_number'] ?? ''; $vat_number = $_POST['vat_number'] ?? ''; $commission_enabled = isset($_POST['commission_enabled']) ? 1 : 0; @@ -67,11 +68,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $exists = $pdo->query("SELECT COUNT(*) FROM company_settings")->fetchColumn(); if ($exists) { - $stmt = $pdo->prepare("UPDATE company_settings SET company_name=?, address=?, phone=?, email=?, vat_rate=?, currency_symbol=?, currency_decimals=?, ctr_number=?, vat_number=?, logo_url=?, favicon_url=?, commission_enabled=?, updated_at=NOW()"); - $stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals, $ctr_number, $vat_number, $logo_url, $favicon_url, $commission_enabled]); + $stmt = $pdo->prepare("UPDATE company_settings SET company_name=?, address=?, phone=?, email=?, vat_rate=?, currency_symbol=?, currency_decimals=?, currency_position=?, ctr_number=?, vat_number=?, logo_url=?, favicon_url=?, commission_enabled=?, updated_at=NOW()"); + $stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals, $currency_position, $ctr_number, $vat_number, $logo_url, $favicon_url, $commission_enabled]); } else { - $stmt = $pdo->prepare("INSERT INTO company_settings (company_name, address, phone, email, vat_rate, currency_symbol, currency_decimals, ctr_number, vat_number, logo_url, favicon_url, commission_enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals, $ctr_number, $vat_number, $logo_url, $favicon_url, $commission_enabled]); + $stmt = $pdo->prepare("INSERT INTO company_settings (company_name, address, phone, email, vat_rate, currency_symbol, currency_decimals, currency_position, ctr_number, vat_number, logo_url, favicon_url, commission_enabled) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$company_name, $address, $phone, $email, $vat_rate, $currency_symbol, $currency_decimals, $currency_position, $ctr_number, $vat_number, $logo_url, $favicon_url, $commission_enabled]); } $message = '
Company settings updated successfully!
'; @@ -138,18 +139,25 @@ include 'includes/header.php';
Financial Settings
-
+
> %
-
+
>
-
+
+ + +
+
>
diff --git a/admin/includes/header.php b/admin/includes/header.php index 51d0364..57f75ff 100644 --- a/admin/includes/header.php +++ b/admin/includes/header.php @@ -489,6 +489,16 @@ function can_view($module) { + diff --git a/admin/integrations.php b/admin/integrations.php index d619343..91b6ff5 100644 --- a/admin/integrations.php +++ b/admin/integrations.php @@ -94,7 +94,7 @@ Thank you for dining with *{company_name}*! 🍽️ *Order Details:* {order_details} -Total: *{total_amount}* OMR +Total: *{total_amount}* You've earned *{points_earned} points* with this order. 💰 *Current Balance: {new_balance} points*"; @@ -193,7 +193,7 @@ require_once __DIR__ . '/includes/header.php';
Available Variables:
{customer_name}, {company_name}, {order_id}, - {order_details} (list of items), {total_amount}, + {order_details} (list of items), {total_amount}, {currency_symbol}, {points_earned}, {points_redeemed}, {new_balance}.
diff --git a/admin/report_products.php b/admin/report_products.php new file mode 100644 index 0000000..5291737 --- /dev/null +++ b/admin/report_products.php @@ -0,0 +1,147 @@ +query("SELECT id, name, name_ar FROM outlets WHERE is_deleted = 0 ORDER BY name ASC"); +$allOutlets = $outletsStmt->fetchAll(); + +// Base query additions +$outletCondition = ""; +$queryParams = [$startDate, $endDate]; +if (!empty($outletId)) { + $outletCondition = " AND o.outlet_id = ? "; + $queryParams[] = $outletId; +} + +// Product Sales & Profit +$productSalesStmt = $pdo->prepare(" + SELECT + p.name as product_name, + p.name_ar as product_name_ar, + SUM(oi.quantity) as qty_sold, + SUM(oi.quantity * oi.unit_price) as total_amount, + SUM(oi.quantity * (oi.unit_price - IFNULL(p.cost_price, 0))) as total_profit + FROM order_items oi + JOIN orders o ON oi.order_id = o.id + JOIN products p ON oi.product_id = p.id + WHERE DATE(o.created_at) BETWEEN ? AND ? + $outletCondition + GROUP BY p.id + ORDER BY qty_sold DESC +"); +$productSalesStmt->execute($queryParams); +$productSales = $productSalesStmt->fetchAll(); + +// Totals for the footer +$totalQty = 0; +$totalAmount = 0; +$totalProfit = 0; +foreach ($productSales as $p) { + $totalQty += $p['qty_sold']; + $totalAmount += $p['total_amount']; + $totalProfit += $p['total_profit']; +} +?> + +
+
+
+

Product Sales Report

+

Detailed breakdown of product performance and profitability

+
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + Reset +
+
+
+
+ +
+
+
Products
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Product NameQty SoldTotal AmountEstimated Profit
No sales found for the selected criteria
+
+ + + +
Total
+
+
+
+
+ + diff --git a/admin/report_staff.php b/admin/report_staff.php new file mode 100644 index 0000000..01f446e --- /dev/null +++ b/admin/report_staff.php @@ -0,0 +1,171 @@ +query("SELECT id, name, name_ar FROM outlets WHERE is_deleted = 0 ORDER BY name ASC"); +$allOutlets = $outletsStmt->fetchAll(); + +// Base query additions +$outletCondition = ""; +$queryParams = [$startDate, $endDate]; +if (!empty($outletId)) { + $outletCondition = " AND o.outlet_id = ? "; + $queryParams[] = $outletId; +} + +// Staff Sales by Payment Type +$staffPaymentStmt = $pdo->prepare(" + SELECT + u.full_name as staff_name, + u.full_name_ar as staff_name_ar, + pt.name as payment_name, + SUM(o.total_amount) as total_amount + FROM orders o + JOIN users u ON o.user_id = u.id + LEFT JOIN payment_types pt ON o.payment_type_id = pt.id + WHERE DATE(o.created_at) BETWEEN ? AND ? + $outletCondition + GROUP BY o.user_id, o.payment_type_id + ORDER BY u.full_name ASC, total_amount DESC +"); +$staffPaymentStmt->execute($queryParams); +$staffPaymentSales = $staffPaymentStmt->fetchAll(); + +// Summary by payment type +$paymentSummary = []; +$grandTotal = 0; +foreach ($staffPaymentSales as $row) { + $pName = $row['payment_name'] ?? 'N/A'; + if (!isset($paymentSummary[$pName])) { + $paymentSummary[$pName] = 0; + } + $paymentSummary[$pName] += $row['total_amount']; + $grandTotal += $row['total_amount']; +} +?> + +
+
+
+

Staff Sales Report

+

Sales breakdown by staff and payment method

+
+
+
+ +
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + Reset +
+
+
+
+ +
+
+
+
+
Detailed Staff Sales
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Staff NamePayment TypeTotal Amount
No sales found for the selected criteria
+ +
+ + + + +
+ +
+
+
+
+
+ +
+
+
+
Payment Summary
+
+
+
    + $amount): ?> +
  • + + +
  • + +
  • + Grand Total + +
  • +
+
+
+
+
+
+ + diff --git a/admin/reports.php b/admin/reports.php index 01edd39..f0d8738 100644 --- a/admin/reports.php +++ b/admin/reports.php @@ -14,7 +14,7 @@ $endDate = $_GET['end_date'] ?? date('Y-m-d'); $outletId = $_GET['outlet_id'] ?? ''; // Fetch Outlets for filter -$outletsStmt = $pdo->query("SELECT id, name, name_ar FROM outlets ORDER BY name ASC"); +$outletsStmt = $pdo->query("SELECT id, name, name_ar FROM outlets WHERE is_deleted = 0 ORDER BY name ASC"); $allOutlets = $outletsStmt->fetchAll(); // Base query additions @@ -124,7 +124,7 @@ $netProfit = ($summary['total_revenue'] ?? 0) - $totalExpenses;
-

Daily Reports

+

Business Reports

Overview of your business performance

@@ -216,86 +216,6 @@ $netProfit = ($summary['total_revenue'] ?? 0) - $totalExpenses;
- -
-
-
-
Sales by Staff
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
Staff NameOrdersTotal Sales
No data found for this period
-
- - - -
-
-
-
-
- - -
-
-
-
Sales by Outlet
-
-
-
- - - - - - - - - - - - - - - - - - - - - -
OutletOrdersTotal Sales
No data found for this period
-
- - - -
-
-
-
-
-
@@ -373,6 +293,86 @@ $netProfit = ($summary['total_revenue'] ?? 0) - $totalExpenses;
+ + +
+
+
+
Sales Summary by Staff
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
Staff NameOrdersTotal Sales
No data found for this period
+
+ + + +
+
+
+
+
+ + +
+
+
+
Sales by Outlet
+
+
+
+ + + + + + + + + + + + + + + + + + + + + +
OutletOrdersTotal Sales
No data found for this period
+
+ + + +
+
+
+
+
diff --git a/api/order.php b/api/order.php index 513e74e..6663482 100644 --- a/api/order.php +++ b/api/order.php @@ -269,8 +269,8 @@ try { $payment_type_id = !empty($data['payment_type_id']) ? intval($data['payment_type_id']) : null; // Commission Calculation - $commission_amount = 0; $companySettings = get_company_settings(); + $commission_amount = 0; if (!empty($companySettings['commission_enabled']) && $user_id) { $userStmt = $pdo->prepare("SELECT commission_rate FROM users WHERE id = ?"); $userStmt->execute([$user_id]); @@ -352,8 +352,9 @@ try { try { $final_points = $current_points - $points_deducted + ($loyalty_enabled ? $points_awarded : 0); $wablas = new WablasService($pdo); - $companySettings = get_company_settings(); $company_name = $companySettings['company_name'] ?? 'Flatlogic POS'; + $currency_symbol = $companySettings['currency_symbol'] ?? 'OMR'; + $currency_position = $companySettings['currency_position'] ?? 'after'; $stmt = $pdo->prepare("SELECT setting_value FROM integration_settings WHERE provider = 'wablas' AND setting_key = 'order_template'"); $stmt->execute(); @@ -367,7 +368,7 @@ Thank you for dining with *{company_name}*! 🍽️ *Order Details:* {order_details} -Total: *{total_amount}* OMR +Total: *{total_amount}* You've earned *{points_earned} points* with this order. 💰 *Current Balance: {new_balance} points* @@ -382,12 +383,20 @@ You've earned *{points_earned} points* with this order. : "You need *" . ($points_threshold - $final_points) . " more points* to unlock a free meal."; } + $formatted_total = number_format($final_total, (int)($companySettings['currency_decimals'] ?? 2)); + if ($currency_position === 'after') { + $total_with_currency = $formatted_total . " " . $currency_symbol; + } else { + $total_with_currency = $currency_symbol . $formatted_total; + } + $replacements = [ '{customer_name}' => $customer_name, '{company_name}' => $company_name, '{order_id}' => $order_id, '{order_details}' => implode("\n", $order_items_list), - '{total_amount}' => number_format($final_total, 3), + '{total_amount}' => $total_with_currency, + '{currency_symbol}' => $currency_symbol, '{points_earned}' => $loyalty_enabled ? $points_awarded : 0, '{points_redeemed}' => $points_deducted, '{new_balance}' => $final_points, @@ -408,4 +417,4 @@ You've earned *{points_earned} points* with this order. if ($pdo->inTransaction()) $pdo->rollBack(); error_log("Order Error: " . $e->getMessage()); echo json_encode(['success' => false, 'error' => $e->getMessage()]); -} +} \ No newline at end of file diff --git a/assets/js/main.js b/assets/js/main.js index 3d71c7b..ec840f6 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -32,6 +32,7 @@ document.addEventListener('DOMContentLoaded', () => { const settings = (typeof COMPANY_SETTINGS !== 'undefined') ? COMPANY_SETTINGS : { currency_symbol: '$', currency_decimals: 2, + currency_position: 'before', vat_rate: 0 }; @@ -101,7 +102,14 @@ document.addEventListener('DOMContentLoaded', () => { function formatCurrency(amount) { const symbol = settings.currency_symbol || '$'; const decimals = parseInt(settings.currency_decimals || 2); - return symbol + parseFloat(Math.abs(amount)).toFixed(decimals); + const position = settings.currency_position || 'before'; + const formatted = parseFloat(Math.abs(amount)).toFixed(decimals); + + if (position === 'after') { + return formatted + ' ' + symbol; + } else { + return symbol + formatted; + } } function filterProducts() { @@ -427,11 +435,11 @@ document.addEventListener('DOMContentLoaded', () => { if (data.success && data.tables.length > 0) { renderTables(data.tables); } else { - grid.innerHTML = `
${_t("none")}
`; + grid.innerHTML = `
${_t("none")}
`; } }) .catch(() => { - grid.innerHTML = `
${_t("error")}
`; + grid.innerHTML = `
${_t("error")}
`; }); } @@ -450,7 +458,7 @@ document.addEventListener('DOMContentLoaded', () => { for (const area in areas) { const areaHeader = document.createElement("div"); areaHeader.className = "col-12 mt-3"; - areaHeader.innerHTML = `
${area}
`; + areaHeader.innerHTML = `
${area}
`; grid.appendChild(areaHeader); areas[area].forEach(table => { @@ -881,4 +889,4 @@ document.addEventListener('DOMContentLoaded', () => { const modal = new bootstrap.Modal(document.getElementById('qrRatingModal')); modal.show(); }; -}); +}); \ No newline at end of file diff --git a/db/migrations/039_add_currency_position.sql b/db/migrations/039_add_currency_position.sql new file mode 100644 index 0000000..7bb98c4 --- /dev/null +++ b/db/migrations/039_add_currency_position.sql @@ -0,0 +1,5 @@ +-- Add currency position setting +ALTER TABLE company_settings ADD COLUMN currency_position ENUM('before', 'after') DEFAULT 'after'; + +-- Update existing Omani settings to use 'after' as it's more common for OMR +UPDATE company_settings SET currency_position = 'after' WHERE currency_symbol IN ('OMR', 'ر.ع.', 'OMRR'); diff --git a/includes/functions.php b/includes/functions.php index 4a2bf5c..6403921 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -22,7 +22,8 @@ function get_company_settings() { 'email' => 'info@restaurant.com', 'vat_rate' => 0.00, 'currency_symbol' => '$', - 'currency_decimals' => 2 + 'currency_decimals' => 2, + 'currency_position' => 'before' ]; } } @@ -32,7 +33,17 @@ function get_company_settings() { // Function to format currency using settings function format_currency($amount) { $settings = get_company_settings(); - return ($settings['currency_symbol'] ?? '$') . number_format((float)$amount, (int)($settings['currency_decimals'] ?? 2)); + $symbol = $settings['currency_symbol'] ?? '$'; + $decimals = (int)($settings['currency_decimals'] ?? 2); + $position = $settings['currency_position'] ?? 'before'; + + $formatted_number = number_format((float)$amount, $decimals); + + if ($position === 'after') { + return $formatted_number . ' ' . $symbol; + } else { + return $symbol . $formatted_number; + } } /** @@ -431,4 +442,4 @@ function trigger_auto_backup() { $stmt->execute(); } } -} +} \ No newline at end of file diff --git a/qorder.php b/qorder.php index 0428e2e..e6e38ec 100644 --- a/qorder.php +++ b/qorder.php @@ -143,12 +143,11 @@ foreach ($variants_raw as $v) { <?= htmlspecialchars($product['name']) ?>
- - + + - + - OMR
@@ -177,7 +176,7 @@ foreach ($variants_raw as $v) {
0 Items
-
0.000 OMR
+
${item.quantity} @@ -424,8 +433,8 @@ foreach ($variants_raw as $v) { }); const total = cart.reduce((sum, item) => sum + (item.unit_price * item.quantity), 0); - document.getElementById('modal-subtotal').textContent = total.toFixed(3) + ' ' + currency; - document.getElementById('modal-total').textContent = total.toFixed(3) + ' ' + currency; + document.getElementById('modal-subtotal').textContent = formatCurrency(total); + document.getElementById('modal-total').textContent = formatCurrency(total); cartModal.show(); } @@ -510,4 +519,4 @@ foreach ($variants_raw as $v) { } - + \ No newline at end of file