diff --git a/assets/css/custom.css b/assets/css/custom.css
index 110658f..0fdc9a2 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -166,4 +166,38 @@ body.auth-body {
[dir="rtl"] .form-select-lg {
padding-right: 1rem;
padding-left: 3rem;
-}
\ No newline at end of file
+}
+
+/* Print specific styles */
+@media print {
+ #sidebar-wrapper, .top-navbar, .d-print-none {
+ display: none !important;
+ }
+ #page-content-wrapper {
+ margin: 0 !important;
+ width: 100% !important;
+ min-width: 100% !important;
+ padding: 0 !important;
+ }
+ body {
+ font-size: 11pt;
+ background-color: #fff;
+ }
+ .table {
+ font-size: 10pt;
+ margin-bottom: 1rem;
+ }
+ .table th, .table td {
+ padding: 0.3rem;
+ }
+ .fs-5 {
+ font-size: 1rem !important;
+ }
+ h2 {
+ font-size: 1.5rem;
+ }
+ .surface-card {
+ box-shadow: none !important;
+ border: none !important;
+ }
+}
diff --git a/cookies.txt b/cookies.txt
index 24dbe0b..0f3bb80 100644
--- a/cookies.txt
+++ b/cookies.txt
@@ -2,4 +2,4 @@
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.
-127.0.0.1 FALSE / FALSE 0 PHPSESSID 6qft39lctsp4e64kmen99qtqfo
+127.0.0.1 FALSE / FALSE 0 PHPSESSID une8u6o0qtfojm7tp3ppv39knh
diff --git a/edit_sale.php b/edit_sale.php
index 4e6c672..7f07565 100644
--- a/edit_sale.php
+++ b/edit_sale.php
@@ -46,6 +46,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} else {
$normalized = [];
$subtotal = 0.0;
+ $totalVat = 0.0;
$itemCount = 0;
foreach ($items as $item) {
$sku = (string) ($item['sku'] ?? '');
@@ -56,6 +57,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$product = $catalog[$sku];
$price = (float) $product['price'];
$lineTotal = $price * $qty;
+
+ $vatPercent = (float) ($product['vat'] ?? 0);
+ $itemVat = $lineTotal - ($lineTotal / (1 + ($vatPercent / 100)));
+ $totalVat += $itemVat;
+
$normalized[] = [
'sku' => $sku,
'name_ar' => $product['name_ar'],
@@ -63,6 +69,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'qty' => $qty,
'price' => $price,
'line_total' => $lineTotal,
+ 'vat_percent' => $vatPercent,
+ 'vat_amount' => $itemVat
];
$subtotal += $lineTotal;
$itemCount += $qty;
@@ -79,6 +87,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
items_json = :items_json,
item_count = :item_count,
subtotal = :subtotal,
+ vat_amount = :vat_amount,
total_amount = :total_amount,
status = :status,
notes = :notes
@@ -380,8 +389,8 @@ require __DIR__ . '/includes/header.php';
0.000
- = h(tr('الضريبة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?>
- = h(tr('مشمولة', 'Included')) ?>
+ = h(tr('الضريبة (مشمولة)', 'VAT (Included)')) ?>
+ 0.000
= h(tr('الإجمالي', 'Total')) ?>
@@ -633,18 +642,24 @@ function renderInvoice() {
if (skus.length === 0) {
tbody.innerHTML = '';
tbody.appendChild(emptyRow);
- updateTotals(0);
+ updateTotals(0, 0);
cartJson.value = '[]';
return;
}
tbody.innerHTML = '';
let totalAmount = 0;
+ let totalVat = 0;
const cartData = [];
skus.forEach(sku => {
const item = invoiceItems[sku];
const lineTotal = item.qty * item.price;
+
+ const vatPercent = parseFloat(catalogData[sku].vat) || 0;
+ const itemVat = lineTotal - (lineTotal / (1 + (vatPercent / 100)));
+ totalVat += itemVat;
+
totalAmount += lineTotal;
cartData.push({ sku: item.sku, qty: item.qty });
@@ -668,12 +683,14 @@ function renderInvoice() {
tbody.appendChild(tr);
});
- updateTotals(totalAmount);
+ updateTotals(totalAmount, totalVat);
cartJson.value = JSON.stringify(cartData);
}
-function updateTotals(total) {
- document.getElementById('displaySubtotal').innerText = total.toFixed(3);
+function updateTotals(total, vat) {
+ const subtotal = total - vat;
+ document.getElementById('displaySubtotal').innerText = subtotal.toFixed(3);
+ document.getElementById('displayVat').innerText = vat.toFixed(3);
document.getElementById('displayTotal').innerText = total.toFixed(3) + currencySuffix;
}
diff --git a/includes/app.php b/includes/app.php
index 6a430e3..e3a935f 100644
--- a/includes/app.php
+++ b/includes/app.php
@@ -115,35 +115,6 @@ function branch_label(string $code): string
return current_lang() === 'ar' ? $branch['name_ar'] : $branch['name_en'];
}
-function demo_users(): array
-{
- return [
- 'owner' => [
- 'username' => 'owner',
- 'password' => 'owner123',
- 'role' => 'owner',
- 'branch_code' => 'muscat',
- 'name_ar' => 'مالك النظام',
- 'name_en' => 'System Owner',
- ],
- 'manager_muscat' => [
- 'username' => 'manager_muscat',
- 'password' => 'manager123',
- 'role' => 'manager',
- 'branch_code' => 'muscat',
- 'name_ar' => 'مدير فرع مسقط',
- 'name_en' => 'Muscat Branch Manager',
- ],
- 'cashier_sohar' => [
- 'username' => 'cashier_sohar',
- 'password' => 'cashier123',
- 'role' => 'cashier',
- 'branch_code' => 'sohar',
- 'name_ar' => 'كاشير فرع صحار',
- 'name_en' => 'Sohar Cashier',
- ],
- ];
-}
function role_label(string $role): string
{
@@ -162,18 +133,21 @@ function current_user(): ?array
function login_attempt(string $username, string $password): bool
{
- $users = demo_users();
- if (!isset($users[$username])) {
+ require_once __DIR__ . "/../db/config.php";
+ $stmt = db()->prepare("SELECT * FROM users WHERE username = ?");
+ $stmt->execute([$username]);
+ $user = $stmt->fetch();
+
+ if (!$user) {
return false;
}
- $user = $users[$username];
- if ($user['password'] !== $password) {
- return false;
+ if (password_verify($password, $user["password"])) {
+ $_SESSION["auth_user"] = $user;
+ return true;
}
- $_SESSION['auth_user'] = $user;
- return true;
+ return false;
}
function logout_user(): void
@@ -525,7 +499,7 @@ function purchase_pipeline(): array
function receipt_code(): string
{
- return 'AR-' . date('ymd-His') . '-' . random_int(100, 999);
+ return (string) random_int(100000, 999999);
}
function create_purchase(array $data): int
diff --git a/includes/sale_form.php b/includes/sale_form.php
index ebf70ab..860bb8b 100644
--- a/includes/sale_form.php
+++ b/includes/sale_form.php
@@ -31,6 +31,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} else {
$normalized = [];
$subtotal = 0.0;
+ $totalVat = 0.0;
$itemCount = 0;
foreach ($items as $item) {
$sku = (string) ($item['sku'] ?? '');
@@ -41,6 +42,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$product = $catalog[$sku];
$price = (float) $product['price'];
$lineTotal = $price * $qty;
+
+ $vatPercent = (float) ($product['vat'] ?? 0);
+ // Assuming price is inclusive of VAT:
+ $itemVat = $lineTotal - ($lineTotal / (1 + ($vatPercent / 100)));
+ $totalVat += $itemVat;
+
$normalized[] = [
'sku' => $sku,
'name_ar' => $product['name_ar'],
@@ -48,6 +55,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'qty' => $qty,
'price' => $price,
'line_total' => $lineTotal,
+ 'vat_percent' => $vatPercent,
+ 'vat_amount' => $itemVat
];
$subtotal += $lineTotal;
$itemCount += $qty;
@@ -68,7 +77,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
'payment_method' => $paymentMethod,
'items' => $normalized,
'item_count' => $itemCount,
- 'subtotal' => $subtotal,
+ 'subtotal' => $subtotal - $totalVat,
+ 'vat_amount' => $totalVat,
'total_amount' => $subtotal,
'status' => $saleStatus,
'notes' => $notes !== '' ? $notes : null,
@@ -360,8 +370,8 @@ require __DIR__ . '/header.php';
0.000
- = h(tr('الضريبة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?>
- = h(tr('مشمولة', 'Included')) ?>
+ = h(tr('الضريبة (مشمولة)', 'VAT (Included)')) ?>
+ 0.000
= h(tr('الإجمالي', 'Total')) ?>
@@ -601,18 +611,24 @@ function renderInvoice() {
if (skus.length === 0) {
tbody.innerHTML = '';
tbody.appendChild(emptyRow);
- updateTotals(0);
+ updateTotals(0, 0);
cartJson.value = '[]';
return;
}
tbody.innerHTML = '';
let totalAmount = 0;
+ let totalVat = 0;
const cartData = [];
skus.forEach(sku => {
const item = invoiceItems[sku];
const lineTotal = item.qty * item.price;
+
+ const vatPercent = parseFloat(catalogData[sku].vat) || 0;
+ const itemVat = lineTotal - (lineTotal / (1 + (vatPercent / 100)));
+ totalVat += itemVat;
+
totalAmount += lineTotal;
cartData.push({ sku: item.sku, qty: item.qty });
@@ -636,12 +652,14 @@ function renderInvoice() {
tbody.appendChild(tr);
});
- updateTotals(totalAmount);
+ updateTotals(totalAmount, totalVat);
cartJson.value = JSON.stringify(cartData);
}
-function updateTotals(total) {
- document.getElementById('displaySubtotal').innerText = total.toFixed(3);
+function updateTotals(total, vat) {
+ const subtotal = total - vat;
+ document.getElementById('displaySubtotal').innerText = subtotal.toFixed(3);
+ document.getElementById('displayVat').innerText = vat.toFixed(3);
document.getElementById('displayTotal').innerText = total.toFixed(3) + currencySuffix;
}
diff --git a/login.php b/login.php
index 81fadc9..4f9690e 100644
--- a/login.php
+++ b/login.php
@@ -35,7 +35,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
$projectName = $_SERVER['PROJECT_NAME'] ?? app_name();
$assetVersion = date('YmdHi');
-$accounts = demo_users();
+$accounts = [];
?>
@@ -175,24 +175,7 @@ $accounts = demo_users();
-
-
- = h(tr('حسابات تجريبية سريعة', 'Quick demo access')) ?>
-
-
-
-
-
-
-
-
-
+
diff --git a/print_receipt.php b/print_receipt.php
index 803e26d..acd5442 100644
--- a/print_receipt.php
+++ b/print_receipt.php
@@ -248,8 +248,8 @@ $registerNo = 'REG-01';
= number_format((float)$sale['subtotal'], 3) ?>
- = h(tr('ضريبة القيمة المضافة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?>
- = h(tr('شامل', 'Inclusive')) ?>
+ = h(tr('ضريبة القيمة المضافة (مشمولة)', 'VAT (Inclusive)')) ?>
+ = number_format((float)($sale['vat_amount'] ?? 0), 3) ?>
= h(tr('الإجمالي', 'Total')) ?>
diff --git a/reports.php b/reports.php
index f728ed6..5bcb0ec 100644
--- a/reports.php
+++ b/reports.php
@@ -3,50 +3,301 @@ require_once __DIR__ . '/includes/app.php';
$user = require_roles(['owner', 'manager']);
$pageTitle = tr('التقارير', 'Reports');
$activeNav = 'reports';
+
+$tab = $_GET['tab'] ?? 'summary';
$dbError = null;
-$report = ['gross' => 0.0, 'branch_totals' => [], 'payment_totals' => [], 'product_totals' => [], 'sales_count' => 0];
-try {
- $report = report_metrics();
-} catch (Throwable $e) {
- $dbError = $e->getMessage();
+
+if ($tab === 'sales') {
+ $dateFrom = $_GET['date_from'] ?? date('Y-m-01');
+ $dateTo = $_GET['date_to'] ?? date('Y-m-t');
+ $branchFilter = $_GET['branch'] ?? '';
+
+ $params = [];
+ $where = base_sales_query_filters($params, null, $branchFilter ?: null);
+ $where .= ' AND DATE(sale_date) >= :date_from AND DATE(sale_date) <= :date_to';
+ $params[':date_from'] = $dateFrom;
+ $params[':date_to'] = $dateTo;
+
+ $sql = 'SELECT * FROM sales_orders' . $where . ' ORDER BY sale_date DESC';
+ try {
+ $stmt = db()->prepare($sql);
+ foreach ($params as $k => $v) {
+ $stmt->bindValue($k, $v);
+ }
+ $stmt->execute();
+ $salesReport = $stmt->fetchAll();
+ } catch(Throwable $e) {
+ $dbError = $e->getMessage();
+ $salesReport = [];
+ }
+} elseif ($tab === 'orders') {
+ $branchFilter = $_GET['branch'] ?? '';
+ $params = [];
+ $where = base_sales_query_filters($params, null, $branchFilter ?: null);
+ $where .= " AND status = 'order'";
+
+ $sql = 'SELECT * FROM sales_orders' . $where . ' ORDER BY sale_date ASC';
+ try {
+ $stmt = db()->prepare($sql);
+ foreach ($params as $k => $v) {
+ $stmt->bindValue($k, $v);
+ }
+ $stmt->execute();
+ $followUpOrders = $stmt->fetchAll();
+ } catch(Throwable $e) {
+ $dbError = $e->getMessage();
+ $followUpOrders = [];
+ }
+} else {
+ $report = ['gross' => 0.0, 'branch_totals' => [], 'payment_totals' => [], 'product_totals' => [], 'sales_count' => 0];
+ try {
+ $report = report_metrics();
+ } catch (Throwable $e) {
+ $dbError = $e->getMessage();
+ }
}
+
require __DIR__ . '/includes/header.php';
?>
-
- = h(tr('إجمالي المبيعات', 'Gross sales')) ?>
= h(currency((float) $report['gross'])) ?>
= h(tr('حسب نطاق صلاحية المستخدم الحالي', 'Scoped to the current viewer permissions')) ?>
- = 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('المبيعات حسب الفرع', 'Sales by branch')) ?>
-
-
= h($dbError) ?>
-
-
= h(tr('لا توجد بيانات', 'No data')) ?>
= h(tr('أضف عملية بيع أولاً لبدء التقارير.', 'Add a first sale to activate reports.')) ?>
-
-
- $amount): ?>
-
= h(branch_label((string) $branchCode)) ?>= h(currency((float) $amount)) ?>
-
-
-
-
+
+
+
+
= h($pageTitle) ?>
-
-
-
= 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)) ?>
-
+
+
+
+
+
+
= h($dbError) ?>
+
+
+
+
+
-
-
-
+
+
+
= h(tr('تقرير المبيعات', 'Sales Report')) ?>
+
= h(tr('الفترة من', 'Period from')) ?> = h($dateFrom) ?> = h(tr('إلى', 'to')) ?> = h($dateTo) ?>
+
+
= h(tr('الفرع:', 'Branch:')) ?> = h(branch_label($branchFilter)) ?>
+
+
+
+
+
+
= h(tr('نتائج التقرير', 'Report Results')) ?>
+
+
+
+
+
= h(tr('لا توجد مبيعات في هذه الفترة.', 'No sales found in this period.')) ?>
+
+
+
+
+
+ | = h(tr('التاريخ', 'Date')) ?> |
+ = h(tr('رقم الإيصال', 'Receipt No')) ?> |
+ = h(tr('الكاشير', 'Cashier')) ?> |
+ = h(tr('الفرع', 'Branch')) ?> |
+ = h(tr('طريقة الدفع', 'Payment Method')) ?> |
+ = h(tr('الحالة', 'Status')) ?> |
+ = h(tr('الإجمالي', 'Total')) ?> |
+
+
+
+
+
+ | = h(date('Y-m-d H:i', strtotime((string)$sale['sale_date']))) ?> |
+ = h((string)$sale['receipt_no']) ?> |
+ = h((string)$sale['cashier_name']) ?> |
+ = h(branch_label((string)$sale['branch_code'])) ?> |
+ = h(ucfirst((string)$sale['payment_method'])) ?> |
+
+
+ = h(tr('طلب حجز', 'Order')) ?>
+
+ = h(tr('مدفوع', 'Paid')) ?>
+
+ |
+ = h(currency((float)$sale['total_amount'])) ?> |
+
+
+
+
+
+ | = h(tr('الإجمالي الكلي', 'Grand Total')) ?> |
+ = h(currency($totalSum)) ?> |
+
+
+
+
+
+
+
+
+
+
+
+
+
= h(tr('طلبات للمتابعة', 'Follow-up Orders')) ?>
+
= h(date('Y-m-d H:i')) ?>
+
+
= h(tr('الفرع:', 'Branch:')) ?> = h(branch_label($branchFilter)) ?>
+
+
+
+
+
+
= h(tr('طلبات حجز بانتظار الدفع', 'Reservation orders pending payment')) ?>
+
+
+
+
+
= h(tr('لا توجد طلبات للمتابعة.', 'No follow-up orders.')) ?>
+
+
+
+
+
+ | = h(tr('التاريخ', 'Date')) ?> |
+ = h(tr('رقم الإيصال', 'Receipt No')) ?> |
+ = h(tr('العميل', 'Customer')) ?> |
+ = h(tr('هاتف العميل', 'Customer Phone')) ?> |
+ = h(tr('الفرع', 'Branch')) ?> |
+ = h(tr('المبلغ المستحق', 'Due Amount')) ?> |
+ = h(tr('إجراءات', 'Actions')) ?> |
+
+
+
+
+
+ | = h(date('Y-m-d H:i', strtotime((string)$sale['sale_date']))) ?> |
+ = h((string)$sale['receipt_no']) ?> |
+ = h((string)($sale['customer_name'] ?: '-')) ?> |
+ = h((string)($sale['customer_phone'] ?: '-')) ?> |
+ = h(branch_label((string)$sale['branch_code'])) ?> |
+ = h(currency((float)$sale['total_amount'])) ?> |
+
+ = h(tr('عرض', 'View')) ?>
+
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+ = h(tr('إجمالي المبيعات', 'Gross sales')) ?>
= h(currency((float) $report['gross'])) ?>
= h(tr('حسب نطاق صلاحية المستخدم الحالي', 'Scoped to the current viewer permissions')) ?>
+ = 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('المبيعات حسب الفرع', '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)) ?>
+
+
+
+
+
+
+
+
= 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)) ?>
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sale.php b/sale.php
index 4b1791c..b1d806b 100644
--- a/sale.php
+++ b/sale.php
@@ -424,8 +424,8 @@ require __DIR__ . '/includes/header.php';
= h(number_format((float) $sale['subtotal'], 3)) ?> |
- | = h(tr('ضريبة القيمة المضافة (' . get_setting('vat_percentage', 5) . '%)', 'VAT (' . get_setting('vat_percentage', 5) . '%)')) ?> |
- = h(tr('شامل', 'Inclusive')) ?> |
+ = h(tr('ضريبة القيمة المضافة (مشمولة)', 'VAT (Inclusive)')) ?> |
+ = number_format((float)($sale['vat_amount'] ?? 0), 3) ?> |
| = h(tr('الإجمالي', 'Total')) ?> |
diff --git a/sales.php b/sales.php
index 2a0c8cd..1628cd2 100644
--- a/sales.php
+++ b/sales.php
@@ -14,7 +14,8 @@ if (isset($_GET['mark_paid']) && is_numeric($_GET['mark_paid'])) {
$id = (int)$_GET['mark_paid'];
db()->prepare("UPDATE sales_orders SET status = 'completed' WHERE id = ?")->execute([$id]);
} catch(Throwable $e) {}
- header("Location: sales.php");
+ $redirect = $_GET["redirect"] ?? "sales.php";
+ header("Location: " . $redirect);
exit;
}
@@ -99,7 +100,7 @@ require __DIR__ . '/includes/header.php';
+
+
+
+ = h($flash['message']) ?>
+
+
+
@@ -71,7 +141,7 @@ require __DIR__ . '/includes/header.php';
| = h(tr('لا توجد بيانات', 'No data found')) ?> |
- $account): ?>
+
|
= h(current_lang() === 'ar' ? $account['name_ar'] : $account['name_en']) ?>
@@ -83,12 +153,19 @@ require __DIR__ . '/includes/header.php';
| = h(in_array($account['role'], ['owner', 'manager'], true) ? tr('نعم', 'Yes') : tr('لا', 'No')) ?> |
= h($account['role'] === 'owner' ? tr('نعم', 'Yes') : tr('لا', 'No')) ?> |
- |
@@ -109,35 +186,52 @@ require __DIR__ . '/includes/header.php';
-
-