DEV Community

Chris Lloyd Fallaria
Chris Lloyd Fallaria

Posted on • Edited on

How to Build a Multi-Department Approval Workflow in Laravel

TL;DR: Step-by-step guide to building a multi-department approval workflow in Laravel with database-driven routing, Events, and role-based access. Code included.

Introduction
One of the most common problems I saw while building VMMS was this: documents get created, emailed to the next department, sit in someone's inbox for days, and when management asks "where's that voucher?" — nobody knows.
In this article I'll walk you through how I built a flexible multi-department approval workflow in Laravel that solves exactly this problem.

The Core Concept
Instead of hardcoding approval steps, I built a pipeline where each document has a current stage, and the next stage is determined by a department configuration stored in the database — not in the code.
This means you can add, remove, or reorder departments without touching a single line of PHP.

Database Structure
Here are the key tables:

// vouchers table
Schema::create('vouchers', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->foreignId('current_department_id')->constrained('departments');
    $table->enum('status', ['ongoing', 'accomplished', 'rejected']);
    $table->timestamps();
});

// departments table
Schema::create('departments', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->integer('order'); // determines routing sequence
    $table->timestamps();
});
Enter fullscreen mode Exit fullscreen mode

Routing Logic
When a staff member approves a voucher, it automatically moves to the next department based on the order column:

public function approve(Voucher $voucher)
{
    $currentOrder = $voucher->department->order;

    $nextDepartment = Department::where('order', '>', $currentOrder)
        ->orderBy('order')
        ->first();

    if ($nextDepartment) {
        $voucher->update([
            'current_department_id' => $nextDepartment->id,
            'status' => 'ongoing'
        ]);
    } else {
        // No more departments — voucher is complete
        $voucher->update(['status' => 'accomplished']);
    }

    // Fire event for email notification
    event(new VoucherAdvanced($voucher));
}
Enter fullscreen mode Exit fullscreen mode

Email Notifications
I used Laravel Events and Listeners to trigger email notifications every time a voucher moves to the next stage:

// VoucherAdvanced Event
class VoucherAdvanced
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Voucher $voucher) {}
}

// Listener
class SendVoucherNotification implements ShouldQueue
{
    public function handle(VoucherAdvanced $event): void
    {
        $voucher = $event->voucher;
        $staff = $voucher->department->staff;

        foreach ($staff as $member) {
            Mail::to($member->email)->send(new VoucherNotification($voucher));
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Role-Based Access
Each department only sees vouchers assigned to them using Laravel Policies:

public function view(User $user, Voucher $voucher): bool
{
    return $user->department_id === $voucher->current_department_id
        || $user->role === 'admin';
}
Enter fullscreen mode Exit fullscreen mode

Real-Time Pipeline Tracker
To show users where a voucher is in real time, I built a simple status tracker on the Vue 3 frontend that reads the current department and renders it as a progress indicator — no websockets needed, just a clean Inertia.js page refresh on action.

What I Learned

Keep routing logic in the database, not the code — it makes the system far more flexible
Laravel Events are perfect for notifications — clean, queueable, and easy to extend
Policies over middleware for row-level access — much cleaner when you have complex role rules

Conclusion
This pattern works for any multi-step approval system — not just vouchers. You could use it for leave requests, purchase orders, or any document that needs to pass through multiple departments.

Want to see this in action?
I built VMMS — a complete Voucher Management & Monitoring System using everything covered in this article.
🔴 Live demo: https://vmms-app-production.up.railway.app/login
💰 Get the full source code: https://getvmms.gumroad.com/l/zeroqz

Happy to answer any questions in the comments! 🚀

Top comments (0)