Template Transformer

Table of contents

  • Use case
  • Guide

Use case

When there is a need to convert incoming order data into a new format for a transport system, instead of writing complex transformation code, you can use the Template Transformer to define the desired output using a template (e.g., Mustache or XSLT), enabling fast, clear, and maintainable data mapping without custom scripts.

Follow the guide below to create the Template Transformer.

Guide

Step 1: Create a new transformer and select the data transformer - “Render a Template” option from the list. Users are able to write a Twig template.

Step 2: Input the template data in the initial format. Apply changes.

Step 3: Add variables, destination and result where applicable.

Step 4: Run a test and verify the result in the Transformer tester.

2 Likes

Awesome to see this in action! Twig is very powerful, and this allows us to write directly in a lot of different languages.

Since this is outputted as a string, you can write for example CSV in this:

name;sku;quantity
{% for item in items %}{{ item.productName }};{{ item.sku }};{{ item.qty }}{% endfor %}

Or use filters to make sure that the formatting of the strings is correct:

name;sku;quantity
{% for item in items %}{{ item.productName|slice(0,20) }};{{ item.sku|slice(0,20)| }};{{ item.qty|format_number({decimal_always_shown:true}, locale: 'en') }}{% endfor %}

Here a small sample of what Gemini could do to create a Shopware order:

