Approval Workflow

Multi-Step Approval System

Home | SoftLixx Creates
Workflow

Approval Workflow System

A flexible, multi-step approval system that integrates seamlessly with CRUD operations across all business modules.

System Overview

The Approval Workflow System provides configurable, multi-step approval processes for any document type in the ERP. It supports CREATE, UPDATE, and DELETE operations with customizable approval chains.

Multi-Step Approvals

Configure sequential approval chains with multiple approvers per step.

Notifications

Automated notifications to approvers with configurable templates.

Audit Trail

Complete history of approval actions with timestamps and remarks.

System Components

Component Purpose Key Fields
ApprovalSetup Defines which operations require approval for a model type model_type, action, is_active
ApprovalSetupStep Defines approvers and step order in the chain setup_id, step_order, user_id
ApprovalRequest Individual approval request instance for a document setup_id, record_id, status
ApprovalRequestStep Tracks each approver's action and timestamp request_id, acted_at, remarks

Workflow States

stateDiagram-v2
    [*] --> PENDING: Document Created
    
    PENDING --> APPROVED: All Steps Approved
    PENDING --> REJECTED: Any Step Rejected
    
    APPROVED --> AWAITING_UPDATE_APPROVAL: Update Requested
    AWAITING_UPDATE_APPROVAL --> APPROVED: Update Approved
    AWAITING_UPDATE_APPROVAL --> APPROVED: Update Rejected (Reverts)
    
    APPROVED --> AWAITING_DELETE_APPROVAL: Delete Requested
    AWAITING_DELETE_APPROVAL --> [*]: Delete Approved
    AWAITING_DELETE_APPROVAL --> APPROVED: Delete Rejected (Reverts)
    
    REJECTED --> [*]
                        

PENDING

Document created, waiting for approval chain completion.

APPROVED

All approval steps completed. Events fired, side effects executed.

REJECTED

Approval denied by any approver in the chain.

AWAITING_*_APPROVAL

Document update or delete pending approval.

Approval Sequence Flow

sequenceDiagram
    autonumber
    participant U as User
    participant LW as Livewire
    participant S as Service
    participant AE as ApprovalEngine
    participant M as Model
    participant N as Notification

    U->>LW: Submit Form
    LW->>LW: Validate Input
    LW->>S: create(data)
    
    S->>S: DB::transaction()
    S->>M: Repository.create()
    
    S->>AE: checkSetupExists('CREATE', Model)
    
    alt Approval Required
        AE-->>S: Setup Found
        S->>AE: createRequest(setupId, type, recordId)
        AE->>N: Notify First Approver
        S-->>LW: Return (Status: PENDING)
    else No Approval Required
        AE-->>S: null
        S->>M: status = 'APPROVED'
        S->>M: fireCreateEvent()
        S-->>LW: Return (Status: APPROVED)
    end
    
    LW-->>U: Success Toast
                        

Critical Rule

Events are ONLY fired after final approval. If approval is required, the fireCreateEvent() method is NOT called until the approval chain completes. This prevents side effects (GL posting, stock updates) from executing prematurely.

ApprovalEngineService

The ApprovalEngineService is the central service managing all approval workflows. It provides methods to check setup existence, create requests, and process approvals/rejections.

Core Methods

// Check if approval is required for an operation
$setup = $this->approvalEngine->checkSetupExists(
    'CREATE',      // Action: CREATE, UPDATE, DELETE
    Transfer::class  // Model class
);

// Create an approval request
$this->approvalEngine->createRequest(
    $setupId,                          // ApprovalSetup ID
    'TRANSFER_CREATE_APPROVAL',        // Notification type key
    $recordId                          // The document's ID
);

// Approve a step (called by approver)
$this->approvalEngine->actOnRequest(
    $request,    // ApprovalRequest instance
    $remarks,    // Approver's comments
    $signature   // Optional digital signature
);

// Reject a request
$this->approvalEngine->rejectRequest(
    $request,    // ApprovalRequest instance
    $remarks     // Rejection reason
);

Service Integration Pattern

public function create(array $data): Model
{
    return DB::transaction(function () use ($data) {
        $record = $this->repository->create($data);
        
        if ($setup = $this->approvalEngine->checkSetupExists('CREATE', Model::class)) {
            // ⚠️ Approval required - create request, DON'T fire event
            $this->approvalEngine->createRequest(
                $setup->id, 
                'MODEL_CREATE_APPROVAL', 
                $record->id
            );
        } else {
            // ✅ No approval - auto-approve and fire event
            $record->status = 'APPROVED';
            $record->save();
            $record->fireCreateEvent();
        }
        
        return $record;
    });
}

Notification Configuration

Approval notification templates are defined in config/approvals.php. Each notification type has a title, action URL, and message template with placeholders.

config/approvals.php

return [
    'TRANSFER_CREATE_APPROVAL' => [
        'title' => 'Transfer Approval Required',
        'action_url' => '/approvals/{id}',
        'message' => '{causer} requests approval for transfer #{display}',
        'type' => 'TRANSFER_CREATE_APPROVAL',
    ],
    
    'TRANSFER_CREATE_REJECTED' => [
        'title' => 'Transfer Rejected',
        'action_url' => '/approvals/{id}',
        'message' => 'Transfer #{display} has been rejected',
        'type' => 'TRANSFER_CREATE_REJECTED',
    ],
    
    'TRANSFER_CREATE_APPROVED' => [
        'title' => 'Transfer Approved',
        'action_url' => '/transfers/{id}',
        'message' => 'Transfer #{display} has been approved',
        'type' => 'TRANSFER_CREATE_APPROVED',
    ],
    
    // ... more notification types
];

Placeholders

  • {id} — Approval request ID
  • {causer} — User who initiated
  • {display} — Document display name

File Location

config/approvals.php

ProvidesApprovalData Interface

Models supporting the approval workflow must implement the ProvidesApprovalData interface. This provides the approval system with the data needed for display and audit purposes.

Interface Definition

interface ProvidesApprovalData
{
    /**
     * Returns data to display in the approval UI
     * and store in the audit trail.
     */
    public function getApprovalData(): array;
}

Model Implementation

class Transfer extends Model implements ProvidesApprovalData
{
    /**
     * Display name shown in approval notifications
     */
    public function getApprovalDisplayNameAttribute(): string
    {
        return $this->transfer_number ?? 'TRANSFER-' . $this->getKey();
    }

    /**
     * Full data for approval display and audit
     */
    public function getApprovalData(): array
    {
        // Ensure relationships are loaded
        $this->loadMissing(['items', 'fromWarehouse', 'toWarehouse']);
        
        return array_merge($this->toArray(), [
            'items' => $this->items->toArray(),
            'from_warehouse_name' => $this->fromWarehouse->name,
            'to_warehouse_name' => $this->toWarehouse->name,
        ]);
    }
}