# Workflows

VARIO Cloud Workflows let you run server-side JavaScript in response to entity lifecycle events in the ERP — an account being saved, a document being created, a CRM deal changing state, and more. Workflows are the primary mechanism for automating business processes, validating data before it is persisted, and orchestrating multi-step logic with user interactions.

***

## Workflow Structure

Every VARIO Cloud workflow consists of a **trigger** and a chain of **elements** connected by successors:

```
Trigger (e.g. account.BEFORE_SAVE)
  → START_EVENT
    → SCRIPT_TASK
      → EXCLUSIVE_SPLIT_GATEWAY
        → SCRIPT_TASK (Branch A)
        → SCRIPT_TASK (Branch B)
      → EXCLUSIVE_JOIN_GATEWAY
    → END_EVENT_SUCCESS
```

***

## Triggers

The trigger determines **when** a VARIO Cloud workflow runs and **what data** is available to your scripts.

### BEFORE\_SAVE vs AFTER\_SAVE

This is the most important distinction in VARIO Cloud workflow scripting — it determines whether your script can modify the entity directly or must read and update it via service calls:

|                       | BEFORE\_SAVE                                                       | AFTER\_SAVE                                                   |
| --------------------- | ------------------------------------------------------------------ | ------------------------------------------------------------- |
| **Execution**         | Synchronous — blocks the save                                      | Asynchronous — runs after save completes                      |
| **Input**             | The entity to save (e.g. `accountToSave`) and its previous version | The saved entity's ID (e.g. `accountId`) and previous version |
| **Can modify entity** | Yes — changes to the ToSave entity are persisted automatically     | Yes — but must read by ID and update via service methods      |
| **Can cancel save**   | Yes — `throw 'message'` aborts with an error shown to the user     | No                                                            |

BEFORE\_SAVE scripts receive the entity object directly and can modify it in place — every change is persisted with the save. AFTER\_SAVE scripts only receive the entity's ID, so they must read the entity via its service, apply changes, and explicitly call the update method.

### Other Triggers

Beyond BEFORE\_SAVE and AFTER\_SAVE, triggers exist for specific lifecycle events such as `document.CREATE`, `document.TRANSFER`, and `document.BEGIN_EDITING`. These behave like AFTER\_SAVE — they run asynchronously and provide IDs rather than entity objects.

The data available to each trigger is accessible via `ctx.availableInput` and `ctx.parameters`. The exact fields depend on the trigger and are visible in the workflow editor.

***

## Script Structure

