} else { fputcsv($output, $headers); foreach ($rows as $row) fputcsv($output, $row); fclose($output); } exit; } // Global data for modals $data['categories'] = db()->query("SELECT * FROM stock_categories ORDER BY name_en ASC")->fetchAll(); $data['units'] = db()->query("SELECT * FROM stock_units ORDER BY name_en ASC")->fetchAll(); $data['suppliers'] = db()->query("SELECT * FROM suppliers ORDER BY name ASC")->fetchAll(); $data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll(); $data['customers_list'] = db()->query("SELECT * FROM customers ORDER BY name ASC")->fetchAll(); $customers = $data['customers_list']; // For backward compatibility in some modals $settings_raw = db()->query("SELECT * FROM settings")->fetchAll(); $data['settings'] = []; foreach ($settings_raw as $s) { $data['settings'][$s['key']] = $s['value']; } $limit = isset($_GET["limit"]) ? max(5, (int)$_GET["limit"]) : 20; $page_num = isset($_GET["p"]) ? (int)$_GET["p"] : 1; if ($page_num < 1) $page_num = 1; $offset = ($page_num - 1) * $limit; switch ($page) { case 'suppliers': $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; } if (!empty($_GET['start_date'])) { $where[] = "DATE(created_at) >= ?"; $params[] = $_GET['start_date']; } if (!empty($_GET['end_date'])) { $where[] = "DATE(created_at) <= ?"; $params[] = $_GET['end_date']; } $whereSql = implode(" AND ", $where); $countStmt = db()->prepare("SELECT COUNT(*) FROM suppliers WHERE $whereSql"); $countStmt->execute($params); $total_records = (int)$countStmt->fetchColumn(); $data['total_pages'] = ceil($total_records / $limit); $data['current_page'] = $page_num; $stmt = db()->prepare("SELECT * FROM suppliers WHERE $whereSql ORDER BY id DESC LIMIT $limit OFFSET $offset"); $stmt->execute($params); $data['customers'] = $stmt->fetchAll(); // Keep 'customers' key for template compatibility if needed, or update template break; case 'customers': $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $where[] = "(name LIKE ? OR email LIKE ? OR phone LIKE ? OR tax_id LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; } if (!empty($_GET['start_date'])) { $where[] = "DATE(created_at) >= ?"; $params[] = $_GET['start_date']; } if (!empty($_GET['end_date'])) { $where[] = "DATE(created_at) <= ?"; $params[] = $_GET['end_date']; } $whereSql = implode(" AND ", $where); $countStmt = db()->prepare("SELECT COUNT(*) FROM customers WHERE $whereSql"); $countStmt->execute($params); $total_records = (int)$countStmt->fetchColumn(); $data['total_pages'] = ceil($total_records / $limit); $data['current_page'] = $page_num; $stmt = db()->prepare("SELECT * FROM customers WHERE $whereSql ORDER BY id DESC LIMIT $limit OFFSET $offset"); $stmt->execute($params); $data['customers'] = $stmt->fetchAll(); break; case 'categories': // Already fetched globally break; case 'units': // Already fetched globally break; case 'items': file_put_contents('debug.log', date('Y-m-d H:i:s') . " - Items case hit\n", FILE_APPEND); $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $where[] = "(i.name_en LIKE ? OR i.name_ar LIKE ? OR i.sku LIKE ?)"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; $params[] = "%{$_GET['search']}%"; } $whereSql = implode(" AND ", $where); $countStmt = db()->prepare("SELECT COUNT(*) FROM stock_items i LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN stock_units u ON i.unit_id = u.id LEFT JOIN suppliers s ON i.supplier_id = s.id WHERE $whereSql"); $countStmt->execute($params); $total_records = (int)$countStmt->fetchColumn(); $data['total_pages'] = ceil($total_records / $limit); $data['current_page'] = $page_num; $stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar, u.short_name_en as unit_en, u.short_name_ar as unit_ar, s.name as supplier_name FROM stock_items i LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN stock_units u ON i.unit_id = u.id LEFT JOIN suppliers s ON i.supplier_id = s.id WHERE $whereSql ORDER BY i.id DESC LIMIT $limit OFFSET $offset"); $stmt->execute($params); $data['items'] = $stmt->fetchAll(); break; case 'quotations': $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; $clean_id = preg_replace('/[^0-9]/', '', $s); if ($clean_id !== '') { $where[] = "(q.id LIKE ? OR c.name LIKE ? OR q.id = ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = $clean_id; } else { $where[] = "(q.id LIKE ? OR c.name LIKE ?)"; $params[] = "%$s%"; $params[] = "%$s%"; } } if (!empty($_GET['customer_id'])) { $where[] = "q.customer_id = ?"; $params[] = $_GET['customer_id']; } if (!empty($_GET['start_date'])) { $where[] = "q.quotation_date >= ?"; $params[] = $_GET['start_date']; } if (!empty($_GET['end_date'])) { $where[] = "q.quotation_date <= ?"; $params[] = $_GET['end_date']; } $whereSql = implode(" AND ", $where); $countStmt = db()->prepare("SELECT COUNT(*) FROM quotations q JOIN customers c ON q.customer_id = c.id WHERE $whereSql"); $countStmt->execute($params); $total_records = (int)$countStmt->fetchColumn(); $data['total_pages'] = ceil($total_records / $limit); $data['current_page'] = $page_num; $stmt = db()->prepare("SELECT q.*, c.name as customer_name FROM quotations q JOIN customers c ON q.customer_id = c.id WHERE $whereSql ORDER BY q.id DESC LIMIT $limit OFFSET $offset"); $stmt->execute($params); $data['quotations'] = $stmt->fetchAll(); break; case 'lpos': $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; $clean_id = preg_replace('/[^0-9]/', '', $s); if ($clean_id !== '') { $where[] = "(q.id LIKE ? OR s.name LIKE ? OR q.id = ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = $clean_id; } else { $where[] = "(q.id LIKE ? OR s.name LIKE ?)"; $params[] = "%$s%"; $params[] = "%$s%"; } } if (!empty($_GET['supplier_id'])) { $where[] = "q.supplier_id = ?"; $params[] = $_GET['supplier_id']; } if (!empty($_GET['start_date'])) { $where[] = "q.lpo_date >= ?"; $params[] = $_GET['start_date']; } if (!empty($_GET['end_date'])) { $where[] = "q.lpo_date <= ?"; $params[] = $_GET['end_date']; } $whereSql = implode(" AND ", $where); $countStmt = db()->prepare("SELECT COUNT(*) FROM lpos q JOIN suppliers s ON q.supplier_id = s.id WHERE $whereSql"); $countStmt->execute($params); $total_records = (int)$countStmt->fetchColumn(); $data['total_pages'] = ceil($total_records / $limit); $data['current_page'] = $page_num; $stmt = db()->prepare("SELECT q.*, s.name as supplier_name FROM lpos q JOIN suppliers s ON q.supplier_id = s.id WHERE $whereSql ORDER BY q.id DESC LIMIT $limit OFFSET $offset"); $stmt->execute($params); $data['lpos'] = $stmt->fetchAll(); break; case 'payment_methods': $data['payment_methods'] = db()->query("SELECT * FROM payment_methods ORDER BY id DESC")->fetchAll(); break; case 'settings': // Already fetched globally break; case 'my_profile': $stmt = db()->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $data['user'] = $stmt->fetch(); break; case 'sales': case 'purchases': $type = ($page === 'sales') ? 'sale' : 'purchase'; $table = ($type === 'purchase') ? 'purchases' : 'invoices'; $cust_supplier_col = ($type === 'purchase') ? 'supplier_id' : 'customer_id'; $cust_supplier_table = ($type === 'purchase') ? 'suppliers' : 'customers'; $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; $clean_id = preg_replace('/[^0-9]/', '', $s); if ($clean_id !== '') { $where[] = "(v.id LIKE ? OR c.name LIKE ? OR v.id = ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = $clean_id; } else { $where[] = "(v.id LIKE ? OR c.name LIKE ?)"; $params[] = "%$s%"; $params[] = "%$s%"; } } if (!empty($_GET['customer_id'])) { $where[] = "v.$cust_supplier_col = ?"; $params[] = $_GET['customer_id']; } if (!empty($_GET['start_date'])) { $where[] = "v.invoice_date >= ?"; $params[] = $_GET['start_date']; } if (!empty($_GET['end_date'])) { $where[] = "v.invoice_date <= ?"; $params[] = $_GET['end_date']; } $whereSql = implode(" AND ", $where); $countStmt = db()->prepare("SELECT COUNT(*) FROM $table v LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id WHERE $whereSql"); $countStmt->execute($params); $total_records = (int)$countStmt->fetchColumn(); $data['total_pages'] = ceil($total_records / $limit); $data['current_page'] = $page_num; $stmt = db()->prepare("SELECT v.*, c.name as customer_name, c.tax_id as customer_tax_id, c.phone as customer_phone FROM $table v LEFT JOIN $cust_supplier_table c ON v.$cust_supplier_col = c.id WHERE $whereSql ORDER BY v.id DESC LIMIT $limit OFFSET $offset"); $stmt->execute($params); $data['invoices'] = $stmt->fetchAll(); foreach ($data['invoices'] as &$inv) { $inv['total_in_words'] = numberToWordsOMR($inv['total_with_vat']); if ($type === 'sale') { $item_stmt = db()->prepare("SELECT ii.*, i.name_en, i.name_ar, i.vat_rate FROM invoice_items ii LEFT JOIN stock_items i ON ii.item_id = i.id WHERE ii.invoice_id = ?"); $item_stmt->execute([$inv['id']]); $inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC); } else { $item_stmt = db()->prepare("SELECT pi.*, i.name_en, i.name_ar, i.vat_rate FROM purchase_items pi LEFT JOIN stock_items i ON pi.item_id = i.id WHERE pi.purchase_id = ?"); $item_stmt->execute([$inv['id']]); $inv['items'] = $item_stmt->fetchAll(PDO::FETCH_ASSOC); } } unset($inv); $items_list_raw = db()->query("SELECT id, name_en, name_ar, sale_price, purchase_price, stock_quantity, vat_rate, is_promotion, promotion_start, promotion_end, promotion_percent FROM stock_items ORDER BY name_en ASC")->fetchAll(PDO::FETCH_ASSOC); foreach ($items_list_raw as &$item) { $item['sale_price'] = getPromotionalPrice($item); } $data['items_list'] = $items_list_raw; $data['customers_list'] = db()->query("SELECT id, name FROM $cust_supplier_table ORDER BY name ASC")->fetchAll(); if ($type === 'sale') { $data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices ORDER BY id DESC")->fetchAll(); } else { $data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM purchases ORDER BY id DESC")->fetchAll(); } break; case 'sales_returns': $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; $clean_id = preg_replace('/[^0-9]/', '', $s); if ($clean_id !== '') { $where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ? OR sr.id = ? OR sr.invoice_id = ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = $clean_id; $params[] = $clean_id; } else { $where[] = "(sr.id LIKE ? OR c.name LIKE ? OR sr.invoice_id LIKE ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = "%$s%"; } } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT sr.*, c.name as customer_name, i.total_with_vat as invoice_total FROM sales_returns sr LEFT JOIN customers c ON sr.customer_id = c.id LEFT JOIN invoices i ON sr.invoice_id = i.id WHERE $whereSql ORDER BY sr.id DESC"); $stmt->execute($params); $data['returns'] = $stmt->fetchAll(); $data['sales_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM invoices ORDER BY id DESC")->fetchAll(); break; case 'purchase_returns': $where = ["1=1"]; $params = []; if (!empty($_GET['search'])) { $s = $_GET['search']; $clean_id = preg_replace('/[^0-9]/', '', $s); if ($clean_id !== '') { $where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.purchase_id LIKE ? OR pr.id = ? OR pr.purchase_id = ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = $clean_id; $params[] = $clean_id; } else { $where[] = "(pr.id LIKE ? OR c.name LIKE ? OR pr.purchase_id LIKE ?)"; $params[] = "%$s%"; $params[] = "%$s%"; $params[] = "%$s%"; } } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT pr.*, c.name as supplier_name, i.total_with_vat as invoice_total FROM purchase_returns pr LEFT JOIN suppliers c ON pr.supplier_id = c.id LEFT JOIN purchases i ON pr.purchase_id = i.id WHERE $whereSql ORDER BY pr.id DESC"); $stmt->execute($params); $data['returns'] = $stmt->fetchAll(); $data['purchase_invoices'] = db()->query("SELECT id, invoice_date, total_with_vat FROM purchases ORDER BY id DESC")->fetchAll(); break; case 'customer_statement': case 'supplier_statement': $isCustomer = ($page === 'customer_statement'); $entityTable = $isCustomer ? 'customers' : 'suppliers'; $invoiceTable = $isCustomer ? 'invoices' : 'purchases'; $paymentTable = $isCustomer ? 'payments' : 'purchase_payments'; $fkColumn = $isCustomer ? 'customer_id' : 'supplier_id'; $invFkColumn = $isCustomer ? 'invoice_id' : 'purchase_id'; $data['entities'] = db()->query("SELECT id, name, balance FROM $entityTable ORDER BY name ASC")->fetchAll(); $entity_id = (int)($_GET['entity_id'] ?? 0); if ($entity_id) { $data['selected_entity'] = db()->query("SELECT * FROM $entityTable WHERE id = $entity_id")->fetch(); $start_date = $_GET['start_date'] ?? date('Y-m-01'); $end_date = $_GET['end_date'] ?? date('Y-m-d'); $stmt = db()->prepare("SELECT 'invoice' as trans_type, id, invoice_date as trans_date, total_with_vat as amount, status, id as ref_no FROM $invoiceTable WHERE $fkColumn = ? AND invoice_date BETWEEN ? AND ?"); $stmt->execute([$entity_id, $start_date, $end_date]); $invoices = $stmt->fetchAll(PDO::FETCH_ASSOC); $stmt = db()->prepare("SELECT 'payment' as trans_type, p.id, p.payment_date as trans_date, p.amount, p.payment_method, p.$invFkColumn as ref_no FROM $paymentTable p JOIN $invoiceTable i ON p.$invFkColumn = i.id WHERE i.$fkColumn = ? AND p.payment_date BETWEEN ? AND ?"); $stmt->execute([$entity_id, $start_date, $end_date]); $payments = $stmt->fetchAll(PDO::FETCH_ASSOC); $transactions = array_merge($invoices, $payments); usort($transactions, function($a, $b) { return strtotime($a['trans_date']) <=> strtotime($b['trans_date']); }); $data['transactions'] = $transactions; } break; case 'expense_categories': $data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll(); break; case 'expenses': $where = ["1=1"]; $params = []; if (!empty($_GET['category_id'])) { $where[] = "e.category_id = ?"; $params[] = $_GET['category_id']; } if (!empty($_GET['start_date'])) { $where[] = "e.expense_date >= ?"; $params[] = $_GET['start_date']; } if (!empty($_GET['end_date'])) { $where[] = "e.expense_date <= ?"; $params[] = $_GET['end_date']; } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT e.*, c.name_en as cat_en, c.name_ar as cat_ar FROM expenses e LEFT JOIN expense_categories c ON e.category_id = c.id WHERE $whereSql ORDER BY e.expense_date DESC, e.id DESC"); $stmt->execute($params); $data['expenses'] = $stmt->fetchAll(); break; case 'role_groups': $data['role_groups'] = db()->query("SELECT * FROM role_groups ORDER BY name ASC")->fetchAll(); break; 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(); break; case 'backups': $data['backups'] = BackupService::getBackups(); $stmt = db()->prepare("SELECT * FROM settings WHERE `key` IN ('backup_limit', 'backup_auto_enabled', 'backup_time')"); $stmt->execute(); $data['backup_settings'] = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); break; case 'accounting': $data['journal_entries'] = db()->query("SELECT je.*, (SELECT SUM(debit) FROM acc_ledger WHERE journal_entry_id = je.id) as total_debit FROM acc_journal_entries je ORDER BY je.entry_date DESC, je.id DESC LIMIT 100")->fetchAll(); $data['accounts'] = db()->query("SELECT * FROM acc_accounts ORDER BY code ASC")->fetchAll(); if (isset($_GET['action']) && $_GET['action'] === 'get_entry_details') { header('Content-Type: application/json'); $id = (int)$_GET['id']; $stmt = db()->prepare("SELECT l.*, a.name_en, a.code FROM acc_ledger l JOIN acc_accounts a ON l.account_id = a.id WHERE l.journal_entry_id = ?"); $stmt->execute([$id]); echo json_encode($stmt->fetchAll()); exit; } if (isset($_GET['view']) && $_GET['view'] === 'trial_balance') { $data['trial_balance'] = db()->query("SELECT a.code, a.name_en, SUM(l.debit) as total_debit, SUM(l.credit) as total_credit FROM acc_accounts a LEFT JOIN acc_ledger l ON a.id = l.account_id GROUP BY a.id HAVING total_debit > 0 OR total_credit > 0 ORDER BY a.code ASC")->fetchAll(); } if (isset($_GET['view']) && $_GET['view'] === 'profit_loss') { $data['revenue_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'revenue' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); $data['expense_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'expense' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); } if (isset($_GET['view']) && $_GET['view'] === 'balance_sheet') { $data['asset_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'asset' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); $data['liability_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'liability' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); $data['equity_accounts'] = db()->query("SELECT code, name_en, name_ar FROM acc_accounts WHERE type = 'equity' AND parent_id IS NOT NULL ORDER BY code ASC")->fetchAll(); } if (isset($_GET['view']) && $_GET['view'] === 'vat_report') { $start = $_GET['start_date'] ?? date('Y-m-01'); $end = $_GET['end_date'] ?? date('Y-m-d'); $data['vat_report'] = getVatReport($start, $end); $data['start_date'] = $start; $data['end_date'] = $end; } if (isset($_GET['view']) && $_GET['view'] === 'coa') { $data['coa'] = db()->query("SELECT a.*, p.name_en as parent_name FROM acc_accounts a LEFT JOIN acc_accounts p ON a.parent_id = p.id ORDER BY a.code ASC")->fetchAll(); } break; case 'expense_report': $start_date = $_GET['start_date'] ?? date('Y-m-01'); $end_date = $_GET['end_date'] ?? date('Y-m-d'); $category_id = $_GET['category_id'] ?? ''; $where = "WHERE e.expense_date BETWEEN ? AND ?"; $params = [$start_date, $end_date]; if ($category_id !== '') { $where .= " AND e.category_id = ?"; $params[] = $category_id; } $stmt = db()->prepare("SELECT c.name_en, c.name_ar, SUM(e.amount) as total FROM expenses e JOIN expense_categories c ON e.category_id = c.id $where GROUP BY c.id ORDER BY total DESC"); $stmt->execute($params); $data['report_by_category'] = $stmt->fetchAll(); $stmt = db()->prepare("SELECT SUM(amount) FROM expenses e $where"); $stmt->execute($params); $data['total_expenses'] = $stmt->fetchColumn() ?: 0; $data['expense_categories'] = db()->query("SELECT * FROM expense_categories ORDER BY name_en ASC")->fetchAll(); break; case 'expiry_report': $where = ["expiry_date IS NOT NULL"]; $params = []; $filter = $_GET['filter'] ?? 'all'; if ($filter === 'expired') { $where[] = "expiry_date <= CURDATE()"; } elseif ($filter === 'near_expiry') { $where[] = "expiry_date > CURDATE() AND expiry_date <= DATE_ADD(CURDATE(), INTERVAL 30 DAY)"; } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar FROM stock_items i LEFT JOIN stock_categories c ON i.category_id = c.id WHERE $whereSql ORDER BY i.expiry_date ASC"); $stmt->execute($params); $data['expiry_items'] = $stmt->fetchAll(); break; case 'low_stock_report': $stmt = db()->prepare("SELECT i.*, c.name_en as cat_en, c.name_ar as cat_ar, s.name as supplier_name FROM stock_items i LEFT JOIN stock_categories c ON i.category_id = c.id LEFT JOIN suppliers s ON i.supplier_id = s.id WHERE i.stock_quantity <= i.min_stock_level ORDER BY (i.min_stock_level - i.stock_quantity) DESC"); $stmt->execute(); $data['low_stock_items'] = $stmt->fetchAll(); break; case 'cashflow_report': $start_date = $_GET['start_date'] ?? date('Y-m-01'); $end_date = $_GET['end_date'] ?? date('Y-m-d'); // Fetch Cash & Bank Account IDs $cash_accounts = db()->query("SELECT id FROM acc_accounts WHERE code IN (1100, 1200)")->fetchAll(PDO::FETCH_COLUMN); $cash_ids_str = implode(',', $cash_accounts); if (!empty($cash_ids_str)) { // Opening Balance $stmt = db()->prepare("SELECT SUM(debit - credit) FROM acc_ledger l JOIN acc_journal_entries je ON l.journal_entry_id = je.id WHERE l.account_id IN ($cash_ids_str) AND je.entry_date < ?"); $stmt->execute([$start_date]); $data['opening_balance'] = $stmt->fetchColumn() ?: 0; // Transactions in range $stmt = db()->prepare("SELECT je.entry_date, je.description, l.debit as inflow, l.credit as outflow, a.name_en as other_account, a.type as other_type FROM acc_ledger l JOIN acc_journal_entries je ON l.journal_entry_id = je.id LEFT JOIN acc_ledger l2 ON l2.journal_entry_id = je.id AND l2.id != l.id LEFT JOIN acc_accounts a ON l2.account_id = a.id WHERE l.account_id IN ($cash_ids_str) AND je.entry_date BETWEEN ? AND ? ORDER BY je.entry_date ASC, je.id ASC"); $stmt->execute([$start_date, $end_date]); $data['cash_transactions'] = $stmt->fetchAll(PDO::FETCH_ASSOC); } else { $data['opening_balance'] = 0; $data['cash_transactions'] = []; } break; case 'hr_departments': $data['departments'] = db()->query("SELECT * FROM hr_departments ORDER BY id DESC")->fetchAll(); break; case 'hr_employees': $data['employees'] = db()->query("SELECT e.*, d.name as dept_name FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id ORDER BY e.id DESC")->fetchAll(); $data['departments'] = db()->query("SELECT * FROM hr_departments ORDER BY name ASC")->fetchAll(); break; case 'hr_attendance': $date = $_GET['date'] ?? date('Y-m-d'); $data['attendance_date'] = $date; $data['employees'] = db()->query("SELECT e.id, e.name, d.name as dept_name, a.status, a.clock_in, a.clock_out FROM hr_employees e LEFT JOIN hr_departments d ON e.department_id = d.id LEFT JOIN hr_attendance a ON e.id = a.employee_id AND a.attendance_date = '$date' WHERE e.status = 'active' ORDER BY e.name ASC")->fetchAll(); break; case 'hr_payroll': $month = (int)($_GET['month'] ?? date('m')); $year = (int)($_GET['year'] ?? date('Y')); $data['month'] = $month; $data['year'] = $year; $data['payroll'] = db()->query("SELECT p.*, e.name as emp_name FROM hr_payroll p JOIN hr_employees e ON p.employee_id = e.id WHERE p.payroll_month = $month AND p.payroll_year = $year ORDER BY p.id DESC")->fetchAll(); $data['employees'] = db()->query("SELECT id, name, salary FROM hr_employees WHERE status = 'active' ORDER BY name ASC")->fetchAll(); break; case 'loyalty_history': $where = ["1=1"]; $params = []; if (!empty($_GET['customer_id'])) { $where[] = "lt.customer_id = ?"; $params[] = (int)$_GET['customer_id']; } if (!empty($_GET['type'])) { $where[] = "lt.transaction_type = ?"; $params[] = $_GET['type']; } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT lt.*, c.name as customer_name, c.loyalty_tier, c.loyalty_points FROM loyalty_transactions lt JOIN customers c ON lt.customer_id = c.id WHERE $whereSql ORDER BY lt.created_at DESC"); $stmt->execute($params); $data['loyalty_transactions'] = $stmt->fetchAll(); break; case 'devices': $data['devices'] = db()->query("SELECT * FROM hr_biometric_devices ORDER BY id DESC")->fetchAll(); break; case 'scale_devices': $data['scale_devices'] = db()->query("SELECT * FROM pos_devices ORDER BY id DESC")->fetchAll(); break; case 'cash_registers': $data['cash_registers'] = db()->query("SELECT * FROM cash_registers ORDER BY id DESC")->fetchAll(); break; case 'register_sessions': $where = ["1=1"]; $params = []; // Filter by user if provided and user has permission if (isset($_GET['user_id']) && !empty($_GET['user_id'])) { if (can('users_view')) { $where[] = "s.user_id = ?"; $params[] = $_GET['user_id']; } } if (!can('users_view')) { $where[] = "s.user_id = ?"; $params[] = $_SESSION['user_id']; } // Filter by date range if (isset($_GET['date_from']) && !empty($_GET['date_from'])) { $where[] = "s.opened_at >= ?"; $params[] = $_GET['date_from'] . ' 00:00:00'; } if (isset($_GET['date_to']) && !empty($_GET['date_to'])) { $where[] = "s.opened_at <= ?"; $params[] = $_GET['date_to'] . ' 23:59:59'; } $whereSql = implode(" AND ", $where); $stmt = db()->prepare("SELECT s.*, r.name as register_name, u.username FROM register_sessions s LEFT JOIN cash_registers r ON s.register_id = r.id LEFT JOIN users u ON s.user_id = u.id WHERE $whereSql ORDER BY s.id DESC"); $stmt->execute($params); $data['sessions'] = $stmt->fetchAll(); $data['cash_registers'] = db()->query("SELECT * FROM cash_registers WHERE status = 'active'")->fetchAll(); $data['users'] = db()->query("SELECT id, username FROM users ORDER BY username ASC")->fetchAll(); break; default: 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(), ]; $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); } break; } $projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? 'Accounting System'; ?>