updating schema

This commit is contained in:
Flatlogic Bot 2026-02-23 18:50:55 +00:00
parent c6c300fdbb
commit 171664feb3
8 changed files with 378 additions and 218 deletions

View File

@ -579,7 +579,7 @@ function can_view($module) {
<li><a class="dropdown-item" href="profile.php"><i class="bi bi-person me-2"></i> My Profile</a></li> <li><a class="dropdown-item" href="profile.php"><i class="bi bi-person me-2"></i> My Profile</a></li>
<li><a class="dropdown-item" href="company.php"><i class="bi bi-building me-2"></i> Company Settings</a></li> <li><a class="dropdown-item" href="company.php"><i class="bi bi-building me-2"></i> Company Settings</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item text-danger" href="/logout.php"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li> <li><a class="dropdown-item text-danger" href="<?= url("logout.php") ?>"><i class="bi bi-box-arrow-right me-2"></i> Logout</a></li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -2,197 +2,151 @@
require_once __DIR__ . '/../db/config.php'; require_once __DIR__ . '/../db/config.php';
require_once __DIR__ . '/../includes/functions.php'; require_once __DIR__ . '/../includes/functions.php';
require_login(); init_session();
$pdo = db();
$currentUser = get_logged_user();
$id = $currentUser['id'];
// Always fetch fresh data from DB // Ensure login
$stmt = $pdo->prepare("SELECT u.*, g.name as group_name, g.permissions if (!get_logged_user()) {
FROM users u header('Location: ' . url('login.php'));
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');
exit; exit;
} }
$message = ''; $user = get_logged_user();
$pdo = db();
$error = '';
$success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$full_name = $_POST['full_name']; $full_name = $_POST['full_name'] ?? '';
$email = $_POST['email']; $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;
}
}
$pdo->beginTransaction();
try { try {
$sql = "UPDATE users SET full_name = ?, email = ? WHERE id = ?"; if (!empty($new_password)) {
$params = [$full_name, $email, $id]; if ($new_password !== $confirm_password) {
$error = 'Passwords do not match.';
$stmt = $pdo->prepare($sql); } else {
$stmt->execute($params); $hashed_pass = password_hash($new_password, PASSWORD_DEFAULT);
$stmt = $pdo->prepare("UPDATE users SET full_name = ?, email = ?, password = ?, profile_pic = ? WHERE id = ?");
// Update password if provided $stmt->execute([$full_name, $email, $hashed_pass, $profile_pic, $user['id']]);
if (!empty($_POST['password'])) { $success = 'Profile and password updated successfully.';
$password = password_hash($_POST['password'], PASSWORD_DEFAULT); }
$pdo->prepare("UPDATE users SET password = ? WHERE id = ?")->execute([$password, $id]); } 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 (empty($error)) {
if (isset($_FILES['profile_pic']) && $_FILES['profile_pic']['error'] === UPLOAD_ERR_OK) { // Update session data
$upload_dir = __DIR__ . '/../assets/images/users/'; $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 = ?");
if (!is_dir($upload_dir)) { $stmt->execute([$user['id']]);
mkdir($upload_dir, 0775, true); $_SESSION['user'] = $stmt->fetch(PDO::FETCH_ASSOC);
} $user = $_SESSION['user'];
$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]);
}
}
} }
$pdo->commit();
$message = '<div class="alert alert-success">Profile updated successfully!</div>';
// 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) { } catch (Exception $e) {
$pdo->rollBack(); $error = 'Error: ' . $e->getMessage();
$message = '<div class="alert alert-danger">Error updating profile: ' . $e->getMessage() . '</div>';
} }
} }
include 'includes/header.php'; include __DIR__ . '/includes/header.php';
?> ?>
<div class="mb-4"> <div class="container-fluid">
<h2 class="fw-bold">My Profile</h2> <div class="row">
<p class="text-muted">Manage your personal information and account settings.</p> <div class="col-md-8 mx-auto">
</div> <div class="card shadow-sm border-0 rounded-4 overflow-hidden mb-4">
<div class="card-header bg-white py-3 border-bottom-0">
<?= $message ?> <h5 class="mb-0 fw-bold">My Profile</h5>
</div>
<div class="row"> <div class="card-body p-4">
<div class="col-md-8"> <?php if ($success): ?>
<div class="card border-0 shadow-sm rounded-4 mb-4"> <div class="alert alert-success alert-dismissible fade show" role="alert">
<div class="card-body p-4"> <?= $success ?>
<form method="POST" enctype="multipart/form-data"> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label small fw-bold text-muted">FULL NAME</label>
<input type="text" name="full_name" class="form-control" value="<?= htmlspecialchars($user['full_name']) ?>" required>
</div> </div>
<div class="col-md-6"> <?php endif; ?>
<label class="form-label small fw-bold text-muted">USERNAME (READ-ONLY)</label>
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['username']) ?>" readonly>
</div>
</div>
<div class="row mb-4"> <?php if ($error): ?>
<div class="col-md-6"> <div class="alert alert-danger alert-dismissible fade show" role="alert">
<label class="form-label small fw-bold text-muted">EMAIL</label> <?= $error ?>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required> <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div> </div>
<div class="col-md-6"> <?php endif; ?>
<label class="form-label small fw-bold text-muted">ROLE / GROUP</label>
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['group_name']) ?>" readonly>
<label class="form-label small fw-bold text-muted mt-3">EMPLOYEE / BIOMETRIC ID</label>
<input type="text" class="form-control bg-light" value="<?= htmlspecialchars($user['employee_id'] ?? 'Not assigned') ?>" readonly>
</div>
</div>
<div class="row mb-4"> <form method="POST" enctype="multipart/form-data">
<div class="col-md-12"> <div class="row mb-4">
<label class="form-label small fw-bold text-muted">PROFILE PICTURE</label> <div class="col-md-4 text-center">
<div class="d-flex align-items-center gap-3"> <div class="mb-3">
<?php if ($user['profile_pic']): ?> <?php if ($user['profile_pic']): ?>
<img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm" style="width: 100px; height: 100px; object-fit: cover;"> <img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" class="rounded-circle shadow-sm border" style="width: 150px; height: 150px; object-fit: cover;">
<?php else: ?> <?php else: ?>
<div class="bg-primary bg-gradient text-white rounded-circle d-flex align-items-center justify-content-center shadow-sm" style="width: 100px; height: 100px; font-weight: 700; font-size: 2rem;"> <div class="bg-primary bg-gradient rounded-circle d-flex align-items-center justify-content-center text-white mx-auto shadow-sm" style="width: 150px; height: 150px; font-size: 4rem;">
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?> <?= strtoupper(substr($user['username'], 0, 1)) ?>
</div> </div>
<?php endif; ?> <?php endif; ?>
<div class="flex-grow-1"> </div>
<input type="file" name="profile_pic" class="form-control" accept="image/*"> <div class="mb-3">
<div class="form-text mt-1">Allowed: JPG, PNG, GIF, WebP. Recommended: Square image.</div> <label class="btn btn-outline-primary btn-sm rounded-pill">
Change Photo
<input type="file" name="profile_pic" class="d-none" accept="image/*">
</label>
</div>
<div class="small text-muted">Role: <?= htmlspecialchars($user['group_name']) ?></div>
</div>
<div class="col-md-8">
<div class="mb-3">
<label class="form-label fw-medium small">Username</label>
<input type="text" class="form-control bg-light border-0" value="<?= htmlspecialchars($user['username']) ?>" readonly>
</div>
<div class="mb-3">
<label class="form-label fw-medium small">Full Name</label>
<input type="text" name="full_name" class="form-control bg-light border-0" value="<?= htmlspecialchars($user['full_name']) ?>" required>
</div>
<div class="mb-3">
<label class="form-label fw-medium small">Email Address</label>
<input type="email" name="email" class="form-control bg-light border-0" value="<?= htmlspecialchars($user['email']) ?>">
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="mb-4"> <hr class="my-4 text-muted opacity-25">
<label class="form-label small fw-bold text-muted">NEW PASSWORD (LEAVE BLANK TO KEEP CURRENT)</label>
<input type="password" name="password" class="form-control" placeholder="******">
</div>
<hr class="my-4"> <h6 class="fw-bold mb-3">Change Password</h6>
<div class="row mb-3">
<div class="d-flex justify-content-end gap-2"> <div class="col-md-6">
<button type="submit" class="btn btn-primary rounded-pill px-5 fw-bold">Save Changes</button> <label class="form-label fw-medium small">New Password</label>
</div> <input type="password" name="new_password" class="form-control bg-light border-0" placeholder="Leave blank to keep current">
</form> </div>
</div> <div class="col-md-6">
</div> <label class="form-label fw-medium small">Confirm Password</label>
</div> <input type="password" name="confirm_password" class="form-control bg-light border-0">
</div>
<div class="col-md-4">
<div class="card border-0 shadow-sm rounded-4 bg-primary bg-gradient text-white shadow mb-4">
<div class="card-body p-4 text-center">
<div class="mb-3">
<?php if ($user['profile_pic']): ?>
<img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" alt="Profile Picture" class="rounded-circle shadow-sm border border-3 border-white mx-auto" style="width: 120px; height: 120px; object-fit: cover;">
<?php else: ?>
<div class="bg-white text-primary rounded-circle d-flex align-items-center justify-content-center mx-auto shadow-sm" style="width:120px;height:120px; font-weight:700; font-size:3rem;">
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
</div> </div>
<?php endif; ?>
</div>
<h4 class="fw-bold mb-1"><?= htmlspecialchars($user['full_name']) ?></h4>
<div class="small opacity-75 mb-3">@<?= htmlspecialchars($user['username']) ?> • <?= htmlspecialchars($user['group_name']) ?></div>
<div class="badge bg-white text-primary rounded-pill px-3 py-2 mb-3"> <div class="mt-4 pt-2">
Active Account <button type="submit" class="btn btn-primary px-4 py-2 fw-bold rounded-pill">Save Profile Changes</button>
</div>
</form>
</div> </div>
<div class="small opacity-75">
Member since <?= date('F d, Y', strtotime($user['created_at'])) ?>
</div>
</div>
</div>
<div class="card border-0 shadow-sm rounded-4">
<div class="card-body p-4">
<h6 class="fw-bold mb-3"><i class="bi bi-shield-check me-2 text-primary"></i> Account Security</h6>
<ul class="list-unstyled small mb-0">
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Password is encrypted</li>
<li class="mb-2"><i class="bi bi-check-circle-fill text-success me-2"></i> Role-based access control</li>
<li><i class="bi bi-check-circle-fill text-success me-2"></i> Session-based authentication</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<?php include 'includes/footer.php'; ?> <?php include __DIR__ . '/includes/footer.php'; ?>

