Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Laravel Process Approval
https://github.com/ringlesoft/laravel-process-approval
Admin
This package enables multi-level approval workflows for Eloquent models in Laravel applications,
...
Tokens:
8,334
Snippets:
55
Trust Score:
5.9
Update:
6 months ago
Context
Skills
Chat
Benchmark
75.6
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Laravel Process Approval Laravel Process Approval is a comprehensive package that enables multi-level approval workflows for Eloquent models in Laravel applications. It provides a flexible system where models can require review and approval from multiple approvers organized in sequential steps before final execution. The package integrates seamlessly with existing role management systems like Spatie's Laravel Permission package. The package handles the complete lifecycle of approval processes including submission, approval, rejection, returning to previous steps, and discarding. It tracks approval status through database tables, dispatches events at each stage for notification purposes, and provides UI components for displaying approval actions and status summaries. Multi-tenancy support is built-in, allowing different approval flows for different tenants while sharing the same flow configuration. ## Installation and Setup Install the package via Composer and publish configuration files ```bash # Install the package composer require ringlesoft/laravel-process-approval # Publish all files (migrations, config, views, translations) php artisan vendor:publish --provider="RingleSoft\LaravelProcessApproval\LaravelProcessApprovalServiceProvider" # Or publish specific files php artisan vendor:publish --provider="RingleSoft\LaravelProcessApproval\LaravelProcessApprovalServiceProvider" --tag="approvals-migrations" php artisan vendor:publish --provider="RingleSoft\LaravelProcessApproval\LaravelProcessApprovalServiceProvider" --tag="approvals-config" # Run migrations to create approval tables php artisan migrate ``` ## Creating Approval Flows via CLI Create and manage approval flows using artisan commands ```bash # Create a new approval flow for a model php artisan process-approval:flow add FundRequest # Add a step to an existing flow (interactive) php artisan process-approval:step add # List all approval flows and their steps php artisan process-approval:flow list # Remove a flow (interactive) php artisan process-approval:flow remove # Remove a step (interactive) php artisan process-approval:step remove ``` ## Making a Model Approvable Implement the contract, apply the trait, and define the callback method ```php <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; use RingleSoft\LaravelProcessApproval\Contracts\ApprovableModel; use RingleSoft\LaravelProcessApproval\Traits\Approvable; use RingleSoft\LaravelProcessApproval\Models\ProcessApproval; class FundRequest extends Model implements ApprovableModel { use Approvable; protected $fillable = ['amount', 'description', 'user_id']; /** * Called when approval process completes successfully * Return true to commit, false to rollback */ public function onApprovalCompleted(ProcessApproval $approval): bool { // Execute business logic after approval completes $this->update(['status' => 'approved', 'approved_at' => now()]); // Transfer funds, send notifications, etc. return true; // Return false to rollback the last approval } /** * Optional: Enable auto-submit on creation */ public function enableAutoSubmit(): bool { return true; // Set to false if you want manual submission } /** * Optional: Bypass approval for certain conditions */ public function bypassApprovalProcess(): bool { return $this->amount < 100; // Skip approval for small amounts } /** * Optional: Pause approvals for intermediate actions */ public function pauseApprovals(): mixed { // Return true to hide approval UI completely // Return 'ONLY_ACTIONS' to show approvals but hide action buttons return $this->requires_additional_documents && !$this->documents_uploaded; } } ``` ## Displaying Approval UI Components Add approval action buttons and status summary to your views ```blade {{-- In your model's show page (e.g., fund-requests/show.blade.php) --}} <div class="approval-section"> <h3>Approval Actions</h3> {{-- Display approval action buttons (Submit, Approve, Reject, etc.) --}} <x-ringlesoft-approval-actions :model="$fundRequest" /> {{-- Display approval status summary with icons --}} <x-ringlesoft-approval-status-summary :model="$fundRequest" :showRole="true" /> </div> ``` ## Using Approvable Model Methods Perform approval actions programmatically in controllers or commands ```php <?php namespace App\Http\Controllers; use App\Models\FundRequest; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use RingleSoft\LaravelProcessApproval\Exceptions\RequestNotSubmittedException; use RingleSoft\LaravelProcessApproval\Exceptions\NoFurtherApprovalStepsException; class FundRequestController extends Controller { public function submit(FundRequest $fundRequest) { try { // Submit the request for approval $approval = $fundRequest->submit(); return redirect()->back()->with('success', 'Request submitted successfully'); } catch (RequestAlreadySubmittedException $e) { return redirect()->back()->with('error', 'Request already submitted'); } } public function approve(Request $request, FundRequest $fundRequest) { try { // Approve the request with optional comment $approval = $fundRequest->approve( comment: $request->input('comment', 'Approved'), user: Auth::user() ); // Check if approval process is complete if ($fundRequest->isApprovalCompleted()) { // All steps approved, final callback was executed } return redirect()->back()->with('success', 'Request approved'); } catch (NoFurtherApprovalStepsException $e) { return redirect()->back()->with('error', 'No further approvals needed'); } } public function reject(Request $request, FundRequest $fundRequest) { $approval = $fundRequest->reject( comment: $request->input('comment', 'Needs revision'), user: Auth::user() ); return redirect()->back()->with('info', 'Request rejected'); } public function return(Request $request, FundRequest $fundRequest) { // Return to previous approval step $approval = $fundRequest->return( comment: $request->input('comment', 'Please review again'), user: Auth::user() ); return redirect()->back()->with('info', 'Request returned to previous step'); } public function discard(Request $request, FundRequest $fundRequest) { // Permanently discard the request $approval = $fundRequest->discard( comment: $request->input('comment', 'No longer needed'), user: Auth::user() ); return redirect()->back()->with('info', 'Request discarded'); } } ``` ## Querying Models by Approval Status Filter models based on their approval status ```php <?php use App\Models\FundRequest; // Get all approved fund requests $approvedRequests = FundRequest::approved()->get(); // Get all rejected fund requests $rejectedRequests = FundRequest::rejected()->get(); // Get all discarded fund requests $discardedRequests = FundRequest::discarded()->get(); // Get all returned fund requests $returnedRequests = FundRequest::returned()->get(); // Get all submitted but not yet fully approved $submittedRequests = FundRequest::submitted()->get(); // Get all non-submitted (draft) requests $draftRequests = FundRequest::nonSubmitted()->get(); // Combine with other query methods $pendingHighValueRequests = FundRequest::submitted() ->where('amount', '>', 10000) ->orderBy('created_at', 'desc') ->paginate(20); ``` ## Checking Approval Status and Permissions Verify approval state and user permissions on models ```php <?php use App\Models\FundRequest; use Illuminate\Support\Facades\Auth; $fundRequest = FundRequest::find(1); // Check approval completion if ($fundRequest->isApprovalCompleted()) { // All approval steps completed successfully } // Check submission status if (!$fundRequest->isSubmitted()) { // Still in draft, not yet submitted } // Check specific statuses if ($fundRequest->isRejected()) { // Request was rejected } if ($fundRequest->isDiscarded()) { // Request was discarded } if ($fundRequest->isReturned()) { // Request was returned to previous step } // Check if current user can approve $user = Auth::user(); if ($fundRequest->canBeApprovedBy($user)) { // Show approve/reject buttons } // Check if current user can submit if ($fundRequest->canBeSubmittedBy($user)) { // Show submit button } // Get next approval step $nextStep = $fundRequest->nextApprovalStep(); if ($nextStep) { $roleName = $nextStep->role->name; $action = $nextStep->action; // APPROVE or CHECK } // Get previous approval step $previousStep = $fundRequest->previousApprovalStep(); // Get list of users who can approve next $nextApprovers = $fundRequest->getNextApprovers(); foreach ($nextApprovers as $approver) { // Send notification to $approver } ``` ## Working with Approval Relations Access approval records and status through Eloquent relationships ```php <?php use App\Models\FundRequest; $fundRequest = FundRequest::with(['approvals', 'approvalStatus', 'lastApproval'])->find(1); // Get all approvals for this request foreach ($fundRequest->approvals as $approval) { echo $approval->user->name; // User who performed action echo $approval->approval_action; // SUBMITTED, APPROVED, REJECTED, etc. echo $approval->comment; // Optional comment echo $approval->created_at; // When action was performed } // Get the last approval action $lastApproval = $fundRequest->lastApproval; if ($lastApproval) { echo "Last approved by: " . $lastApproval->user->name; } // Get approval status (contains current state and steps) $status = $fundRequest->approvalStatus; echo $status->status; // Current status: Created, Submitted, Pending, Approved, etc. echo $status->creator_id; // ID of user who created the request // Access individual step statuses foreach ($status->steps as $step) { echo $step['id']; // Step ID echo $step['process_approval_id']; // Approval record ID if approved echo $step['process_approval_action']; // Action taken on this step } // Get creator of the request $creator = $fundRequest->creator; ``` ## HTTP API Endpoints Submit approval actions via HTTP POST requests (JSON or form data) ```bash # Submit a request curl -X POST https://example.com/process-approval/submit/1 \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "model_name": "App\\Models\\FundRequest", "user_id": 5 }' # Approve a request curl -X POST https://example.com/process-approval/approve/1 \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "model_name": "App\\Models\\FundRequest", "comment": "Approved by manager", "user_id": 3 }' # Reject a request curl -X POST https://example.com/process-approval/reject/1 \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "model_name": "App\\Models\\FundRequest", "comment": "Budget exceeded", "user_id": 3 }' # Return to previous step curl -X POST https://example.com/process-approval/return/1 \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "model_name": "App\\Models\\FundRequest", "comment": "Please review the amounts", "user_id": 4 }' # Discard a request curl -X POST https://example.com/process-approval/discard/1 \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "model_name": "App\\Models\\FundRequest", "comment": "Duplicate request", "user_id": 2 }' ``` ## Seeding Approval Flows Programmatically create approval flows in database seeders ```php <?php namespace Database\Seeders; use Illuminate\Database\Seeder; use App\Models\FundRequest; use Spatie\Permission\Models\Role; use RingleSoft\LaravelProcessApproval\Enums\ApprovalTypeEnum; class ApprovalFlowSeeder extends Seeder { public function run() { // Ensure roles exist $manager = Role::firstOrCreate(['name' => 'Manager']); $director = Role::firstOrCreate(['name' => 'Director']); $cfo = Role::firstOrCreate(['name' => 'CFO']); // Basic: Simple array of role IDs (all steps use APPROVE action) FundRequest::makeApprovable([ $manager->id, $director->id, $cfo->id ], 'Fund Request Approval Flow'); // Advanced: Associative array with role IDs and actions FundRequest::makeApprovable([ $manager->id => ApprovalTypeEnum::CHECK, // Manager checks $director->id => ApprovalTypeEnum::APPROVE, // Director approves $cfo->id => ApprovalTypeEnum::APPROVE // CFO final approval ], 'Fund Request Advanced Flow'); // Complex: Array of arrays with role_id and action FundRequest::makeApprovable([ [ 'role_id' => $manager->id, 'action' => ApprovalTypeEnum::CHECK->value ], [ 'role_id' => $director->id, 'action' => ApprovalTypeEnum::CHECK->value ], [ 'role_id' => $director->id, 'action' => ApprovalTypeEnum::APPROVE->value // Director checks then approves ], [ 'role_id' => $cfo->id, 'action' => ApprovalTypeEnum::APPROVE->value ] ], 'Multi-Step Fund Request Flow'); } } ``` ## Listening to Approval Events Subscribe to events for notifications and custom logic ```php <?php namespace App\Listeners; use RingleSoft\LaravelProcessApproval\Events\ProcessSubmittedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessApprovedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessRejectedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessReturnedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessDiscardedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessApprovalCompletedEvent; use RingleSoft\LaravelProcessApproval\Events\ApprovalNotificationEvent; use App\Notifications\AwaitingApprovalNotification; use Illuminate\Support\Facades\Log; class ProcessSubmittedListener { public function handle(ProcessSubmittedEvent $event): void { $approvable = $event->approvable; // Notify next approvers $nextApprovers = $approvable->getNextApprovers(); foreach ($nextApprovers as $approver) { $approver->notify(new AwaitingApprovalNotification($approvable)); } Log::info("Process submitted: {$approvable->id}"); } } class ProcessApprovedListener { public function handle(ProcessApprovedEvent $event): void { $approval = $event->approval; $approvable = $approval->approvable; // Notify next approvers if approval not complete if (!$approvable->isApprovalCompleted()) { $nextApprovers = $approvable->getNextApprovers(); foreach ($nextApprovers as $approver) { $approver->notify(new AwaitingApprovalNotification($approvable)); } } Log::info("Step approved by: {$approval->user->name}"); } } class ApprovalCompletedListener { public function handle(ProcessApprovalCompletedEvent $event): void { $approvable = $event->approvable; // Notify creator that approval is complete $creator = $approvable->creator; if ($creator) { $creator->notify(new ApprovalCompletedNotification($approvable)); } Log::info("Approval completed for: {$approvable->id}"); } } class ApprovalNotificationListener { public function handle(ApprovalNotificationEvent $event): void { // Display user-facing notifications session()->flash( $event->type === 'ERROR' ? 'error' : 'success', $event->message ); } } ``` Register listeners in `EventServiceProvider`: ```php <?php namespace App\Providers; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; use RingleSoft\LaravelProcessApproval\Events\ProcessSubmittedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessApprovedEvent; use RingleSoft\LaravelProcessApproval\Events\ProcessApprovalCompletedEvent; use RingleSoft\LaravelProcessApproval\Events\ApprovalNotificationEvent; use App\Listeners\ProcessSubmittedListener; use App\Listeners\ProcessApprovedListener; use App\Listeners\ApprovalCompletedListener; use App\Listeners\ApprovalNotificationListener; class EventServiceProvider extends ServiceProvider { protected $listen = [ ProcessSubmittedEvent::class => [ ProcessSubmittedListener::class, ], ProcessApprovedEvent::class => [ ProcessApprovedListener::class, ], ProcessApprovalCompletedEvent::class => [ ApprovalCompletedListener::class, ], ApprovalNotificationEvent::class => [ ApprovalNotificationListener::class, ], ]; } ``` ## Configuration Options Customize package behavior via config/process_approval.php ```php <?php return [ /** * Role model class (default: Spatie Permission) */ 'roles_model' => "\\Spatie\\Permission\\Models\\Role", /** * User model class for authentication */ 'users_model' => "\\App\\Models\\User", /** * Default namespace for application models */ 'models_path' => "\\App\\Models", /** * Middlewares applied to approval routes * 'web' middleware is always applied */ 'approval_controller_middlewares' => ['auth'], /** * CSS framework for UI components * Options: 'tailwind', 'bootstrap', 'bootstrap3', 'bootstrap4', null */ 'css_library' => 'tailwind', /** * Multi-tenancy field in users table */ 'multi_tenancy_field' => 'tenant_id', ]; ``` ## Multi-Tenancy Support Configure tenant-specific approval steps using the tenant_id field ```php <?php namespace Database\Seeders; use Illuminate\Database\Seeder; use RingleSoft\LaravelProcessApproval\ProcessApproval; use RingleSoft\LaravelProcessApproval\Models\ProcessApprovalFlow; use Spatie\Permission\Models\Role; class TenantApprovalSeeder extends Seeder { public function run() { $processApproval = new ProcessApproval(); // Create shared flow $flow = $processApproval->createFlow('Expense Approval', 'App\\Models\\Expense'); $manager = Role::where('name', 'Manager')->first(); $director = Role::where('name', 'Director')->first(); // Tenant 1: Requires only manager approval $processApproval->createStep( flowId: $flow->id, roleId: $manager->id, action: 'APPROVE', tenantId: 1 ); // Tenant 2: Requires both manager and director approval $processApproval->createStep( flowId: $flow->id, roleId: $manager->id, action: 'APPROVE', tenantId: 2 ); $processApproval->createStep( flowId: $flow->id, roleId: $director->id, action: 'APPROVE', tenantId: 2 ); // When a user with tenant_id = 1 submits an Expense, // only the manager step will be required. // When a user with tenant_id = 2 submits, both steps apply. } } ``` ## Adding User Signatures Display signatures in approval records by implementing getSignature method ```php <?php namespace App\Models; use Illuminate\Foundation\Auth\User as Authenticatable; use Spatie\Permission\Traits\HasRoles; class User extends Authenticatable { use HasRoles; protected $fillable = ['name', 'email', 'password', 'signature_path']; /** * Return the user's signature image URL * Used by approval package to display signatures */ public function getSignature(): ?string { if ($this->signature_path) { return asset('storage/' . $this->signature_path); } return null; // Package will show check/times icon instead } } ``` Upload signature in controller: ```php <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Storage; class ProfileController extends Controller { public function uploadSignature(Request $request) { $request->validate([ 'signature' => 'required|image|max:2048' ]); $user = Auth::user(); // Delete old signature if exists if ($user->signature_path) { Storage::disk('public')->delete($user->signature_path); } // Store new signature $path = $request->file('signature')->store('signatures', 'public'); $user->update(['signature_path' => $path]); return redirect()->back()->with('success', 'Signature uploaded'); } } ``` ## Summary Laravel Process Approval provides a complete solution for implementing multi-level approval workflows in Laravel applications. The package handles common approval patterns including sequential approval steps, role-based authorization, submission gates, rejection flows, and the ability to return items to previous steps. It integrates naturally with Laravel's ecosystem including Eloquent ORM, event system, Blade components, and authentication mechanisms. The package is designed for flexibility and extensibility. Developers can customize approval logic through model methods like `bypassApprovalProcess()`, `pauseApprovals()`, and `onApprovalCompleted()`. Multi-tenancy support enables different approval requirements per tenant while maintaining shared flow definitions. Event listeners provide hooks for sending notifications, logging activities, or triggering additional business logic. The UI components work with both Tailwind CSS and Bootstrap, and can be fully customized by publishing the views. Whether building a simple two-step approval or a complex multi-tier workflow with conditional logic, this package provides the foundation needed for robust approval management.