updating schema
This commit is contained in:
parent
c6c300fdbb
commit
171664feb3
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
$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]);
|
||||
}
|
||||
|
||||
// Handle Profile Picture Upload
|
||||
// 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, 0775, true);
|
||||
}
|
||||
if (!is_dir($upload_dir)) mkdir($upload_dir, 0777, 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'];
|
||||
$ext = pathinfo($_FILES['profile_pic']['name'], PATHINFO_EXTENSION);
|
||||
$filename = 'user_' . $user['id'] . '_' . uniqid() . '.' . $ext;
|
||||
$target = $upload_dir . $filename;
|
||||
|
||||
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 (move_uploaded_file($_FILES['profile_pic']['tmp_name'], $target)) {
|
||||
$profile_pic = 'assets/images/users/' . $filename;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
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.';
|
||||
}
|
||||
|
||||
$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']);
|
||||
|
||||
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'];
|
||||
}
|
||||
} 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="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">
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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>
|
||||
</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>
|
||||
|
||||
<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)) ?>
|
||||
<?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>
|
||||
<?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>
|
||||
|
||||
<?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>
|
||||
<?php endif; ?>
|
||||
|
||||
<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 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="******">
|
||||
<hr class="my-4 text-muted opacity-25">
|
||||
|
||||
<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>
|
||||
|
||||
<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 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>
|
||||
</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>
|
||||
<?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>
|
||||
|
||||
<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'; ?>
|
||||
|
||||
58
db/init.php
58
db/init.php
@ -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();
|
||||
|
||||
183
db/schema.sql
183
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
|
||||
);
|
||||
|
||||
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
|
||||
);
|
||||
@ -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();
|
||||
}
|
||||
@ -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%);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<?php
|
||||
require_once __DIR__ . '/includes/functions.php';
|
||||
logout_user();
|
||||
header('Location: /login.php');
|
||||
header('Location: ' . url('login.php'));
|
||||
exit;
|
||||
11
test_url.php
11
test_url.php
@ -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";
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user