View File

@ -4,35 +4,63 @@ require_once __DIR__ . '/config.php';
try { try {
$pdo = db(); $pdo = db();
// Execute schema // 1. Run Consolidated Schema
$sql = file_get_contents(__DIR__ . '/schema.sql'); $schema = file_get_contents(__DIR__ . '/schema.sql');
$pdo->exec($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"); $stmt = $pdo->query("SELECT COUNT(*) FROM outlets");
if ($stmt->fetchColumn() == 0) { if ($stmt->fetchColumn() == 0) {
// Seed Outlets
$pdo->exec("INSERT INTO outlets (name, address) VALUES ('Main Downtown', '123 Main St'), ('Westside Hub', '456 West Blvd')"); $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)"); $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 $pdo->exec("INSERT INTO products (category_id, name, description, price) VALUES
(1, 'Signature Burger', 'Juicy beef patty with special sauce', 12.99), (1, 'Signature Burger', 'Juicy beef patty with special sauce', 12.99),
(1, 'Veggie Delight', 'Plant-based patty with fresh avocado', 11.50), (1, 'Veggie Delight', 'Plant-based patty with fresh avocado', 11.50),
(2, 'Truffle Fries', 'Crispy fries with truffle oil and parmesan', 5.99), (2, 'Truffle Fries', 'Crispy fries with truffle oil and parmesan', 5.99),
(3, 'Craft Cola', 'House-made sparkling cola', 3.50)"); (3, 'Craft Cola', 'House-made sparkling cola', 3.50)");
// Seed Variants // Ensure Admin group and user exist
$pdo->exec("INSERT INTO product_variants (product_id, name, price_adjustment) VALUES $pdo->exec("INSERT IGNORE INTO user_groups (id, name, permissions) VALUES (1, 'Administrator', 'all')");
(1, 'Double Patty', 4.00),
(1, 'Extra Cheese', 1.00), // admin123 hash
(3, 'Large Portion', 2.00)"); $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."; echo "Database initialized and seeded successfully.";
} else { } else {
echo "Database already initialized."; echo "Database structure updated. Data already exists.";
} }
} catch (Exception $e) { } catch (Exception $e) {
echo "Error: " . $e->getMessage(); echo "Error: " . $e->getMessage();

View File

@ -17,7 +17,12 @@ CREATE TABLE IF NOT EXISTS products (
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
description TEXT, description TEXT,
price DECIMAL(10, 2) NOT NULL, price DECIMAL(10, 2) NOT NULL,
cost_price DECIMAL(10, 2) DEFAULT 0.00,
stock_quantity INT DEFAULT 0,
image_url VARCHAR(255), 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) FOREIGN KEY (category_id) REFERENCES categories(id)
); );
@ -46,18 +51,72 @@ CREATE TABLE IF NOT EXISTS tables (
FOREIGN KEY (area_id) REFERENCES areas(id) 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 ( CREATE TABLE IF NOT EXISTS orders (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
outlet_id INT, outlet_id INT,
user_id INT(11) NULL,
customer_id INT DEFAULT NULL,
table_id INT, table_id INT,
table_number VARCHAR(50), 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', status ENUM('pending', 'preparing', 'ready', 'completed', 'cancelled') DEFAULT 'pending',
payment_type_id INT DEFAULT NULL,
total_amount DECIMAL(10, 2) NOT NULL, total_amount DECIMAL(10, 2) NOT NULL,
customer_name VARCHAR(255), customer_name VARCHAR(255),
customer_phone VARCHAR(50), customer_phone VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (outlet_id) REFERENCES outlets(id), 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) 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) FOREIGN KEY (product_id) REFERENCES products(id)
); );
-- Loyalty CREATE TABLE IF NOT EXISTS company_settings (
CREATE TABLE IF NOT EXISTS loyalty_customers (
id INT AUTO_INCREMENT PRIMARY KEY, id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255), company_name VARCHAR(255) NOT NULL DEFAULT 'My Restaurant',
email VARCHAR(255) UNIQUE, address TEXT,
points INT DEFAULT 0, 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
);
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 created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
); );

