Erp Workflow Automation

Production engineering guide for erp workflow automation covering patterns, implementation strategies, and operational best practices.

ERP workflow automation orchestrates business processes across departments using real-time, event-driven triggers and structured task routing. It matters when a purchase requisition must initiate vendor onboarding, inventory allocation, and financial posting—each step synchronized with approval gates, notifications, and data consistency checks. The workflow is not a sequence of manual steps but a repeatable, monitored, and executable pipeline, where each process instance is a distinct entity with its own state, history, and metadata.

Defining a Workflow

A workflow is a declarative specification of how a business process unfolds. It consists of a sequence of steps, each with a defined action, input, output, and execution condition. The core unit is the *workflow definition*, a JSON document describing the process structure, including:

{
  "id": "purchase-requisition-approval",
  "version": "1.2.0",
  "start": {
    "type": "event-trigger",
    "event": "purchase.requisition.created"
  },
  "steps": [
    {
      "id": "validate-requisition",
      "type": "service-call",
      "service": "inventory.validate",
      "inputs": {
        "itemCode": "{{requisition.itemCode}}",
        "quantity": "{{requisition.quantity}}"
      },
      "outputs": {
        "available": "{{result.available}}",
        "leadTime": "{{result.leadTime}}"
      },
      "next": "assign-approver"
    },
    {
      "id": "assign-approver",
      "type": "task",
      "title": "Approve Purchase Requisition",
      "assignee": "{{requisition.requestedBy}}",
      "dueInDays": 3,
      "next": "notify-approver"
    },
    {
      "id": "notify-approver",
      "type": "service-call",
      "service": "notification.send",
      "inputs": {
        "to": "{{approver.email}}",
        "subject": "Approve Requisition #{{requisition.id}}",
        "body": "The requisition for {{requisition.quantity}} units of {{requisition.itemCode}} is pending approval."
      },
      "next": "wait-for-approval"
    },
    {
      "id": "wait-for-approval",
      "type": "condition",
      "expression": "{{approval.status}} == 'approved'",
      "trueNext": "create-purchase-order",
      "falseNext": "escalate-to-manager"
    }
  ]
}

Each step may produce side effects: creating a record, sending an email, triggering a webhook, or updating a database. The workflow engine tracks the state of each instance, stored in a workflow_instances table with fields: id, workflow_id, state, data, started_at, completed_at, error.

Executing a Workflow Instance

When an event occurs—such as purchase.requisition.created—the workflow engine looks up the purchase-requisition-approval definition and creates a new workflow_instance with state: "pending". The engine then executes the start step, which may be a service call, a timer, or a message queue trigger.

The event is a structured payload, typically JSON sent via HTTP or message queue (e.g., RabbitMQ, Kafka):

{
  "event": "purchase.requisition.created",
  "timestamp": "2024-04-05T12:34:56Z",
  "data": {
    "id": "PR-2024-001",
    "requestedBy": "[email protected]",
    "itemCode": "LAP-1000",
    "quantity": 15,
    "department": "IT",
    "approved": false
  }
}

The engine deserializes the event, populates the workflow instance’s data field, and begins execution. Each step runs asynchronously. The engine maintains a pending_steps queue and a workflow_instances table.

If a step fails (e.g., inventory.validate returns a 503), the instance enters state: "failed" and logs the error in workflow_instance.errors:

{
  "stepId": "validate-requisition",
  "error": {
    "code": "service-unavailable",
    "message": "Inventory service returned 503: Service Unavailable",
    "details": {
      "httpStatus": 503,
      "responseBody": "{\"error\":\"Service is overloaded\",\"retryAfter\":60}"
    }
  }
}

The workflow can retry failed steps with configurable policies: maxRetries, delay, backoff: exponential, linear, or fixed.

Handling Conditions and Branching

Conditions are evaluated using a simple expression language. Expressions can reference any variable from the workflow instance or step outputs. The engine supports logical, comparison, and function expressions.

"expression": "{{approval.status}} == 'approved' and {{requisition.quantity}} > 10"

Conditions may be if, switch, or case blocks. The next field can be a single step ID or a list of steps for parallel branching.

{
  "id": "approve-shipment",
  "type": "condition",
  "expression": "{{shipment.totalValue}} > 10000",
  "trueNext": ["send-to-approval", "notify-finance"],
  "falseNext": "record-inventory"
}

