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) {
= t('daily_reports') ?>
+
+
+ Product Sales
+
+
+
+
+ Staff Sales
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+ | Product Name |
+ Qty Sold |
+ Total Amount |
+ Estimated Profit |
+
+
+
+
+ | No sales found for the selected criteria |
+
+
+
+ |
+ = htmlspecialchars($prod['product_name']) ?>
+
+ = htmlspecialchars($prod['product_name_ar']) ?>
+
+ |
+ = number_format($prod['qty_sold']) ?> |
+ = format_currency($prod['total_amount']) ?> |
+ = format_currency($prod['total_profit']) ?> |
+
+
+
+
+
+
+
+ | Total |
+ = number_format($totalQty) ?> |
+ = format_currency($totalAmount) ?> |
+ = format_currency($totalProfit) ?> |
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Staff Name |
+ Payment Type |
+ Total Amount |
+
+
+
+
+ | No sales found for the selected criteria |
+
+
+
+ |
+
+ = htmlspecialchars($row['staff_name']) ?>
+
+ = htmlspecialchars($row['staff_name_ar']) ?>
+
+
+ |
+
+ = htmlspecialchars($row['payment_name'] ?? 'N/A') ?>
+ |
+ = format_currency($row['total_amount']) ?> |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $amount): ?>
+ -
+ = htmlspecialchars($pName) ?>
+ = format_currency($amount) ?>
+
+
+ -
+ Grand Total
+ = format_currency($grandTotal) ?>
+
+
+
+
+
+
+
+
+
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
-
-
-
-
-
-
-
-
-
- | Staff Name |
- Orders |
- Total Sales |
-
-
-
-
- | No data found for this period |
-
-
-
- |
- = htmlspecialchars($staff['staff_name'] ?: $staff['username']) ?>
-
- = htmlspecialchars($staff['staff_name_ar']) ?>
-
- |
- = $staff['order_count'] ?> |
- = format_currency($staff['total_sales']) ?> |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Outlet |
- Orders |
- Total Sales |
-
-
-
-
- | No data found for this period |
-
-
-
- |
- = htmlspecialchars($outlet['outlet_name']) ?>
-
- = htmlspecialchars($outlet['outlet_name_ar']) ?>
-
- |
- = $outlet['order_count'] ?> |
- = format_currency($outlet['total_sales']) ?> |
-
-
-
-
-
-
-
-
-
-
@@ -373,6 +293,86 @@ $netProfit = ($summary['total_revenue'] ?? 0) - $totalExpenses;
+
+
+
+
+
+
+
+
+
+
+ | Staff Name |
+ Orders |
+ Total Sales |
+
+
+
+
+ | No data found for this period |
+
+
+
+ |
+ = htmlspecialchars($staff['staff_name'] ?: $staff['username']) ?>
+
+ = htmlspecialchars($staff['staff_name_ar']) ?>
+
+ |
+ = $staff['order_count'] ?> |
+ = format_currency($staff['total_sales']) ?> |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | Outlet |
+ Orders |
+ Total Sales |
+
+
+
+
+ | No data found for this period |
+
+
+
+ |
+ = htmlspecialchars($outlet['outlet_name']) ?>
+
+ = htmlspecialchars($outlet['outlet_name_ar']) ?>
+
+ |
+ = $outlet['order_count'] ?> |
+ = format_currency($outlet['total_sales']) ?> |
+
+
+
+
+
+
+
+
+
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) {
- = number_format($effective_price, 3) ?>
- = number_format((float)$product['price'], 3) ?>
+ = format_currency($effective_price) ?>
+ = format_currency((float)$product['price']) ?>
- = number_format((float)$product['price'], 3) ?>
+ = format_currency((float)$product['price']) ?>
- OMR
@@ -177,7 +176,7 @@ foreach ($variants_raw as $v) {
0 Items
-
0.000 OMR
+
= format_currency(0) ?>