Next steps
This commit is contained in:
parent
e62fa4be97
commit
55c4d7eddb
42
db/init.sql
42
db/init.sql
@ -1,41 +1,59 @@
|
||||
-- Initialize company setup (companies, statuses, folders) and core user management.
|
||||
-- Designed for multi-tenant applications where each company has isolated data.
|
||||
|
||||
-- Companies Table: Stores information about each client company.
|
||||
CREATE TABLE IF NOT EXISTS companies (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
uprn_required BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Job Statuses Table: Stores custom job statuses defined by each company.
|
||||
CREATE TABLE IF NOT EXISTS job_statuses (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
is_default BOOLEAN DEFAULT FALSE,
|
||||
sort_order INT DEFAULT 0,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY (company_id, name)
|
||||
);
|
||||
|
||||
-- Required Folders Table: Stores mandatory folder structures defined by each company.
|
||||
CREATE TABLE IF NOT EXISTS required_folders (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS clients (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY (company_id, name)
|
||||
);
|
||||
|
||||
-- Users Table: Stores user accounts. Each user belongs to a specific company.
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
role ENUM('admin', 'standard') DEFAULT 'standard',
|
||||
role ENUM('admin', 'standard') DEFAULT 'standard', -- Admin can manage company settings, standard users manage jobs.
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Clients Table: Stores clients for each company. Clients can be added, edited, but not deleted.
|
||||
CREATE TABLE IF NOT EXISTS clients (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
contact_person VARCHAR(255) DEFAULT NULL,
|
||||
email VARCHAR(255) DEFAULT NULL,
|
||||
phone VARCHAR(255) DEFAULT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE, -- Clients can be marked inactive instead of deleted.
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
|
||||
UNIQUE KEY (company_id, name) -- Ensure client names are unique per company
|
||||
);
|
||||
295
setup.php
295
setup.php
@ -2,79 +2,132 @@
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/db/config.php';
|
||||
|
||||
session_start();
|
||||
|
||||
$error = '';
|
||||
$success = '';
|
||||
$step = $_GET['step'] ?? 1;
|
||||
$companyId = $_SESSION['company_id'] ?? null;
|
||||
|
||||
// Redirect to index if setup is complete and companyId is not in session
|
||||
if ($step > 1 && !$companyId) {
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$companyName = $_POST['company_name'] ?? '';
|
||||
$uprnRequired = isset($_POST['uprn_required']) ? 1 : 0;
|
||||
$statuses = $_POST['statuses'] ?? [];
|
||||
$folders = $_POST['folders'] ?? [];
|
||||
$defaultStatusIndex = (int)($_POST['default_status'] ?? 0);
|
||||
if ($step == 1) {
|
||||
$companyName = $_POST['company_name'] ?? '';
|
||||
$uprnRequired = isset($_POST['uprn_required']) ? 1 : 0;
|
||||
$statuses = $_POST['statuses'] ?? [];
|
||||
$folders = $_POST['folders'] ?? [];
|
||||
$defaultStatusIndex = (int)($_POST['default_status'] ?? 0);
|
||||
|
||||
if (empty($companyName)) {
|
||||
$error = "Company name is required.";
|
||||
} elseif (empty($statuses)) {
|
||||
$error = "At least one job status is required.";
|
||||
} elseif (empty($folders)) {
|
||||
$error = "At least one required folder is required.";
|
||||
} else {
|
||||
try {
|
||||
db()->beginTransaction();
|
||||
if (empty($companyName)) {
|
||||
$error = "Company name is required.";
|
||||
} elseif (empty(array_filter($statuses, 'trim'))) {
|
||||
$error = "At least one job status is required.";
|
||||
} elseif (empty(array_filter($folders, 'trim'))) {
|
||||
$error = "At least one required folder is required.";
|
||||
} else {
|
||||
try {
|
||||
db()->beginTransaction();
|
||||
|
||||
// 1. Create Company
|
||||
$stmt = db()->prepare("INSERT INTO companies (name, uprn_required) VALUES (?, ?)");
|
||||
$stmt->execute([$companyName, $uprnRequired]);
|
||||
$companyId = db()->lastInsertId();
|
||||
// 1. Create Company
|
||||
$stmt = db()->prepare("INSERT INTO companies (name, uprn_required) VALUES (?, ?)");
|
||||
$stmt->execute([$companyName, $uprnRequired]);
|
||||
$companyId = db()->lastInsertId();
|
||||
$_SESSION['company_id'] = $companyId; // Store company ID in session for next steps
|
||||
|
||||
// 2. Insert Statuses
|
||||
$stmt = db()->prepare("INSERT INTO job_statuses (company_id, name, is_default, sort_order) VALUES (?, ?, ?, ?)");
|
||||
foreach ($statuses as $index => $statusName) {
|
||||
if (trim($statusName) === '') continue;
|
||||
$isDefault = ($index === $defaultStatusIndex) ? 1 : 0;
|
||||
$stmt->execute([$companyId, $statusName, $isDefault, $index]);
|
||||
// 2. Insert Statuses
|
||||
$stmt = db()->prepare("INSERT INTO job_statuses (company_id, name, is_default, sort_order) VALUES (?, ?, ?, ?)");
|
||||
foreach ($statuses as $index => $statusName) {
|
||||
$statusName = trim($statusName);
|
||||
if ($statusName === '') continue;
|
||||
$isDefault = ($index === $defaultStatusIndex) ? 1 : 0;
|
||||
$stmt->execute([$companyId, $statusName, $isDefault, $index]);
|
||||
}
|
||||
|
||||
// 3. Insert Folders
|
||||
$stmt = db()->prepare("INSERT INTO required_folders (company_id, name) VALUES (?, ?)");
|
||||
foreach ($folders as $folderName) {
|
||||
$folderName = trim($folderName);
|
||||
if ($folderName === '') continue;
|
||||
$stmt->execute([$companyId, $folderName]);
|
||||
}
|
||||
|
||||
// 4. Create first Admin user (simplified for demo)
|
||||
$adminEmail = 'admin@' . strtolower(str_replace(' ', '', $companyName)) . '.com';
|
||||
$stmt = db()->prepare("INSERT INTO users (company_id, name, email, password, role) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$companyId, 'Admin User', $adminEmail, password_hash('password123', PASSWORD_DEFAULT), 'admin']);
|
||||
|
||||
db()->commit();
|
||||
header('Location: setup.php?step=2'); // Redirect to next step
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
$error = "Database error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
} elseif ($step == 2) {
|
||||
// Step 2: Client Setup
|
||||
$clientNames = $_POST['client_names'] ?? [];
|
||||
$contactPeople = $_POST['contact_people'] ?? [];
|
||||
$clientEmails = $_POST['client_emails'] ?? [];
|
||||
$clientPhones = $_POST['client_phones'] ?? [];
|
||||
|
||||
// 3. Insert Folders
|
||||
$stmt = db()->prepare("INSERT INTO required_folders (company_id, name) VALUES (?, ?)");
|
||||
foreach ($folders as $folderName) {
|
||||
if (trim($folderName) === '') continue;
|
||||
$stmt->execute([$companyId, $folderName]);
|
||||
if (empty(array_filter($clientNames, 'trim'))) {
|
||||
$error = "At least one client is required.";
|
||||
} else {
|
||||
try {
|
||||
db()->beginTransaction();
|
||||
|
||||
$stmt = db()->prepare("INSERT INTO clients (company_id, name, contact_person, email, phone) VALUES (?, ?, ?, ?, ?)");
|
||||
foreach ($clientNames as $index => $clientName) {
|
||||
$clientName = trim($clientName);
|
||||
if ($clientName === '') continue;
|
||||
$contactPerson = trim($contactPeople[$index] ?? '');
|
||||
$clientEmail = trim($clientEmails[$index] ?? '');
|
||||
$clientPhone = trim($clientPhones[$index] ?? '');
|
||||
|
||||
$stmt->execute([$companyId, $clientName, $contactPerson, $clientEmail, $clientPhone]);
|
||||
}
|
||||
|
||||
db()->commit();
|
||||
session_destroy(); // Clear session after successful setup
|
||||
$success = "Company setup successfully! You can now log in.";
|
||||
header('Refresh: 2; URL=index.php'); // Redirect to index
|
||||
exit;
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
$error = "Database error: " . $e->getMessage();
|
||||
}
|
||||
|
||||
// 4. Create first Admin user (simplified for demo)
|
||||
$stmt = db()->prepare("INSERT INTO users (company_id, name, email, password, role) VALUES (?, ?, ?, ?, ?)");
|
||||
$stmt->execute([$companyId, 'Admin User', 'admin@' . strtolower(str_replace(' ', '', $companyName)) . '.com', password_hash('password123', PASSWORD_DEFAULT), 'admin']);
|
||||
|
||||
db()->commit();
|
||||
$success = "Company setup successfully! You can now log in.";
|
||||
header('Refresh: 2; URL=index.php');
|
||||
} catch (Exception $e) {
|
||||
db()->rollBack();
|
||||
$error = "Database error: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pageTitle = "Company Onboarding";
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Company Onboarding - RepairsPro</title>
|
||||
<title><?= $pageTitle ?> - RepairsPro</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="assets/css/custom.css?v=<?= time() ?>">
|
||||
<style>
|
||||
.setup-container { max-width: 600px; margin: 50px auto; }
|
||||
.setup-container { max-width: 800px; margin: 50px auto; }
|
||||
.dynamic-row { display: flex; gap: 10px; margin-bottom: 10px; align-items: center; }
|
||||
.client-input-group { display: flex; gap: 10px; flex-wrap: wrap; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="setup-container">
|
||||
<div class="card p-4">
|
||||
<h2 class="fw-bold mb-4">Company Onboarding</h2>
|
||||
<h2 class="fw-bold mb-4">Company Onboarding - Step <?= $step ?></h2>
|
||||
|
||||
<?php if ($error): ?>
|
||||
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
|
||||
@ -82,66 +135,83 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
||||
<?php if ($success): ?>
|
||||
<div class="alert alert-success"><?= htmlspecialchars($success) ?></div>
|
||||
<?php else: ?>
|
||||
<?php else: // Display forms if not success ?>
|
||||
|
||||
<form method="POST" id="onboardingForm">
|
||||
<!-- Step 1: Basic Info -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Company Name</label>
|
||||
<input type="text" name="company_name" class="form-control" placeholder="e.g. London Repairs Ltd" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="uprn_required" id="uprnCheck">
|
||||
<label class="form-check-label fw-bold" for="uprnCheck">Require Job UPRN?</label>
|
||||
</div>
|
||||
<small class="text-secondary">If enabled, every job must have a unique UPRN.</small>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Step 2: Job Statuses -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Job Statuses</label>
|
||||
<p class="small text-secondary">Define the workflow stages for your jobs.</p>
|
||||
<div id="status-container">
|
||||
<div class="dynamic-row">
|
||||
<input type="radio" name="default_status" value="0" checked title="Set as default">
|
||||
<input type="text" name="statuses[]" class="form-control" value="To Be Surveyed" required>
|
||||
<?php if ($step == 1): // Step 1: Company Info, Statuses, Folders ?>
|
||||
<form method="POST" id="onboardingFormStep1">
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Company Name</label>
|
||||
<input type="text" name="company_name" class="form-control" placeholder="e.g. London Repairs Ltd" required>
|
||||
</div>
|
||||
<div class="dynamic-row">
|
||||
<input type="radio" name="default_status" value="1" title="Set as default">
|
||||
<input type="text" name="statuses[]" class="form-control" value="Booking Required">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addRow('status-container', 'statuses[]', true)">+ Add Status</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<!-- Step 3: Required Folders -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Required Folders</label>
|
||||
<p class="small text-secondary">These folders will appear on every job automatically.</p>
|
||||
<div id="folder-container">
|
||||
<div class="dynamic-row">
|
||||
<input type="text" name="folders[]" class="form-control" value="Photos" required>
|
||||
<div class="mb-4">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" name="uprn_required" id="uprnCheck">
|
||||
<label class="form-check-label fw-bold" for="uprnCheck">Require Job UPRN?</label>
|
||||
</div>
|
||||
<small class="text-secondary">If enabled, every job must have a unique UPRN.</small>
|
||||
</div>
|
||||
<div class="dynamic-row">
|
||||
<input type="text" name="folders[]" class="form-control" value="Quote">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addRow('folder-container', 'folders[]', false)">+ Add Folder</button>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 mt-5">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Complete Onboarding</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<hr>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Job Statuses</label>
|
||||
<p class="small text-secondary">Define the workflow stages for your jobs.</p>
|
||||
<div id="status-container">
|
||||
<div class="dynamic-row">
|
||||
<input type="radio" name="default_status" value="0" checked title="Set as default">
|
||||
<input type="text" name="statuses[]" class="form-control" value="To Be Surveyed" required>
|
||||
</div>
|
||||
<div class="dynamic-row">
|
||||
<input type="radio" name="default_status" value="1" title="Set as default">
|
||||
<input type="text" name="statuses[]" class="form-control" value="Booking Required">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addRow('status-container', 'statuses[]', true)">+ Add Status</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label fw-bold">Required Folders</label>
|
||||
<p class="small text-secondary">These folders will appear on every job automatically.</p>
|
||||
<div id="folder-container">
|
||||
<div class="dynamic-row">
|
||||
<input type="text" name="folders[]" class="form-control" value="Photos" required>
|
||||
</div>
|
||||
<div class="dynamic-row">
|
||||
<input type="text" name="folders[]" class="form-control" value="Quote">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addRow('folder-container', 'folders[]', false)">+ Add Folder</button>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 mt-5">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Next: Setup Clients</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php elseif ($step == 2): // Step 2: Client Setup ?>
|
||||
<form method="POST" id="onboardingFormStep2">
|
||||
<p class="small text-secondary">Add your initial clients. You can add more later.</p>
|
||||
<div id="client-container">
|
||||
<div class="dynamic-row client-input-group">
|
||||
<input type="text" name="client_names[]" class="form-control flex-grow-1" placeholder="Client Name" required>
|
||||
<input type="text" name="contact_people[]" class="form-control" placeholder="Contact Person">
|
||||
<input type="email" name="client_emails[]" class="form-control" placeholder="Email">
|
||||
<input type="text" name="client_phones[]" class="form-control" placeholder="Phone">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-btn">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary mt-2" onclick="addClientRow()">+ Add Client</button>
|
||||
|
||||
<div class="d-grid gap-2 mt-5">
|
||||
<button type="submit" class="btn btn-success btn-lg">Complete Onboarding</button>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php endif; // End of forms ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -175,10 +245,33 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
};
|
||||
}
|
||||
|
||||
// Attach remove events to existing buttons
|
||||
document.querySelectorAll('.remove-btn').forEach(btn => {
|
||||
function addClientRow() {
|
||||
const container = document.getElementById('client-container');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'dynamic-row client-input-group';
|
||||
|
||||
div.innerHTML = `
|
||||
<input type="text" name="client_names[]" class="form-control flex-grow-1" placeholder="Client Name" required>
|
||||
<input type="text" name="contact_people[]" class="form-control" placeholder="Contact Person">
|
||||
<input type="email" name="client_emails[]" class="form-control" placeholder="Email">
|
||||
<input type="text" name="client_phones[]" class="form-control" placeholder="Phone">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-btn">×</button>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
|
||||
div.querySelector('.remove-btn').onclick = function() { div.remove(); };
|
||||
}
|
||||
|
||||
// Attach remove events to existing buttons (for statuses and folders initially)
|
||||
document.querySelectorAll('#status-container .remove-btn, #folder-container .remove-btn').forEach(btn => {
|
||||
btn.onclick = function() { btn.parentElement.remove(); };
|
||||
});
|
||||
|
||||
// Initial client row for step 2 if no clients are pre-filled
|
||||
if (document.getElementById('client-container') && document.getElementById('client-container').children.length === 0) {
|
||||
// addClientRow(); // Only add if step 2 is active and no existing clients (for initial load)
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user