# Custom Fields

Custom Fields are the primary way for apps to extend the VARIO Cloud ERP data model. They let you attach your own fields to any ERP entity — articles, accounts, documents, CRM activities, and more. This is central to most integrations: storing external reference IDs, tracking synchronization state, mapping data between systems, or adding any app-specific information directly on ERP records.

Once registered, Custom Fields appear automatically in the ERP UI under the "Zusatzfelder" tab and are fully queryable via the API and VQL.

{% hint style="info" %}
In the API, Custom Fields are represented as **EAV Groups** (Entity-Attribute-Value) containing **Attributes**.
{% endhint %}

```mermaid
graph TD
  G["EAV Group «myapp»\nexternalId (TEXT)\nisActive (BOOLEAN)"]
  G -->|"custom.myapp.<attributeKey>"| A["Article"]
  G -->|"custom.myapp.<attributeKey>"| D["Document"]
```

Access pattern: `custom.{groupKey}.{attributeKey}`

***

### Definition

Define an EAV group as a JSON file in `migrations/{n}/static/erp/eav-groups/`:

```json
{
  "key": "myapp",
  "label": "My App",
  "description": "Custom fields for the integration",
  "attributes": [
    {
      "key": "externalId",
      "label": "External ID",
      "description": "Reference ID in the external system",
      "position": 1,
      "type": "TEXT",
      "viewMode": "EDITABLE",
      "active": true,
      "fulltextField": true,
      "searchIndex": true
    }
  ],
  "uiSettings": {},
  "entities": ["document"]
}
```

### Group Properties

| Field         | Type     | Description                                                      |
| ------------- | -------- | ---------------------------------------------------------------- |
| `key`         | `string` | Unique identifier — determines the access path `custom.{key}.*`  |
| `label`       | `string` | Display name in the ERP                                          |
| `description` | `string` | Description                                                      |
| `attributes`  | `array`  | The fields in this group                                         |
| `uiSettings`  | `object` | UI display configuration (optional)                              |
| `entities`    | `array`  | Target entities, e.g. `["account"]` or `["account", "document"]` |

### Attribute Properties

| Field           | Type           | Description                                     |
| --------------- | -------------- | ----------------------------------------------- |
| `key`           | `string`       | Unique key within the group                     |
| `label`         | `string`       | Display name                                    |
| `description`   | `string`       | Description                                     |
| `position`      | `number`       | Sort order                                      |
| `type`          | `string`       | Data type (see below)                           |
| `viewMode`      | `string`       | `"EDITABLE"`, `"READ_ONLY"`, or `"NOT_VISIBLE"` |
| `active`        | `boolean`      | Whether the field is active                     |
| `fulltextField` | `boolean`      | Include in fulltext search                      |
| `searchIndex`   | `boolean`      | Create a search index                           |
| `textGroupRef`  | `string\|null` | Reference to a text enum group (for dropdowns)  |

### Attribute Types

| Type        | Description   |
| ----------- | ------------- |
| `TEXT`      | Text          |
| `MEMO`      | Long text     |
| `BOOLEAN`   | Boolean       |
| `INTEGER`   | Integer       |
| `DECIMAL`   | Decimal       |
| `CURRENCY`  | Currency      |
| `DATE`      | Date          |
| `DATE_TIME` | Date and time |
| `JSON`      | JSON object   |

***

## Migration

The following section on Custom Field migration is only relevant for developers building VARIO apps. If you are working exclusively with the API, you may skip ahead to the next chapter. Custom Fields are registered during app installation via the migration system.

```
backend/services/maintenance/install/migrations/
  1/
    index.js
    static/erp/eav-groups/myapp.json
  2/                                    ← later update
    index.js
```

### Creating Fields

```javascript
const MigratorErp = require('@vario-software/vario-app-framework-backend/utils/migrator.js');
const eavGroup = require('./static/erp/eav-groups/myapp.json');

const handle = async function()
{
  const migrator = new MigratorErp('migration1');

  await migrator.setMigration('createEav', async (methods) =>
  {
    await methods.createEavGroup(eavGroup);
  });
};

module.exports = handle;
```

### Updating Fields

Add a new migration folder (e.g. `migrations/2/`) and use `changeEavGroup`:

```javascript
// migrations/2/index.js
const MigratorErp = require('@vario-software/vario-app-framework-backend/utils/migrator.js');

const handle = async function()
{
  const migrator = new MigratorErp('migration2');

  await migrator.setMigration('addStatusField', async (methods) =>
  {
    await methods.changeEavGroup('myapp', (group) =>
    {
      group.attributes.push({
        key: 'syncStatus',
        label: 'Sync Status',
        type: 'TEXT',
        viewMode: 'EDITABLE',
        active: true,
        searchIndex: false,
      });
      return group;
    });
  });
};

module.exports = handle;
```

### Migration Methods

| Method                                            | Description                              |
| ------------------------------------------------- | ---------------------------------------- |
| `createEavGroup(json)`                            | Create a group                           |
| `getEavGroup(groupKey)`                           | Read a group                             |
| `changeEavGroup(groupKey, callback)`              | Modify a group via callback              |
| `deleteEavGroup(groupKey)`                        | Delete a group including its data        |
| `removeDataFromEavGroup(groupKey, attributeKeys)` | Remove specific attributes or their data |

***

## API Access

Custom Fields live under the `custom` namespace on every entity they are bound to:

```javascript
// Read
const { data } = await app.erp.fetch('/crm/activities/123', {
  useInternalApi: true,
});
const value = data.custom.myapp.externalId;
```

```javascript
// Create
await app.erp.fetch('/crm/activities', {
  method: 'POST',
  useInternalApi: true,
  body: {
    comment: 'Created via app',
    custom: { myapp: { externalId: 'EXT-456' } },
  },
});
```

```javascript
// Update
await app.erp.fetch('/crm/activities/123', {
  method: 'PUT',
  useInternalApi: true,
  body: {
    custom: { myapp: { externalId: 'EXT-789' } },
  },
});
```

### VQL

```javascript
const { data } = await app.erp.vql({
  statement: `
    SELECT id, comment, custom.myapp.externalId
    FROM crm.activities
    WHERE custom.myapp.externalId NOT NULL
    LIMIT 50
  `,
});
```

***

## Permissions

Required in `app-manifest.json`:

```json
{
  "requirements": {
    "permissions": [
      { "resource": "eav", "verb": "read" },
      { "resource": "eav", "verb": "create" },
      { "resource": "eav", "verb": "update" },
      { "resource": "eav", "verb": "delete" }
    ]
  }
}
```
