Skip to content

Commit 3f40e75

Browse files
author
Karl Rankla
committed
Update ERP toolkit documentation
1 parent 365044a commit 3f40e75

1 file changed

Lines changed: 225 additions & 2 deletions

File tree

docs/integrations/erp-toolkit-mapping.md

Lines changed: 225 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
title: ERP Toolkit Mappings
33
sidebar_position: 20
44
---
5-
65
# ERP Integration Mapping Specification v2.0
76

87
## Overview
@@ -14,6 +13,7 @@ The ERP Integration Mapping v2.0 provides a powerful and flexible way to transfo
1413
- [Key Concepts](#key-concepts)
1514
- [Basic Structure](#basic-structure)
1615
- [Field Mapping Types](#field-mapping-types)
16+
- [Repeatable Field Types as Unique Identifiers](#repeatable-field-types-as-unique-identifiers)
1717
- [Entity-Level Processing](#entity-level-processing)
1818
- [Relations](#relations)
1919
- [Relation Operations](#relation-operations)
@@ -86,7 +86,7 @@ Entity Updates (ready for epilot)
8686
- **`events`**: Object with event names as keys
8787
- **`entities`**: Array of entity configurations for each event
8888
- **`entity_schema`**: The epilot entity schema (e.g., "contact", "contract", "billing_account", see [Core Entities](https://docs.epilot.io/docs/entities/core-entities/))
89-
- **`unique_ids`**: Array of attribute names that uniquely identify this entity
89+
- **`unique_ids`**: Array of unique identifier configurations (see [Repeatable Field Types as Unique Identifiers](#repeatable-field-types-as-unique-identifiers))
9090
- **`jsonataExpression`**: Optional JSONata expression to pre-process the event data
9191
- **`enabled`**: Optional boolean or JSONata expression to conditionally enable/disable entity processing (defaults to `true`)
9292
- **`fields`**: Array of field mappings
@@ -206,6 +206,229 @@ Set a fixed value regardless of input data. Useful for setting tags, purposes, e
206206
}
207207
```
208208

209+
## Repeatable Field Types as Unique Identifiers
210+
211+
Some entity fields in epilot are repeatable fields stored as arrays of objects. The most common examples are **email** and **phone** fields:
212+
213+
```json
214+
{
215+
"email": [{ "email": "user@example.com" }],
216+
"phone": [{ "phone": "+49123456789" }]
217+
}
218+
```
219+
220+
When using these fields as unique identifiers, special handling is required because:
221+
1. **Search**: The Elasticsearch filter must use the nested path (e.g., `email.email.keyword` instead of `email.keyword`)
222+
2. **Create/Update**: Values must be transformed to the repeatable format when creating or updating entities
223+
224+
### Configuration
225+
226+
To use a repeatable field type as a unique identifier, add the `_type` hint to the **field definition** in the `fields` array:
227+
228+
**Standard field (no _type needed):**
229+
```json
230+
{
231+
"entity_schema": "contact",
232+
"unique_ids": ["customer_number"],
233+
"fields": [
234+
{
235+
"attribute": "customer_number",
236+
"field": "customerId"
237+
}
238+
]
239+
}
240+
```
241+
242+
**Repeatable field with _type hint:**
243+
```json
244+
{
245+
"entity_schema": "contact",
246+
"unique_ids": ["email"],
247+
"fields": [
248+
{
249+
"attribute": "email",
250+
"field": "Email",
251+
"_type": "email"
252+
}
253+
]
254+
}
255+
```
256+
257+
**Mixed unique identifiers:**
258+
```json
259+
{
260+
"entity_schema": "contact",
261+
"unique_ids": ["customer_number", "email"],
262+
"fields": [
263+
{
264+
"attribute": "customer_number",
265+
"field": "customerId"
266+
},
267+
{
268+
"attribute": "email",
269+
"field": "Email",
270+
"_type": "email"
271+
}
272+
]
273+
}
274+
```
275+
276+
### Supported Types
277+
278+
| Type | Storage Format | Search Path | Use Case |
279+
|------|----------------|-------------|----------|
280+
| `email` | `[{ "email": "value" }]` | `email.email.keyword` | Contact email addresses |
281+
| `phone` | `[{ "phone": "value" }]` | `phone.phone.keyword` | Contact phone numbers |
282+
283+
### Complete Example
284+
285+
**Configuration:**
286+
```json
287+
{
288+
"version": "2.0",
289+
"mapping": {
290+
"events": {
291+
"CustomerChanged": {
292+
"entities": [
293+
{
294+
"entity_schema": "contact",
295+
"unique_ids": ["email"],
296+
"fields": [
297+
{
298+
"attribute": "email",
299+
"field": "Email",
300+
"_type": "email"
301+
},
302+
{
303+
"attribute": "first_name",
304+
"field": "FirstName"
305+
},
306+
{
307+
"attribute": "last_name",
308+
"field": "LastName"
309+
}
310+
]
311+
}
312+
]
313+
}
314+
}
315+
}
316+
}
317+
```
318+
319+
**Input Event:**
320+
```json
321+
{
322+
"Email": "anna.schmidt@example.com",
323+
"FirstName": "Anna",
324+
"LastName": "Schmidt"
325+
}
326+
```
327+
328+
**Behavior:**
329+
1. **Search**: System searches for existing contact using `email.email.keyword: "anna.schmidt@example.com"`
330+
2. **Create**: If not found, creates new contact with email as `[{ "email": "anna.schmidt@example.com" }]`
331+
3. **Update**: If found, patches the existing contact
332+
333+
### Using in Relations
334+
335+
Repeatable field types can also be used in relation unique identifiers using the `_type` property:
336+
337+
```json
338+
{
339+
"attribute": "primary_contact",
340+
"relations": {
341+
"operation": "_set",
342+
"items": [
343+
{
344+
"entity_schema": "contact",
345+
"unique_ids": [
346+
{
347+
"attribute": "email",
348+
"_type": "email",
349+
"field": "contactEmail"
350+
}
351+
]
352+
}
353+
]
354+
}
355+
}
356+
```
357+
358+
This allows you to look up related entities by their email address, using the correct search path for the email field.
359+
360+
### Backward Compatibility
361+
362+
Existing configurations without `_type` hints continue to work unchanged. The `_type` hint is only needed for repeatable fields like `email` and `phone`.
363+
364+
### Direct Entity Lookup with `_id`
365+
366+
When using `_id` as the **only** unique identifier, the system performs an optimized direct entity lookup instead of an Elasticsearch search. This is useful for:
367+
- Linking to entities where you already know the exact entity ID
368+
- Performance-critical scenarios where you want to avoid search latency
369+
- Post-action callbacks where entity IDs are already resolved
370+
371+
**Configuration:**
372+
```json
373+
{
374+
"entity_schema": "contact",
375+
"unique_ids": ["_id"],
376+
"fields": [
377+
{
378+
"attribute": "_id",
379+
"field": "entityId"
380+
},
381+
{
382+
"attribute": "first_name",
383+
"field": "firstName"
384+
}
385+
]
386+
}
387+
```
388+
389+
**Input:**
390+
```json
391+
{
392+
"entityId": "019a0c06-7190-7509-91c4-ff5bbe3680d8",
393+
"firstName": "Anna"
394+
}
395+
```
396+
397+
**Behavior:**
398+
1. **Direct lookup**: System fetches entity directly by ID (bypasses Elasticsearch)
399+
2. **Schema validation**: Verifies the entity's schema matches the expected `entity_schema`
400+
3. **Update**: If found and schema matches, patches the entity with the mapped attributes
401+
402+
**Important Notes:**
403+
- The `_id` must be a valid, truthy value (not empty string, null, or undefined)
404+
- If `_id` is combined with other unique identifiers, the standard search behavior is used
405+
- If the entity doesn't exist, an error will be thrown (unlike search which returns null)
406+
- If the entity's schema doesn't match the expected `entity_schema`, an error will be thrown
407+
408+
**Using `_id` in Relations:**
409+
410+
You can also use `_id` as a unique identifier in relations for direct lookup:
411+
412+
```json
413+
{
414+
"attribute": "primary_contact",
415+
"relations": {
416+
"operation": "_set",
417+
"items": [
418+
{
419+
"entity_schema": "contact",
420+
"unique_ids": [
421+
{
422+
"attribute": "_id",
423+
"field": "contactEntityId"
424+
}
425+
]
426+
}
427+
]
428+
}
429+
}
430+
```
431+
209432
## Field-Level Processing Control
210433

211434
### Using `enabled` for Conditional Fields

0 commit comments

Comments
 (0)