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="company.php"><i class="bi bi-building me-2"></i> Company Settings</a></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>
</div>
</div>

View File

@ -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'];
$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;
}
}
$pdo->beginTransaction();
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 = '<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) {
$pdo->rollBack();
$message = '<div class="alert alert-danger">Error updating profile: ' . $e->getMessage() . '</div>';
$error = 'Error: ' . $e->getMessage();
}
}
include 'includes/header.php';
include __DIR__ . '/includes/header.php';
?>
<div class="mb-4">
<h2 class="fw-bold">My Profile</h2>
<p class="text-muted">Manage your personal information and account settings.</p>
</div>
<?= $message ?>
<div class="row">
<div class="col-md-8">
<div class="card border-0 shadow-sm rounded-4 mb-4">
<div class="card-body p-4">
<form method="POST" enctype="multipart/form-data">
<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 class="container-fluid">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card shadow-sm border-0 rounded-4 overflow-hidden mb-4">
<div class="card-header bg-white py-3 border-bottom-0">
<h5 class="mb-0 fw-bold">My Profile</h5>
</div>
<div class="card-body p-4">
<?php if ($success): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= $success ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div class="col-md-6">
<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>
<?php endif; ?>
<div class="row mb-4">
<div class="col-md-6">
<label class="form-label small fw-bold text-muted">EMAIL</label>
<input type="email" name="email" class="form-control" value="<?= htmlspecialchars($user['email']) ?>" required>
<?php if ($error): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= $error ?>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<div class="col-md-6">
<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>
<?php endif; ?>
<div class="row mb-4">
<div class="col-md-12">
<label class="form-label small fw-bold text-muted">PROFILE PICTURE</label>
<div class="d-flex align-items-center gap-3">
<?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;">
<?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;">
<?= strtoupper(substr($user['full_name'] ?: $user['username'], 0, 1)) ?>
</div>
<?php endif; ?>
<div class="flex-grow-1">
<input type="file" name="profile_pic" class="form-control" accept="image/*">
<div class="form-text mt-1">Allowed: JPG, PNG, GIF, WebP. Recommended: Square image.</div>
<form method="POST" enctype="multipart/form-data">
<div class="row mb-4">
<div class="col-md-4 text-center">
<div class="mb-3">
<?php if ($user['profile_pic']): ?>
<img src="../<?= htmlspecialchars($user['profile_pic']) ?>?v=<?= time() ?>" class="rounded-circle shadow-sm border" style="width: 150px; height: 150px; object-fit: cover;">
<?php else: ?>
<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['username'], 0, 1)) ?>
</div>
<?php endif; ?>
</div>
<div class="mb-3">
<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 class="mb-4">
<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 text-muted opacity-25">
<hr class="my-4">
<div class="d-flex justify-content-end gap-2">
<button type="submit" class="btn btn-primary rounded-pill px-5 fw-bold">Save Changes</button>
</div>
</form>
</div>
</div>
</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)) ?>
<h6 class="fw-bold mb-3">Change Password</h6>
<div class="row mb-3">
<div class="col-md-6">
<label class="form-label fw-medium small">New Password</label>
<input type="password" name="new_password" class="form-control bg-light border-0" placeholder="Leave blank to keep current">
</div>
<div class="col-md-6">
<label class="form-label fw-medium small">Confirm Password</label>
<input type="password" name="confirm_password" class="form-control bg-light border-0">
</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">
Active Account
<div class="mt-4 pt-2">
<button type="submit" class="btn btn-primary px-4 py-2 fw-bold rounded-pill">Save Profile Changes</button>
</div>
</form>
</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>
<?php include 'includes/footer.php'; ?>
<?php include __DIR__ . '/includes/footer.php'; ?>

View File

@ -4,35 +4,63 @@ 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();

View File

@ -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
);
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
);

View File

@ -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();
}

View File

@ -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();
<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 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>
body {
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);

View File

@ -1,5 +1,5 @@
<?php
require_once __DIR__ . '/includes/functions.php';
logout_user();
header('Location: /login.php');
header('Location: ' . url('login.php'));
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";
}