From 171664feb3caa6f78d419bd51c3b4bdc47aec7fc Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 23 Feb 2026 18:50:55 +0000 Subject: [PATCH] updating schema --- admin/includes/header.php | 2 +- admin/profile.php | 282 ++++++++++++++++---------------------- db/init.php | 60 +++++--- db/schema.sql | 185 ++++++++++++++++++++++++- includes/functions.php | 44 ++++-- login.php | 8 +- logout.php | 4 +- test_url.php | 11 -- 8 files changed, 378 insertions(+), 218 deletions(-) delete mode 100644 test_url.php diff --git a/admin/includes/header.php b/admin/includes/header.php index b8c3b0f..f11b1fc 100644 --- a/admin/includes/header.php +++ b/admin/includes/header.php @@ -579,7 +579,7 @@ function can_view($module) {
  • My Profile
  • Company Settings
  • -
  • Logout
  • +
  • "> Logout
  • diff --git a/admin/profile.php b/admin/profile.php index c464fee..e01a1c8 100644 --- a/admin/profile.php +++ b/admin/profile.php @@ -2,197 +2,151 @@ require_once __DIR__ . '/../db/config.php'; require_once __DIR__ . '/../includes/functions.php'; -require_login(); -$pdo = db(); -$currentUser = get_logged_user(); -$id = $currentUser['id']; +init_session(); -// Always fetch fresh data from DB -$stmt = $pdo->prepare("SELECT u.*, g.name as group_name, g.permissions - FROM users u - LEFT JOIN user_groups g ON u.group_id = g.id - WHERE u.id = ?"); -$stmt->execute([$id]); -$user = $stmt->fetch(PDO::FETCH_ASSOC); - -if (!$user) { - logout_user(); - header('Location: /login.php'); +// Ensure login +if (!get_logged_user()) { + header('Location: ' . url('login.php')); exit; } -$message = ''; +$user = get_logged_user(); +$pdo = db(); +$error = ''; +$success = ''; if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $full_name = $_POST['full_name']; - $email = $_POST['email']; - - $pdo->beginTransaction(); + $full_name = $_POST['full_name'] ?? ''; + $email = $_POST['email'] ?? ''; + $new_password = $_POST['new_password'] ?? ''; + $confirm_password = $_POST['confirm_password'] ?? ''; + $profile_pic = $user['profile_pic'] ?? null; + + // Profile Pic Upload + if (isset($_FILES['profile_pic']) && $_FILES['profile_pic']['error'] === UPLOAD_ERR_OK) { + $upload_dir = __DIR__ . '/../assets/images/users/'; + if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, true); + + $ext = pathinfo($_FILES['profile_pic']['name'], PATHINFO_EXTENSION); + $filename = 'user_' . $user['id'] . '_' . uniqid() . '.' . $ext; + $target = $upload_dir . $filename; + + if (move_uploaded_file($_FILES['profile_pic']['tmp_name'], $target)) { + $profile_pic = 'assets/images/users/' . $filename; + } + } + try { - $sql = "UPDATE users SET full_name = ?, email = ? WHERE id = ?"; - $params = [$full_name, $email, $id]; - - $stmt = $pdo->prepare($sql); - $stmt->execute($params); - - // Update password if provided - if (!empty($_POST['password'])) { - $password = password_hash($_POST['password'], PASSWORD_DEFAULT); - $pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$password, $id]); + if (!empty($new_password)) { + if ($new_password !== $confirm_password) { + $error = 'Passwords do not match.'; + } else { + $hashed_pass = password_hash($new_password, PASSWORD_DEFAULT); + $stmt = $pdo->prepare("UPDATE users SET full_name = ?, email = ?, password = ?, profile_pic = ? WHERE id = ?"); + $stmt->execute([$full_name, $email, $hashed_pass, $profile_pic, $user['id']]); + $success = 'Profile and password updated successfully.'; + } + } else { + $stmt = $pdo->prepare("UPDATE users SET full_name = ?, email = ? , profile_pic = ? WHERE id = ?"); + $stmt->execute([$full_name, $email, $profile_pic, $user['id']]); + $success = 'Profile updated successfully.'; } - // Handle Profile Picture Upload - if (isset($_FILES['profile_pic']) && $_FILES['profile_pic']['error'] === UPLOAD_ERR_OK) { - $upload_dir = __DIR__ . '/../assets/images/users/'; - if (!is_dir($upload_dir)) { - mkdir($upload_dir, 0775, true); - } - - $file_tmp = $_FILES['profile_pic']['tmp_name']; - $file_name = $_FILES['profile_pic']['name']; - $file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION)); - $allowed_exts = ['jpg', 'jpeg', 'png', 'gif', 'webp']; - - if (in_array($file_ext, $allowed_exts)) { - $new_file_name = 'user_' . $id . '_' . uniqid() . '.' . $file_ext; - $upload_path = $upload_dir . $new_file_name; - - if (move_uploaded_file($file_tmp, $upload_path)) { - // Delete old profile pic if exists - if ($user['profile_pic'] && file_exists(__DIR__ . '/../' . $user['profile_pic'])) { - unlink(__DIR__ . '/../' . $user['profile_pic']); - } - - $profile_pic_path = 'assets/images/users/' . $new_file_name; - $pdo->prepare("UPDATE users SET profile_pic = ? WHERE id = ?")->execute([$profile_pic_path, $id]); - } - } + if (empty($error)) { + // Update session data + $stmt = $pdo->prepare("SELECT u.*, g.name as group_name, g.permissions FROM users u LEFT JOIN user_groups g ON u.group_id = g.id WHERE u.id = ?"); + $stmt->execute([$user['id']]); + $_SESSION['user'] = $stmt->fetch(PDO::FETCH_ASSOC); + $user = $_SESSION['user']; } - - $pdo->commit(); - $message = '
    Profile updated successfully!
    '; - - // Refresh user data and update session - $stmt->execute([$id]); - $user = $stmt->fetch(PDO::FETCH_ASSOC); - $_SESSION['user'] = $user; - unset($_SESSION['user']['password']); - } catch (Exception $e) { - $pdo->rollBack(); - $message = '
    Error updating profile: ' . $e->getMessage() . '
    '; + $error = 'Error: ' . $e->getMessage(); } } -include 'includes/header.php'; +include __DIR__ . '/includes/header.php'; ?> -
    -

    My Profile

    -

    Manage your personal information and account settings.

    -
    - - - -
    -
    -
    -
    -
    -
    -
    - - +
    +
    +
    +
    +
    +
    My Profile
    +
    +
    + + -
    - - + + + + -
    + -
    -
    - - -
    -
    - - - - -
    -
    - -
    -
    - -
    - - Profile Picture - -
    - -
    - -
    - -
    Allowed: JPG, PNG, GIF, WebP. Recommended: Square image.
    + +
    +
    +
    + + + +
    + +
    + +
    +
    + +
    +
    Role:
    +
    +
    +
    + + +
    +
    + + +
    +
    + +
    -
    -
    - - -
    - -
    - -
    - -
    - -
    -
    -
    - -
    -
    -
    -
    - - Profile Picture - -
    - +
    + +
    Change Password
    +
    +
    + + +
    +
    + + +
    - -
    -

    -
    @
    - -
    - Active Account -
    - -
    - Member since -
    -
    -
    -
    -
    -
    Account Security
    -
      -
    • Password is encrypted
    • -
    • Role-based access control
    • -
    • Session-based authentication
    • -
    +
    + +
    + +
    - \ No newline at end of file + diff --git a/db/init.php b/db/init.php index 9375db9..6f9d174 100644 --- a/db/init.php +++ b/db/init.php @@ -4,36 +4,64 @@ require_once __DIR__ . '/config.php'; try { $pdo = db(); - // Execute schema - $sql = file_get_contents(__DIR__ . '/schema.sql'); - $pdo->exec($sql); + // 1. Run Consolidated Schema + $schema = file_get_contents(__DIR__ . '/schema.sql'); + // Split by ; to handle multiple statements if PDO::exec has issues, + // but schema.sql is designed to be executed as one block or via a loop. + // For simplicity and to handle the large schema: + $queries = array_filter(array_map('trim', explode(';', $schema))); + foreach ($queries as $query) { + if (!empty($query)) { + try { + $pdo->exec($query); + } catch (PDOException $e) { + // Ignore errors like "Duplicate column" if re-running on existing DB + if (strpos($e->getMessage(), 'Duplicate column') === false && + strpos($e->getMessage(), 'already exists') === false) { + echo "Notice (Schema): " . $e->getMessage() . "\n"; + } + } + } + } - // Check if data exists + // 2. Run Company Settings if table exists + if (file_exists(__DIR__ . '/company_settings.sql')) { + $cs_sql = file_get_contents(__DIR__ . '/company_settings.sql'); + $cs_queries = array_filter(array_map('trim', explode(';', $cs_sql))); + foreach ($cs_queries as $query) { + if (!empty($query)) { + try { + $pdo->exec($query); + } catch (PDOException $e) { + // Ignore + } + } + } + } + + // 3. Seed initial data if empty $stmt = $pdo->query("SELECT COUNT(*) FROM outlets"); if ($stmt->fetchColumn() == 0) { - // Seed Outlets $pdo->exec("INSERT INTO outlets (name, address) VALUES ('Main Downtown', '123 Main St'), ('Westside Hub', '456 West Blvd')"); - - // Seed Categories $pdo->exec("INSERT INTO categories (name, sort_order) VALUES ('Burgers', 1), ('Sides', 2), ('Drinks', 3)"); - - // Seed Products $pdo->exec("INSERT INTO products (category_id, name, description, price) VALUES (1, 'Signature Burger', 'Juicy beef patty with special sauce', 12.99), (1, 'Veggie Delight', 'Plant-based patty with fresh avocado', 11.50), (2, 'Truffle Fries', 'Crispy fries with truffle oil and parmesan', 5.99), (3, 'Craft Cola', 'House-made sparkling cola', 3.50)"); - // Seed Variants - $pdo->exec("INSERT INTO product_variants (product_id, name, price_adjustment) VALUES - (1, 'Double Patty', 4.00), - (1, 'Extra Cheese', 1.00), - (3, 'Large Portion', 2.00)"); + // Ensure Admin group and user exist + $pdo->exec("INSERT IGNORE INTO user_groups (id, name, permissions) VALUES (1, 'Administrator', 'all')"); + + // admin123 hash + $hashed_pass = password_hash('admin123', PASSWORD_DEFAULT); + $pdo->exec("INSERT IGNORE INTO users (group_id, username, password, full_name, is_active) + VALUES (1, 'admin', '$hashed_pass', 'Super Admin', 1)"); echo "Database initialized and seeded successfully."; } else { - echo "Database already initialized."; + echo "Database structure updated. Data already exists."; } } catch (Exception $e) { echo "Error: " . $e->getMessage(); -} +} \ No newline at end of file diff --git a/db/schema.sql b/db/schema.sql index 78d2335..439f88e 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -17,7 +17,12 @@ CREATE TABLE IF NOT EXISTS products ( name VARCHAR(255) NOT NULL, description TEXT, price DECIMAL(10, 2) NOT NULL, + cost_price DECIMAL(10, 2) DEFAULT 0.00, + stock_quantity INT DEFAULT 0, image_url VARCHAR(255), + promo_discount_percent DECIMAL(5, 2) DEFAULT NULL, + promo_date_from DATE DEFAULT NULL, + promo_date_to DATE DEFAULT NULL, FOREIGN KEY (category_id) REFERENCES categories(id) ); @@ -46,18 +51,72 @@ CREATE TABLE IF NOT EXISTS tables ( FOREIGN KEY (area_id) REFERENCES areas(id) ); +CREATE TABLE IF NOT EXISTS user_groups ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + permissions TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS users ( + id INT AUTO_INCREMENT PRIMARY KEY, + group_id INT, + username VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + full_name VARCHAR(255), + employee_id VARCHAR(50) UNIQUE, + email VARCHAR(255) UNIQUE, + profile_pic VARCHAR(255) DEFAULT NULL, + is_active BOOLEAN DEFAULT TRUE, + is_ratable TINYINT(1) DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS user_outlets ( + user_id INT(11) NOT NULL, + outlet_id INT(11) NOT NULL, + PRIMARY KEY (user_id, outlet_id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (outlet_id) REFERENCES outlets(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS customers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + phone VARCHAR(20) UNIQUE, + email VARCHAR(255), + points INT DEFAULT 0, + loyalty_redemptions_count INT DEFAULT 0, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS payment_types ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + type ENUM('cash', 'card', 'api') DEFAULT 'cash', + api_provider VARCHAR(50) DEFAULT NULL, + is_active BOOLEAN DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + CREATE TABLE IF NOT EXISTS orders ( id INT AUTO_INCREMENT PRIMARY KEY, outlet_id INT, + user_id INT(11) NULL, + customer_id INT DEFAULT NULL, table_id INT, table_number VARCHAR(50), - order_type ENUM('dine-in', 'delivery', 'drive-thru') DEFAULT 'dine-in', + order_type ENUM('dine-in', 'delivery', 'drive-thru', 'takeaway') DEFAULT 'dine-in', status ENUM('pending', 'preparing', 'ready', 'completed', 'cancelled') DEFAULT 'pending', + payment_type_id INT DEFAULT NULL, total_amount DECIMAL(10, 2) NOT NULL, customer_name VARCHAR(255), customer_phone VARCHAR(50), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (outlet_id) REFERENCES outlets(id), + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL, + FOREIGN KEY (customer_id) REFERENCES customers(id) ON DELETE SET NULL, FOREIGN KEY (table_id) REFERENCES tables(id) ); @@ -72,11 +131,123 @@ CREATE TABLE IF NOT EXISTS order_items ( FOREIGN KEY (product_id) REFERENCES products(id) ); --- Loyalty -CREATE TABLE IF NOT EXISTS loyalty_customers ( +CREATE TABLE IF NOT EXISTS company_settings ( id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255), - email VARCHAR(255) UNIQUE, - points INT DEFAULT 0, + company_name VARCHAR(255) NOT NULL DEFAULT 'My Restaurant', + address TEXT, + phone VARCHAR(50), + email VARCHAR(255), + vat_rate DECIMAL(5, 2) DEFAULT 0.00, + currency_symbol VARCHAR(10) DEFAULT '$', + currency_decimals INT DEFAULT 2, + logo_url VARCHAR(255), + favicon_url VARCHAR(255), + ctr_number VARCHAR(50), + vat_number VARCHAR(50), + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS loyalty_settings ( + id INT PRIMARY KEY, + points_per_order INT DEFAULT 10, + points_for_free_meal INT DEFAULT 70, + is_enabled TINYINT(1) DEFAULT 1 +); + +CREATE TABLE IF NOT EXISTS integration_settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + provider VARCHAR(50) NOT NULL, + setting_key VARCHAR(100) NOT NULL, + setting_value TEXT, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY unique_provider_key (provider, setting_key) +); + +CREATE TABLE IF NOT EXISTS expense_categories ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS expenses ( + id INT AUTO_INCREMENT PRIMARY KEY, + category_id INT NOT NULL, + outlet_id INT NOT NULL, + amount DECIMAL(10, 2) NOT NULL, + description TEXT, + expense_date DATE NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (category_id) REFERENCES expense_categories(id), + FOREIGN KEY (outlet_id) REFERENCES outlets(id) +); + +CREATE TABLE IF NOT EXISTS suppliers ( + id INT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + contact_person VARCHAR(255), + email VARCHAR(255), + phone VARCHAR(50), + address TEXT, + vat_no VARCHAR(50), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS purchases ( + id INT AUTO_INCREMENT PRIMARY KEY, + supplier_id INT NULL, + purchase_date DATE NOT NULL, + total_amount DECIMAL(10, 2) DEFAULT 0.00, + status ENUM('pending', 'completed', 'cancelled') DEFAULT 'pending', + notes TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS purchase_items ( + id INT AUTO_INCREMENT PRIMARY KEY, + purchase_id INT NOT NULL, + product_id INT NOT NULL, + quantity INT NOT NULL, + cost_price DECIMAL(10, 2) NOT NULL, + total_price DECIMAL(10, 2) NOT NULL, + FOREIGN KEY (purchase_id) REFERENCES purchases(id) ON DELETE CASCADE, + FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS ads_images ( + id INT AUTO_INCREMENT PRIMARY KEY, + image_path VARCHAR(255) NOT NULL, + title VARCHAR(255) DEFAULT NULL, + sort_order INT DEFAULT 0, + is_active TINYINT(1) DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS attendance_logs ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT, + employee_id VARCHAR(50), + log_timestamp DATETIME, + log_type ENUM('IN', 'OUT', 'OTHER') DEFAULT 'IN', + device_id VARCHAR(100), + ip_address VARCHAR(45), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL +); + +CREATE TABLE IF NOT EXISTS staff_ratings ( + id INT AUTO_INCREMENT PRIMARY KEY, + user_id INT NOT NULL, + rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5), + comment TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS service_ratings ( + id INT AUTO_INCREMENT PRIMARY KEY, + rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5), + comment TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); diff --git a/includes/functions.php b/includes/functions.php index 70110bd..9265677 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -239,7 +239,7 @@ function login_user($username, $password) { LEFT JOIN user_groups g ON u.group_id = g.id WHERE u.username = ? AND u.is_active = 1 LIMIT 1"); - $stmt->execute([$username]); + $stmt->execute([username]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user && password_verify($password, $user['password'])) { @@ -264,7 +264,7 @@ function get_logged_user() { function require_login() { if (!get_logged_user()) { - header('Location: /login.php'); + header('Location: ' . url('login.php')); exit; } } @@ -291,17 +291,35 @@ function require_permission($permission) { } /** - * Get the base URL of the application. - * - * @return string The base URL. + * Get the base path of the application. + */ +function get_base_path() { + static $base_path = null; + if ($base_path === null) { + $script_dir = dirname($_SERVER['SCRIPT_NAME'] ?? ''); + // Replace known subfolders at the end of the path + $base_path = preg_replace('/(\/admin|\/api|\/includes)$|(\/admin\/.*|\/api\/.*|\/includes\/.*)$/', '', $script_dir); + if ($base_path === DIRECTORY_SEPARATOR || $base_path === '/') { + $base_path = ''; + } + } + return $base_path; +} + +/** + * Generate a URL relative to the application base. + */ +function url($path = '') { + $path = ltrim($path, '/'); + return get_base_path() . '/' . $path; +} + +/** + * Get the full base URL including domain and protocol. */ function get_base_url() { - $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; - $domainName = $_SERVER['HTTP_HOST']; - // Remove admin/ if we are in it - $script_dir = dirname($_SERVER['SCRIPT_NAME']); - $script_dir = str_replace(['/admin', '/api'], '', $script_dir); - if ($script_dir === '/') $script_dir = ''; + $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || ($_SERVER['SERVER_PORT'] ?? '') == 443) ? "https://" : "http://"; + $domainName = $_SERVER['HTTP_HOST'] ?? 'localhost'; - return $protocol . $domainName . $script_dir . '/'; -} + return $protocol . $domainName . url(); +} \ No newline at end of file diff --git a/login.php b/login.php index a66e114..967bed7 100644 --- a/login.php +++ b/login.php @@ -6,7 +6,7 @@ init_session(); // Redirect if already logged in if (get_logged_user()) { - header('Location: /admin/index.php'); + header('Location: ' . url('admin/index.php')); exit; } @@ -17,7 +17,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $password = $_POST['password'] ?? ''; if (login_user($username, $password)) { - header('Location: /admin/index.php'); + header('Location: ' . url('admin/index.php')); exit; } else { $error = 'Invalid username or password.'; @@ -34,7 +34,7 @@ $settings = get_company_settings(); Login - <?= htmlspecialchars($settings['company_name']) ?> - +