View File

@ -239,7 +239,7 @@ function login_user($username, $password) {
LEFT JOIN user_groups g ON u.group_id = g.id LEFT JOIN user_groups g ON u.group_id = g.id
WHERE u.username = ? AND u.is_active = 1 WHERE u.username = ? AND u.is_active = 1
LIMIT 1"); LIMIT 1");
$stmt->execute([$username]); $stmt->execute([username]);
$user = $stmt->fetch(PDO::FETCH_ASSOC); $user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user && password_verify($password, $user['password'])) { if ($user && password_verify($password, $user['password'])) {
@ -264,7 +264,7 @@ function get_logged_user() {
function require_login() { function require_login() {
if (!get_logged_user()) { if (!get_logged_user()) {
header('Location: /login.php'); header('Location: ' . url('login.php'));
exit; exit;
} }
} }
@ -291,17 +291,35 @@ function require_permission($permission) {
} }
/** /**
* Get the base URL of the application. * Get the base path of the application.
* */
* @return string The base URL. 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() { function get_base_url() {
$protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || $_SERVER['SERVER_PORT'] == 443) ? "https://" : "http://"; $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' || ($_SERVER['SERVER_PORT'] ?? '') == 443) ? "https://" : "http://";
$domainName = $_SERVER['HTTP_HOST']; $domainName = $_SERVER['HTTP_HOST'] ?? 'localhost';
// 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 = '';
return $protocol . $domainName . $script_dir . '/'; return $protocol . $domainName . url();
} }

View File

@ -6,7 +6,7 @@ init_session();
// Redirect if already logged in // Redirect if already logged in
if (get_logged_user()) { if (get_logged_user()) {
header('Location: /admin/index.php'); header('Location: ' . url('admin/index.php'));
exit; exit;
} }
@ -17,7 +17,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$password = $_POST['password'] ?? ''; $password = $_POST['password'] ?? '';
if (login_user($username, $password)) { if (login_user($username, $password)) {
header('Location: /admin/index.php'); header('Location: ' . url('admin/index.php'));
exit; exit;
} else { } else {
$error = 'Invalid username or password.'; $error = 'Invalid username or password.';
@ -34,7 +34,7 @@ $settings = get_company_settings();
<title>Login - <?= htmlspecialchars($settings['company_name']) ?></title> <title>Login - <?= htmlspecialchars($settings['company_name']) ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<link rel="stylesheet" href="/assets/css/custom.css"> <link rel="stylesheet" href="<?= url('assets/css/custom.css') ?>">
<style> <style>
body { body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);

View File

@ -1,5 +1,5 @@
<?php <?php
require_once __DIR__ . '/includes/functions.php'; require_once __DIR__ . '/includes/functions.php';
logout_user(); logout_user();
header('Location: /login.php'); header('Location: ' . url('login.php'));
exit; exit;

View File

@ -1,11 +0,0 @@
<?php
$protocol = "http://";
$host = "localhost";
$_SERVER['PHP_SELF'] = "/admin/tables.php";
try {
$baseUrl = $protocol . $host . rtrim(dirname($_SERVER['PHP_SELF'], 2), '/\') . '/qorder.php';
echo "Base URL: " . $baseUrl . "\n";
} catch (Throwable $e) {
echo "Error: " . $e->getMessage() . "\n";
}