The engine evaluates the condition and schedules all trueNext steps to run in parallel. Each step runs independently, and the parent workflow waits for all to complete before proceeding.

Integrating with External Services

A workflow step often calls an external service via HTTP. The service-call step uses a service registry to resolve endpoints, authentication, and input/output mapping.

The service registry is a table with:

Example service entry:

{
  "name": "inventory.validate",
  "endpoint": "https://inventory.company.com/api/v1/validate",
  "auth": {
    "type": "bearer",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  },
  "inputMap": {
    "itemCode": "itemCode",
    "quantity": "quantity"
  },
  "outputMap": {
    "available": "result.available",
    "leadTime": "result.leadTime"
  }
}

The engine sends a POST request to https://inventory.company.com/api/v1/validate with:

{
  "itemCode": "LAP-1000",
  "quantity": 15
}

It expects a 200 OK response with Content-Type: application/json and maps the result to variables as defined.

If the service returns 400 Bad Request, the engine captures the error and logs it. If the service is unreachable, the step retries after a delay.

State Management and Error Recovery

Each workflow instance maintains a state field that tracks progress. Possible states include:

The engine uses a state table:

CREATE TABLE workflow_instances (
  id UUID PRIMARY KEY,
  workflow_id TEXT NOT NULL,
  state TEXT NOT NULL,
  data JSONB,
  started_at TIMESTAMPTZ,
  completed_at TIMESTAMPTZ,
  error JSONB,
  version TEXT
);

The data field is a JSONB column that stores the state of the workflow instance. It may include:

When a step fails, the engine updates the instance and logs the error. It may also emit a workflow.failed event:

{
  "event": "workflow.failed",
  "timestamp": "2024-04-05T12:40:00Z",
  "data": {
    "instanceId": "wfi-2024-001",
    "workflowId": "purchase-requisition-approval",
    "stepId": "assign-approver",
    "error": {
      "code": "assignee-not-found",
      "message": "User [email protected] not in system"
    }
  }
}

A failed instance can be manually resumed, retried, or escalated. The engine supports *compensation steps*: actions executed when a workflow fails, such as sending a Slack alert or rolling back a purchase order.

Monitoring and Operational Visibility

A workflow engine exposes an HTTP API for monitoring and control. Key endpoints:

Example request to resume a failed instance:

POST /workflows/instances/wfi-2024-001/retry HTTP/1.1
Host: workflow-engine.company.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "stepId": "assign-approver",
  "reason": "User was on leave; reassigning to manager"
}

The engine responds with 200 OK and updated instance state.

The engine logs every step execution to a step_logs table:

CREATE TABLE step_logs (
  id UUID PRIMARY KEY,
  instance_id UUID REFERENCES workflow_instances(id),
  step_id TEXT,
  started_at TIMESTAMPTZ,
  completed_at TIMESTAMPTZ,
  status TEXT, -- "success", "failed", "running"
  inputs JSONB,
  outputs JSONB,
  error JSONB
);

A common failure mode is missing or misnamed variables. The engine raises a variable-not-found error when a step references a variable that does not exist in the instance data.

Another sharp edge: timing out on long-running workflows. A timer step can be configured with duration: "PT24H" (24 hours), but if the engine restarts, it may lose the timer. The engine must persist pending timers to a timers table and use a background job to check for expired timers every minute.

Common Pitfalls and Failure Modes

Workflow Orchestration in Practice

A typical workflow runs end-to-end:

  1. An employee creates a purchase requisition via a web form.
  2. The form publishes purchase.requisition.created with id: "PR-2024-001".
  3. The workflow engine finds purchase-requisition-approval and creates workflow_instance with state: "pending".
  4. It executes validate-requisition step: calls inventory.validate service.
  5. The service returns available: true, leadTime: 5.
  6. The engine runs assign-approver, setting assignee: "[email protected]", dueInDays: 3.
  7. The engine schedules notify-approver step.
  8. After 3 days, the workflow instance is in wait-for-approval.
  9. Alice approves the requisition. The system updates approval.status: "approved".
  10. The engine evaluates the condition: approval.status == 'approved' and requisition.quantity > 10 → true.
  11. It schedules create-purchase-order and notify-finance.
  12. Both steps complete. The workflow instance enters completed state.

The engine records the full history, including all step logs, event payloads, and error details. A dashboard can now show the average time from requisition to purchase order, the number of failed approvals, and the most common errors.

This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.