Client Intake & Portal Flow Guide
Last Updated: 2026-01-17 Status: Implemented Plan Reference: 117-client-intake-portal-flow.md, 129-discovery-flow-email-notifications.md, 130-lead-scoring-automated-qualification.md
Overview
The Client Intake & Portal Flow transforms the portal from a project-centric view to a stage-based experience that guides clients from first contact through ongoing engagement. It implements a complete lead management pipeline with discovery questionnaires, call booking, and progressive feature disclosure based on client lifecycle stage.
Table of Contents
- Client Lifecycle Stages
- Accessing the Features
- Client Discovery Flow
- Admin Lead Management
- Lead Scoring & Automated Qualification
- Call Booking System
- Stage-Based Portal Visibility
- Technical Architecture
- Workflow Integration
- Related Features
Client Lifecycle Stages
Clients progress through a defined set of stages from initial sign-up to active engagement:
| Stage | Value | Description | Portal Access |
|---|---|---|---|
| Prospect | prospect | New potential client, not yet engaged | Discovery only |
| Questionnaire Pending | questionnaire_pending | Waiting for client to complete questionnaire | Questionnaire form |
| Questionnaire Submitted | questionnaire_submitted | Questionnaire submitted, awaiting review | Status view |
| Qualified | qualified | Lead approved, can book discovery call | Call booking |
| Discovery Scheduled | discovery_scheduled | Discovery call has been scheduled | Call details |
| Discovery Completed | discovery_completed | Call completed, preparing proposal | Status view |
| Proposal Sent | proposal_sent | Proposal delivered, awaiting decision | Proposal view |
| Proposal Accepted | proposal_accepted | Agreement signed | Full portal |
| Active | active | Project in progress | Full portal |
| Completed | completed | Project delivered | Full portal |
| Support | support | Ongoing support phase | Full portal |
| Declined | declined | Lead declined or not a fit | Limited |
| Churned | churned | Former client, no longer active | Limited |
Stage Categories
- Discovery Phase: Prospect through Proposal Sent (pre-active clients)
- Active Phase: Proposal Accepted through Support (engaged clients)
- Lead Stages: All pre-Proposal Accepted stages
Accessing the Features
Portal Navigation (Clients)
| Access Point | Location | URL | Stage Requirement |
|---|---|---|---|
| Discovery Hub | Portal sidebar | /discovery | Discovery phase |
| Questionnaire | Discovery section | /discovery/questionnaire | Prospect, Questionnaire Pending |
| Status | Discovery section | /discovery/status | Any discovery stage |
| Book Call | Discovery section | /discovery/book-call | Qualified only |
Admin Navigation
| Access Point | Location | URL | Role |
|---|---|---|---|
| Lead List | Admin sidebar | /admin/leads | Admin |
| Pipeline View | Lead section | /admin/leads/pipeline | Admin |
| Lead Detail | Lead list | /admin/leads/{id} | Admin |
| Questionnaire Review | Lead detail | /admin/leads/{id}/questionnaire | Admin |
| Availability Settings | Admin settings | /admin/settings/availability | Admin |
Client Discovery Flow
Step 1: Sign Up & Questionnaire
When a new user signs up, they are associated with a client in the prospect stage. Upon accessing the portal, they are redirected to the discovery flow.
- Client accesses portal
- System creates discovery questionnaire
- Client stage advances to
questionnaire_pending - Client fills out 10-question discovery form
Questionnaire Questions
| # | Key | Question | Type | Required |
|---|---|---|---|---|
| 1 | project_description | What are you trying to build or solve? | textarea | Yes |
| 2 | project_type | Is this a new project or existing system? | select | Yes |
| 3 | project_driver | What's driving this project? | multiselect | Yes |
| 4 | timeline | What's your timeline expectation? | select | Yes |
| 5 | budget_range | What's your budget range? | select | Yes |
| 6 | current_systems | Describe existing systems involved | textarea | No |
| 7 | previous_dev | Previous development efforts? | textarea | No |
| 8 | success_criteria | What does success look like? | textarea | Yes |
| 9 | how_found_us | How did you hear about us? | select | No |
| 10 | additional_info | Anything else we should know? | textarea | No |
Auto-Save Functionality
The questionnaire supports automatic progress saving via AJAX:
// Auto-saves every 30 seconds and on field blur
POST /discovery/questionnaire/save
{
"responses": {
"project_description": "...",
"project_type": "new_project",
...
}
}
Step 2: Questionnaire Submission
- Client submits completed questionnaire
- Validation ensures required fields are filled
- Client stage advances to
questionnaire_submitted - Budget, timeline, and source are copied to client record
- Workflow triggers send admin notifications
Step 3: Admin Review & Qualification
- Admin reviews questionnaire in lead management
- Admin qualifies or declines the lead
- If qualified: Client stage →
qualified - Client receives notification with call booking link
Step 4: Call Booking
- Qualified client accesses call booking page
- System shows available slots from admin availability
- Client selects a time slot
- Discovery call is created and scheduled
- Client stage advances to
discovery_scheduled
Step 5: Discovery Call & Proposal
- Admin conducts discovery call
- Admin marks call as completed with outcome
- Client stage advances to
discovery_completed - Admin prepares and sends proposal
- Client stage →
proposal_sent
Step 6: Activation
- Client accepts proposal (signs agreement)
- Client stage advances to
proposal_accepted - Full portal access is granted
- Project creation begins
Admin Lead Management
Lead List View
The lead list provides filtering and search capabilities:
Filters:
- Stage (tabs for each lead stage)
- Search (by name or company)
- Source (how they found us)
Sort Options:
- Created date
- Name
- Stage
- Stage changed date
Pipeline/Kanban View
Visual representation of leads across stages with drag-and-drop functionality:
┌─────────────┬───────────────────┬───────────────────┬───────────────┐
│ Prospect │ Quest. Pending │ Quest. Submitted │ Qualified │
├─────────────┼───────────────────┼───────────────────┼───────────────┤
│ [Lead Card] │ [Lead Card] │ [Lead Card] │ [Lead Card] │
│ [Lead Card] │ │ [Lead Card] │ │
└─────────────┴───────────────────┴───────────────────┴───────────────┘
Lead Detail View
Shows complete lead information:
- Client profile and contact info
- Questionnaire responses (collapsible)
- Stage timeline
- Available stage transitions
- Action buttons (Qualify, Decline, Advance)
- Discovery call history
Actions
| Action | Endpoint | Description |
|---|---|---|
| Qualify | POST /admin/leads/{id}/qualify | Approve lead for call booking |
| Decline | POST /admin/leads/{id}/decline | Mark lead as not a fit |
| Advance Stage | POST /admin/leads/{id}/advance-stage | Move to any valid next stage |
| Update Pipeline | POST /admin/leads/{id}/update-pipeline | AJAX drag-and-drop update |
| Rescore | POST /admin/leads/{id}/rescore | Recalculate lead score |
Lead Scoring & Automated Qualification
Overview
The lead scoring system automatically evaluates questionnaire responses and assigns a score from 0-100. Based on configurable thresholds, leads can be automatically qualified or declined, streamlining the lead review process.
Score Components
The score is calculated from 6 weighted components:
| Component | Default Weight | Description |
|---|---|---|
| Budget Range | 30 pts | Higher budgets score higher |
| Timeline | 20 pts | Realistic timelines (3+ months) score higher |
| Project Type | 15 pts | New projects score higher than consultation |
| Success Criteria | 15 pts | Detailed criteria (100+ chars) score higher |
| Company Info | 10 pts | Company name + existing systems |
| Project Description | 10 pts | Detailed descriptions (200+ chars) score higher |
Total weights must equal 100.
Score Thresholds
| Threshold | Default | Action |
|---|---|---|
| Auto-Qualify | ≥70 | Lead automatically advances to Qualified stage |
| Manual Review | 40-69 | Lead stays at Questionnaire Submitted for review |
| Auto-Decline | <40 | Lead automatically moves to Declined stage |
Admin Settings
Access: Admin → Settings → Lead Scoring
The settings page allows admins to:
- Adjust qualification thresholds (auto-qualify, auto-decline)
- Enable/disable auto-qualify and auto-decline
- Modify component weights (must sum to 100)
- View a visual score range diagram
- Reset to defaults
Scoring on Submission
When a questionnaire is submitted:
QuestionnaireService::submit()is calledLeadScoringService::scoreAndSave()calculates and stores the score- Score, breakdown, and recommendation are saved to the questionnaire
- If auto-qualify/decline is enabled:
- Score ≥ threshold: Client stage →
qualified - Score < threshold: Client stage →
declined
- Score ≥ threshold: Client stage →
Manual Rescoring
Admins can rescore any submitted questionnaire:
- From lead detail page: Click "Rescore" button
- Useful after changing scoring weights
- New score replaces old score
Score Display
Scores are displayed in multiple locations:
| Location | Display |
|---|---|
| Lead List | Score badge with color coding |
| Lead Detail Header | Score badge with recommendation |
| Lead Detail Sidebar | Full breakdown with component scores |
| Questionnaire Review | Score badge and expandable breakdown |
Score Color Coding
| Score Range | Color | Meaning |
|---|---|---|
| 70-100 | Green | High quality lead |
| 40-69 | Yellow | Needs manual review |
| 0-39 | Red | Low quality lead |
Database Schema
lead_scoring_settings table:
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
key | string | Setting identifier (unique) |
type | string | integer, boolean, json, string |
value | text | Setting value |
group | string | thresholds, weights, rules |
label | string | Display name |
description | text | Help text |
discovery_questionnaires table additions:
| Column | Type | Description |
|---|---|---|
lead_score | integer | Calculated score (0-100) |
score_breakdown | json | Component-by-component scores |
score_recommendation | string | auto_qualify, manual_review, auto_decline |
scored_at | timestamp | When score was calculated |
Services
| Service | Location | Purpose |
|---|---|---|
LeadScoringService | app/Services/LeadScoringService.php | Score calculation |
LeadScoringSettingsService | app/Services/LeadScoringSettingsService.php | Settings management |
Data Transfer Object
App\DataTransferObjects\LeadScore
Properties:
- total: int (0-100)
- breakdown: array (component scores)
- recommendation: string (auto_qualify|manual_review|auto_decline)
Methods:
- getColorClass(): string - Tailwind classes for display
- getRecommendationLabel(): string - Human-readable recommendation
- toArray(): array - Array representation
Seeder
# Seed default settings
php artisan db:seed --class=LeadScoringSettingsSeeder
Default settings:
- Auto-qualify threshold: 70
- Auto-decline threshold: 40
- Both auto-actions enabled
- Weights: Budget 30, Timeline 20, Project Type 15, Success Criteria 15, Company Info 10, Description 10
Call Booking System
Admin Availability Settings
Admins configure their weekly availability:
// Default availability: 9am-5pm weekdays
$availabilityService->setAvailability($admin, [
['day_of_week' => 1, 'start_time' => '09:00', 'end_time' => '17:00'], // Monday
['day_of_week' => 2, 'start_time' => '09:00', 'end_time' => '17:00'], // Tuesday
// ...
]);
Configuration Options:
day_of_week: 0 (Sunday) through 6 (Saturday)start_time/end_time: Time rangeslot_duration: Meeting length (default: 45 minutes)buffer_before/buffer_after: Buffer time (default: 15 minutes)is_active: Enable/disable day
Client Booking Interface
Clients see a calendar UI with available slots:
┌─────────────────────────────────────────────────────┐
│ January 2026 │
├─────┬─────┬─────┬─────┬─────┬─────┬─────┤
│ Sun │ Mon │ Tue │ Wed │ Thu │ Fri │ Sat │
├─────┼─────┼─────┼─────┼─────┼─────┼─────┤
│ │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │
│ │ ●●● │ ●●● │ ●●● │ ●●● │ ●●● │ │
└─────┴─────┴─────┴─────┴─────┴─────┴─────┘
● = Available slot (click to expand times)
Slot Generation
The AvailabilityService generates available slots considering:
- Admin's weekly availability settings
- Existing booked meetings
- Buffer times before/after meetings
- Past slots (not shown)
- Today's remaining slots (adjusted for current time)
// Get slots for next 2 weeks
$slots = $availabilityService->getAvailableSlots(
$admin,
now()->addDay(),
now()->addWeeks(2),
45 // duration in minutes
);
Meeting Outcomes
After a discovery call, admins record outcomes:
| Outcome | Value | Description |
|---|---|---|
| Qualified | qualified | Ready for proposal |
| Needs Audit | needs_audit | Requires technical audit first |
| Not a Fit | declined | Not proceeding |
| Reschedule | reschedule | Call needs to be rescheduled |
Stage-Based Portal Visibility
Navigation Configuration
Portal navigation items are filtered based on client stage using visible_stages or hidden_stages in config/navigation.php:
// Only show during discovery phases
[
'route' => 'portal.discovery.index',
'label' => 'Discovery',
'visible_stages' => [
'prospect',
'questionnaire_pending',
'questionnaire_submitted',
'qualified',
'discovery_scheduled',
'discovery_completed',
'proposal_sent',
],
],
// Only show during active phases
[
'route' => 'portal.projects.index',
'label' => 'Projects',
'visible_stages' => [
'proposal_accepted',
'active',
'completed',
'support',
],
],
Visibility Matrix
| Feature | Prospect | Quest. Pending | Quest. Submitted | Qualified | Discovery | Active |
|---|---|---|---|---|---|---|
| Questionnaire | - | Edit | Read-only | Read-only | Read-only | - |
| Status Tracker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Call Booking | - | - | - | ✓ | Read-only | - |
| Proposal | - | - | - | - | - | Read-only |
| Projects | - | - | - | - | - | ✓ |
| Invoices | - | - | - | - | - | ✓ |
| Files | - | - | - | - | ✓ | ✓ |
| Conversations | - | - | - | ✓ | ✓ | ✓ |
Dashboard Redirect
The PortalDashboardController redirects discovery-phase clients to the discovery hub:
public function index(Request $request): View|RedirectResponse
{
$client = $request->user()->clients()->first();
// Discovery clients go to discovery flow
if ($client->isInDiscoveryPhase()) {
return redirect()->route('portal.discovery.index');
}
// Active clients see full dashboard
return view('portal.dashboard', [...]);
}
Technical Architecture
Models
| Model | Location | Purpose |
|---|---|---|
ClientStage | app/Enums/ClientStage.php | Stage enum with helpers |
DiscoveryQuestionnaire | app/Models/DiscoveryQuestionnaire.php | Questionnaire record |
QuestionnaireResponse | app/Models/QuestionnaireResponse.php | Individual responses |
DiscoveryCall | app/Models/DiscoveryCall.php | Scheduled/completed calls |
AvailabilitySlot | app/Models/AvailabilitySlot.php | Admin availability |
BookedMeeting | app/Models/BookedMeeting.php | Booked meeting records |
Services
| Service | Location | Purpose |
|---|---|---|
ClientStageService | app/Services/ClientStageService.php | Stage transitions, validation |
QuestionnaireService | app/Services/QuestionnaireService.php | Questionnaire CRUD |
AvailabilityService | app/Services/AvailabilityService.php | Slot management, booking |
Controllers
| Controller | Location | Purpose |
|---|---|---|
DiscoveryController | app/Http/Controllers/Portal/DiscoveryController.php | Client discovery flow |
LeadController | app/Http/Controllers/Admin/LeadController.php | Admin lead management |
AvailabilityController | app/Http/Controllers/Admin/AvailabilityController.php | Availability settings |
Database Schema
clients table additions:
| Column | Type | Description |
|---|---|---|
stage | string | Current ClientStage value |
stage_changed_at | timestamp | When stage last changed |
qualified_at | timestamp | When lead was qualified |
source | string | How they found us |
budget_range | string | Budget from questionnaire |
timeline_urgency | string | Timeline from questionnaire |
discovery_questionnaires table:
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
client_id | bigint | FK to clients |
status | string | draft, submitted, reviewed |
submitted_at | timestamp | When submitted |
reviewed_at | timestamp | When reviewed |
reviewed_by | bigint | FK to users |
reviewer_notes | text | Internal notes |
questionnaire_responses table:
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
questionnaire_id | bigint | FK to questionnaires |
question_key | string | Question identifier |
question_text | string | Display text |
answer_type | string | text, select, multiselect, etc. |
answer_value | json | The answer |
display_order | int | Sort order |
discovery_calls table:
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
client_id | bigint | FK to clients |
questionnaire_id | bigint | FK to questionnaires |
assigned_to | bigint | FK to users (admin) |
scheduled_at | timestamp | Call datetime |
completed_at | timestamp | When completed |
status | string | pending, scheduled, completed, cancelled |
outcome | string | qualified, needs_audit, declined, reschedule |
notes | text | Call notes |
next_steps | text | Follow-up actions |
availability_slots table:
| Column | Type | Description |
|---|---|---|
id | bigint | Primary key |
user_id | bigint | FK to users (admin) |
day_of_week | int | 0-6 (Sunday-Saturday) |
start_time | time | Start of availability |
end_time | time | End of availability |
slot_duration | int | Minutes per slot |
buffer_before | int | Buffer before meetings |
buffer_after | int | Buffer after meetings |
is_active | boolean | Enabled/disabled |
Views
| View | Path | Purpose |
|---|---|---|
| Discovery Index | resources/views/portal/discovery/index.blade.php | Stage-aware landing |
| Questionnaire | resources/views/portal/discovery/questionnaire.blade.php | Multi-section form |
| Status | resources/views/portal/discovery/status.blade.php | Progress timeline |
| Book Call | resources/views/portal/discovery/book-call.blade.php | Calendar booking UI |
| Lead Index | resources/views/admin/leads/index.blade.php | Lead list with filters |
| Lead Show | resources/views/admin/leads/show.blade.php | Lead detail |
| Pipeline | resources/views/admin/leads/pipeline.blade.php | Kanban board |
| Questionnaire Review | resources/views/admin/leads/questionnaire.blade.php | Response review |
| Availability | resources/views/admin/settings/availability.blade.php | Settings form |
Routes
Portal Discovery Routes (routes/portal-routes.php):
Route::prefix('discovery')->name('discovery.')->group(function () {
Route::get('/', [DiscoveryController::class, 'index'])->name('index');
Route::get('/questionnaire', [DiscoveryController::class, 'questionnaire'])->name('questionnaire');
Route::post('/questionnaire/save', [DiscoveryController::class, 'saveProgress'])->name('save-progress');
Route::post('/questionnaire/submit', [DiscoveryController::class, 'submit'])->name('submit');
Route::get('/status', [DiscoveryController::class, 'status'])->name('status');
Route::get('/book-call', [DiscoveryController::class, 'bookCall'])->name('book-call');
Route::post('/book-call', [DiscoveryController::class, 'confirmBooking'])->name('confirm-booking');
Route::get('/available-slots', [DiscoveryController::class, 'getAvailableSlots'])->name('available-slots');
});
Admin Lead Routes (routes/admin/leads.php):
Route::prefix('leads')->name('leads.')->group(function () {
Route::get('/', [LeadController::class, 'index'])->name('index');
Route::get('/pipeline', [LeadController::class, 'pipeline'])->name('pipeline');
Route::get('/{client}', [LeadController::class, 'show'])->name('show');
Route::get('/{client}/questionnaire', [LeadController::class, 'questionnaire'])->name('questionnaire');
Route::post('/{client}/qualify', [LeadController::class, 'qualify'])->name('qualify');
Route::post('/{client}/decline', [LeadController::class, 'decline'])->name('decline');
Route::post('/{client}/advance-stage', [LeadController::class, 'advanceStage'])->name('advance-stage');
Route::post('/{client}/update-pipeline', [LeadController::class, 'updatePipelinePosition'])->name('update-pipeline');
});
Automated Email Notifications
The discovery flow includes automated email notifications at every stage transition to keep prospects informed and engaged.
Email Notification Templates
| # | Trigger | Template Name | |
|---|---|---|---|
| 1 | Questionnaire Received | Questionnaire submitted | discovery_questionnaire_received |
| 2 | Lead Qualified | Admin qualifies lead | discovery_lead_qualified |
| 3 | Lead Declined | Admin declines lead | discovery_lead_declined |
| 4 | Call Booked | Client books call | discovery_call_booked |
| 5 | 24h Reminder | 24 hours before call | discovery_call_reminder_24h |
| 6 | 1h Reminder | 1 hour before call | discovery_call_reminder_1h |
| 7 | Call Completed | Admin marks call complete | discovery_call_completed |
| 8 | Proposal Sent | Client stage → proposal_sent | discovery_proposal_sent |
| 9 | Proposal Accepted | Client stage → proposal_accepted | discovery_proposal_accepted |
Implementation Architecture
The email notifications use a hybrid approach:
-
Workflows (7 immediate notifications):
- Triggered by
model_updatedorclient_stage_changedevents - Use the
send_template_emailaction - Configurable via Admin → Workflows
- Triggered by
-
Scheduled Command (2 time-based reminders):
- Command:
php artisan discovery:send-call-reminders - Runs every 15 minutes via scheduler
- Sends 24h reminders (calls 23-25 hours away)
- Sends 1h reminders (calls 30-90 minutes away)
- Command:
Discovery Workflows
| Workflow Name | Trigger | Condition |
|---|---|---|
| Discovery: Questionnaire Submitted | model_updated on DiscoveryQuestionnaire | status changed_to 'submitted' |
| Discovery: Lead Qualified | client_stage_changed | stage = 'qualified' |
| Discovery: Lead Declined | client_stage_changed | stage = 'declined' |
| Discovery: Call Scheduled | model_updated on DiscoveryCall | status changed_to 'scheduled' |
| Discovery: Call Completed | model_updated on DiscoveryCall | status changed_to 'completed' |
| Discovery: Proposal Sent | client_stage_changed | stage = 'proposal_sent' |
| Discovery: Proposal Accepted | client_stage_changed | stage = 'proposal_accepted' |
Reminder Tracking
The discovery_calls table tracks reminder status:
| Column | Type | Description |
|---|---|---|
reminder_24h_sent_at | timestamp | When 24h reminder was sent |
reminder_1h_sent_at | timestamp | When 1h reminder was sent |
This prevents duplicate reminders when the command runs multiple times.
Seeding Templates and Workflows
# Seed notification templates (9 templates)
php artisan db:seed --class=DiscoveryNotificationTemplatesSeeder
# Seed workflows (7 workflows)
php artisan db:seed --class=DiscoveryWorkflowsSeeder
Testing Reminders
# Preview what reminders would be sent
php artisan discovery:send-call-reminders --dry-run
# Send reminders now
php artisan discovery:send-call-reminders
Email Template Variables
Templates support these variables:
| Variable | Description | Available In |
|---|---|---|
{{ client_name }} | Client's name | All templates |
{{ client_email }} | Client's email | All templates |
{{ scheduled_date }} | Call date (F j, Y) | Call-related templates |
{{ scheduled_time }} | Call time (g:i A) | Call-related templates |
{{ meeting_url }} | Video meeting link | Call-related templates |
{{ portal_url }} | Portal login URL | Qualified, Proposal templates |
{{ submitted_at }} | Questionnaire submission date | Questionnaire template |
Workflow Integration
The client intake flow integrates with the workflow automation system to trigger notifications and actions at key stage changes.
Registered Triggers
| Trigger | Fires When | Typical Actions |
|---|---|---|
model_updated | Model status changes | Email notifications |
client_stage_changed | Client stage transitions | Stage-specific emails |
Trigger Configuration
model_updated triggers use conditions to filter events:
// WorkflowCondition for questionnaire submission
[
'field' => 'status',
'operator' => 'changed_to',
'value' => 'submitted',
]
client_stage_changed triggers use trigger_config for stage filtering:
// Workflow trigger_config for qualified stage
[
'trigger_type' => 'client_stage_changed',
'trigger_config' => ['stage' => 'qualified'],
]
Usage Example
// Stage changes trigger workflows automatically via ClientStageService
$stageService->advanceStage($client, ClientStage::Qualified, [
'changed_by' => auth()->id(),
'notes' => 'Great fit for enterprise plan',
]);
// This fires the 'client_stage_changed' trigger with stage='qualified'
Best Practices
For Administrators
- Review questionnaires promptly - Clients expect timely responses
- Use qualification notes - Document why a lead was qualified/declined
- Keep availability current - Update slots when schedule changes
- Record call outcomes - Helps with pipeline analysis
- Use the pipeline view - Visual overview of lead flow
For Developers
- Use ClientStageService - Always use the service for stage changes to trigger workflows
- Check stage access - Use
$client->canAccessFeature()or stage methods - Handle transitions gracefully - Invalid transitions throw
InvalidArgumentException - Leverage scopes - Models have scopes like
leads(),active()for filtering
Code Examples
Changing a client's stage:
use App\Services\ClientStageService;
use App\Enums\ClientStage;
$stageService = app(ClientStageService::class);
// This validates the transition and triggers workflows
$stageService->advanceStage($client, ClientStage::Qualified, [
'changed_by' => auth()->id(),
'notes' => 'Great potential fit',
]);
Checking stage permissions:
// Using enum methods
if ($client->stage->canBookCall()) {
// Show booking UI
}
if ($client->stage->isDiscoveryPhase()) {
// Redirect to discovery
}
// Using service
$stageService = app(ClientStageService::class);
if ($stageService->canAccessFeature($client, 'projects')) {
// Show projects
}
Getting available transitions:
$validNextStages = $client->stage->validTransitions();
// Returns array of ClientStage enums
Troubleshooting
| Issue | Solution |
|---|---|
| Client can't see questionnaire | Check stage->canAccessQuestionnaire() |
| Can't book call | Must be in qualified stage |
| Stage won't advance | Check validTransitions() for allowed moves |
| No available slots | Admin needs to configure availability |
| Workflow not triggering | Check WorkflowEngine logs for errors |
| Navigation item not showing | Verify visible_stages in config/navigation.php |
Related Features
Dependencies
| Feature | Relationship |
|---|---|
| Workflow Automation | Triggers notifications on stage changes |
| Client Management | Client model with stage field |
| Authentication | User login and client association |
Complementary Features
| Feature | Description |
|---|---|
| Notifications | Stage change notifications |
| Activity Logging | Audit trail for stage changes |
| Client Collaboration | Post-activation features |
| Invoicing | Available in active phase |
See Also
- Client Management - Managing client records
- Workflow Automation - Automated actions
- Notifications - Email and in-app notifications
- Portal UI - Portal navigation patterns