created a new story for guest booking
This commit is contained in:
parent
a880b3fd1d
commit
45e2be8468
|
|
@ -29,6 +29,8 @@ Clients book consultations through a calendar interface. Each consultation is 45
|
||||||
- [ ] Consultation reminders (24hr, 2hr)
|
- [ ] Consultation reminders (24hr, 2hr)
|
||||||
- [ ] Calendar file (.ics) generation
|
- [ ] Calendar file (.ics) generation
|
||||||
- [ ] Admin can mark as completed/no-show/cancelled
|
- [ ] Admin can mark as completed/no-show/cancelled
|
||||||
|
- [ ] Guests can submit booking requests from public page
|
||||||
|
- [ ] Admin can approve guest requests and create accounts
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -226,6 +228,34 @@ Clients book consultations through a calendar interface. Each consultation is 45
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Story 3.9: Guest Booking Request
|
||||||
|
|
||||||
|
**Description:** Allow website visitors to submit consultation booking requests without an account.
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
- [ ] Public `/booking` page accessible without login
|
||||||
|
- [ ] Guest can select date/time from availability calendar
|
||||||
|
- [ ] Guest provides full contact details (name, national ID, email, phone)
|
||||||
|
- [ ] Support for individual and company account types
|
||||||
|
- [ ] Duplicate check: block if email OR national_id already exists (prompt to login)
|
||||||
|
- [ ] 1-per-day limit by email for guest requests
|
||||||
|
- [ ] Confirmation step before submission
|
||||||
|
- [ ] Guest receives confirmation email
|
||||||
|
- [ ] Admin receives notification email
|
||||||
|
- [ ] Guest requests appear in admin queue with "Guest" badge
|
||||||
|
- [ ] Admin can: Approve + Create Account, Reject, Create Account Only
|
||||||
|
- [ ] On approval: user account created, welcome email sent, booking approved
|
||||||
|
- [ ] On rejection: rejection email sent, guest data discarded
|
||||||
|
- [ ] All actions logged in audit trail
|
||||||
|
|
||||||
|
**Technical Notes:**
|
||||||
|
- Store in guest_booking_requests table (separate from consultations)
|
||||||
|
- Admin workflow extends Story 3.5 approval process
|
||||||
|
- On approval, creates user and converts to consultation
|
||||||
|
- Rejected requests are fully deleted (data not retained)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
| Epic | Dependency |
|
| Epic | Dependency |
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,658 @@
|
||||||
|
# Story 3.9: Guest Booking Request
|
||||||
|
|
||||||
|
## Status
|
||||||
|
**Draft**
|
||||||
|
|
||||||
|
## Epic Reference
|
||||||
|
**Epic 3:** Booking & Consultation System
|
||||||
|
|
||||||
|
## Story
|
||||||
|
**As a** guest visitor,
|
||||||
|
**I want to** submit a consultation booking request with my contact details,
|
||||||
|
**So that** I can request legal services without needing an account first.
|
||||||
|
|
||||||
|
## Story Context
|
||||||
|
|
||||||
|
### Existing System Integration
|
||||||
|
- **Integrates with:** Availability calendar (Story 3.3), Admin booking review (Story 3.5), User creation (Epic 2)
|
||||||
|
- **Technology:** Livewire Volt, Flux UI, existing availability calendar component
|
||||||
|
- **Follows pattern:** Client booking submission (Story 3.4), Admin approval workflow (Story 3.5)
|
||||||
|
- **Touch points:** Public `/booking` route, admin pending bookings queue, user creation, email notifications
|
||||||
|
|
||||||
|
### Business Context
|
||||||
|
The landing page mentions a "simplified booking form" for first-time visitors. Currently, only authenticated clients can book consultations. This story enables guests to submit booking requests with their full details, allowing the admin to review and optionally create an account upon approval.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
### Public Booking Page (AC1-6)
|
||||||
|
- [ ] **AC1:** Public `/booking` page accessible without login
|
||||||
|
- [ ] **AC2:** Display availability calendar (reuse existing component)
|
||||||
|
- [ ] **AC3:** Guest can select date and available time slot
|
||||||
|
- [ ] **AC4:** Account type toggle: Individual / Company
|
||||||
|
- [ ] **AC5:** Form fields for Individual:
|
||||||
|
- Full Name (required)
|
||||||
|
- National ID (required)
|
||||||
|
- Email (required, valid email)
|
||||||
|
- Phone (required)
|
||||||
|
- Preferred Language (Arabic/English)
|
||||||
|
- [ ] **AC6:** Additional fields for Company:
|
||||||
|
- Company Name (required)
|
||||||
|
- Company Registration Number (required)
|
||||||
|
- Contact Person Name (required)
|
||||||
|
- Contact Person ID (required)
|
||||||
|
- Email (required)
|
||||||
|
- Phone (required)
|
||||||
|
- Preferred Language (Arabic/English)
|
||||||
|
|
||||||
|
### Problem Summary (AC7-8)
|
||||||
|
- [ ] **AC7:** Problem summary field (required, textarea, min 20 characters, max 2000)
|
||||||
|
- [ ] **AC8:** Confirmation step before submission showing all entered data
|
||||||
|
|
||||||
|
### Validation & Constraints (AC9-12)
|
||||||
|
- [ ] **AC9:** Duplicate check: If email OR national_id exists in users table, show "Account already exists. Please login to book." with login link
|
||||||
|
- [ ] **AC10:** 1-per-day limit by email: Check guest_booking_requests table for same email on same booking_date
|
||||||
|
- [ ] **AC11:** Validate selected slot is still available
|
||||||
|
- [ ] **AC12:** Clear error messages for all validation failures (bilingual)
|
||||||
|
|
||||||
|
### Submission Flow (AC13-16)
|
||||||
|
- [ ] **AC13:** On submit, create record in `guest_booking_requests` table with status 'pending'
|
||||||
|
- [ ] **AC14:** Guest sees success page: "Request received. We will contact you soon."
|
||||||
|
- [ ] **AC15:** Admin receives email notification about new guest booking request
|
||||||
|
- [ ] **AC16:** Guest receives submission confirmation email
|
||||||
|
|
||||||
|
### Admin Review Workflow (AC17-22)
|
||||||
|
- [ ] **AC17:** Guest booking requests appear in admin pending bookings queue with "Guest" badge
|
||||||
|
- [ ] **AC18:** Admin can view full guest details and problem summary
|
||||||
|
- [ ] **AC19:** Admin action: **Approve + Create Account**
|
||||||
|
- Creates user account with provided data
|
||||||
|
- Generates random password
|
||||||
|
- Creates consultation from guest request
|
||||||
|
- Sends welcome email with credentials
|
||||||
|
- Sends booking approved email with .ics
|
||||||
|
- Deletes guest_booking_request record
|
||||||
|
- [ ] **AC20:** Admin action: **Reject**
|
||||||
|
- Sends rejection email to guest
|
||||||
|
- Deletes guest_booking_request record (data discarded)
|
||||||
|
- [ ] **AC21:** Admin action: **Create Account Only**
|
||||||
|
- Creates user account without booking
|
||||||
|
- Sends welcome email with credentials
|
||||||
|
- Deletes guest_booking_request record
|
||||||
|
- [ ] **AC22:** All admin actions logged in audit trail
|
||||||
|
|
||||||
|
### Quality Requirements (AC23-26)
|
||||||
|
- [ ] **AC23:** Bilingual support (Arabic/English) for all UI and emails
|
||||||
|
- [ ] **AC24:** Mobile-responsive design
|
||||||
|
- [ ] **AC25:** Race condition prevention for slot availability
|
||||||
|
- [ ] **AC26:** Comprehensive test coverage
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### New Database Table: guest_booking_requests
|
||||||
|
|
||||||
|
```php
|
||||||
|
Schema::create('guest_booking_requests', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->enum('account_type', ['individual', 'company']);
|
||||||
|
|
||||||
|
// Individual fields
|
||||||
|
$table->string('full_name');
|
||||||
|
$table->string('national_id');
|
||||||
|
|
||||||
|
// Company fields (nullable for individuals)
|
||||||
|
$table->string('company_name')->nullable();
|
||||||
|
$table->string('company_cert_number')->nullable();
|
||||||
|
$table->string('contact_person_name')->nullable();
|
||||||
|
$table->string('contact_person_id')->nullable();
|
||||||
|
|
||||||
|
// Common fields
|
||||||
|
$table->string('email');
|
||||||
|
$table->string('phone');
|
||||||
|
$table->string('preferred_language')->default('ar');
|
||||||
|
|
||||||
|
// Booking fields
|
||||||
|
$table->date('booking_date');
|
||||||
|
$table->time('booking_time');
|
||||||
|
$table->text('problem_summary');
|
||||||
|
|
||||||
|
$table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Indexes for duplicate checking
|
||||||
|
$table->index('email');
|
||||||
|
$table->index('national_id');
|
||||||
|
$table->index(['email', 'booking_date']); // For 1-per-day check
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Model: GuestBookingRequest
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class GuestBookingRequest extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'account_type',
|
||||||
|
'full_name',
|
||||||
|
'national_id',
|
||||||
|
'company_name',
|
||||||
|
'company_cert_number',
|
||||||
|
'contact_person_name',
|
||||||
|
'contact_person_id',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'preferred_language',
|
||||||
|
'booking_date',
|
||||||
|
'booking_time',
|
||||||
|
'problem_summary',
|
||||||
|
'status',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'booking_date' => 'date',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isIndividual(): bool
|
||||||
|
{
|
||||||
|
return $this->account_type === 'individual';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCompany(): bool
|
||||||
|
{
|
||||||
|
return $this->account_type === 'company';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopePending($query)
|
||||||
|
{
|
||||||
|
return $query->where('status', 'pending');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Volt Component Structure (Public Booking)
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\GuestBookingRequest;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Services\AvailabilityService;
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Mail;
|
||||||
|
use Livewire\Volt\Component;
|
||||||
|
|
||||||
|
new class extends Component {
|
||||||
|
// Step tracking
|
||||||
|
public int $step = 1; // 1: calendar, 2: details, 3: confirm
|
||||||
|
|
||||||
|
// Booking selection
|
||||||
|
public ?string $selectedDate = null;
|
||||||
|
public ?string $selectedTime = null;
|
||||||
|
|
||||||
|
// Account type
|
||||||
|
public string $accountType = 'individual';
|
||||||
|
|
||||||
|
// Individual fields
|
||||||
|
public string $fullName = '';
|
||||||
|
public string $nationalId = '';
|
||||||
|
|
||||||
|
// Company fields
|
||||||
|
public string $companyName = '';
|
||||||
|
public string $companyCertNumber = '';
|
||||||
|
public string $contactPersonName = '';
|
||||||
|
public string $contactPersonId = '';
|
||||||
|
|
||||||
|
// Common fields
|
||||||
|
public string $email = '';
|
||||||
|
public string $phone = '';
|
||||||
|
public string $preferredLanguage = 'ar';
|
||||||
|
public string $problemSummary = '';
|
||||||
|
|
||||||
|
public function selectSlot(string $date, string $time): void
|
||||||
|
{
|
||||||
|
$this->selectedDate = $date;
|
||||||
|
$this->selectedTime = $time;
|
||||||
|
$this->step = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function goToConfirm(): void
|
||||||
|
{
|
||||||
|
$this->validateDetails();
|
||||||
|
$this->step = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function submit(): void
|
||||||
|
{
|
||||||
|
// Full validation and submission logic
|
||||||
|
// See detailed implementation below
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateDetails(): void
|
||||||
|
{
|
||||||
|
$rules = [
|
||||||
|
'selectedDate' => ['required', 'date', 'after_or_equal:today'],
|
||||||
|
'selectedTime' => ['required'],
|
||||||
|
'accountType' => ['required', 'in:individual,company'],
|
||||||
|
'fullName' => ['required', 'string', 'max:255'],
|
||||||
|
'nationalId' => ['required', 'string', 'max:50'],
|
||||||
|
'email' => ['required', 'email', 'max:255'],
|
||||||
|
'phone' => ['required', 'string', 'max:20'],
|
||||||
|
'preferredLanguage' => ['required', 'in:ar,en'],
|
||||||
|
'problemSummary' => ['required', 'string', 'min:20', 'max:2000'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($this->accountType === 'company') {
|
||||||
|
$rules['companyName'] = ['required', 'string', 'max:255'];
|
||||||
|
$rules['companyCertNumber'] = ['required', 'string', 'max:100'];
|
||||||
|
$rules['contactPersonName'] = ['required', 'string', 'max:255'];
|
||||||
|
$rules['contactPersonId'] = ['required', 'string', 'max:50'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validate($rules);
|
||||||
|
|
||||||
|
// Check for existing account
|
||||||
|
$existingUser = User::where('email', $this->email)
|
||||||
|
->orWhere('national_id', $this->nationalId)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($existingUser) {
|
||||||
|
$this->addError('email', __('booking.account_exists_please_login'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check 1-per-day limit by email
|
||||||
|
$existingRequest = GuestBookingRequest::where('email', $this->email)
|
||||||
|
->whereDate('booking_date', $this->selectedDate)
|
||||||
|
->where('status', 'pending')
|
||||||
|
->exists();
|
||||||
|
|
||||||
|
if ($existingRequest) {
|
||||||
|
$this->addError('email', __('booking.guest_already_requested_this_day'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify slot still available
|
||||||
|
$service = app(AvailabilityService::class);
|
||||||
|
$availableSlots = $service->getAvailableSlots(Carbon::parse($this->selectedDate));
|
||||||
|
|
||||||
|
if (!in_array($this->selectedTime, $availableSlots)) {
|
||||||
|
$this->addError('selectedTime', __('booking.slot_no_longer_available'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin Review Extension
|
||||||
|
|
||||||
|
Extend the existing `admin.bookings.pending` component to include guest booking requests:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function with(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'clientBookings' => Consultation::where('status', ConsultationStatus::Pending)
|
||||||
|
->with('user:id,full_name,email,phone,user_type')
|
||||||
|
->orderBy('booking_date')
|
||||||
|
->orderBy('booking_time')
|
||||||
|
->get(),
|
||||||
|
'guestBookings' => GuestBookingRequest::pending()
|
||||||
|
->orderBy('booking_date')
|
||||||
|
->orderBy('booking_time')
|
||||||
|
->get(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Admin Actions for Guest Bookings
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function approveGuestAndCreateAccount(int $guestRequestId): void
|
||||||
|
{
|
||||||
|
$guestRequest = GuestBookingRequest::findOrFail($guestRequestId);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($guestRequest) {
|
||||||
|
// Generate random password
|
||||||
|
$password = Str::random(12);
|
||||||
|
|
||||||
|
// Create user account
|
||||||
|
$user = User::create([
|
||||||
|
'user_type' => $guestRequest->isIndividual() ? UserType::Individual : UserType::Company,
|
||||||
|
'full_name' => $guestRequest->full_name,
|
||||||
|
'national_id' => $guestRequest->national_id,
|
||||||
|
'company_name' => $guestRequest->company_name,
|
||||||
|
'company_cert_number' => $guestRequest->company_cert_number,
|
||||||
|
'contact_person_name' => $guestRequest->contact_person_name,
|
||||||
|
'contact_person_id' => $guestRequest->contact_person_id,
|
||||||
|
'email' => $guestRequest->email,
|
||||||
|
'phone' => $guestRequest->phone,
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
'status' => UserStatus::Active,
|
||||||
|
'preferred_language' => $guestRequest->preferred_language,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create consultation
|
||||||
|
$consultation = Consultation::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'booking_date' => $guestRequest->booking_date,
|
||||||
|
'booking_time' => $guestRequest->booking_time,
|
||||||
|
'problem_summary' => $guestRequest->problem_summary,
|
||||||
|
'status' => ConsultationStatus::Pending,
|
||||||
|
'payment_status' => PaymentStatus::NotApplicable,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Send welcome email with credentials
|
||||||
|
$user->notify(new WelcomeAccountNotification($password));
|
||||||
|
|
||||||
|
// Delete guest request
|
||||||
|
$guestRequest->delete();
|
||||||
|
|
||||||
|
// Log action
|
||||||
|
AdminLog::create([
|
||||||
|
'admin_id' => auth()->id(),
|
||||||
|
'action' => 'create',
|
||||||
|
'target_type' => 'user',
|
||||||
|
'target_id' => $user->id,
|
||||||
|
'new_values' => ['source' => 'guest_booking', 'guest_request_id' => $guestRequestId],
|
||||||
|
'ip_address' => request()->ip(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
session()->flash('success', __('admin.guest_approved_account_created'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rejectGuestBooking(int $guestRequestId, ?string $reason = null): void
|
||||||
|
{
|
||||||
|
$guestRequest = GuestBookingRequest::findOrFail($guestRequestId);
|
||||||
|
|
||||||
|
// Send rejection email
|
||||||
|
Mail::to($guestRequest->email)->queue(
|
||||||
|
new GuestBookingRejectedMail($guestRequest, $reason)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Log action before deletion
|
||||||
|
AdminLog::create([
|
||||||
|
'admin_id' => auth()->id(),
|
||||||
|
'action' => 'reject',
|
||||||
|
'target_type' => 'guest_booking_request',
|
||||||
|
'target_id' => $guestRequestId,
|
||||||
|
'old_values' => $guestRequest->toArray(),
|
||||||
|
'ip_address' => request()->ip(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Delete guest request (discard data)
|
||||||
|
$guestRequest->delete();
|
||||||
|
|
||||||
|
session()->flash('success', __('admin.guest_booking_rejected'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAccountOnly(int $guestRequestId): void
|
||||||
|
{
|
||||||
|
$guestRequest = GuestBookingRequest::findOrFail($guestRequestId);
|
||||||
|
|
||||||
|
DB::transaction(function () use ($guestRequest) {
|
||||||
|
$password = Str::random(12);
|
||||||
|
|
||||||
|
$user = User::create([
|
||||||
|
'user_type' => $guestRequest->isIndividual() ? UserType::Individual : UserType::Company,
|
||||||
|
'full_name' => $guestRequest->full_name,
|
||||||
|
'national_id' => $guestRequest->national_id,
|
||||||
|
'company_name' => $guestRequest->company_name,
|
||||||
|
'company_cert_number' => $guestRequest->company_cert_number,
|
||||||
|
'contact_person_name' => $guestRequest->contact_person_name,
|
||||||
|
'contact_person_id' => $guestRequest->contact_person_id,
|
||||||
|
'email' => $guestRequest->email,
|
||||||
|
'phone' => $guestRequest->phone,
|
||||||
|
'password' => Hash::make($password),
|
||||||
|
'status' => UserStatus::Active,
|
||||||
|
'preferred_language' => $guestRequest->preferred_language,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->notify(new WelcomeAccountNotification($password));
|
||||||
|
|
||||||
|
$guestRequest->delete();
|
||||||
|
|
||||||
|
AdminLog::create([
|
||||||
|
'admin_id' => auth()->id(),
|
||||||
|
'action' => 'create',
|
||||||
|
'target_type' => 'user',
|
||||||
|
'target_id' => $user->id,
|
||||||
|
'new_values' => ['source' => 'guest_booking_account_only', 'guest_request_id' => $guestRequest->id],
|
||||||
|
'ip_address' => request()->ip(),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
session()->flash('success', __('admin.account_created_no_booking'));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Email Notifications
|
||||||
|
|
||||||
|
**New Mail Classes:**
|
||||||
|
- `App\Mail\GuestBookingSubmittedMail` - Confirmation to guest
|
||||||
|
- `App\Mail\NewGuestBookingRequestMail` - Notification to admin
|
||||||
|
- `App\Mail\GuestBookingRejectedMail` - Rejection notification to guest
|
||||||
|
|
||||||
|
### Route Configuration
|
||||||
|
|
||||||
|
```php
|
||||||
|
// routes/web.php
|
||||||
|
|
||||||
|
// Public guest booking route
|
||||||
|
Route::get('/booking', function () {
|
||||||
|
return view('livewire.booking.guest');
|
||||||
|
})->name('booking');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tasks / Subtasks
|
||||||
|
|
||||||
|
- [ ] **Task 1: Create migration for guest_booking_requests table** (AC13)
|
||||||
|
- [ ] Create migration file
|
||||||
|
- [ ] Run migration
|
||||||
|
|
||||||
|
- [ ] **Task 2: Create GuestBookingRequest model** (AC13)
|
||||||
|
- [ ] Create model with fillable, casts, scopes
|
||||||
|
- [ ] Create factory for testing
|
||||||
|
|
||||||
|
- [ ] **Task 3: Create public booking Volt component** (AC1-8, AC12)
|
||||||
|
- [ ] Create `resources/views/livewire/booking/guest.blade.php`
|
||||||
|
- [ ] Implement 3-step flow: calendar selection, details form, confirmation
|
||||||
|
- [ ] Integrate availability calendar component
|
||||||
|
- [ ] Implement account type toggle with conditional fields
|
||||||
|
- [ ] Add confirmation step with data review
|
||||||
|
|
||||||
|
- [ ] **Task 4: Implement validation logic** (AC9-11)
|
||||||
|
- [ ] Duplicate user check (email OR national_id)
|
||||||
|
- [ ] 1-per-day limit by email
|
||||||
|
- [ ] Slot availability verification
|
||||||
|
|
||||||
|
- [ ] **Task 5: Implement submission flow** (AC13-16)
|
||||||
|
- [ ] Create guest_booking_request record
|
||||||
|
- [ ] Send confirmation email to guest
|
||||||
|
- [ ] Send notification email to admin
|
||||||
|
- [ ] Display success page
|
||||||
|
|
||||||
|
- [ ] **Task 6: Create email classes** (AC15-16, AC20)
|
||||||
|
- [ ] Create GuestBookingSubmittedMail
|
||||||
|
- [ ] Create NewGuestBookingRequestMail
|
||||||
|
- [ ] Create GuestBookingRejectedMail
|
||||||
|
- [ ] Create bilingual email templates
|
||||||
|
|
||||||
|
- [ ] **Task 7: Extend admin pending bookings** (AC17-18)
|
||||||
|
- [ ] Modify admin.bookings.pending to include guest requests
|
||||||
|
- [ ] Add "Guest" badge to distinguish from client bookings
|
||||||
|
- [ ] Create guest booking detail view
|
||||||
|
|
||||||
|
- [ ] **Task 8: Implement admin actions** (AC19-22)
|
||||||
|
- [ ] Implement approveGuestAndCreateAccount method
|
||||||
|
- [ ] Implement rejectGuestBooking method
|
||||||
|
- [ ] Implement createAccountOnly method
|
||||||
|
- [ ] Add confirmation modals for each action
|
||||||
|
- [ ] Implement audit logging
|
||||||
|
|
||||||
|
- [ ] **Task 9: Add translation keys** (AC23)
|
||||||
|
- [ ] Add keys to lang/en/booking.php
|
||||||
|
- [ ] Add keys to lang/ar/booking.php
|
||||||
|
- [ ] Add keys to lang/en/admin.php
|
||||||
|
- [ ] Add keys to lang/ar/admin.php
|
||||||
|
|
||||||
|
- [ ] **Task 10: Write tests** (AC26)
|
||||||
|
- [ ] Test public booking page access
|
||||||
|
- [ ] Test form validation
|
||||||
|
- [ ] Test duplicate user detection
|
||||||
|
- [ ] Test 1-per-day limit
|
||||||
|
- [ ] Test successful submission
|
||||||
|
- [ ] Test admin approve + create account
|
||||||
|
- [ ] Test admin reject
|
||||||
|
- [ ] Test admin create account only
|
||||||
|
- [ ] Test email notifications
|
||||||
|
|
||||||
|
- [ ] **Task 11: Run Pint and verify**
|
||||||
|
- [ ] Run `vendor/bin/pint --dirty`
|
||||||
|
- [ ] Verify all tests pass
|
||||||
|
|
||||||
|
## Files to Create
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `database/migrations/xxxx_create_guest_booking_requests_table.php` | Database table for guest requests |
|
||||||
|
| `app/Models/GuestBookingRequest.php` | Eloquent model |
|
||||||
|
| `database/factories/GuestBookingRequestFactory.php` | Test factory |
|
||||||
|
| `resources/views/livewire/booking/guest.blade.php` | Public booking Volt component |
|
||||||
|
| `app/Mail/GuestBookingSubmittedMail.php` | Guest confirmation email |
|
||||||
|
| `app/Mail/NewGuestBookingRequestMail.php` | Admin notification email |
|
||||||
|
| `app/Mail/GuestBookingRejectedMail.php` | Guest rejection email |
|
||||||
|
| `resources/views/emails/guest-booking-submitted.blade.php` | Email template |
|
||||||
|
| `resources/views/emails/new-guest-booking-request.blade.php` | Email template |
|
||||||
|
| `resources/views/emails/guest-booking-rejected.blade.php` | Email template |
|
||||||
|
| `resources/views/livewire/admin/bookings/guest-review.blade.php` | Admin guest review component |
|
||||||
|
| `tests/Feature/Booking/GuestBookingTest.php` | Feature tests |
|
||||||
|
| `tests/Feature/Admin/GuestBookingReviewTest.php` | Admin action tests |
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `routes/web.php` | Add public `/booking` route |
|
||||||
|
| `resources/views/livewire/admin/bookings/pending.blade.php` | Include guest bookings with badge |
|
||||||
|
| `lang/en/booking.php` | Add guest booking translation keys |
|
||||||
|
| `lang/ar/booking.php` | Add Arabic translations |
|
||||||
|
| `lang/en/admin.php` | Add admin action translations |
|
||||||
|
| `lang/ar/admin.php` | Add Arabic translations |
|
||||||
|
| `lang/en/emails.php` | Add email translation keys |
|
||||||
|
| `lang/ar/emails.php` | Add Arabic translations |
|
||||||
|
|
||||||
|
## Translation Keys Required
|
||||||
|
|
||||||
|
**lang/en/booking.php:**
|
||||||
|
```php
|
||||||
|
'guest_booking_title' => 'Book a Consultation',
|
||||||
|
'guest_booking_subtitle' => 'Fill in your details to request a consultation',
|
||||||
|
'account_type' => 'Account Type',
|
||||||
|
'individual' => 'Individual',
|
||||||
|
'company' => 'Company',
|
||||||
|
'full_name' => 'Full Name',
|
||||||
|
'national_id' => 'National ID',
|
||||||
|
'company_name' => 'Company Name',
|
||||||
|
'company_cert_number' => 'Company Registration Number',
|
||||||
|
'contact_person_name' => 'Contact Person Name',
|
||||||
|
'contact_person_id' => 'Contact Person ID',
|
||||||
|
'email' => 'Email Address',
|
||||||
|
'phone' => 'Phone Number',
|
||||||
|
'preferred_language' => 'Preferred Language',
|
||||||
|
'account_exists_please_login' => 'An account with this email or ID already exists. Please login to book.',
|
||||||
|
'guest_already_requested_this_day' => 'You have already submitted a request for this day. Please wait for a response.',
|
||||||
|
'guest_booking_submitted' => 'Your booking request has been submitted. We will contact you soon.',
|
||||||
|
'step_select_time' => 'Select Date & Time',
|
||||||
|
'step_your_details' => 'Your Details',
|
||||||
|
'step_confirm' => 'Confirm',
|
||||||
|
```
|
||||||
|
|
||||||
|
**lang/en/admin.php:**
|
||||||
|
```php
|
||||||
|
'guest_booking_request' => 'Guest Booking Request',
|
||||||
|
'guest_badge' => 'Guest',
|
||||||
|
'approve_create_account' => 'Approve & Create Account',
|
||||||
|
'reject_guest' => 'Reject',
|
||||||
|
'create_account_only' => 'Create Account Only',
|
||||||
|
'guest_approved_account_created' => 'Guest approved. Account created and booking confirmed.',
|
||||||
|
'guest_booking_rejected' => 'Guest booking request rejected.',
|
||||||
|
'account_created_no_booking' => 'Account created. No booking was created.',
|
||||||
|
'confirm_approve_guest' => 'This will create a new user account and approve the booking. Continue?',
|
||||||
|
'confirm_reject_guest' => 'This will reject the request and discard all guest data. Continue?',
|
||||||
|
'confirm_create_account_only' => 'This will create a user account but NOT approve the booking. Continue?',
|
||||||
|
```
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
- [ ] Migration created and run successfully
|
||||||
|
- [ ] GuestBookingRequest model with factory
|
||||||
|
- [ ] Public /booking page accessible without login
|
||||||
|
- [ ] Guest can select date/time from calendar
|
||||||
|
- [ ] Guest can fill individual or company details
|
||||||
|
- [ ] Duplicate user detection works (email OR national_id)
|
||||||
|
- [ ] 1-per-day limit by email enforced
|
||||||
|
- [ ] Confirmation step shows all data before submit
|
||||||
|
- [ ] Guest request saved to database
|
||||||
|
- [ ] Guest receives confirmation email
|
||||||
|
- [ ] Admin receives notification email
|
||||||
|
- [ ] Guest requests appear in admin queue with badge
|
||||||
|
- [ ] Admin can approve + create account
|
||||||
|
- [ ] Admin can reject (data discarded)
|
||||||
|
- [ ] Admin can create account only
|
||||||
|
- [ ] All actions logged in audit trail
|
||||||
|
- [ ] Rejection email sent to guest
|
||||||
|
- [ ] Welcome email with credentials sent on account creation
|
||||||
|
- [ ] Bilingual support complete
|
||||||
|
- [ ] Mobile responsive
|
||||||
|
- [ ] All tests passing
|
||||||
|
- [ ] Code formatted with Pint
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **Story 3.3:** Availability calendar component (COMPLETED)
|
||||||
|
- **Story 3.4:** Booking submission patterns (COMPLETED)
|
||||||
|
- **Story 3.5:** Admin approval workflow (COMPLETED)
|
||||||
|
- **Epic 2:** User creation logic and WelcomeAccountNotification (COMPLETED)
|
||||||
|
|
||||||
|
## Risk Assessment
|
||||||
|
|
||||||
|
| Risk | Impact | Mitigation |
|
||||||
|
|------|--------|------------|
|
||||||
|
| Data exposure if guest request not properly deleted | High | Ensure deletion in all paths, add cleanup job |
|
||||||
|
| Race condition on slot booking | High | DB transaction with locking |
|
||||||
|
| Spam submissions | Medium | Rate limiting, honeypot field |
|
||||||
|
| Email delivery failure | Medium | Queue with retries |
|
||||||
|
|
||||||
|
### Compatibility Verification
|
||||||
|
|
||||||
|
- [ ] No breaking changes to existing booking flow
|
||||||
|
- [ ] Existing client booking (`/client/consultations/book`) unchanged
|
||||||
|
- [ ] Admin pending list shows both types clearly
|
||||||
|
- [ ] All existing tests continue to pass
|
||||||
|
|
||||||
|
## Estimation
|
||||||
|
|
||||||
|
**Complexity:** High
|
||||||
|
**Estimated Effort:** 8-10 hours
|
||||||
|
|
||||||
|
This is a significant feature that introduces a new model, extends admin workflows, and adds a public-facing booking page. It requires careful attention to data handling (guest data must be properly discarded on rejection) and security (prevent duplicate accounts).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Change Log
|
||||||
|
|
||||||
|
| Date | Version | Description | Author |
|
||||||
|
|------|---------|-------------|--------|
|
||||||
|
| 2025-12-29 | 1.0 | Initial draft | PM Agent |
|
||||||
Loading…
Reference in New Issue