Every workflow script in VARIO Cloud starts with an import that determines its type. Scripts can be written inline in each element or saved centrally as [Script Modules](https://developer.vario-software.de/documentation/scripting/script-modules) and referenced by name.

### SCRIPT\_TASK

```javascript
import workItem from "work_item_script";

workItem.setAction((ctx) =>
{
  // Your business logic
});
```

You can optionally add a **guard** to skip the element, and a **prepare** step to set up variables before the action:

```javascript
workItem.setPrepare((ctx) =>
{
  // Optional — runs before setAction, e.g. to initialize variables
});

workItem.setGuard((ctx) =>
{
  // Return true to execute setAction, false to skip this element
  return ctx.availableInput.accountToSave.customer !== null;
});

workItem.setAction((ctx) =>
{
  // Only runs if guard returned true
});
```

### EXCLUSIVE\_SPLIT\_GATEWAY

```javascript
import workItem from "work_item_split_gateway";

workItem.setSplit((ctx) =>
{
  const targetCategory = ctx.availableInput.targetCategory;

  const creditNoteTypes = [
    'CUSTOMER_CREDIT_NOTE_WITH_STOCK',
    'CUSTOMER_CREDIT_NOTE_WITHOUT_STOCK',
    'SUPPLIER_CREDIT_NOTE_WITH_STOCK',
    'SUPPLIER_CREDIT_NOTE_WITHOUT_STOCK'
  ];

  if (creditNoteTypes.includes(targetCategory))
  {
    return 'TRANSFER_INTO_CREDIT_NOTE';
  }

  return 'END_WORKFLOW';
});
```

The returned string must match a key in the element's `successors` map.

***

## The Context Object (ctx)

Every script in a VARIO Cloud workflow receives a context object `ctx` with the following properties:

| Property             | Description                                                            |
| -------------------- | ---------------------------------------------------------------------- |
| `ctx.availableInput` | Trigger-specific data — the entity being saved, IDs, previous versions |
| `ctx.services`       | All available scripting services                                       |
| `ctx.parameters`     | Workflow parameters and user task results                              |

***

## Reading and Modifying Data

How you read and modify ERP data in VARIO Cloud depends on the trigger type.

### BEFORE\_SAVE — Direct Modification

In BEFORE\_SAVE triggers, `ctx.availableInput` contains the entity object that is about to be saved, named with a `ToSave` suffix — e.g. `accountToSave`, `documentToSave`, or `crmDealToSave`. This is the actual object being persisted. Any property you set on it is automatically included in the save without calling a service method. Because the save is still in progress, you can also cancel it by throwing an error.

```javascript
import workItem from "work_item_script";

workItem.setAction((ctx) =>
{
  const document = ctx.availableInput.documentToSave;
  if (!document) return;

  if (!document.documentCategory || !document.documentCategory.startsWith('CUSTOMER'))
  {
    return;
  }

  if (document.reverseCharge === true)
  {
    const vatId = document.vatId;

    if (!vatId || vatId.trim() === '')
    {
      throw 'Bei Belegen mit §13b (Reverse Charge) muss eine USt.-ID angegeben werden.';
    }

    ctx.services.logger.info('§13b Validierung erfolgreich: USt.-ID ist vorhanden.');
  }
});
```

### AFTER\_SAVE — Read, Modify, Update

In AFTER\_SAVE triggers, read the entity by ID, modify it, and call the service's update method:

```javascript
import workItem from "work_item_script";

workItem.setAction((ctx) =>
{
  if (ctx.availableInput.previousAccount != null)
  {
    return;
  }

  const account = ctx.services.accountService.readById(ctx.availableInput.accountId);
  const createdBy = ctx.services.userAndGroupService.findUserById(account.info.createdFrom);

  if (createdBy?.username !== 'niko.neumann') return;

  let taskDto = ctx.services.crmTaskService.createNewDtoByTemplate('Adress-Prüfung');
  ctx.services.crmTaskService.create(taskDto);
});
```

### Document Updates

When updating documents via service (e.g. in AFTER\_SAVE or CREATE triggers), `documentService` requires a request wrapper. In `document.ON_MERGE_CHANGES` triggers, the document is available as `documentToSave` and can be modified directly — like any other BEFORE\_SAVE entity.

```javascript
workItem.setAction((ctx) =>
{
  const { documentId } = ctx.parameters;
  const { documentService, accountService, deliveryMethodService, paymentMethodService, utils, logger } = ctx.services;

  const document = documentService.readById(documentId);
  const account = accountService.readById(document.accountId);

  if (account.types.includes('CUSTOMER'))
  {
    return;
  }

  if (!document.deliveryMethodRef)
  {
    logger.info('Setze Versandart: DL');
    const deliveryMethod = deliveryMethodService.findByLabel('DL');
    document.deliveryMethodRef = utils.toApiReference(deliveryMethod);
  }

  if (!document.paymentMethodRef)
  {
    logger.info('Setze Zahlungsart: UEB');
    const paymentMethod = paymentMethodService.findByLabel('UEB');
    document.paymentMethodRef = utils.toApiReference(paymentMethod);
  }

  const updateRequest = documentService.getUpdateDocumentRequest();
  updateRequest.document = document;
  documentService.update(updateRequest);
});
```

***

## Validation and Cancellation

In BEFORE\_SAVE triggers, throw an error to cancel the save:

```javascript
workItem.setAction((ctx) =>
{
  const account = ctx.availableInput.accountToSave;

  if (!account.defaultAddress.postcode)
  {
    throw 'A postal code is required.';
  }

  if (account.defaultAddress.country.isoAlpha2 === 'DE'
    && account.defaultAddress.postcode.length !== 5)
  {
    throw 'German postal codes must be exactly 5 digits.';
  }
});
```

The error message is displayed to the user and the save operation is aborted.

***

## Logging

Use `ctx.services.logger` to write log entries:

```javascript
ctx.services.logger.info('PLZ-Validierung erfolgreich abgeschlossen.');
ctx.services.logger.warn('Unexpected value encountered.');
ctx.services.logger.error('Failed to update document.');
```

| Method              | Description           |
| ------------------- | --------------------- |
| `logger.info(msg)`  | Informational message |
| `logger.warn(msg)`  | Warning               |
| `logger.error(msg)` | Error                 |

Log entries are visible in the workflow execution log in the ERP.

***

## VQL in Workflows

Use `ctx.services.vqlService` to query ERP data within scripts:

```javascript
const results = ctx.services.vqlService.queryAll(
  `SELECT id, number FROM article.query WHERE number = '12345' LIMIT 1`
);

if (results && results.length > 0)
{
  const articleId = results[0].id;
}
```

{% hint style="info" %}
`vqlService.queryAll()` returns a `List<Map>`. Access fields directly by their attribute path: `results[0].id`, `results[0].number`.
{% endhint %}

***

## User Tasks

VARIO Cloud workflows support user interaction through `USER_TASK` elements. A user task pauses workflow execution and prompts the user for input. The user's response is available in subsequent scripts via `ctx.parameters`:

```javascript
// In the element after a USER_TASK with resultName 'closingReasonId'
workItem.setAction((ctx) =>
{
  const crmDealService = ctx.services.crmDealService;
  const dealId = ctx.parameters['crmId'];
  const closingReasonId = ctx.parameters['closingReasonId'];

  crmDealService.dealWon(dealId, closingReasonId);
});
```

***

## Custom Fields

VARIO Cloud [Custom Fields](https://developer.vario-software.de/documentation/fundamentals/custom-fields) are accessed directly on entity objects in scripts:

```javascript
// Read
const externalId = account.custom.myapp.externalId;
```

```javascript
// Write (in BEFORE_SAVE)
account.custom.myapp.externalId = 'EXT-123';
account.custom.myapp.syncedAt = ctx.services.utils.dateTimeNow();
```

***

## Comparing with Previous State

Both BEFORE\_SAVE and AFTER\_SAVE triggers in VARIO Cloud provide the previous version of the entity. Use it to detect field-level changes and react only when specific values have changed:

```javascript
workItem.setAction((ctx) =>
{
  const current = ctx.availableInput.crmDealToSave;
  const previous = ctx.availableInput.crmDealPrevious;

  // Only act if the state actually changed
  if (current.state.id === previous.state.id) return;

  if (current.state.label === 'Won')
  {
    // Deal just became "Won"
  }
});
```
