PHPackages                             asetkita/laravel-approval-workflow - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. asetkita/laravel-approval-workflow

ActiveLibrary

asetkita/laravel-approval-workflow
==================================

1141PHP

Since Nov 4Pushed 6mo agoCompare

[ Source](https://github.com/jobsrey/LaravelApprovalWorkflow)[ Packagist](https://packagist.org/packages/asetkita/laravel-approval-workflow)[ RSS](/packages/asetkita-laravel-approval-workflow/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependenciesVersions (1)Used By (0)

Laravel Approval Workflow
=========================

[](#laravel-approval-workflow)

A flexible and powerful approval workflow system for Laravel 12+ applications with Spatie Media Library support.

Features
--------

[](#features)

- ✅ Multi-step approval workflows
- ✅ Dynamic approver assignment (User, Group, System Group)
- ✅ Conditional workflow steps using Expression Language
- ✅ Department-based approvals (Staff, Manager, Head)
- ✅ Asset coordinator approvals
- ✅ Complete approval history tracking
- ✅ Spatie Media Library integration for file attachments
- ✅ System-level rejections
- ✅ Approval reset/resubmission
- ✅ Multi-company support
- ✅ Laravel 12 compatible

Requirements
------------

[](#requirements)

- PHP 8.2 or higher
- Laravel 11.0 or 12.0 or higher
- MySQL 5.7+ or PostgreSQL 9.6+

Installation
------------

[](#installation)

### 1. Install via Composer

[](#1-install-via-composer)

```
composer require asetkita/laravel-approval-workflow
```

### 2. Publish Configuration and Migrations

[](#2-publish-configuration-and-migrations)

```
php artisan vendor:publish --provider="AsetKita\LaravelApprovalWorkflow\ApprovalWorkflowServiceProvider"
```

Or publish them separately:

```
# Publish config file
php artisan vendor:publish --tag=approval-workflow-config

# Publish migrations
php artisan vendor:publish --tag=approval-workflow-migrations
```

### 3. Run Migrations

[](#3-run-migrations)

```
php artisan migrate
```

### 4. Install Spatie Media Library (if not already installed)

[](#4-install-spatie-media-library-if-not-already-installed)

```
php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate
```

Configuration
-------------

[](#configuration)

Edit `config/approval-workflow.php`:

```
return [
    'default_company_id' => env('APPROVAL_WORKFLOW_COMPANY_ID', 1),
    'user_model' => env('APPROVAL_WORKFLOW_USER_MODEL', \App\Models\User::class),
    'media_disk' => env('APPROVAL_WORKFLOW_MEDIA_DISK', 'public'),
    'notifications_enabled' => env('APPROVAL_WORKFLOW_NOTIFICATIONS', false),
    'notification_channels' => ['mail', 'database'],
];
```

Database Tables
---------------

[](#database-tables)

The package creates the following tables:

Table NameDescription`wf_department_users`Maps users to departments with job levels`wf_asset_coordinator_users`Maps users to asset categories`wf_approver_groups`Custom approver groups`wf_approver_group_users`Users in approver groups`wf_flows`Approval flow definitions`wf_flow_steps`Steps in each flow`wf_flow_step_approvers`Approvers for each step`wf_approvals`Active approval instances`wf_approval_active_users`Current approvers for active approvals`wf_approval_histories`Complete history of approval actionsUsage
-----

[](#usage)

### Basic Usage

[](#basic-usage)

#### 1. Create an Approval Flow

[](#1-create-an-approval-flow)

First, create a flow in the `wf_flows` table:

```
use AsetKita\LaravelApprovalWorkflow\Models\Flow;

$flow = Flow::create([
    'type' => 'PR', // Purchase Request
    'company_id' => 1,
    'is_active' => 1,
    'label' => 'Purchase Request Approval',
]);
```

#### 2. Add Flow Steps

[](#2-add-flow-steps)

```
use AsetKita\LaravelApprovalWorkflow\Models\FlowStep;

// Step 1: Department Manager
$step1 = FlowStep::create([
    'order' => 1,
    'flow_id' => $flow->id,
    'name' => 'Department Manager Approval',
    'condition' => null, // No condition
]);

// Step 2: Department Head (only if amount > 10000)
$step2 = FlowStep::create([
    'order' => 2,
    'flow_id' => $flow->id,
    'name' => 'Department Head Approval',
    'condition' => 'amount > 10000', // Expression Language
]);

// Step 3: Finance Manager
$step3 = FlowStep::create([
    'order' => 3,
    'flow_id' => $flow->id,
    'name' => 'Finance Approval',
    'condition' => null,
]);
```

#### 3. Configure Approvers for Each Step

[](#3-configure-approvers-for-each-step)

```
use AsetKita\LaravelApprovalWorkflow\Models\FlowStepApprover;

// Step 1: Department Manager (System Group)
FlowStepApprover::create([
    'flow_step_id' => $step1->id,
    'type' => 'SYSTEM_GROUP',
    'data' => 'department-manager',
]);

// Step 2: Department Head (System Group)
FlowStepApprover::create([
    'flow_step_id' => $step2->id,
    'type' => 'SYSTEM_GROUP',
    'data' => 'department-head',
]);

// Step 3: Specific User
FlowStepApprover::create([
    'flow_step_id' => $step3->id,
    'type' => 'USER',
    'data' => '5', // User ID
]);
```

### Starting an Approval

[](#starting-an-approval)

```
use AsetKita\LaravelApprovalWorkflow\Services\ApprovalHandler;

$handler = new ApprovalHandler($companyId = 1);

$result = $handler->start(
    flowType: 'PR',
    userId: auth()->id(),
    parameters: [
        'departmentId' => 10,
        'amount' => 15000,
        'description' => 'Office supplies',
    ]
);

$approvalId = $result['id'];
```

### Approving a Step

[](#approving-a-step)

```
$result = $handler->approve(
    approvalId: $approvalId,
    userId: auth()->id(),
    notes: 'Approved by manager',
    file: null
);
```

### Rejecting a Step

[](#rejecting-a-step)

```
$result = $handler->reject(
    approvalId: $approvalId,
    userId: auth()->id(),
    notes: 'Budget exceeded',
    file: null
);
```

### Resetting a Rejected Approval

[](#resetting-a-rejected-approval)

```
$result = $handler->reset(
    approvalId: $approvalId,
    userId: auth()->id(),
    notes: 'Resubmitting with corrected amount',
    file: null,
    parameters: ['amount' => 8000] // Updated parameters
);
```

### System Rejection (Admin Override)

[](#system-rejection-admin-override)

```
$result = $handler->rejectBySystem(
    approvalId: $approvalId,
    relatedUserId: auth()->id(),
    notes: 'System auto-rejected due to policy violation',
    file: null
);
```

### Getting Approval History

[](#getting-approval-history)

```
$histories = $handler->getApprovalHistories($approvalId);

foreach ($histories as $history) {
    echo "{$history['title']} by {$history['user_name']} at {$history['date_time']}\n";
}
```

### Getting Approval Path

[](#getting-approval-path)

```
$path = $handler->getApprovalPath($approvalId);

foreach ($path as $step) {
    echo "Step: {$step['name']} - Status: {$step['type']}\n";
}
```

### Using Facade

[](#using-facade)

```
use AsetKita\LaravelApprovalWorkflow\Facades\ApprovalWorkflow;

$result = ApprovalWorkflow::start('PR', auth()->id(), [
    'departmentId' => 10,
    'amount' => 5000,
]);
```

Approver Types
--------------

[](#approver-types)

### 1. USER

[](#1-user)

Direct user assignment by user ID.

```
FlowStepApprover::create([
    'flow_step_id' => $stepId,
    'type' => 'USER',
    'data' => '123', // User ID
]);
```

### 2. GROUP

[](#2-group)

Custom approver group (all users in the group).

```
FlowStepApprover::create([
    'flow_step_id' => $stepId,
    'type' => 'GROUP',
    'data' => '5', // Approver Group ID
]);
```

### 3. SYSTEM\_GROUP

[](#3-system_group)

Dynamic system groups based on parameters.

#### Available System Groups:

[](#available-system-groups)

- `department-manager` - Requires `departmentId` parameter
- `department-head` - Requires `departmentId` parameter
- `department-staff` - Requires `departmentId` parameter
- `asset-coordinator` - Requires `assetCategoryId` parameter
- `origin-asset-user` - Requires `originAssetUserId` parameter
- `destination-asset-user` - Requires `destinationAssetUserId` parameter

```
FlowStepApprover::create([
    'flow_step_id' => $stepId,
    'type' => 'SYSTEM_GROUP',
    'data' => 'department-manager',
]);
```

Parameters
----------

[](#parameters)

### Standard Parameters

[](#standard-parameters)

ParameterDescription`departmentId`Required for department-based approvers`overrideManagerUserId`Override department manager approver`overrideHeadUserId`Override department head approver`assetCategoryId`Required for asset-coordinator`originAssetUserId`Required for origin-asset-user`destinationAssetUserId`Required for destination-asset-user### Custom Parameters

[](#custom-parameters)

You can pass any custom parameters for conditional steps:

```
$handler->start('PR', auth()->id(), [
    'departmentId' => 10,
    'amount' => 15000,
    'category' => 'IT',
    'priority' => 'high',
    'requestType' => 'urgent',
]);
```

Use them in conditions:

```
// Only if amount > 5000 AND priority is high
'condition' => 'amount > 5000 and priority == "high"'

// Only if category is IT
'condition' => 'category == "IT"'

// Complex condition
'condition' => '(amount > 10000 or priority == "urgent") and category in ["IT", "Finance"]'
```

Conditional Steps (Expression Language)
---------------------------------------

[](#conditional-steps-expression-language)

The package uses Symfony Expression Language for conditional steps.

### Examples:

[](#examples)

```
// Simple comparison
'amount > 10000'

// Multiple conditions
'amount > 10000 and category == "IT"'

// Using OR
'priority == "high" or amount > 50000'

// Using IN
'category in ["IT", "Finance", "HR"]'

// Complex expression
'(amount > 10000 or priority == "urgent") and status == "pending"'
```

Spatie Media Library Integration
--------------------------------

[](#spatie-media-library-integration)

The `ApprovalHistory` model supports file attachments via Spatie Media Library with **automatic upload** feature.

### 🆕 Automatic File Upload (New!)

[](#-automatic-file-upload-new)

Files are automatically uploaded to Media Library when you approve/reject:

```
// Approve with single file - file automatically saved!
$handler->approve(
    $approvalId,
    $userId,
    'Approved',
    $request->file('attachment')
);

// Approve with multiple files - all files automatically saved!
$handler->approve(
    $approvalId,
    $userId,
    'Approved',
    $request->file('attachments') // Array of files
);

// Same for reject and reset
$handler->reject($approvalId, $userId, 'Rejected', $request->file('document'));
$handler->reset($approvalId, $userId, 'Reset', $request->file('files'));
```

**No need to find the history record first!** Just pass the `UploadedFile` instance directly.

See [FILE\_UPLOAD\_GUIDE.md](FILE_UPLOAD_GUIDE.md) for complete examples.

### Getting Attachments

[](#getting-attachments)

```
use AsetKita\LaravelApprovalWorkflow\Models\ApprovalHistory;

$history = ApprovalHistory::find($historyId);

// Get all attachments
$attachments = $history->getMedia('attachments');

// Get first attachment URL
$url = $history->getFirstMediaUrl('attachments');

// Get all attachment URLs
foreach ($history->getMedia('attachments') as $media) {
    echo $media->getUrl();
    echo $media->file_name;
    echo $media->size;
}
```

History Flags
-------------

[](#history-flags)

The package tracks different types of history events:

FlagConstantDescription`created``HFLAG_CREATED`Approval was created`reset``HFLAG_RESET`Approval was reset/resubmitted`approved``HFLAG_APPROVED`Step was approved`rejected``HFLAG_REJECTED`Step was rejected`system_rejected``HFLAG_SYSTEM_REJECTED`Rejected by system`done``HFLAG_DONE`Approval completed`skip``HFLAG_SKIP`Step was skipped (no approvers)Advanced Features
-----------------

[](#advanced-features)

### Rebuilding Approvers

[](#rebuilding-approvers)

Useful when department assignments or groups change:

```
$handler->rebuildApprovers();
```

### Getting Next Steps

[](#getting-next-steps)

```
$nextStep = $handler->getNextSteps($approvalId);
```

### Querying Approvals

[](#querying-approvals)

```
use AsetKita\LaravelApprovalWorkflow\Models\Approval;

// Get all on-progress approvals
$approvals = Approval::onProgress()->get();

// Get approved approvals for company
$approved = Approval::approved()->forCompany(1)->get();

// Get all approvals for a specific flow
$prApprovals = Approval::whereHas('flow', function($q) {
    $q->where('type', 'PR');
})->get();
```

Exception Handling
------------------

[](#exception-handling)

The package throws the following exceptions:

```
use AsetKita\LaravelApprovalWorkflow\Services\ApprovalHandler;

try {
    $handler->approve($approvalId, $userId);
} catch (\Exception $e) {
    switch ($e->getMessage()) {
        case ApprovalHandler::EXC_USER_NOT_FOUND:
            // Handle user not found
            break;
        case ApprovalHandler::EXC_FLOW_NOT_FOUND:
            // Handle flow not found
            break;
        case ApprovalHandler::EXC_PERMISSION_DENIED:
            // Handle permission denied
            break;
        case ApprovalHandler::EXC_APPROVAL_NOT_RUNNING:
            // Handle approval not running
            break;
        case ApprovalHandler::EXC_APPROVAL_NOT_REJECTED:
            // Handle approval not rejected
            break;
    }
}
```

Complete Example
----------------

[](#complete-example)

```
use AsetKita\LaravelApprovalWorkflow\Services\ApprovalHandler;
use AsetKita\LaravelApprovalWorkflow\Models\Flow;
use AsetKita\LaravelApprovalWorkflow\Models\FlowStep;
use AsetKita\LaravelApprovalWorkflow\Models\FlowStepApprover;

// 1. Create flow
$flow = Flow::create([
    'type' => 'LEAVE_REQUEST',
    'company_id' => 1,
    'is_active' => 1,
    'label' => 'Leave Request Approval',
]);

// 2. Create steps
$step1 = FlowStep::create([
    'order' => 1,
    'flow_id' => $flow->id,
    'name' => 'Manager Approval',
]);

$step2 = FlowStep::create([
    'order' => 2,
    'flow_id' => $flow->id,
    'name' => 'HR Approval',
    'condition' => 'days > 3', // Only if more than 3 days
]);

// 3. Configure approvers
FlowStepApprover::create([
    'flow_step_id' => $step1->id,
    'type' => 'SYSTEM_GROUP',
    'data' => 'department-manager',
]);

FlowStepApprover::create([
    'flow_step_id' => $step2->id,
    'type' => 'USER',
    'data' => '10', // HR Manager User ID
]);

// 4. Start approval
$handler = new ApprovalHandler(1);

$result = $handler->start('LEAVE_REQUEST', auth()->id(), [
    'departmentId' => auth()->user()->department_id,
    'days' => 5,
    'startDate' => '2024-12-01',
    'endDate' => '2024-12-05',
    'reason' => 'Family vacation',
]);

$approvalId = $result['id'];

// 5. Manager approves
$handler->approve($approvalId, $managerId, 'Approved');

// 6. HR approves (automatic if condition met)
$handler->approve($approvalId, $hrManagerId, 'Approved by HR');

// 7. Check history
$histories = $handler->getApprovalHistories($approvalId);
```

Testing
-------

[](#testing)

```
composer test
```

License
-------

[](#license)

MIT License

Author
------

[](#author)

**Rey**
Email:

Support
-------

[](#support)

For issues and feature requests, please use the GitHub issue tracker.

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance47

Moderate activity, may be stable

Popularity8

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity13

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://www.gravatar.com/avatar/02e031705f3b1ea89965da6300e6f927e79104f64d8725ea8c9c47d544f3b184?d=identicon)[jobsrey](/maintainers/jobsrey)

---

Top Contributors

[![issbox](https://avatars.githubusercontent.com/u/174172551?v=4)](https://github.com/issbox "issbox (16 commits)")

### Embed Badge

![Health badge](/badges/asetkita-laravel-approval-workflow/health.svg)

```
[![Health](https://phpackages.com/badges/asetkita-laravel-approval-workflow/health.svg)](https://phpackages.com/packages/asetkita-laravel-approval-workflow)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
