Purchase Request Approval

Alpha notice: This walkthrough mirrors the hosted demo. Status names and thresholds are demo-specific.

This example shows procurement approval with amount-based escalation, host status mapping through hooks, and guarded downstream actions. It builds on the same patterns as Refund Approval with a clearer draft → review → approved lifecycle.

What you will build

Draft / Rejected → Submit for internal review
    → Procurement Manager Review → Amount Gate
        → [amount >= 10000] Finance Review → Approved
        → [default] → Approved

After approval, host actions such as archive become available. ERP, PO creation, and payment logic stay outside DBFlow Core.

Workflow key and constants

Item Value
Workflow key procurement_request_approval
Model App\Models\ProcurementRequest
Constants class App\DBFlow\ProcurementRequest\ProcurementRequestWorkflow

Node keys:

  • procurement_manager_review
  • amount_gate
  • finance_review (high amount only)
  • end_approved

Amount condition: amount >= 10000 (ProcurementRequestWorkflow::CONDITION_HIGH_AMOUNT).

Host status mapping

Demo business status (ProcurementRequestStatus enum):

Status Meaning
draft Editable request, not in review
pending_review Submitted; workflow running or awaiting completion
approved Workflow approved; ready for downstream purchasing steps
rejected Workflow rejected; may return to draft
archived Host archival after approval (not a DBFlow terminal state)

Hooks keep DBFlow and host columns aligned:

Hook Host update
onStarted draft or rejectedpending_review, set submitted_at
onApproved approved, set reviewed_at
onRejected rejected, set reviewed_at
onCancelled conservative return to draft (unless already archived)

Implementation: app/DBFlow/ProcurementRequest/ProcurementRequestWorkflowHooks.php.

Where the demo code lives

Area Path
Model app/Models/ProcurementRequest.php
Workflow constants app/DBFlow/ProcurementRequest/ProcurementRequestWorkflow.php
Hooks app/DBFlow/ProcurementRequest/ProcurementRequestWorkflowHooks.php
Status guard (optional) app/DBFlow/ProcurementRequest/ProcurementRequestStatusGuard.php
Definition seeder database/seeders/Demo/ProcurementRequestWorkflowSeeder.php
Resource actions app/Filament/Resources/ProcurementRequestResource/Concerns/HasLifecycleActions.php
Presenter app/Filament/Resources/ProcurementRequestResource/Support/ProcurementRequestWorkflowPresenter.php
Integration test tests/Feature/ProcurementRequestDbflowIntegrationTest.php

Model integration

ProcurementRequest implements Workflowable and WorkflowContextInterface:

public function getWorkflowVariables(): array
{
    return [
        'amount' => (float) $this->amount,
        'department' => $this->department,
        // ...
    ];
}

Workflow definition source

ProcurementRequestWorkflowSeeder builds the JSON graph, validates, and publishes — same pipeline as refund disputes. Constants in ProcurementRequestWorkflow keep keys and conditions centralized.

Start the workflow

Submit for internal review is visible when status is draft or rejected, no running workflow exists, and the record is not archived.

With DBFlow enabled:

DBFlow::start(
    ProcurementRequestWorkflow::KEY,
    $record,
    Auth::user(),
);

With DBFlow disabled, the demo falls back to setting pending_review directly — useful for baseline comparisons only.

Approvals

Assignees use My Tasks. Finance reviewers receive tasks only when amount >= 10000 routes through finance_review.

Guard downstream actions

Archive and other post-approval operations must check host status — not workflow internals:

->visible(fn (ProcurementRequest $record): bool =>
    $record->status === ProcurementRequestStatus::Approved
)

DBFlow does not ship ERP adapters. If you sync to an external system, invoke that from WorkflowHooks::onApproved() or a host job — keep integration code in app/, not in Core.

Filament resource integration

ProcurementRequestResource composes HasLifecycleActions for submit, return-to-draft, and archive actions. ProcurementRequestWorkflowPresenter surfaces workflow metadata on the resource view.

Timeline and instance detail

Use Workflow Instances or the presenter link to inspect dbflow_workflow_logs through the Standard timeline. See Workflow Timeline.

Alpha caveats

  • Threshold (10000) and assignee user IDs are demo constants
  • archived is a host-only state after approval — not an DBFlow end node
  • Cancel hook rolls back to draft; adjust for your production policy
  • Published definition must exist before DBFlow::start() — run Host Integration sync on deploy
  • permission / callback assignees require registerAssigneeResolver() — not Laravel permission names

What's next

Something wrong? Open an issue on GitHub