Currently, we have Customers and Pipeline Stages in our system. Still, there is no easy way to move our Customers within the Pipeline while saving its history. Let's fix that by adding a table Action:
In this lesson, we will do the following:
CustomerPipelineStage
Model to save the history of the Customer's Pipeline Stage changes and any comments added.Our CustomerPipelineStage
Model will be a simple table with the following fields:
customer_id
- the Customer IDpipeline_stage_id
(nullable) - the Pipeline Stage ID. It is nullable to allow notes without status change.user_id
(nullable) - the User who made the change. It is nullable to allow system-triggered changes to be logged.notes
(nullable, text) - any notes added to the changeLet's create the Migration:
Migration
use App\Models\Customer;use App\Models\PipelineStage;use App\Models\User; // ... Schema::create('customer_pipeline_stages', function (Blueprint $table) { $table->id(); $table->foreignIdFor(Customer::class)->constrained(); $table->foreignIdFor(PipelineStage::class)->nullable()->constrained(); $table->foreignIdFor(User::class)->nullable()->constrained(); $table->text('notes')->nullable(); $table->timestamps();});
Next, we will create the Model:
app/Models/CustomerPipelineStage.php
use Illuminate\Database\Eloquent\Model;use Illuminate\Database\Eloquent\Relations\BelongsTo; class CustomerPipelineStage extends Model{ protected $fillable = [ 'customer_id', 'pipeline_stage_id', 'user_id', 'notes' ]; public function user(): BelongsTo { return $this->belongsTo(User::class); } public function customer(): BelongsTo { return $this->belongsTo(Customer::class); } public function pipelineStage(): BelongsTo { return $this->belongsTo(PipelineStage::class); }}
And finally, we will add the relationship to our Customer
Model:
app/Models/Customer.php
use Illuminate\Database\Eloquent\Relations\HasMany; // ... public function pipelineStageLogs(): HasMany{ return $this->hasMany(CustomerPipelineStage::class);}
That's it. We can run:
php artisan migrate:fresh --seed
And have the Database table ready for our customer logs.
Now that we have our Database and Models ready, we can implement the table Action:
use Filament\Notifications\Notification; // ... public static function table(Table $table): Table{ return $table ->columns([ // ... ]) ->filters([ // ... ]) ->actions([ Tables\Actions\EditAction::make(), Tables\Actions\Action::make('Move to Stage') ->icon('heroicon-m-pencil-square') ->form([ Forms\Components\Select::make('pipeline_stage_id') ->label('Status') ->options(PipelineStage::pluck('name', 'id')->toArray()) ->default(function (Customer $record) { $currentPosition = $record->pipelineStage->position; return PipelineStage::where('position', '>', $currentPosition)->first()?->id; }), Forms\Components\Textarea::make('notes') ->label('Notes') ]) ->action(function (Customer $customer, array $data): void { $customer->pipeline_stage_id = $data['pipeline_stage_id']; $customer->save(); $customer->pipelineStageLogs()->create([ 'pipeline_stage_id' => $data['pipeline_stage_id'], 'notes' => $data['notes'], 'user_id' => auth()->id() ]); Notification::make() ->title('Customer Pipeline Updated') ->success() ->send(); }), ]) ->bulkActions([ // ... ]);} // ...
Few things to note here:
notes
are being written into a temporary field that will be removed from the Model at the update event.Here's what this looks like in the UI:
The last thing to do is to create the Observers to log all the Pipeline Stage changes:
app/Models/Customer.php
// ... public static function booted(): void{ self::created(function (Customer $customer) { $customer->pipelineStageLogs()->create([ 'pipeline_stage_id' => $customer->pipeline_stage_id, 'user_id' => auth()->check() ? auth()->id() : null ]); });} // ...
This is going to listen for create event and do the following:
CustomerPipelineStage
record with the pipeline_stage_id
and user_id
(if logged in) from the Customer.That's it. In the next lesson, we will build the table filters.