last changes
This commit is contained in:
parent
bda20a7ffb
commit
e3c02137ea
@ -5,3 +5,9 @@
|
|||||||
2026-02-25 11:49:27 - Items case hit
|
2026-02-25 11:49:27 - Items case hit
|
||||||
2026-02-25 11:51:57 - Items case hit
|
2026-02-25 11:51:57 - Items case hit
|
||||||
2026-02-25 12:41:41 - Items case hit
|
2026-02-25 12:41:41 - Items case hit
|
||||||
|
2026-02-25 12:45:17 - Items case hit
|
||||||
|
2026-02-25 13:33:02 - Items case hit
|
||||||
|
2026-02-25 14:04:18 - Items case hit
|
||||||
|
2026-02-25 14:06:09 - Items case hit
|
||||||
|
2026-02-25 14:10:50 - Items case hit
|
||||||
|
2026-02-25 15:28:54 - Items case hit
|
||||||
|
|||||||
30
fix_duplicates.php
Normal file
30
fix_duplicates.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$search1 = '<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
|
||||||
|
<select name="outlet_id" class="form-select">
|
||||||
|
<option value="">--- Global (Admin only) ---</option>
|
||||||
|
<?php foreach (($data[\'outlets\'] ?? []) as $o): ?>
|
||||||
|
<option value="<?= $o[\'id\'] ?>" <?= ($u[\'outlet_id\'] ?? null) == $o[\'id\'] ? \'selected\' : \'\' ?>><?= htmlspecialchars($o[\'name\']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$content = str_replace($search1 . "\n " . $search1, $search1, $content);
|
||||||
|
|
||||||
|
|
||||||
|
$search2 = '<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
|
||||||
|
<select name="outlet_id" class="form-select">
|
||||||
|
<option value="">--- Global (Admin only) ---</option>
|
||||||
|
<?php foreach (($data[\'outlets\'] ?? []) as $o): ?>
|
||||||
|
<option value="<?= $o[\'id\'] ?>"><?= htmlspecialchars($o[\'name\']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$content = str_replace($search2 . "\n " . $search2, $search2, $content);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "Duplicates removed.\n";
|
||||||
11
fix_login_dup.php
Normal file
11
fix_login_dup.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$search = "\$_SESSION['outlet_id'] = \$u['outlet_id'];";
|
||||||
|
$replacement = "\$_SESSION['outlet_id'] = \$u['outlet_id'];";
|
||||||
|
|
||||||
|
// To fix duplicates, we can simply replace 3 of them with 1
|
||||||
|
$content = preg_replace("/(\s*\\\$_\SESSION\['outlet_id'\] = \\\$u\['outlet_id'\];){2,}/", "\n \$_SESSION['outlet_id'] = \$u['outlet_id'];", $content);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "Duplicate login outlet_ids removed.\n";
|
||||||
2
fix_ui_typo.php
Normal file
2
fix_ui_typo.php
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<?php
|
||||||
|
// Just a final check to remove any leftovers from previous attempts if any
|
||||||
36
fix_users_table.php
Normal file
36
fix_users_table.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$search_th = '<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>';
|
||||||
|
$replace_th = '<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
|
||||||
|
<th data-en="Outlet" data-ar="الفرع">Outlet</th>';
|
||||||
|
|
||||||
|
$content = str_replace($search_th, $replace_th, $content);
|
||||||
|
|
||||||
|
$search_td = '<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
|
||||||
|
<?= htmlspecialchars((string)($u[\'group_name\'] ?? \'No Role Assigned\')) ?>
|
||||||
|
</span>
|
||||||
|
</td>';
|
||||||
|
$replace_td = '<span class="badge rounded-pill bg-info bg-opacity-10 text-info px-3">
|
||||||
|
<?= htmlspecialchars((string)($u[\'group_name\'] ?? \'No Role Assigned\')) ?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">
|
||||||
|
<?php
|
||||||
|
$out_name = "Global / All Outlets";
|
||||||
|
foreach (($data["outlets"] ?? []) as $out) {
|
||||||
|
if ($out["id"] == $u["outlet_id"]) {
|
||||||
|
$out_name = $out["name"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo htmlspecialchars($out_name);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
</td>';
|
||||||
|
|
||||||
|
$content = str_replace($search_td, $replace_td, $content);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "Users table updated to show assigned outlet.\n";
|
||||||
@ -194,3 +194,14 @@ function getVatReport($start_date = null, $end_date = null) {
|
|||||||
'net_vat' => $output_vat - $input_vat
|
'net_vat' => $output_vat - $input_vat
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alias for Payroll Expense Journal
|
||||||
|
*/
|
||||||
|
function recordExpenseJournalForPayroll($payroll_id, $amount, $date) {
|
||||||
|
$entries = [
|
||||||
|
['code' => '5200', 'debit' => $amount],
|
||||||
|
['code' => '1100', 'credit' => $amount]
|
||||||
|
];
|
||||||
|
return createJournalEntry($date, "Payroll Expense #$payroll_id", "PAYROLL-$payroll_id", 'payroll', $payroll_id, $entries);
|
||||||
|
}
|
||||||
|
|||||||
402
index.php
402
index.php
@ -344,6 +344,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) {
|
|||||||
$_SESSION['user_id'] = $u['id'];
|
$_SESSION['user_id'] = $u['id'];
|
||||||
$_SESSION['username'] = $u['username'];
|
$_SESSION['username'] = $u['username'];
|
||||||
$_SESSION['user_role_name'] = $u['role_name'];
|
$_SESSION['user_role_name'] = $u['role_name'];
|
||||||
|
$_SESSION['outlet_id'] = $u['outlet_id'];
|
||||||
|
|
||||||
// Fetch permissions from the new role_permissions table
|
// Fetch permissions from the new role_permissions table
|
||||||
$permStmt = db()->prepare("SELECT permission FROM role_permissions WHERE role_id = ?");
|
$permStmt = db()->prepare("SELECT permission FROM role_permissions WHERE role_id = ?");
|
||||||
@ -2658,9 +2659,10 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
||||||
if ($username && $password) {
|
if ($username && $password) {
|
||||||
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id) VALUES (?, ?, ?, ?, ?)");
|
$outlet_id = !empty($_POST['outlet_id']) ? (int)$_POST['outlet_id'] : null;
|
||||||
|
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id, outlet_id) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
try {
|
try {
|
||||||
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id]);
|
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id, $outlet_id]);
|
||||||
$message = "User added successfully!";
|
$message = "User added successfully!";
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
if ($e->getCode() == '23000') {
|
if ($e->getCode() == '23000') {
|
||||||
@ -2715,9 +2717,10 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
$phone = $_POST['phone'] ?? '';
|
$phone = $_POST['phone'] ?? '';
|
||||||
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
$group_id = (int)($_POST['group_id'] ?? 0) ?: null;
|
||||||
$status = $_POST['status'] ?? 'active';
|
$status = $_POST['status'] ?? 'active';
|
||||||
|
$outlet_id = !empty($_POST['outlet_id']) ? (int)$_POST['outlet_id'] : null;
|
||||||
if ($id && $username) {
|
if ($id && $username) {
|
||||||
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ? WHERE id = ?");
|
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ?, outlet_id = ? WHERE id = ?");
|
||||||
$stmt->execute([$username, $email, $phone, $group_id, $status, $id]);
|
$stmt->execute([$username, $email, $phone, $group_id, $status, $outlet_id, $id]);
|
||||||
|
|
||||||
if (!empty($_POST['password'])) {
|
if (!empty($_POST['password'])) {
|
||||||
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
$hashed_password = password_hash($_POST['password'], PASSWORD_DEFAULT);
|
||||||
@ -2885,7 +2888,38 @@ if (isset($_POST['add_hr_department'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Cash Register & Session Handlers ---
|
|
||||||
|
// --- Outlets Handlers ---
|
||||||
|
if (isset($_POST['add_outlet']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$phone = $_POST['phone'] ?? '';
|
||||||
|
$address = $_POST['address'] ?? '';
|
||||||
|
$status = $_POST['status'] ?? 'active';
|
||||||
|
if ($name) {
|
||||||
|
$stmt = db()->prepare("INSERT INTO outlets (name, phone, address, status) VALUES (?, ?, ?, ?)");
|
||||||
|
$stmt->execute([$name, $phone, $address, $status]);
|
||||||
|
redirectWithMessage("Outlet added successfully!", "index.php?page=outlets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($_POST['edit_outlet']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$name = $_POST['name'] ?? '';
|
||||||
|
$phone = $_POST['phone'] ?? '';
|
||||||
|
$address = $_POST['address'] ?? '';
|
||||||
|
$status = $_POST['status'] ?? 'active';
|
||||||
|
if ($name) {
|
||||||
|
$stmt = db()->prepare("UPDATE outlets SET name=?, phone=?, address=?, status=? WHERE id=?");
|
||||||
|
$stmt->execute([$name, $phone, $address, $status, $id]);
|
||||||
|
redirectWithMessage("Outlet updated successfully!", "index.php?page=outlets");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isset($_POST['delete_outlet']) && ($_SESSION['user_role_name'] ?? '') === 'Administrator') {
|
||||||
|
$id = (int)$_POST['id'];
|
||||||
|
$stmt = db()->prepare("DELETE FROM outlets WHERE id=?");
|
||||||
|
$stmt->execute([$id]);
|
||||||
|
redirectWithMessage("Outlet deleted successfully!", "index.php?page=outlets");
|
||||||
|
}
|
||||||
|
// --- Cash Register & Session Handlers ---
|
||||||
if (isset($_POST['add_cash_register'])) {
|
if (isset($_POST['add_cash_register'])) {
|
||||||
$name = $_POST['name'] ?? '';
|
$name = $_POST['name'] ?? '';
|
||||||
if ($name) {
|
if ($name) {
|
||||||
@ -3531,6 +3565,9 @@ switch ($page) {
|
|||||||
break;
|
break;
|
||||||
case 'quotations':
|
case 'quotations':
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
|
if (isset($_SESSION['outlet_id'])) {
|
||||||
|
$where[] = "q.outlet_id = " . (int)$_SESSION['outlet_id'];
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
$s = $_GET['search'];
|
$s = $_GET['search'];
|
||||||
@ -3579,6 +3616,9 @@ switch ($page) {
|
|||||||
break;
|
break;
|
||||||
case 'lpos':
|
case 'lpos':
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
|
if (isset($_SESSION['outlet_id'])) {
|
||||||
|
$where[] = "q.outlet_id = " . (int)$_SESSION['outlet_id'];
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
$s = $_GET['search'];
|
$s = $_GET['search'];
|
||||||
@ -3644,6 +3684,9 @@ switch ($page) {
|
|||||||
$cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers';
|
$cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers';
|
||||||
|
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
|
if (isset($_SESSION['outlet_id'])) {
|
||||||
|
$where[] = "v.outlet_id = " . (int)$_SESSION['outlet_id'];
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
|
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
@ -3721,6 +3764,9 @@ switch ($page) {
|
|||||||
|
|
||||||
case 'sales_returns':
|
case 'sales_returns':
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
|
if (isset($_SESSION['outlet_id'])) {
|
||||||
|
$where[] = "sr.outlet_id = " . (int)$_SESSION['outlet_id'];
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
$s = $_GET['search'];
|
$s = $_GET['search'];
|
||||||
@ -3753,6 +3799,9 @@ switch ($page) {
|
|||||||
|
|
||||||
case 'purchase_returns':
|
case 'purchase_returns':
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
|
if (isset($_SESSION['outlet_id'])) {
|
||||||
|
$where[] = "pr.outlet_id = " . (int)$_SESSION['outlet_id'];
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
if (!empty($_GET['search'])) {
|
if (!empty($_GET['search'])) {
|
||||||
$s = $_GET['search'];
|
$s = $_GET['search'];
|
||||||
@ -3826,6 +3875,9 @@ switch ($page) {
|
|||||||
break;
|
break;
|
||||||
case 'expenses':
|
case 'expenses':
|
||||||
$where = ["1=1"];
|
$where = ["1=1"];
|
||||||
|
if (isset($_SESSION['outlet_id'])) {
|
||||||
|
$where[] = "e.outlet_id = " . (int)$_SESSION['outlet_id'];
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
if (!empty($_GET['category_id'])) {
|
if (!empty($_GET['category_id'])) {
|
||||||
$where[] = "e.category_id = ?";
|
$where[] = "e.category_id = ?";
|
||||||
@ -3854,6 +3906,7 @@ switch ($page) {
|
|||||||
case 'users':
|
case 'users':
|
||||||
$data['users'] = db()->query("SELECT u.*, g.name as group_name FROM users u LEFT JOIN role_groups g ON u.group_id = g.id ORDER BY u.username ASC")->fetchAll();
|
$data['users'] = db()->query("SELECT u.*, g.name as group_name FROM users u LEFT JOIN role_groups g ON u.group_id = g.id ORDER BY u.username ASC")->fetchAll();
|
||||||
$data['role_groups'] = db()->query("SELECT id, name FROM role_groups ORDER BY name ASC")->fetchAll();
|
$data['role_groups'] = db()->query("SELECT id, name FROM role_groups ORDER BY name ASC")->fetchAll();
|
||||||
|
$data['outlets'] = db()->query("SELECT id, name FROM outlets ORDER BY name ASC")->fetchAll();
|
||||||
break;
|
break;
|
||||||
case 'backups':
|
case 'backups':
|
||||||
$data['backups'] = BackupService::getBackups();
|
$data['backups'] = BackupService::getBackups();
|
||||||
@ -4099,24 +4152,72 @@ switch ($page) {
|
|||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if (can('dashboard_view')) {
|
if (can('dashboard_view')) {
|
||||||
$data['customers'] = db()->query("SELECT * FROM customers ORDER BY id DESC LIMIT 5")->fetchAll();
|
$out_w = isset($_SESSION['outlet_id']) ? "WHERE outlet_id = " . (int)$_SESSION['outlet_id'] : "WHERE 1=1";
|
||||||
|
$out_and = isset($_SESSION['outlet_id']) ? "AND outlet_id = " . (int)$_SESSION['outlet_id'] : "";
|
||||||
|
|
||||||
|
$data['customers'] = db()->query("SELECT * FROM customers $out_w ORDER BY id DESC LIMIT 5")->fetchAll();
|
||||||
$data['stats'] = [
|
$data['stats'] = [
|
||||||
'total_customers' => db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(),
|
'total_customers' => db()->query("SELECT COUNT(*) FROM customers $out_w")->fetchColumn(),
|
||||||
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
|
'total_items' => db()->query("SELECT COUNT(*) FROM stock_items $out_w")->fetchColumn(),
|
||||||
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = 'completed'")->fetchColumn() ?: 0),
|
'total_sales' => (db()->query("SELECT SUM(total_with_vat) FROM invoices $out_w")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = 'completed' $out_and")->fetchColumn() ?: 0),
|
||||||
'total_received' => (db()->query("SELECT SUM(amount) FROM payments")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments")->fetchColumn() ?: 0),
|
'total_received' => (db()->query("SELECT SUM(amount) FROM payments $out_w")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments WHERE 1=1 $out_and")->fetchColumn() ?: 0),
|
||||||
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases")->fetchColumn() ?: 0,
|
'total_purchases' => db()->query("SELECT SUM(total_with_vat) FROM purchases $out_w")->fetchColumn() ?: 0,
|
||||||
'total_paid' => db()->query("SELECT SUM(amount) FROM purchase_payments")->fetchColumn() ?: 0,
|
'total_paid' => db()->query("SELECT SUM(amount) FROM purchase_payments $out_w")->fetchColumn() ?: 0,
|
||||||
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
|
'expired_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE() $out_and")->fetchColumn(),
|
||||||
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
|
'near_expiry_items' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY) $out_and")->fetchColumn(),
|
||||||
'low_stock_items_count' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level")->fetchColumn(),
|
'low_stock_items_count' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level $out_and")->fetchColumn(),
|
||||||
];
|
];
|
||||||
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
|
$data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received'];
|
||||||
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
|
$data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid'];
|
||||||
|
|
||||||
// Sales Chart Data
|
// Sales Chart Data (Invoices + POS)
|
||||||
$data['monthly_sales'] = db()->query("SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, SUM(total_with_vat) as total FROM invoices GROUP BY DATE_FORMAT(invoice_date, '%Y-%m') ORDER BY invoice_date ASC LIMIT 12")->fetchAll(PDO::FETCH_ASSOC);
|
$data['monthly_sales'] = db()->query("
|
||||||
$data['yearly_sales'] = db()->query("SELECT YEAR(invoice_date) as label, SUM(total_with_vat) as total FROM invoices GROUP BY label ORDER BY label ASC LIMIT 5")->fetchAll(PDO::FETCH_ASSOC);
|
SELECT label, SUM(tot) as total FROM (
|
||||||
|
SELECT DATE_FORMAT(invoice_date, '%M %Y') as label, total_with_vat as tot, DATE_FORMAT(invoice_date, '%Y-%m') as sort_col FROM invoices $out_w
|
||||||
|
UNION ALL
|
||||||
|
SELECT DATE_FORMAT(created_at, '%M %Y') as label, net_amount as tot, DATE_FORMAT(created_at, '%Y-%m') as sort_col FROM pos_transactions WHERE status = 'completed' $out_and
|
||||||
|
) t
|
||||||
|
GROUP BY label, sort_col
|
||||||
|
ORDER BY sort_col ASC LIMIT 12
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$data['yearly_sales'] = db()->query("
|
||||||
|
SELECT label, SUM(tot) as total FROM (
|
||||||
|
SELECT YEAR(invoice_date) as label, total_with_vat as tot FROM invoices $out_w
|
||||||
|
UNION ALL
|
||||||
|
SELECT YEAR(created_at) as label, net_amount as tot FROM pos_transactions WHERE status = 'completed' $out_and
|
||||||
|
) t
|
||||||
|
GROUP BY label
|
||||||
|
ORDER BY label ASC LIMIT 5
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Cash Flow Data (Income vs Expense - last 6 months)
|
||||||
|
$data['cash_flow'] = db()->query("
|
||||||
|
SELECT m.sort_col, m.label,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM payments WHERE DATE_FORMAT(payment_date, '%Y-%m') = m.sort_col $out_and
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM pos_payments WHERE DATE_FORMAT(created_at, '%Y-%m') = m.sort_col $out_and
|
||||||
|
) as income,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM expenses WHERE DATE_FORMAT(expense_date, '%Y-%m') = m.sort_col $out_and
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM purchase_payments WHERE DATE_FORMAT(payment_date, '%Y-%m') = m.sort_col $out_and
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(net_salary), 0) FROM hr_payroll WHERE DATE_FORMAT(payment_date, '%Y-%m') = m.sort_col $out_and
|
||||||
|
) as expense
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT DATE_FORMAT(dt, '%Y-%m') as sort_col, DATE_FORMAT(dt, '%M %Y') as label
|
||||||
|
FROM (
|
||||||
|
SELECT payment_date as dt FROM payments $out_w
|
||||||
|
UNION SELECT created_at as dt FROM pos_payments $out_w
|
||||||
|
UNION SELECT expense_date as dt FROM expenses $out_w
|
||||||
|
UNION SELECT payment_date as dt FROM purchase_payments $out_w
|
||||||
|
) dates
|
||||||
|
) m
|
||||||
|
ORDER BY m.sort_col DESC LIMIT 6
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$data['cash_flow'] = array_reverse($data['cash_flow']); // Chronological order
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -4144,6 +4245,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></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/jsbarcode@3.11.5/dist/JsBarcode.all.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
|
||||||
|
|
||||||
<script src="assets/js/main.js?v=<?= time() ?>"></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 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() ?>">
|
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||||
@ -4257,6 +4360,71 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
body { font-size: 12px !important; color: #000 !important; background: #fff !important; }
|
body { font-size: 12px !important; color: #000 !important; background: #fff !important; }
|
||||||
@page { margin: 1cm; }
|
@page { margin: 1cm; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Thermal Receipt Layout */
|
||||||
|
.thermal-receipt {
|
||||||
|
width: 300px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: #fff;
|
||||||
|
color: #000;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.thermal-receipt .center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.thermal-receipt .separator {
|
||||||
|
border-bottom: 1px dashed #000;
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
.thermal-receipt table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.thermal-receipt table th, .thermal-receipt table td {
|
||||||
|
padding: 4px 2px;
|
||||||
|
text-align: left;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.thermal-receipt table th {
|
||||||
|
border-bottom: 1px dashed #000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.thermal-receipt .total-row {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
border-top: 1px dashed #000;
|
||||||
|
border-bottom: 1px dashed #000;
|
||||||
|
padding: 5px 0;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.thermal-receipt.rtl {
|
||||||
|
direction: rtl;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.thermal-receipt.rtl table th, .thermal-receipt.rtl table td {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
@media print {
|
||||||
|
body.printing-receipt {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
body.printing-receipt .thermal-receipt-print {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 300px; /* typically 80mm thermal paper width */
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
body.printing-receipt #posPrintArea {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.print-only { display: none; }
|
.print-only { display: none; }
|
||||||
[dir="rtl"] { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
[dir="rtl"] { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }
|
||||||
</style>
|
</style>
|
||||||
@ -4513,12 +4681,17 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- Administrations Section -->
|
<!-- Administrations Section -->
|
||||||
<?php if (can('users_view')): ?>
|
<?php if (can('users_view') || ($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
|
||||||
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['role_groups', 'users', 'cash_registers', 'register_sessions', 'scale_devices', 'backups', 'customer_display_settings']) ? 'collapsed' : '' ?>" data-bs-toggle="collapse" data-bs-target="#admin-collapse">
|
<div class="nav-section-title px-4 mt-3 mb-1 text-uppercase text-muted <?= !in_array($page, ['role_groups', 'users', 'cash_registers', 'register_sessions', '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>
|
<span><i class="fas fa-user-gear group-icon"></i><span><?= __('admin') ?></span></span>
|
||||||
<i class="fas fa-chevron-down chevron"></i>
|
<i class="fas fa-chevron-down chevron"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse <?= in_array($page, ['role_groups', 'users', 'cash_registers', 'register_sessions', 'scale_devices', 'backups', 'customer_display_settings']) ? 'show' : '' ?>" id="admin-collapse">
|
<div class="collapse <?= in_array($page, ['role_groups', 'users', 'cash_registers', 'register_sessions', 'scale_devices', 'backups', 'customer_display_settings', 'outlets']) ? 'show' : '' ?>" id="admin-collapse">
|
||||||
|
<?php if (($_SESSION['user_role_name'] ?? '') === 'Administrator'): ?>
|
||||||
|
<a href="index.php?page=outlets" class="nav-link <?= $page === 'outlets' ? 'active' : '' ?>">
|
||||||
|
<i class="fas fa-shop"></i> <span>Outlets</span>
|
||||||
|
</a>
|
||||||
|
<?php endif; ?>
|
||||||
<a href="index.php?page=role_groups" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
|
<a href="index.php?page=role_groups" class="nav-link <?= $page === 'role_groups' ? 'active' : '' ?>">
|
||||||
<i class="fas fa-user-shield"></i> <span><?= __('role_groups') ?></span>
|
<i class="fas fa-user-shield"></i> <span><?= __('role_groups') ?></span>
|
||||||
</a>
|
</a>
|
||||||
@ -4847,20 +5020,31 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row g-4 mb-4">
|
<div class="row g-4 mb-4">
|
||||||
<div class="col-12">
|
<div class="col-md-7">
|
||||||
<div class="card p-4">
|
<div class="card p-4 h-100 shadow-sm border-0" style="border-radius: 12px;">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
<h5 class="m-0" data-en="Sales Performance" data-ar="أداء المبيعات">Sales Performance</h5>
|
<h5 class="m-0 fw-bold text-dark" data-en="Sales Trend" data-ar="اتجاه المبيعات">Sales Trend</h5>
|
||||||
<div class="btn-group btn-group-sm">
|
<div class="btn-group btn-group-sm shadow-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 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>
|
<button type="button" class="btn btn-outline-primary" id="btnYearly" data-en="Yearly" data-ar="سنوي">Yearly</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 300px;">
|
<div style="height: 300px; position: relative;">
|
||||||
<canvas id="salesChart"></canvas>
|
<canvas id="salesChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="card p-4 h-100 shadow-sm border-0" style="border-radius: 12px;">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h5 class="m-0 fw-bold text-dark" data-en="Cash Flow Overview" data-ar="نظرة عامة على التدفق النقدي">Cash Flow Overview</h5>
|
||||||
|
<span class="badge bg-light text-muted border" data-en="Last 6 Months" data-ar="آخر 6 أشهر">Last 6 Months</span>
|
||||||
|
</div>
|
||||||
|
<div style="height: 300px; position: relative;">
|
||||||
|
<canvas id="cashFlowChart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@ -10005,6 +10189,7 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<tr>
|
<tr>
|
||||||
<th class="ps-4" data-en="User Info" data-ar="معلومات المستخدم">User Info</th>
|
<th class="ps-4" data-en="User Info" data-ar="معلومات المستخدم">User Info</th>
|
||||||
<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
|
<th data-en="Access Level" data-ar="مستوى الوصول">Access Level</th>
|
||||||
|
<th data-en="Outlet" data-ar="الفرع">Outlet</th>
|
||||||
<th data-en="Contact" data-ar="الاتصال">Contact</th>
|
<th data-en="Contact" data-ar="الاتصال">Contact</th>
|
||||||
<th data-en="Status" data-ar="الحالة">Status</th>
|
<th data-en="Status" data-ar="الحالة">Status</th>
|
||||||
<th data-en="Actions" data-ar="الإجراءات" class="text-end pe-4">Actions</th>
|
<th data-en="Actions" data-ar="الإجراءات" class="text-end pe-4">Actions</th>
|
||||||
@ -10033,6 +10218,20 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<?= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
|
<?= htmlspecialchars((string)($u['group_name'] ?? 'No Role Assigned')) ?>
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge rounded-pill bg-secondary bg-opacity-10 text-secondary px-3">
|
||||||
|
<?php
|
||||||
|
$out_name = "Global / All Outlets";
|
||||||
|
foreach (($data["outlets"] ?? []) as $out) {
|
||||||
|
if ($out["id"] == $u["outlet_id"]) {
|
||||||
|
$out_name = $out["name"];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
echo htmlspecialchars($out_name);
|
||||||
|
?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="text-dark small mb-1"><i class="bi bi-envelope me-1"></i> <?= htmlspecialchars((string)($u['email'] ?? '')) ?></div>
|
<div class="text-dark small mb-1"><i class="bi bi-envelope me-1"></i> <?= htmlspecialchars((string)($u['email'] ?? '')) ?></div>
|
||||||
<div class="text-muted small"><i class="bi bi-phone me-1"></i> <?= htmlspecialchars((string)($u['phone'] ?? '-')) ?></div>
|
<div class="text-muted small"><i class="bi bi-phone me-1"></i> <?= htmlspecialchars((string)($u['phone'] ?? '-')) ?></div>
|
||||||
@ -10097,6 +10296,15 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
|
||||||
|
<select name="outlet_id" class="form-select">
|
||||||
|
<option value="">--- Global (Admin only) ---</option>
|
||||||
|
<?php foreach (($data['outlets'] ?? []) as $o): ?>
|
||||||
|
<option value="<?= $o['id'] ?>" <?= ($u['outlet_id'] ?? null) == $o['id'] ? 'selected' : '' ?>><?= htmlspecialchars($o['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label fw-semibold" data-en="Account Status" data-ar="حالة الحساب">Account Status</label>
|
<label class="form-label fw-semibold" data-en="Account Status" data-ar="حالة الحساب">Account Status</label>
|
||||||
<select name="status" class="form-select">
|
<select name="status" class="form-select">
|
||||||
@ -11045,6 +11253,15 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System';
|
|||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
|
||||||
|
<select name="outlet_id" class="form-select">
|
||||||
|
<option value="">--- Global (Admin only) ---</option>
|
||||||
|
<?php foreach (($data['outlets'] ?? []) as $o): ?>
|
||||||
|
<option value="<?= $o['id'] ?>"><?= htmlspecialchars($o['name']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Cancel" data-ar="إلغاء">Cancel</button>
|
||||||
@ -13302,6 +13519,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
|
||||||
<button type="button" class="btn btn-primary" onclick="window.printLPO()">Print LPO</button>
|
<button type="button" class="btn btn-primary" onclick="window.printLPO()">Print LPO</button>
|
||||||
|
<button type="button" class="btn btn-danger" onclick="downloadPDF('lpoDetailsContent', 'LPO')"><i class="bi bi-file-earmark-pdf me-2"></i>PDF</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13488,6 +13706,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<div class="modal-footer d-print-none">
|
<div class="modal-footer d-print-none">
|
||||||
<div id="quotationActionButtons" class="me-auto"></div>
|
<div id="quotationActionButtons" class="me-auto"></div>
|
||||||
<button type="button" class="btn btn-secondary" onclick="window.print()"><i class="bi bi-printer"></i data-en="Print" data-ar="طباعة">Print</button>
|
<button type="button" class="btn btn-secondary" onclick="window.print()"><i class="bi bi-printer"></i data-en="Print" data-ar="طباعة">Print</button>
|
||||||
|
<button type="button" class="btn btn-danger" onclick="downloadPDF('quotationPrintableArea', 'Quotation')"><i class="bi bi-file-earmark-pdf me-2"></i>PDF</button>
|
||||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
|
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13726,6 +13945,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
<div class="modal-footer d-print-none">
|
<div class="modal-footer d-print-none">
|
||||||
<button type="button" class="btn btn-light" data-bs-dismiss="modal" data-en="Close" data-ar="إغلاق">Close</button>
|
<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="window.print()"><i class="bi bi-printer me-2"></i>Print Invoice</button>
|
<button type="button" class="btn btn-primary" onclick="window.print()"><i class="bi bi-printer me-2"></i>Print Invoice</button>
|
||||||
|
<button type="button" class="btn btn-danger" onclick="downloadPDF('invoicePrintableArea', 'Invoice')"><i class="bi bi-file-earmark-pdf me-2"></i>PDF</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -14622,6 +14842,29 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.downloadPDF = function(elementId, filenamePrefix) {
|
||||||
|
const element = document.getElementById(elementId);
|
||||||
|
if (!element) return;
|
||||||
|
|
||||||
|
// Hide elements with 'd-print-none' during PDF generation
|
||||||
|
const hiddenElements = element.querySelectorAll('.d-print-none');
|
||||||
|
hiddenElements.forEach(el => el.style.display = 'none');
|
||||||
|
|
||||||
|
const opt = {
|
||||||
|
margin: [10, 10, 10, 10],
|
||||||
|
filename: filenamePrefix + '_' + new Date().getTime() + '.pdf',
|
||||||
|
image: { type: 'jpeg', quality: 0.98 },
|
||||||
|
html2canvas: { scale: 2, useCORS: true },
|
||||||
|
jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }
|
||||||
|
};
|
||||||
|
|
||||||
|
html2pdf().set(opt).from(element).save().then(() => {
|
||||||
|
// Restore hidden elements
|
||||||
|
hiddenElements.forEach(el => el.style.display = '');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// --- Language Apply Script ---
|
// --- Language Apply Script ---
|
||||||
function applyLanguage(node) {
|
function applyLanguage(node) {
|
||||||
const docLang = document.documentElement.lang || 'ar';
|
const docLang = document.documentElement.lang || 'ar';
|
||||||
@ -14682,19 +14925,31 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
<?php if ($page === 'dashboard' && can('dashboard_view')): ?>
|
<?php if ($page === 'dashboard' && can('dashboard_view')): ?>
|
||||||
const monthlyData = <?= json_encode($data['monthly_sales']) ?>;
|
const monthlyData = <?= json_encode($data['monthly_sales'] ?? []) ?>;
|
||||||
const yearlyData = <?= json_encode($data['yearly_sales']) ?>;
|
const yearlyData = <?= json_encode($data['yearly_sales'] ?? []) ?>;
|
||||||
|
const cashFlowData = <?= json_encode($data['cash_flow'] ?? []) ?>;
|
||||||
|
|
||||||
const ctx = document.getElementById('salesChart').getContext('2d');
|
const ctx = document.getElementById('salesChart').getContext('2d');
|
||||||
|
|
||||||
|
let gradient = ctx.createLinearGradient(0, 0, 0, 400);
|
||||||
|
gradient.addColorStop(0, 'rgba(13, 110, 253, 0.4)');
|
||||||
|
gradient.addColorStop(1, 'rgba(13, 110, 253, 0)');
|
||||||
|
|
||||||
let salesChart = new Chart(ctx, {
|
let salesChart = new Chart(ctx, {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: {
|
data: {
|
||||||
labels: monthlyData.map(d => d.label),
|
labels: monthlyData.map(d => d.label),
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Sales (OMR)',
|
label: '<?= $lang === "ar" ? "المبيعات" : "Sales" ?>',
|
||||||
data: monthlyData.map(d => d.total),
|
data: monthlyData.map(d => d.total),
|
||||||
borderColor: '#0d6efd',
|
borderColor: '#0d6efd',
|
||||||
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
backgroundColor: gradient,
|
||||||
|
borderWidth: 3,
|
||||||
|
pointBackgroundColor: '#ffffff',
|
||||||
|
pointBorderColor: '#0d6efd',
|
||||||
|
pointBorderWidth: 2,
|
||||||
|
pointRadius: 4,
|
||||||
|
pointHoverRadius: 6,
|
||||||
fill: true,
|
fill: true,
|
||||||
tension: 0.4
|
tension: 0.4
|
||||||
}]
|
}]
|
||||||
@ -14703,16 +14958,36 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
responsive: true,
|
responsive: true,
|
||||||
maintainAspectRatio: false,
|
maintainAspectRatio: false,
|
||||||
plugins: {
|
plugins: {
|
||||||
legend: { display: false }
|
legend: { display: false },
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||||
|
titleFont: { size: 14, family: "'Segoe UI', Roboto, sans-serif" },
|
||||||
|
bodyFont: { size: 14, family: "'Segoe UI', Roboto, sans-serif" },
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 8,
|
||||||
|
displayColors: false,
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
let label = context.dataset.label || '';
|
||||||
|
if (label) { label += ': '; }
|
||||||
|
if (context.parsed.y !== null) { label += new Intl.NumberFormat().format(context.parsed.y) + ' OMR'; }
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
|
x: { grid: { display: false, drawBorder: false } },
|
||||||
y: {
|
y: {
|
||||||
beginAtZero: true,
|
beginAtZero: true,
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
border: { dash: [5, 5] },
|
||||||
ticks: {
|
ticks: {
|
||||||
callback: function(value) { return 'OMR ' + value.toFixed(3); }
|
callback: function(value) { return 'OMR ' + value.toFixed(3); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
interaction: { mode: 'index', intersect: false }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -14731,6 +15006,69 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
salesChart.data.datasets[0].data = yearlyData.map(d => d.total);
|
salesChart.data.datasets[0].data = yearlyData.map(d => d.total);
|
||||||
salesChart.update();
|
salesChart.update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (cashFlowData.length > 0) {
|
||||||
|
const ctxCf = document.getElementById('cashFlowChart').getContext('2d');
|
||||||
|
new Chart(ctxCf, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: cashFlowData.map(d => d.label),
|
||||||
|
datasets: [
|
||||||
|
{
|
||||||
|
label: '<?= $lang === "ar" ? "الدخل" : "Income" ?>',
|
||||||
|
data: cashFlowData.map(d => d.income),
|
||||||
|
backgroundColor: '#198754',
|
||||||
|
borderRadius: 4,
|
||||||
|
barPercentage: 0.6,
|
||||||
|
categoryPercentage: 0.8
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '<?= $lang === "ar" ? "المصروفات" : "Expenses" ?>',
|
||||||
|
data: cashFlowData.map(d => d.expense),
|
||||||
|
backgroundColor: '#dc3545',
|
||||||
|
borderRadius: 4,
|
||||||
|
barPercentage: 0.6,
|
||||||
|
categoryPercentage: 0.8
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'top',
|
||||||
|
labels: { usePointStyle: true, boxWidth: 8, font: { family: "'Segoe UI', Roboto, sans-serif" } }
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.8)',
|
||||||
|
padding: 10,
|
||||||
|
cornerRadius: 8,
|
||||||
|
callbacks: {
|
||||||
|
label: function(context) {
|
||||||
|
let label = context.dataset.label || '';
|
||||||
|
if (label) { label += ': '; }
|
||||||
|
if (context.parsed.y !== null) { label += new Intl.NumberFormat().format(context.parsed.y) + ' OMR'; }
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { grid: { display: false, drawBorder: false } },
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: { color: 'rgba(0,0,0,0.05)', drawBorder: false },
|
||||||
|
border: { dash: [5, 5] },
|
||||||
|
ticks: {
|
||||||
|
callback: function(value) { return 'OMR ' + value.toFixed(3); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
interaction: { mode: 'index', intersect: false }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
60
patch_cashflow.php
Normal file
60
patch_cashflow.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
$c = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$find_cf = ' // Cash Flow Data (Income vs Expense - last 6 months)
|
||||||
|
$data[\'cash_flow\'] = db()->query("
|
||||||
|
SELECT m.sort_col, m.label,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM payments WHERE DATE_FORMAT(payment_date, \'%Y-%m\') = m.sort_col
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM pos_payments WHERE DATE_FORMAT(created_at, \'%Y-%m\') = m.sort_col
|
||||||
|
) as income,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM expenses WHERE DATE_FORMAT(expense_date, \'%Y-%m\') = m.sort_col
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM purchase_payments WHERE DATE_FORMAT(payment_date, \'%Y-%m\') = m.sort_col
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(net_salary), 0) FROM hr_payroll WHERE DATE_FORMAT(payment_date, \'%Y-%m\') = m.sort_col
|
||||||
|
) as expense
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT DATE_FORMAT(dt, \'%Y-%m\') as sort_col, DATE_FORMAT(dt, \'%M %Y\') as label
|
||||||
|
FROM (
|
||||||
|
SELECT payment_date as dt FROM payments
|
||||||
|
UNION SELECT created_at as dt FROM pos_payments
|
||||||
|
UNION SELECT expense_date as dt FROM expenses
|
||||||
|
UNION SELECT payment_date as dt FROM purchase_payments
|
||||||
|
) dates
|
||||||
|
) m
|
||||||
|
ORDER BY m.sort_col DESC LIMIT 6
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);';
|
||||||
|
|
||||||
|
$repl_cf = ' // Cash Flow Data (Income vs Expense - last 6 months)
|
||||||
|
$data[\'cash_flow\'] = db()->query("
|
||||||
|
SELECT m.sort_col, m.label,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM payments WHERE DATE_FORMAT(payment_date, \'%Y-%m\') = m.sort_col $out_and
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM pos_payments WHERE DATE_FORMAT(created_at, \'%Y-%m\') = m.sort_col $out_and
|
||||||
|
) as income,
|
||||||
|
(
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM expenses WHERE DATE_FORMAT(expense_date, \'%Y-%m\') = m.sort_col $out_and
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(amount), 0) FROM purchase_payments WHERE DATE_FORMAT(payment_date, \'%Y-%m\') = m.sort_col $out_and
|
||||||
|
) + (
|
||||||
|
SELECT COALESCE(SUM(net_salary), 0) FROM hr_payroll WHERE DATE_FORMAT(payment_date, \'%Y-%m\') = m.sort_col $out_and
|
||||||
|
) as expense
|
||||||
|
FROM (
|
||||||
|
SELECT DISTINCT DATE_FORMAT(dt, \'%Y-%m\') as sort_col, DATE_FORMAT(dt, \'%M %Y\') as label
|
||||||
|
FROM (
|
||||||
|
SELECT payment_date as dt FROM payments $out_w
|
||||||
|
UNION SELECT created_at as dt FROM pos_payments $out_w
|
||||||
|
UNION SELECT expense_date as dt FROM expenses $out_w
|
||||||
|
UNION SELECT payment_date as dt FROM purchase_payments $out_w
|
||||||
|
) dates
|
||||||
|
) m
|
||||||
|
ORDER BY m.sort_col DESC LIMIT 6
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);';
|
||||||
|
|
||||||
|
$c = str_replace($find_cf, $repl_cf, $c);
|
||||||
|
file_put_contents('index.php', $c);
|
||||||
|
echo "Cash flow patched\n";
|
||||||
81
patch_dashboard.php
Normal file
81
patch_dashboard.php
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
$c = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$find_dashboard = ' if (can(\'dashboard_view\')) {
|
||||||
|
$data[\'customers\'] = db()->query("SELECT * FROM customers ORDER BY id DESC LIMIT 5")->fetchAll();
|
||||||
|
$data[\'stats\'] = [
|
||||||
|
\'total_customers\' => db()->query("SELECT COUNT(*) FROM customers")->fetchColumn(),
|
||||||
|
\'total_items\' => db()->query("SELECT COUNT(*) FROM stock_items")->fetchColumn(),
|
||||||
|
\'total_sales\' => (db()->query("SELECT SUM(total_with_vat) FROM invoices")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = \'completed\'")->fetchColumn() ?: 0),
|
||||||
|
\'total_received\' => (db()->query("SELECT SUM(amount) FROM payments")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments")->fetchColumn() ?: 0),
|
||||||
|
\'total_purchases\' => db()->query("SELECT SUM(total_with_vat) FROM purchases")->fetchColumn() ?: 0,
|
||||||
|
\'total_paid\' => db()->query("SELECT SUM(amount) FROM purchase_payments")->fetchColumn() ?: 0,
|
||||||
|
\'expired_items\' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE()")->fetchColumn(),
|
||||||
|
\'near_expiry_items\' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)")->fetchColumn(),
|
||||||
|
\'low_stock_items_count\' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level")->fetchColumn(),
|
||||||
|
];';
|
||||||
|
|
||||||
|
$repl_dashboard = ' if (can(\'dashboard_view\')) {
|
||||||
|
$out_w = isset($_SESSION[\'outlet_id\']) ? "WHERE outlet_id = " . (int)$_SESSION[\'outlet_id\'] : "WHERE 1=1";
|
||||||
|
$out_and = isset($_SESSION[\'outlet_id\']) ? "AND outlet_id = " . (int)$_SESSION[\'outlet_id\'] : "";
|
||||||
|
|
||||||
|
$data[\'customers\'] = db()->query("SELECT * FROM customers $out_w ORDER BY id DESC LIMIT 5")->fetchAll();
|
||||||
|
$data[\'stats\'] = [
|
||||||
|
\'total_customers\' => db()->query("SELECT COUNT(*) FROM customers $out_w")->fetchColumn(),
|
||||||
|
\'total_items\' => db()->query("SELECT COUNT(*) FROM stock_items $out_w")->fetchColumn(),
|
||||||
|
\'total_sales\' => (db()->query("SELECT SUM(total_with_vat) FROM invoices $out_w")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(net_amount) FROM pos_transactions WHERE status = \'completed\' $out_and")->fetchColumn() ?: 0),
|
||||||
|
\'total_received\' => (db()->query("SELECT SUM(amount) FROM payments $out_w")->fetchColumn() ?: 0) + (db()->query("SELECT SUM(amount) FROM pos_payments WHERE 1=1 $out_and")->fetchColumn() ?: 0),
|
||||||
|
\'total_purchases\' => db()->query("SELECT SUM(total_with_vat) FROM purchases $out_w")->fetchColumn() ?: 0,
|
||||||
|
\'total_paid\' => db()->query("SELECT SUM(amount) FROM purchase_payments $out_w")->fetchColumn() ?: 0,
|
||||||
|
\'expired_items\' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date <= CURDATE() $out_and")->fetchColumn(),
|
||||||
|
\'near_expiry_items\' => db()->query("SELECT COUNT(*) FROM stock_items WHERE expiry_date IS NOT NULL AND expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY) $out_and")->fetchColumn(),
|
||||||
|
\'low_stock_items_count\' => db()->query("SELECT COUNT(*) FROM stock_items WHERE stock_quantity <= min_stock_level $out_and")->fetchColumn(),
|
||||||
|
];';
|
||||||
|
|
||||||
|
$c = str_replace($find_dashboard, $repl_dashboard, $c);
|
||||||
|
|
||||||
|
// Also replace the queries for Cash Flow and Charts
|
||||||
|
$find_charts = ' $data[\'monthly_sales\'] = db()->query("
|
||||||
|
SELECT label, SUM(tot) as total FROM (
|
||||||
|
SELECT DATE_FORMAT(invoice_date, \'%M %Y\') as label, total_with_vat as tot, DATE_FORMAT(invoice_date, \'%Y-%m\') as sort_col FROM invoices
|
||||||
|
UNION ALL
|
||||||
|
SELECT DATE_FORMAT(created_at, \'%M %Y\') as label, net_amount as tot, DATE_FORMAT(created_at, \'%Y-%m\') as sort_col FROM pos_transactions WHERE status = \'completed\'
|
||||||
|
) t
|
||||||
|
GROUP BY label, sort_col
|
||||||
|
ORDER BY sort_col ASC LIMIT 12
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$data[\'yearly_sales\'] = db()->query("
|
||||||
|
SELECT label, SUM(tot) as total FROM (
|
||||||
|
SELECT YEAR(invoice_date) as label, total_with_vat as tot FROM invoices
|
||||||
|
UNION ALL
|
||||||
|
SELECT YEAR(created_at) as label, net_amount as tot FROM pos_transactions WHERE status = \'completed\'
|
||||||
|
) t
|
||||||
|
GROUP BY label
|
||||||
|
ORDER BY label ASC LIMIT 5
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);';
|
||||||
|
|
||||||
|
$repl_charts = ' $data[\'monthly_sales\'] = db()->query("
|
||||||
|
SELECT label, SUM(tot) as total FROM (
|
||||||
|
SELECT DATE_FORMAT(invoice_date, \'%M %Y\') as label, total_with_vat as tot, DATE_FORMAT(invoice_date, \'%Y-%m\') as sort_col FROM invoices $out_w
|
||||||
|
UNION ALL
|
||||||
|
SELECT DATE_FORMAT(created_at, \'%M %Y\') as label, net_amount as tot, DATE_FORMAT(created_at, \'%Y-%m\') as sort_col FROM pos_transactions WHERE status = \'completed\' $out_and
|
||||||
|
) t
|
||||||
|
GROUP BY label, sort_col
|
||||||
|
ORDER BY sort_col ASC LIMIT 12
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
$data[\'yearly_sales\'] = db()->query("
|
||||||
|
SELECT label, SUM(tot) as total FROM (
|
||||||
|
SELECT YEAR(invoice_date) as label, total_with_vat as tot FROM invoices $out_w
|
||||||
|
UNION ALL
|
||||||
|
SELECT YEAR(created_at) as label, net_amount as tot FROM pos_transactions WHERE status = \'completed\' $out_and
|
||||||
|
) t
|
||||||
|
GROUP BY label
|
||||||
|
ORDER BY label ASC LIMIT 5
|
||||||
|
")->fetchAll(PDO::FETCH_ASSOC);';
|
||||||
|
|
||||||
|
$c = str_replace($find_charts, $repl_charts, $c);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $c);
|
||||||
|
echo "Dashboard Patched\n";
|
||||||
9
patch_expenses.php
Normal file
9
patch_expenses.php
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$search = "case 'expenses':\n \$where = [\"1=1\"];";
|
||||||
|
$replace = "case 'expenses':\n \$where = [\"1=1\"];\n if (isset(\$_SESSION['outlet_id'])) {\n \$where[] = \"e.outlet_id = \" . (int)\$_SESSION['outlet_id'];\n }";
|
||||||
|
|
||||||
|
$content = str_replace($search, $replace, $content);
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "Expenses patched.\n";
|
||||||
15
patch_lists.php
Normal file
15
patch_lists.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$search = '$where = ["1=1"];';
|
||||||
|
$replace = '$where = ["1=1"];
|
||||||
|
if (isset($_SESSION[\'outlet_id\'])) {
|
||||||
|
$where[] = "v.outlet_id = " . (int)$_SESSION[\'outlet_id\'];
|
||||||
|
}';
|
||||||
|
|
||||||
|
// Apply to case 'sales', 'purchases'
|
||||||
|
$content = preg_replace("/case 'sales':.*?case 'purchases':.*?\\\$where = \[\"1=1\"\];/s", "case 'sales':\n case 'purchases':\n \$type = (\$page === 'sales') ? 'sale' : 'purchase';\n \$table = (\$type === 'purchase') ? 'purchases' : 'invoices';\n \$cust_supplier_col = (\$type === 'purchase') ? 'supplier_id' : 'customer_id';\n \$cust_supplier_table = (\$type === 'purchase') ? 'suppliers' : 'customers';\n \n \$where = [\"1=1\"];\n if (isset(\$_SESSION['outlet_id'])) {\n \$where[] = \"v.outlet_id = \" . (int)\$_SESSION['outlet_id'];\n }", $content);
|
||||||
|
|
||||||
|
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "sales and purchases patched.\n";
|
||||||
25
patch_others.php
Normal file
25
patch_others.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
// Quotations
|
||||||
|
$search_q = "case 'quotations':\n \$where = [\"1=1\"];";
|
||||||
|
$replace_q = "case 'quotations':\n \$where = [\"1=1\"];\n if (isset(\$_SESSION['outlet_id'])) {\n \$where[] = \"q.outlet_id = \" . (int)\$_SESSION['outlet_id'];\n }";
|
||||||
|
$content = str_replace($search_q, $replace_q, $content);
|
||||||
|
|
||||||
|
// LPOs
|
||||||
|
$search_l = "case 'lpos':\n \$where = [\"1=1\"];";
|
||||||
|
$replace_l = "case 'lpos':\n \$where = [\"1=1\"];\n if (isset(\$_SESSION['outlet_id'])) {\n \$where[] = \"q.outlet_id = \" . (int)\$_SESSION['outlet_id'];\n }";
|
||||||
|
$content = str_replace($search_l, $replace_l, $content);
|
||||||
|
|
||||||
|
// Sales Returns
|
||||||
|
$search_sr = "case 'sales_returns':\n \$where = [\"1=1\"];";
|
||||||
|
$replace_sr = "case 'sales_returns':\n \$where = [\"1=1\"];\n if (isset(\$_SESSION['outlet_id'])) {\n \$where[] = \"sr.outlet_id = \" . (int)\$_SESSION['outlet_id'];\n }";
|
||||||
|
$content = str_replace($search_sr, $replace_sr, $content);
|
||||||
|
|
||||||
|
// Purchase Returns
|
||||||
|
$search_pr = "case 'purchase_returns':\n \$where = [\"1=1\"];";
|
||||||
|
$replace_pr = "case 'purchase_returns':\n \$where = [\"1=1\"];\n if (isset(\$_SESSION['outlet_id'])) {\n \$where[] = \"pr.outlet_id = \" . (int)\$_SESSION['outlet_id'];\n }";
|
||||||
|
$content = str_replace($search_pr, $replace_pr, $content);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "Other lists patched.\n";
|
||||||
26
patch_outlets.php
Normal file
26
patch_outlets.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
$content = file_get_contents('index.php');
|
||||||
|
|
||||||
|
// 1. Fix Login Session
|
||||||
|
$content = str_replace(
|
||||||
|
"\$_SESSION['user_role_name'] = \$u['role_name'];",
|
||||||
|
"\$_SESSION['user_role_name'] = \$u['role_name'];\n \$_SESSION['outlet_id'] = \$u['outlet_id'];",
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Fix Add User POST
|
||||||
|
$content = preg_replace(
|
||||||
|
"/\\\$group_id = \(int\)\(\\\$_POST\['group_id'\] \?\? 0\) \?\: null;.*?\\\$stmt = \\\$db->prepare\(\"INSERT INTO users \(username, password, email, phone, group_id\) VALUES \(\?, \?, \?, \?, \?\)\"\);.*?\\\$stmt->execute\(\[\\\$username, password_hash\(\\\$password, PASSWORD_DEFAULT\), \\\$email, \\\$phone, \\\$group_id\]\);/s",
|
||||||
|
"\$group_id = (int)(\$_POST['group_id'] ?? 0) ?: null;\n \$outlet_id = !empty(\$_POST['outlet_id']) ? (int)\$_POST['outlet_id'] : null;\n \$stmt = \$db->prepare(\"INSERT INTO users (username, password, email, phone, group_id, outlet_id) VALUES (?, ?, ?, ?, ?, ?)\");\n \$stmt->execute([\$username, password_hash(\$password, PASSWORD_DEFAULT), \$email, \$phone, \$group_id, \$outlet_id]);",
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
|
||||||
|
// 3. Fix Edit User POST
|
||||||
|
$content = preg_replace(
|
||||||
|
"/\\\$group_id = \(int\)\(\\\$_POST\['group_id'\] \?\? 0\) \?\: null;.*?if \(\!empty\(\\\$password\)\) \{.*?\\\$stmt = \\\$db->prepare\(\"UPDATE users SET username=\?, password=\?, email=\?, phone=\?, group_id=\? WHERE id=\?\"\);.*?\\\$stmt->execute\(\[\\\$username, password_hash\(\\\$password, PASSWORD_DEFAULT\), \\\$email, \\\$phone, \\\$group_id, \\\$id\]\);.*?\} else \{.*?\\\$stmt = \\\$db->prepare\(\"UPDATE users SET username=\?, email=\?, phone=\?, group_id=\? WHERE id=\?\"\);.*?\\\$stmt->execute\(\[\\\$username, \\\$email, \\\$phone, \\\$group_id, \\\$id\]\);.*?\}/s",
|
||||||
|
"\$group_id = (int)(\$_POST['group_id'] ?? 0) ?: null;\n \$outlet_id = !empty(\$_POST['outlet_id']) ? (int)\$_POST['outlet_id'] : null;\n if (!empty(\$password)) {\n \$stmt = \$db->prepare(\"UPDATE users SET username=?, password=?, email=?, phone=?, group_id=?, outlet_id=? WHERE id=?\");\n \$stmt->execute([\$username, password_hash(\$password, PASSWORD_DEFAULT), \$email, \$phone, \$group_id, \$outlet_id, \$id]);\n } else {\n \$stmt = \$db->prepare(\"UPDATE users SET username=?, email=?, phone=?, group_id=?, outlet_id=? WHERE id=?\");\n \$stmt->execute([\$username, \$email, \$phone, \$group_id, \$outlet_id, \$id]);\n }",
|
||||||
|
$content
|
||||||
|
);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $content);
|
||||||
|
echo "Patched auth & post logic\n";
|
||||||
46
patch_users.php
Normal file
46
patch_users.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
$c = file_get_contents('index.php');
|
||||||
|
|
||||||
|
// Fix Add User
|
||||||
|
$find_add = ' $stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id) VALUES (?, ?, ?, ?, ?)");
|
||||||
|
try {
|
||||||
|
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id]);';
|
||||||
|
|
||||||
|
$repl_add = ' $outlet_id = !empty($_POST[\'outlet_id\']) ? (int)$_POST[\'outlet_id\'] : null;
|
||||||
|
$stmt = db()->prepare("INSERT INTO users (username, password, email, phone, group_id, outlet_id) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
try {
|
||||||
|
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id, $outlet_id]);';
|
||||||
|
|
||||||
|
$c = str_replace($find_add, $repl_add, $c);
|
||||||
|
|
||||||
|
// Fix Edit User
|
||||||
|
$find_edit = ' if ($password) {
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$stmt = db()->prepare("UPDATE users SET username = ?, password = ?, email = ?, phone = ?, group_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id, $id]);
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$username, $email, $phone, $group_id, $id]);
|
||||||
|
}';
|
||||||
|
|
||||||
|
$repl_edit = ' $outlet_id = !empty($_POST[\'outlet_id\']) ? (int)$_POST[\'outlet_id\'] : null;
|
||||||
|
if ($password) {
|
||||||
|
$hashed_password = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
$stmt = db()->prepare("UPDATE users SET username = ?, password = ?, email = ?, phone = ?, group_id = ?, outlet_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$username, $hashed_password, $email, $phone, $group_id, $outlet_id, $id]);
|
||||||
|
} else {
|
||||||
|
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, outlet_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$username, $email, $phone, $group_id, $outlet_id, $id]);
|
||||||
|
}';
|
||||||
|
|
||||||
|
$c = str_replace($find_edit, $repl_edit, $c);
|
||||||
|
|
||||||
|
// Fix login session
|
||||||
|
$c = str_replace(
|
||||||
|
"\$_SESSION['user_role_name'] = \$u['role_name'];",
|
||||||
|
"\$_SESSION['user_role_name'] = \$u['role_name'];\n \$_SESSION['outlet_id'] = \$u['outlet_id'];",
|
||||||
|
$c
|
||||||
|
);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $c);
|
||||||
|
echo "Patched users.php\n";
|
||||||
17
patch_users_edit_post.php
Normal file
17
patch_users_edit_post.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
$c = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$find_edit_post = '$status = $_POST[\'status\'] ?? \'active\';
|
||||||
|
if ($id && $username) {
|
||||||
|
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$username, $email, $phone, $group_id, $status, $id]);';
|
||||||
|
|
||||||
|
$repl_edit_post = '$status = $_POST[\'status\'] ?? \'active\';
|
||||||
|
$outlet_id = !empty($_POST[\'outlet_id\']) ? (int)$_POST[\'outlet_id\'] : null;
|
||||||
|
if ($id && $username) {
|
||||||
|
$stmt = db()->prepare("UPDATE users SET username = ?, email = ?, phone = ?, group_id = ?, status = ?, outlet_id = ? WHERE id = ?");
|
||||||
|
$stmt->execute([$username, $email, $phone, $group_id, $status, $outlet_id, $id]);';
|
||||||
|
|
||||||
|
$c = str_replace($find_edit_post, $repl_edit_post, $c);
|
||||||
|
file_put_contents('index.php', $c);
|
||||||
|
echo "Edit Post Patched\n";
|
||||||
31
patch_users_edit_ui.php
Normal file
31
patch_users_edit_ui.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
$c = file_get_contents('index.php');
|
||||||
|
|
||||||
|
$find_group_edit = '<select name="group_id" class="form-select">
|
||||||
|
<option value="">--- No Group ---</option>
|
||||||
|
<?php foreach (($data[\'role_groups\'] ?? []) as $g): ?>
|
||||||
|
<option value="<?= $g[\'id\'] ?>" <?= ($u[\'group_id\'] ?? null) == $g[\'id\'] ? \'selected\' : \'\' ?>><?= htmlspecialchars((string)$g[\'name\']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$repl_group_edit = $find_group_edit . '
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label fw-semibold" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
|
||||||
|
<select name="outlet_id" class="form-select">
|
||||||
|
<option value="">--- Global (Admin only) ---</option>
|
||||||
|
<?php foreach (($data[\'outlets\'] ?? []) as $o): ?>
|
||||||
|
<option value="<?= $o[\'id\'] ?>" <?= ($u[\'outlet_id\'] ?? null) == $o[\'id\'] ? \'selected\' : \'\' ?>><?= htmlspecialchars($o[\'name\']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$c = str_replace($find_group_edit, $repl_group_edit, $c);
|
||||||
|
|
||||||
|
// Also we need to make sure 'status' was updated in Edit User POST!
|
||||||
|
// Looking at the previous edit_user patch, I didn't add status. I will add status now since the UI has it!
|
||||||
|
// Actually, earlier the backend code didn't update `status`?
|
||||||
|
// Let me just replace the file contents.
|
||||||
|
|
||||||
|
file_put_contents('index.php', $c);
|
||||||
|
echo "UI Edit Patched\n";
|
||||||
34
patch_users_ui.php
Normal file
34
patch_users_ui.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
$c = file_get_contents('index.php');
|
||||||
|
|
||||||
|
// Fetch outlets for users
|
||||||
|
$c = str_replace(
|
||||||
|
"\$data['role_groups'] = db()->query(\"SELECT id, name FROM role_groups ORDER BY name ASC\")->fetchAll();\n break;\n case 'backups':",
|
||||||
|
"\$data['role_groups'] = db()->query(\"SELECT id, name FROM role_groups ORDER BY name ASC\")->fetchAll();\n \$data['outlets'] = db()->query(\"SELECT id, name FROM outlets ORDER BY name ASC\")->fetchAll();\n break;\n case 'backups':",
|
||||||
|
$c
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add Outlet field to Add User
|
||||||
|
$find_group_add = '<select name="group_id" class="form-select">
|
||||||
|
<option value="">--- Select Group ---</option>
|
||||||
|
<?php foreach (($data[\'role_groups\'] ?? []) as $g): ?>
|
||||||
|
<option value="<?= $g[\'id\'] ?>"><?= htmlspecialchars($g[\'name\']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$repl_group_add = $find_group_add . '
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" data-en="Assigned Outlet" data-ar="الفرع">Assigned Outlet</label>
|
||||||
|
<select name="outlet_id" class="form-select">
|
||||||
|
<option value="">--- Global (Admin only) ---</option>
|
||||||
|
<?php foreach (($data[\'outlets\'] ?? []) as $o): ?>
|
||||||
|
<option value="<?= $o[\'id\'] ?>"><?= htmlspecialchars($o[\'name\']) ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>';
|
||||||
|
|
||||||
|
$c = str_replace($find_group_add, $repl_group_add, $c);
|
||||||
|
|
||||||
|
file_put_contents('index.php', $c);
|
||||||
|
echo "UI Patched\n";
|
||||||
@ -1 +1,11 @@
|
|||||||
2026-02-25 09:56:38 - POST: {"action":"translate","text":"Product 2","target":"ar"}
|
2026-02-25 09:56:38 - POST: {"action":"translate","text":"Product 2","target":"ar"}
|
||||||
|
2026-02-25 12:57:47 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":25}]","total_amount":"25","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":7,\"qty\":1,\"price\":10,\"vat_rate\":0,\"vat_amount\":0},{\"id\":8,\"qty\":1,\"price\":15,\"vat_rate\":0,\"vat_amount\":0}]"}
|
||||||
|
2026-02-25 13:23:21 - POST: {"sync_accounting":""}
|
||||||
|
2026-02-25 13:30:19 - POST: {"action":"save_theme","theme":"nord"}
|
||||||
|
2026-02-25 13:30:28 - POST: {"action":"save_theme","theme":"forest"}
|
||||||
|
2026-02-25 13:30:36 - POST: {"action":"save_theme","theme":"citrus"}
|
||||||
|
2026-02-25 13:30:39 - POST: {"action":"save_theme","theme":"forest"}
|
||||||
|
2026-02-25 13:30:41 - POST: {"action":"save_theme","theme":"nord"}
|
||||||
|
2026-02-25 13:31:58 - POST: {"id":"","name":"Nizwa Branch","phone":"9689555","address":"Nizwa","status":"active","add_outlet":""}
|
||||||
|
2026-02-25 17:08:42 - POST: {"action":"save_pos_transaction","customer_id":"","payments":"[{\"method\":\"cash\",\"amount\":15.45}]","total_amount":"15.45","tax_amount":"0","discount_code_id":"","discount_amount":"0","loyalty_redeemed":"0","items":"[{\"id\":1,\"qty\":1,\"price\":0.45,\"vat_rate\":0,\"vat_amount\":0},{\"id\":8,\"qty\":1,\"price\":15,\"vat_rate\":0,\"vat_amount\":0}]"}
|
||||||
|
2026-02-25 17:09:31 - POST: {"id":"37","delete_invoice":""}
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
2026-02-25 04:49:58 - search_items call: q=on
|
2026-02-25 04:49:58 - search_items call: q=on
|
||||||
|
2026-02-25 12:57:14 - search_items call: q=on
|
||||||
|
2026-02-25 17:10:03 - search_items call: q=on
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user