Generic CRM software rarely fits perfectly. Custom Laravel CRM systems align exactly with business processes while maintaining flexibility. At ZIRA Software, we've built custom CRMs that increased sales team efficiency by 40%.
Off-the-Shelf vs Custom
Off-the-shelf (Salesforce, HubSpot):
- ✓ Fast deployment
- ✓ Proven features
- ✗ Expensive licenses
- ✗ Limited customization
- ✗ Vendor lock-in
Custom Laravel CRM:
- ✓ Perfect fit for business
- ✓ Full control
- ✓ Lower long-term cost
- ✗ Development time
- ✗ Maintenance responsibility
When to Build Custom
Build custom CRM when:
- Unique business processes
- Complex integrations needed
- Existing software licenses expensive
- Industry-specific requirements
- Data ownership critical
Core CRM Features
1. Contact Management
// app/Models/Contact.php
class Contact extends Model
{
protected $fillable = [
'name', 'email', 'phone', 'company',
'job_title', 'source', 'owner_id'
];
public function owner()
{
return $this->belongsTo(User::class, 'owner_id');
}
public function activities()
{
return $this->hasMany(Activity::class);
}
public function deals()
{
return $this->hasMany(Deal::class);
}
}
2. Deal Pipeline
// app/Models/Deal.php
class Deal extends Model
{
const STATUS_LEAD = 'lead';
const STATUS_QUALIFIED = 'qualified';
const STATUS_PROPOSAL = 'proposal';
const STATUS_NEGOTIATION = 'negotiation';
const STATUS_CLOSED_WON = 'closed_won';
const STATUS_CLOSED_LOST = 'closed_lost';
protected $fillable = [
'title', 'value', 'status', 'expected_close_date',
'contact_id', 'owner_id'
];
protected $casts = [
'value' => 'decimal:2',
'expected_close_date' => 'date',
];
public function moveToNextStage()
{
$stages = [
self::STATUS_LEAD => self::STATUS_QUALIFIED,
self::STATUS_QUALIFIED => self::STATUS_PROPOSAL,
self::STATUS_PROPOSAL => self::STATUS_NEGOTIATION,
self::STATUS_NEGOTIATION => self::STATUS_CLOSED_WON,
];
if (isset($stages[$this->status])) {
$this->update(['status' => $stages[$this->status]]);
Activity::create([
'type' => 'deal_stage_change',
'contact_id' => $this->contact_id,
'deal_id' => $this->id,
'description' => "Deal moved to {$stages[$this->status]}",
]);
}
}
}
3. Activity Timeline
// app/Models/Activity.php
class Activity extends Model
{
protected $fillable = [
'type', 'description', 'contact_id', 'deal_id',
'user_id', 'scheduled_at', 'completed_at'
];
protected $casts = [
'scheduled_at' => 'datetime',
'completed_at' => 'datetime',
];
public function contact()
{
return $this->belongsTo(Contact::class);
}
public function scopeUpcoming($query)
{
return $query->whereNull('completed_at')
->where('scheduled_at', '>', now())
->orderBy('scheduled_at');
}
}
Dashboard and Reporting
Sales dashboard:
// app/Http/Controllers/DashboardController.php
public function index()
{
$user = auth()->user();
$metrics = [
'total_deals' => Deal::where('owner_id', $user->id)->count(),
'open_deals' => Deal::where('owner_id', $user->id)
->whereNotIn('status', [Deal::STATUS_CLOSED_WON, Deal::STATUS_CLOSED_LOST])
->count(),
'total_value' => Deal::where('owner_id', $user->id)
->where('status', Deal::STATUS_CLOSED_WON)
->sum('value'),
'conversion_rate' => $this->calculateConversionRate($user),
];
$pipeline = Deal::where('owner_id', $user->id)
->selectRaw('status, count(*) as count, sum(value) as total_value')
->groupBy('status')
->get();
$recentActivities = Activity::with(['contact', 'deal'])
->where('user_id', $user->id)
->latest()
->limit(10)
->get();
return view('dashboard', compact('metrics', 'pipeline', 'recentActivities'));
}
Email Integration
Track emails:
// app/Mail/ContactEmail.php
use Illuminate\Mail\Mailable;
class ContactEmail extends Mailable
{
protected $contact;
public function __construct(Contact $contact)
{
$this->contact = $contact;
}
public function build()
{
// Log activity
Activity::create([
'type' => 'email_sent',
'contact_id' => $this->contact->id,
'user_id' => auth()->id(),
'description' => "Email sent: {$this->subject}",
]);
return $this->view('emails.contact')
->subject($this->subject);
}
}
Permission System
Role-based access:
// app/Policies/DealPolicy.php
class DealPolicy
{
public function view(User $user, Deal $deal)
{
// Admins can view all
if ($user->isAdmin()) {
return true;
}
// Users can view their own deals
return $user->id === $deal->owner_id;
}
public function update(User $user, Deal $deal)
{
return $user->id === $deal->owner_id || $user->isAdmin();
}
}
API for Mobile/Integrations
// routes/api.php
Route::middleware('auth:api')->group(function () {
Route::apiResource('contacts', 'Api\ContactController');
Route::apiResource('deals', 'Api\DealController');
Route::apiResource('activities', 'Api\ActivityController');
Route::get('dashboard/metrics', 'Api\DashboardController@metrics');
Route::get('dashboard/pipeline', 'Api\DashboardController@pipeline');
});
Third-Party Integrations
Email service:
// Integrate with Gmail API, Outlook, etc.
// Store email threads linked to contacts
Calendar sync:
// Sync activities with Google Calendar, Outlook Calendar
VoIP integration:
// Log calls automatically from phone system
Conclusion
Custom Laravel CRM provides exact business fit at lower long-term cost. Start with core features and iterate based on team feedback.
Need a custom CRM solution? Contact ZIRA Software for CRM development consultation.