# 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 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) --}}

Approval Actions

{{-- Display approval action buttons (Submit, Approve, Reject, etc.) --}} {{-- Display approval status summary with icons --}}
``` ## Using Approvable Model Methods Perform approval actions programmatically in controllers or commands ```php 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 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 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 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 '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 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 [ 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 "\\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 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 signature_path) { return asset('storage/' . $this->signature_path); } return null; // Package will show check/times icon instead } } ``` Upload signature in controller: ```php 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.