{# This Twig template transforms an order from a Magento-like structure to a Shopware-like structure. #}
{# It assumes the original order data is available in a variable named 'data'. #}
{# It also handles deposits as separate line items and converts shipping to a line item. #}

{% set order = data %} {# Alias 'data' to 'order' for readability #}

{
  "salesChannelId": "YOUR_SALES_CHANNEL_ID", {# Replace with your actual Sales Channel ID #}
  "orderDateTime": "{{ order.created_at|date('Y-m-d H:i:s') }}",
  "price": {
    "netPrice": {{ (order.subtotal - order.discount_amount)|number_format(2, '.', '') }},
    "totalPrice": {{ order.grand_total|number_format(2, '.', '') }},
    "calculatedTaxes": [], {# You would typically calculate taxes here based on items and shipping #}
    "taxRules": [],
    "positionPrice": {{ order.subtotal|number_format(2, '.', '') }},
    "taxStatus": "gross" {# Or "net" depending on your Shopware configuration #}
  },
  "shippingCosts": {
    "unitPrice": 0, {# Shipping is moved to line items #}
    "totalPrice": 0,
    "calculatedTaxes": [],
    "taxRules": [],
    "referencePrice": null,
    "listPrice": null,
    "extensions": []
  },
  "stateId": "YOUR_ORDER_STATE_ID", {# Replace with your actual Order State ID (e.g., for "Open") #}
  "billingAddressId": null, {# These will be set by Shopware during creation or you can map existing IDs #}
  "shippingAddressId": null, {# Same as above #}
  "addresses": [
    {
      "salutationId": "YOUR_SALUTATION_ID", {# e.g., for "Mr." or "Mrs." #}
      "firstName": "{{ order.billing_address.first_name }}",
      "lastName": "{{ order.billing_address.last_name }}",
      "street": "{{ order.billing_address.street|first }}",
      "zipcode": "{{ order.billing_address.postcode }}",
      "city": "{{ order.billing_address.city }}",
      "countryId": "YOUR_COUNTRY_ID", {# Map country code (e.g., "US") to Shopware's country UUID #}
      "countryStateId": null, {# If you have states configured in Shopware #}
      "company": "{{ order.billing_address.company|default('') }}",
      "phoneNumber": "{{ order.billing_address.telephone|default('') }}",
      "email": "{{ order.customer_email }}",
      "billingAddress": true,
      "shippingAddress": false
    },
    {% if order.shipping_address.street|first != order.billing_address.street|first or
            order.shipping_address.postcode != order.billing_address.postcode or
            order.shipping_address.city != order.billing_address.city %}
    {
      "salutationId": "YOUR_SALUTATION_ID",
      "firstName": "{{ order.shipping_address.first_name }}",
      "lastName": "{{ order.shipping_address.last_name }}",
      "street": "{{ order.shipping_address.street|first }}",
      "zipcode": "{{ order.shipping_address.postcode }}",
      "city": "{{ order.shipping_address.city }}",
      "countryId": "YOUR_COUNTRY_ID",
      "countryStateId": null,
      "company": "{{ order.shipping_address.company|default('') }}",
      "phoneNumber": "{{ order.shipping_address.telephone|default('') }}",
      "email": "{{ order.customer_email }}",
      "billingAddress": false,
      "shippingAddress": true
    }
    {% endif %}
  ],
  "lineItems": [
    {% for item in order.items %}
    {
      "id": "{{ item.item_id }}", {# Can be a UUID if you generate one, or rely on Shopware #}
      "productId": null, {# You'd map this to an existing product ID in Shopware if possible #}
      "referencedId": "{{ item.sku }}",
      "type": "product",
      "quantity": {{ item.qty_ordered }},
      "label": "{{ item.name }}",
      "unitPrice": {{ item.price|number_format(2, '.', '') }},
      "totalPrice": {{ item.row_total|number_format(2, '.', '') }},
      "price": {
        "netPrice": {{ (item.row_total / (1 + 0.19))|number_format(2, '.', '') }}, {# Example: assuming 19% VAT #}
        "totalPrice": {{ item.row_total|number_format(2, '.', '') }},
        "calculatedTaxes": [],
        "taxRules": [],
        "positionPrice": {{ item.price|number_format(2, '.', '') }},
        "taxStatus": "gross"
      },
      "good": true {# Indicates it's a shippable product #}
    }{% if loop.last and order.shipping_amount <= 0 and item.deposit <= 0 %}{% else %},{% endif %} {# Add comma unless it's the very last line item #}

    {% if item.deposit > 0 %}
    {
      "id": null, {# Shopware will generate an ID #}
      "productId": null,
      "referencedId": "DEPOSIT",
      "type": "custom", {# Or "product" if you have a "deposit" product #}
      "quantity": 1,
      "label": "Deposit for {{ item.name }}",
      "unitPrice": {{ item.deposit|number_format(2, '.', '') }},
      "totalPrice": {{ item.deposit|number_format(2, '.', '') }},
      "price": {
        "netPrice": {{ (item.deposit / (1 + 0.19))|number_format(2, '.', '') }}, {# Example: assuming 19% VAT #}
        "totalPrice": {{ item.deposit|number_format(2, '.', '') }},
        "calculatedTaxes": [],
        "taxRules": [],
        "positionPrice": {{ item.deposit|number_format(2, '.', '') }},
        "taxStatus": "gross"
      },
      "good": false {# Deposits are typically not shippable #}
    }{% if loop.last and order.shipping_amount <= 0 %}{% else %},{% endif %}
    {% endif %}
    {% endfor %}

    {# Add shipping as a separate line item if greater than 0 #}
    {% if order.shipping_amount > 0 %}
    {
      "id": null, {# Shopware will generate an ID #}
      "productId": null,
      "referencedId": "SHIPPING",
      "type": "shipping", {# Shopware has a 'shipping' type for line items #}
      "quantity": 1,
      "label": "Shipping Costs",
      "unitPrice": {{ order.shipping_amount|number_format(2, '.', '') }},
      "totalPrice": {{ order.shipping_amount|number_format(2, '.', '') }},
      "price": {
        "netPrice": {{ (order.shipping_amount / (1 + 0.19))|number_format(2, '.', '') }}, {# Example: assuming 19% VAT #}
        "totalPrice": {{ order.shipping_amount|number_format(2, '.', '') }},
        "calculatedTaxes": [],
        "taxRules": [],
        "positionPrice": {{ order.shipping_amount|number_format(2, '.', '') }},
        "taxStatus": "gross"
      },
      "good": false {# Shipping is not a physical product #}
    }
    {% endif %}
  ],
  "deliveries": [
    {
      "shippingMethodId": "YOUR_SHIPPING_METHOD_ID", {# Map to your Shopware shipping method UUID #}
      "shippingOrderAddressId": null, {# Will be set by Shopware #}
      "positions": [
        {# This section typically lists the line item IDs that are part of this delivery. #}
        {# For simplicity here, we're assuming all shippable items are in one delivery. #}
        {% for item in order.items %}
          {% if item.product_type == 'simple' %} {# Assuming simple products are shippable #}
          { "orderLineItemId": "{{ item.item_id }}" }{% if not loop.last or order.shipping_amount > 0 and item.deposit <= 0 %},{% endif %}
          {% endif %}
        {% endfor %}
        {% if order.shipping_amount > 0 %}
          { "orderLineItemId": "SHIPPING_LINE_ITEM_ID" } {# You might need to generate a UUID for the shipping line item here #}
        {% endif %}
      ]
    }
  ],
  "transactions": [
    {
      "paymentMethodId": "YOUR_PAYMENT_METHOD_ID", {# Map to your Shopware payment method UUID #}
      "amount": {
        "unitPrice": {{ order.payment.amount_ordered|number_format(2, '.', '') }},
        "totalPrice": {{ order.payment.amount_ordered|number_format(2, '.', '') }},
        "calculatedTaxes": [],
        "taxRules": [],
        "positionPrice": {{ order.payment.amount_ordered|number_format(2, '.', '') }},
        "taxStatus": "gross"
      },
      "stateId": "YOUR_TRANSACTION_STATE_ID" {# e.g., for "Open" or "Paid" #}
    }
  ],
  "customerId": null, {# If you have an existing customer in Shopware, provide their ID #}
  "customer": {
    "email": "{{ order.customer_email }}",
    "firstName": "{{ order.customer_firstname }}",
    "lastName": "{{ order.customer_lastname }}",
    "salutationId": "YOUR_SALUTATION_ID",
    "guest": true {# Set to false if you are mapping to an existing customer #}
  },
  "languageId": "YOUR_LANGUAGE_ID", {# e.g., for "en-GB" or "de-DE" #}
  "currencyId": "YOUR_CURRENCY_ID", {# Map currency code (e.g., "USD") to Shopware's currency UUID #}
  "countryId": "YOUR_COUNTRY_ID",
  "taxStatus": "gross" {# Or "net" depending on your Shopware configuration #}
}
3 Likes