feat: initial foundation for collaborative canteen ordering system in vanilla PHP
This commit is contained in:
@@ -0,0 +1,228 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Core\Database;
|
||||
|
||||
use App\Core\Config;
|
||||
use PDO;
|
||||
use PDOException;
|
||||
|
||||
class Database
|
||||
{
|
||||
private static ?PDO $instance = null;
|
||||
|
||||
public static function getConnection(): PDO
|
||||
{
|
||||
if (self::$instance === null) {
|
||||
$connection = Config::get('DB_CONNECTION', 'sqlite');
|
||||
|
||||
try {
|
||||
if ($connection === 'sqlite') {
|
||||
$databasePath = Config::get('DB_DATABASE', __DIR__ . '/../../../storage/database.sqlite');
|
||||
$dir = dirname($databasePath);
|
||||
if (!is_dir($dir)) {
|
||||
mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
self::$instance = new PDO("sqlite:{$databasePath}", null, null, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
|
||||
self::$instance->exec('PRAGMA foreign_keys = ON;');
|
||||
} else {
|
||||
$host = Config::get('DB_HOST', '127.0.0.1');
|
||||
$port = Config::get('DB_PORT', '3306');
|
||||
$database = Config::get('DB_DATABASE', 'order_system');
|
||||
$username = Config::get('DB_USERNAME', 'root');
|
||||
$password = Config::get('DB_PASSWORD', '');
|
||||
|
||||
$dsn = "mysql:host={$host};port={$port};dbname={$database};charset=utf8mb4";
|
||||
self::$instance = new PDO($dsn, $username, $password, [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]);
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
die('Database Connection Error: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes database tables and seed data for the Collaborative Canteen Ordering System
|
||||
*/
|
||||
public static function initSchema(): void
|
||||
{
|
||||
$db = self::getConnection();
|
||||
$isSqlite = Config::get('DB_CONNECTION', 'sqlite') === 'sqlite';
|
||||
|
||||
// Auto-migration check: If old schema exists without user_id / creator_id, drop old tables
|
||||
if ($isSqlite) {
|
||||
$checkTable = $db->query("SELECT name FROM sqlite_master WHERE type='table' AND name='employee_orders'")->fetchColumn();
|
||||
if ($checkTable) {
|
||||
$columns = $db->query("PRAGMA table_info(employee_orders)")->fetchAll();
|
||||
$hasUserId = false;
|
||||
foreach ($columns as $col) {
|
||||
if ($col['name'] === 'user_id') {
|
||||
$hasUserId = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$hasUserId) {
|
||||
$db->exec("DROP TABLE IF EXISTS employee_order_items;");
|
||||
$db->exec("DROP TABLE IF EXISTS employee_orders;");
|
||||
$db->exec("DROP TABLE IF EXISTS session_menus;");
|
||||
$db->exec("DROP TABLE IF EXISTS daily_sessions;");
|
||||
$db->exec("DROP TABLE IF EXISTS employees;");
|
||||
$db->exec("DROP TABLE IF EXISTS users;");
|
||||
$db->exec("DROP TABLE IF EXISTS orders;");
|
||||
$db->exec("DROP TABLE IF EXISTS order_items;");
|
||||
$db->exec("DROP TABLE IF EXISTS products;");
|
||||
$db->exec("DROP TABLE IF EXISTS customers;");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$autoIncrement = $isSqlite ? 'AUTOINCREMENT' : 'AUTO_INCREMENT';
|
||||
$dateTimeDefault = $isSqlite ? "DATETIME DEFAULT (datetime('now', 'localtime'))" : 'DATETIME DEFAULT CURRENT_TIMESTAMP';
|
||||
|
||||
// Users Table
|
||||
$db->exec("
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY {$autoIncrement},
|
||||
name VARCHAR(255) NOT NULL,
|
||||
username VARCHAR(100) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
created_at {$dateTimeDefault}
|
||||
);
|
||||
");
|
||||
|
||||
// Daily Sessions Table
|
||||
$db->exec("
|
||||
CREATE TABLE IF NOT EXISTS daily_sessions (
|
||||
id INTEGER PRIMARY KEY {$autoIncrement},
|
||||
creator_id INTEGER NOT NULL,
|
||||
session_date DATE NOT NULL,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
canteen_name VARCHAR(255) NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'open',
|
||||
notes TEXT DEFAULT NULL,
|
||||
created_at {$dateTimeDefault},
|
||||
updated_at {$dateTimeDefault},
|
||||
FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
|
||||
// Session Menus Table
|
||||
$db->exec("
|
||||
CREATE TABLE IF NOT EXISTS session_menus (
|
||||
id INTEGER PRIMARY KEY {$autoIncrement},
|
||||
session_id INTEGER NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
final_price DECIMAL(10,2) DEFAULT 0.00,
|
||||
created_at {$dateTimeDefault},
|
||||
FOREIGN KEY (session_id) REFERENCES daily_sessions(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
|
||||
// Employee Orders Table
|
||||
$db->exec("
|
||||
CREATE TABLE IF NOT EXISTS employee_orders (
|
||||
id INTEGER PRIMARY KEY {$autoIncrement},
|
||||
session_id INTEGER NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
status VARCHAR(50) NOT NULL DEFAULT 'unpaid',
|
||||
notes TEXT DEFAULT NULL,
|
||||
total_bill DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
created_at {$dateTimeDefault},
|
||||
updated_at {$dateTimeDefault},
|
||||
FOREIGN KEY (session_id) REFERENCES daily_sessions(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
|
||||
// Employee Order Items Table
|
||||
$db->exec("
|
||||
CREATE TABLE IF NOT EXISTS employee_order_items (
|
||||
id INTEGER PRIMARY KEY {$autoIncrement},
|
||||
employee_order_id INTEGER NOT NULL,
|
||||
session_menu_id INTEGER DEFAULT NULL,
|
||||
item_name VARCHAR(255) NOT NULL,
|
||||
quantity INTEGER NOT NULL DEFAULT 1,
|
||||
unit_price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
total_price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
|
||||
notes VARCHAR(255) DEFAULT NULL,
|
||||
created_at {$dateTimeDefault},
|
||||
FOREIGN KEY (employee_order_id) REFERENCES employee_orders(id) ON DELETE CASCADE
|
||||
);
|
||||
");
|
||||
|
||||
// Check if seed data exists
|
||||
$stmt = $db->query("SELECT COUNT(*) FROM users");
|
||||
$count = (int)$stmt->fetchColumn();
|
||||
|
||||
if ($count === 0) {
|
||||
self::seedInitialData($db);
|
||||
}
|
||||
}
|
||||
|
||||
private static function seedInitialData(PDO $db): void
|
||||
{
|
||||
$hash = password_hash('password', PASSWORD_DEFAULT);
|
||||
|
||||
// Seed Users
|
||||
$users = [
|
||||
['Dea (Coordinator)', 'dea', 'dea@company.com', $hash],
|
||||
['Alex Turner', 'alex', 'alex@company.com', $hash],
|
||||
['Sarah Jenkins', 'sarah', 'sarah@company.com', $hash],
|
||||
['Budi Santoso', 'budi', 'budi@company.com', $hash],
|
||||
['Michael Chen', 'michael', 'michael@company.com', $hash],
|
||||
];
|
||||
|
||||
$stmtUser = $db->prepare("INSERT INTO users (name, username, email, password_hash) VALUES (?, ?, ?, ?)");
|
||||
foreach ($users as $u) {
|
||||
$stmtUser->execute($u);
|
||||
}
|
||||
|
||||
// Seed Today's Session created by Dea (id = 1)
|
||||
$today = date('Y-m-d');
|
||||
$title = "Dea's Lunch Hub - Kantin Bu Siti";
|
||||
$stmtSession = $db->prepare("INSERT INTO daily_sessions (creator_id, session_date, title, canteen_name, status, notes) VALUES (1, ?, ?, 'Kantin Bu Siti', 'open', 'Please submit your lunch orders before 11:30 AM!')");
|
||||
$stmtSession->execute([$today, $title]);
|
||||
$sessionId = (int)$db->lastInsertId();
|
||||
|
||||
// Seed Menu Items
|
||||
$menuItems = [
|
||||
'Ayam Bakar Madu + Nasi',
|
||||
'Ayam Geprek Sambal Bawang + Nasi',
|
||||
'Nasi Goreng Spesial',
|
||||
'Mie Goreng Seafood',
|
||||
'Soto Ayam Lamongan + Nasi',
|
||||
'Gado-Gado Lontong',
|
||||
'Tahu & Tempe Goreng (Extra)',
|
||||
'Es Teh Manis',
|
||||
'Es Jeruk Segar',
|
||||
];
|
||||
|
||||
$stmtMenu = $db->prepare("INSERT INTO session_menus (session_id, name, final_price) VALUES (?, ?, 0.00)");
|
||||
foreach ($menuItems as $item) {
|
||||
$stmtMenu->execute([$sessionId, $item]);
|
||||
}
|
||||
|
||||
// Seed an order for Alex (user_id = 2)
|
||||
$stmtOrder = $db->prepare("INSERT INTO employee_orders (session_id, user_id, status, notes, total_bill) VALUES (?, 2, 'unpaid', 'Pedas sedang ya', 0.00)");
|
||||
$stmtOrder->execute([$sessionId]);
|
||||
$alexOrderId = (int)$db->lastInsertId();
|
||||
|
||||
$stmtItem = $db->prepare("INSERT INTO employee_order_items (employee_order_id, session_menu_id, item_name, quantity, unit_price, total_price, notes) VALUES (?, ?, ?, ?, 0.00, 0.00, ?)");
|
||||
$stmtItem->execute([$alexOrderId, 1, 'Ayam Bakar Madu + Nasi', 1, 'Pedas sedang']);
|
||||
$stmtItem->execute([$alexOrderId, 8, 'Es Teh Manis', 1, 'Kurang manis / less sugar']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user