This repository demonstrates how to use epilot's ERP Integration Toolkit mapping configuration (v2.0) to transform ERP system events into epilot entities.
- ERP Toolkit Mapping Documentation - Complete guide on mapping configuration
- Interactive Mapping Playground - Test and debug your mappings in the epilot portal
- epilot data model - epilot core entities documented with full json schema and examples
- JSONata Language Reference - Learn about JSONata expressions used in mappings
- epilot API Documentation - API reference
- epilot SDK - JavaScript/TypeScript SDK
This repository contains complete, working examples of:
Maps an ERP customer update to three epilot entities:
- Contact - Customer contact information
- Account - Business account details
- Billing Account - Payment and billing information
Maps an ERP order to two epilot entities:
- Contact - Customer from order
- Order - Order details with line items
Maps an ERP power contract with meter data to multiple epilot entities:
- Contract - Service agreement with tariff details
- Meter - Smart meter device information
- Meter Counter - Peak (HT) and off-peak (NT) counters for dual-tariff metering
- Meter Readings - 4 monthly readings demonstrating the
meter_readingsmapping feature
.
├── samples/
│ ├── mapping.CustomerChanged.json # CustomerChanged event configuration
│ ├── mapping.OrderChanged.json # OrderChanged event configuration
│ ├── mapping.ContractChanged.json # ContractChanged event configuration (with meter_readings)
│ ├── payload.customer.json # Sample CustomerChanged event
│ ├── payload.order.json # Sample OrderChanged event
│ └── payload.contract.json # Sample ContractChanged event (with meter readings)
├── tests/
│ └── sample-mappings.test.ts # Integration tests with expected outputs
├── package.json
└── README.md
- Node.js
- An epilot API token
# Clone the repository
git clone https://github.com/epilot-dev/erp-toolkit-mapping-examples.git
cd erp-toolkit-mapping-examples
# Install dependencies
npm installCreate a .env file in the project root:
# Get your API token from https://portal.epilot.cloud/app/settings/tokens
EPILOT_API_TOKEN=your_api_token_hereNote: Never commit your
.envfile to version control!
# Run all mapping tests
npm test
# Run tests in watch mode
npm run test:watchThe tests will validate that the sample events are correctly transformed into epilot entities according to the mapping configuration.
Each event type has its own mapping configuration file in samples/:
mapping.CustomerChanged.json- Customer to contact, account, and billing accountmapping.OrderChanged.json- Order with line items and customer relationmapping.ContractChanged.json- Contract with meter, counters, and meter readings
Key concepts demonstrated:
- Entity mapping with unique identifiers
- JSONata expressions for complex transformations
- Multi-value attributes (arrays with tags)
- Entity relations with
_setoperation - Conditional entity creation
- Array iteration with entity-level
jsonataExpression - Meter readings mapping for energy/utility data
payload.customer.json- Business customer with addresses, contacts, and payment infopayload.order.json- Order with line items and customer referencepayload.contract.json- Power contract with smart meter, dual-tariff counters, and monthly readings
The test file tests/sample-mappings.test.ts shows the exact output expected for each entity using toMatchObject assertions. This serves as living documentation of the transformation.
{
"attribute": "external_id",
"field": "customerId"
}{
"attribute": "full_name",
"jsonataExpression": "customerType = 'business' ? companyName : (firstName & ' ' & lastName)"
}{
"attribute": "email",
"jsonataExpression": "$exists(email) ? [{ \"_tags\": [\"Primary\"], \"email\": email }] : undefined"
}{
"attribute": "account",
"relations": {
"operation": "_set",
"items": [{
"entity_schema": "account",
"unique_ids": [{
"attribute": "customer_number",
"jsonataExpression": "customerId"
}]
}]
}
}{
"entity_schema": "account",
"condition": "customerType = 'business'",
"fields": [...]
}{
"entity_schema": "meter_counter",
"jsonataExpression": "meter.counters",
"unique_ids": ["external_id"],
"fields": [
{ "attribute": "external_id", "field": "counterId" },
{ "attribute": "tariff_type", "field": "tariffType" }
]
}{
"attribute": "meter_type",
"jsonataExpression": "(\n $meterTypeMap := {\n \"SMART\": \"smart-meter\",\n \"IMS\": \"intelligent-measuring-system\",\n \"TP\": \"three-phase-meter\",\n \"AC\": \"alternating-current-meter\",\n \"EM\": \"electronic-meter\"\n };\n $lookup($meterTypeMap, meter.meterType) ? $lookup($meterTypeMap, meter.meterType) : \"electronic-meter\"\n)"
}This pattern uses a JSONata object as a lookup table to map ERP enum values to epilot values, with a default fallback.
{
"meter_readings": [{
"jsonataExpression": "meterReadings",
"meter": {
"unique_ids": [{ "attribute": "external_id", "field": "meterId" }]
},
"meter_counter": {
"unique_ids": [{ "attribute": "external_id", "field": "counterId" }]
},
"fields": [
{ "attribute": "external_id", "field": "readingId" },
{ "attribute": "timestamp", "field": "timestamp" },
{ "attribute": "value", "jsonataExpression": "$string(value)" },
{ "attribute": "source", "field": "source" }
]
}]
}Each test validates the complete transformation for one entity using the simulateMappingV2 endpoint:
it('should map to contact entity', async () => {
const eventConfig = loadEventConfig('CustomerChanged');
const event = loadInboundEvent('customer');
const response = await erpClient.simulateMappingV2(null, {
event_configuration: eventConfig,
format: 'json',
payload: event,
});
const contactUpdate = response.data.entity_updates.find(
(update) => update.entity_slug === 'contact'
);
expect(contactUpdate).toMatchObject({
entity_slug: 'contact',
attributes: {
external_id: 'CUST-9876',
full_name: 'Acme Corporation',
email: [{ _tags: ['Primary'], email: 'max.mustermann@acme.com' }],
// ... complete expected structure
}
});
});For meter readings:
it('should map 4 meter readings', async () => {
const response = await erpClient.simulateMappingV2(null, {
event_configuration: eventConfig,
format: 'json',
payload: event,
});
const meterReadings = response.data.meter_readings_updates;
expect(meterReadings).toHaveLength(4);
expect(meterReadings[0]).toMatchObject({
meter: { $entity_unique_ids: { external_id: 'MTR-001234' } },
meter_counter: { $entity_unique_ids: { external_id: 'CNT-001234-HT' } },
attributes: {
external_id: 'RDG-2024-001',
timestamp: '2024-10-01T00:00:00Z',
value: '12345.67',
source: 'ERP'
}
});
});- Create a new mapping file in
samples/mapping.YourEvent.json - Add sample payload in
samples/payload.yourevent.json - Run tests to validate your mappings work correctly
- Deploy the mapping configuration to your epilot organization
{
"attribute": "my_custom_field",
"field": "sourceField"
}{
"attribute": "company_name",
"jsonataExpression": "$exists(companyName) ? companyName : undefined"
}{
"attribute": "order_items",
"jsonataExpression": "items.{ \"product_id\": productId, \"quantity\": $string(quantity) }"
}{
"attribute": "related_entity",
"relations": {
"operation": "_set",
"items": [{
"entity_schema": "target_schema",
"unique_ids": [{ "attribute": "unique_field", "field": "sourceField" }]
}]
}
}