From e3c02137eac385e880cd679478206783cf94e3b9 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Wed, 25 Feb 2026 17:48:02 +0000 Subject: [PATCH] last changes --- debug.log | 6 + fix_duplicates.php | 30 +++ fix_login_dup.php | 11 + fix_ui_typo.php | 2 + fix_users_table.php | 36 +++ includes/accounting_helper.php | 11 + index.php | 406 ++++++++++++++++++++++++++++++--- patch_cashflow.php | 60 +++++ patch_dashboard.php | 81 +++++++ patch_expenses.php | 9 + patch_lists.php | 15 ++ patch_others.php | 25 ++ patch_outlets.php | 26 +++ patch_users.php | 46 ++++ patch_users_edit_post.php | 17 ++ patch_users_edit_ui.php | 31 +++ patch_users_ui.php | 34 +++ post_debug.log | 10 + search_debug.log | 2 + 19 files changed, 824 insertions(+), 34 deletions(-) create mode 100644 fix_duplicates.php create mode 100644 fix_login_dup.php create mode 100644 fix_ui_typo.php create mode 100644 fix_users_table.php create mode 100644 patch_cashflow.php create mode 100644 patch_dashboard.php create mode 100644 patch_expenses.php create mode 100644 patch_lists.php create mode 100644 patch_others.php create mode 100644 patch_outlets.php create mode 100644 patch_users.php create mode 100644 patch_users_edit_post.php create mode 100644 patch_users_edit_ui.php create mode 100644 patch_users_ui.php diff --git a/debug.log b/debug.log index 04084e8..8450966 100644 --- a/debug.log +++ b/debug.log @@ -5,3 +5,9 @@ 2026-02-25 11:49:27 - 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: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 diff --git a/fix_duplicates.php b/fix_duplicates.php new file mode 100644 index 0000000..3895568 --- /dev/null +++ b/fix_duplicates.php @@ -0,0 +1,30 @@ + + + + '; + +$content = str_replace($search1 . "\n " . $search1, $search1, $content); + + +$search2 = '
+ + +
'; + +$content = str_replace($search2 . "\n " . $search2, $search2, $content); + +file_put_contents('index.php', $content); +echo "Duplicates removed.\n"; diff --git a/fix_login_dup.php b/fix_login_dup.php new file mode 100644 index 0000000..5b37f83 --- /dev/null +++ b/fix_login_dup.php @@ -0,0 +1,11 @@ +Access Level'; +$replace_th = 'Access Level + Outlet'; + +$content = str_replace($search_th, $replace_th, $content); + +$search_td = ' + + + '; +$replace_td = ' + + + + + + + + '; + +$content = str_replace($search_td, $replace_td, $content); + +file_put_contents('index.php', $content); +echo "Users table updated to show assigned outlet.\n"; diff --git a/includes/accounting_helper.php b/includes/accounting_helper.php index 5c47885..28da9ce 100644 --- a/includes/accounting_helper.php +++ b/includes/accounting_helper.php @@ -194,3 +194,14 @@ function getVatReport($start_date = null, $end_date = null) { '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); +} diff --git a/index.php b/index.php index 9894280..d996228 100644 --- a/index.php +++ b/index.php @@ -344,6 +344,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['login'])) { $_SESSION['user_id'] = $u['id']; $_SESSION['username'] = $u['username']; $_SESSION['user_role_name'] = $u['role_name']; + $_SESSION['outlet_id'] = $u['outlet_id']; // Fetch permissions from the new role_permissions table $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; if ($username && $password) { $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 { - $stmt->execute([$username, $hashed_password, $email, $phone, $group_id]); + $stmt->execute([$username, $hashed_password, $email, $phone, $group_id, $outlet_id]); $message = "User added successfully!"; } catch (PDOException $e) { if ($e->getCode() == '23000') { @@ -2715,9 +2717,10 @@ if (isset($_POST['add_hr_department'])) { $phone = $_POST['phone'] ?? ''; $group_id = (int)($_POST['group_id'] ?? 0) ?: null; $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 = ? WHERE id = ?"); - $stmt->execute([$username, $email, $phone, $group_id, $status, $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, $outlet_id, $id]); if (!empty($_POST['password'])) { $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'])) { $name = $_POST['name'] ?? ''; if ($name) { @@ -3531,6 +3565,9 @@ switch ($page) { break; case 'quotations': $where = ["1=1"]; + if (isset($_SESSION['outlet_id'])) { + $where[] = "q.outlet_id = " . (int)$_SESSION['outlet_id']; + } $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; @@ -3579,6 +3616,9 @@ switch ($page) { break; case 'lpos': $where = ["1=1"]; + if (isset($_SESSION['outlet_id'])) { + $where[] = "q.outlet_id = " . (int)$_SESSION['outlet_id']; + } $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; @@ -3644,6 +3684,9 @@ switch ($page) { $cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers'; $where = ["1=1"]; + if (isset($_SESSION['outlet_id'])) { + $where[] = "v.outlet_id = " . (int)$_SESSION['outlet_id']; + } $params = []; if (!empty($_GET['search'])) { @@ -3721,6 +3764,9 @@ switch ($page) { case 'sales_returns': $where = ["1=1"]; + if (isset($_SESSION['outlet_id'])) { + $where[] = "sr.outlet_id = " . (int)$_SESSION['outlet_id']; + } $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; @@ -3753,6 +3799,9 @@ switch ($page) { case 'purchase_returns': $where = ["1=1"]; + if (isset($_SESSION['outlet_id'])) { + $where[] = "pr.outlet_id = " . (int)$_SESSION['outlet_id']; + } $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; @@ -3826,6 +3875,9 @@ switch ($page) { break; case 'expenses': $where = ["1=1"]; + if (isset($_SESSION['outlet_id'])) { + $where[] = "e.outlet_id = " . (int)$_SESSION['outlet_id']; + } $params = []; if (!empty($_GET['category_id'])) { $where[] = "e.category_id = ?"; @@ -3854,6 +3906,7 @@ switch ($page) { 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['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; case 'backups': $data['backups'] = BackupService::getBackups(); @@ -4099,24 +4152,72 @@ switch ($page) { break; default: 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'] = [ - '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(), + '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(), ]; $data['stats']['total_receivable'] = $data['stats']['total_sales'] - $data['stats']['total_received']; $data['stats']['total_payable'] = $data['stats']['total_purchases'] - $data['stats']['total_paid']; - // Sales Chart Data - $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['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); + // Sales Chart Data (Invoices + POS) + $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); + + // 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; } @@ -4144,6 +4245,8 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; + + @@ -4257,6 +4360,71 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; body { font-size: 12px !important; color: #000 !important; background: #fff !important; } @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; } [dir="rtl"] { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; } @@ -4513,12 +4681,17 @@ $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; - - '; + +$repl_group_edit = $find_group_edit . ' +
+ + +
'; + +$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"; diff --git a/patch_users_ui.php b/patch_users_ui.php new file mode 100644 index 0000000..bf504f5 --- /dev/null +++ b/patch_users_ui.php @@ -0,0 +1,34 @@ +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 = ' + '; + +$repl_group_add = $find_group_add . ' +
+ + +
'; + +$c = str_replace($find_group_add, $repl_group_add, $c); + +file_put_contents('index.php', $c); +echo "UI Patched\n"; diff --git a/post_debug.log b/post_debug.log index 91d0fb1..3f00bcd 100644 --- a/post_debug.log +++ b/post_debug.log @@ -1 +1,11 @@ 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":""} diff --git a/search_debug.log b/search_debug.log index cca9321..2ac2aca 100644 --- a/search_debug.log +++ b/search_debug.log @@ -1 +1,3 @@ 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