Case study · Outdoor apparel · 8 markets

They replaced the ERP and the commerce stack. At the same time.

Didriksons moved from an on-premise NAV to Business Central, and off Litium and a custom Nuxt storefront — in the same quarter, against the same fixed date. It went live on 9 June 2026, the date set three months earlier.

3mo
Start to go-live
112.25h
Backend work, total
8
Markets, 5 currencies
On date
9 June 2026
Two ships, one tide

Most replatforms hold something still. This one held nothing.

The ERP was moving. The storefront was moving. Product data was moving to a new PIM and content to a new headless CMS. Every integration point had a moving target on both ends — and the go-live date did not move at all.

Before

  • On-premise NAV
  • Litium commerce
  • Custom Nuxt storefront
  • Product data in Litium
  • Content in Litium

After

  • Business Central (SaaS)
  • Hantera_ Commerce engine, OMS & Integration hub
  • New storefront
  • Dedicated PIM
  • Headless CMS
Before we start claiming things

Six questions. Four were ours to answer.

These are the questions the old stack couldn't answer — including the two that had nothing to do with us. Each of the four we owned gets a section below, under the same heading.

Two of the biggest complaints were not ours to fix. We mention them because they were half the reason for the project — and because everything we did own had to land on the same date for any of it to matter. Cart and checkout are fast because Hantera_ serves them; overall page speed is the storefront team's achievement, not ours.

What we built

Hantera_ in the middle. The order in the middle of that.

Three jobs at once: the commerce engine behind cart and checkout, the OMS that runs fulfilment, and the hub every other system meets in. Business Central keeps finance and base prices. The PIM keeps product content. The storefront keeps presentation.

Sources

Business CentralFinance, base prices, invoicing
PIMProduct content and attributes
WMSParcel tracking and stock

Hantera_

Commerce engineCart, checkout, promotions, price authority
OMSOrders, deliveries, returns, refunds
Integration hubEvery system writes here

Surfaces

StorefrontHeadless commerce API
OperationsOne order view
CRMCustomer information, back-in-stock, abandoned cart, transactional e-mails

One order record. Every system writes to it; every team reads from it.

Back to the six questions The order's promises

Why was this order charged that price?

An order from last October comes back. On the old stack, answering that meant reconstructing a promotion that may since have been edited, against a product list that may since have changed — the order pointed at a definition that had moved. Now the price is decided in one place, and the order remembers the decision.

/ 01

Base prices

Imported from Business Central into a staging mirror, so a bad import is visible before it reaches a cart. A reconciliation job checks it.

ERP owns this
/ 02

Derived price lists

“Archive sale, −30%, this weekend.” One condition on a product attribute, one percentage, switched on from the portal. No code, no deployment.

Marketing owns this
/ 03

Promotions

Cart-level logic: targeting, spend thresholds in each currency, and the “add €20 more” message that comes with it.

Marketing owns this
/ 04

Then the order
remembers.

Every promotion is stored on the order with the component that ran, the exact parameters it ran with, what it took off, when, and the combination rules in force at that moment. Every discount on every line references what caused it.

Which promotion ranThe exact settings it ran withWhat it took offWhenWhat it was allowed to combine with

The old stack referenced its promotions. Hantera_ remembers them.

Edit a promotion tomorrow and yesterday's orders are untouched. That is what makes a refund, a return, or a dispute answerable a year later — and why an upstream system can't corrupt the price in the cart.

Every order is a stack of promises. Price is the first one it has to keep.

Back to the six questions Promotion types · A developer's job

Can we run this promotion by Friday?

It's a Friday in October and marketing wants something the system can't express yet. On the old stack that was a ticket, then a sprint, then a release window. By the time it shipped the weekend was over — so the idea never got proposed in the first place.

Now it's written from the specification, and mostly by a model. A promotion type is a single declarative Filtrera file — close enough to the spec itself that generating it is a reasonable thing to ask an AI to do, leaving a developer to review the logic and finish the edges.

And it goes live the same day, because it ships inside a versioned app. Installing a new version of that app is the deployment; there's no platform release to queue behind and nothing else to redeploy for the new type to exist. The two we built for Didriksons weren't “apply a discount” either — both handle order-value thresholds and product conditions, evaluated in each market's own currency.

Litium-era custom typeWritten by hand, then a release window to wait for
3–6 wk
Hantera_ promotion typeGenerated from a spec, shipped as an app version
Same day

Two types were built on this project, at 1.25 h and 0.5 h per the timesheets. The first was the first promotion type built on Hantera_ on any tenant — no prior example to copy — and it was written twice, because testing changed the requirements. Lead times are our own experience from building this work on Litium previously, not measured data.

It was never the eight hours that hurt. It was the six weeks.

commerce/promotions/productPercentageDiscount.hpr
param discount: number = 0
param productTags: [text] = []
param productCollections: [text] = []
param useThreshold: boolean = false
param thresholds: { text -> number } = {}

// qualifies per the order's own currency
let qualifies = useThreshold match
  false |> true
  |> thresholds->(order.currencyCode) match
       (n: number) |> n <= order.orderTotal
       |> false

from qualifies match
  true |> percentage(target(…), discount * 1%)
  false |> { type = 'message', … }

And once the type exists, marketing stops needing a developer.

A campaign becomes a configuration, not a request: which products, which markets, what threshold in which currency, and when it runs. Across 8 markets and 5 currencies, without a ticket.

The promotion editor in Hantera_ — the promotion's name and type, the products it targets, an order-value threshold entered per currency, and the dates it runs between.
Back to the six questions Campaign performance

What did that campaign actually earn?

Revenue was always easy to count. What survived the returns was the hard part — because the coupon, the order it was used on, and the refund that came weeks later lived in three separate systems. Reconciling them was a manual export, so in practice nobody ran it and campaign performance stayed a matter of opinion.

Now it's a screen. Every promotion has its own statistics view, and the number that matters most is the one the old stack could never produce: revenue after the returns came back.

Promotion statistics in Hantera_ — redemptions, gross and net revenue, discounted value, discount rate and average order value across the top; redemptions and discounted value charted over time; returns, coupon funnel and order outcome below.

Why we can show you that number.

Any platform can chart redemptions. Netting off the returns is only possible because the coupon, the order, the line and the credit memo are one record instead of three systems. The dashboard isn't doing clever arithmetic — it's walking one graph.

coupon redemption order order line returnedQuantity

Coupons are nodes in the same Graph as orders, products and promotions, and every redemption is its own node with an edge to the order that used it. Returns land on the order line itself — including the ones Business Central raises as credit memos, which arrive as returns against the original lines. So the refund hangs off the same record as the coupon, and the traversal terminates on a real quantity rather than an estimate.

The codeWhich promotion it belongs toWhen it expiresUses allowedUses left

Which is also why coupons stopped living in a spreadsheet. Codes are generated when a campaign needs them — one at a time or in batches, from the portal — instead of thousands pre-generated up front because creating them later was too painful.

Revenue was easy to count. What survived the returns was the hard part.

Back to the six questions Operations

Has this payment been refunded?

The refund happens where the order lives. A customer wants their money back, the agent has the order open — and what happened next used to depend on whether an API call succeeded.

Before

  • The payment call fails
  • Agent opens the PayPal or Kustom dashboard
  • Refund goes through there
  • The money moves — the order doesn't know
Two systems, two answers.

After

  • Agent stays in the order
  • Capture and refund run from the record itself
  • Payment state sits on the same screen
  • Deliveries, invoices, returns and tracking too
One system, one answer.
The payment panel inside a Didriksons order in Hantera_ — authorized, charged, captured and credit balances, a Refund action, the invoices the payment covers, and the full charge and capture ledger with timestamps.

This isn't about having fewer tabs open. It's that the systems can no longer disagree about whether money moved.

The hub

Eight systems. One record.

The order carries what the ERP knows, what the warehouses know, what the carrier knows, what the payment provider knows, and what the marketing platform knows. Nobody has to ask another department what happened.

Business Central

Sales orders, invoices, credit memos, release and cancellation.

Two WMS platforms

Stock levels, picking, shipment confirmation, tracking links.

nShift DeliveryCheckout

Delivery options, pickup points, carrier services.

Kustom + PayPal

Payments, captures, refunds — driven from the order.

PIM

Product data, images, attributes, storefront slugs.

CRM + marketing

Customers, consent, receipts, back-in-stock signals.

Four fulfilment locations sit in the routing configuration; three are integrated directly with Hantera_, across two WMS platforms.

The payoff

Three months. 112 hours. On the original date.

They said 9 June. It went live 9 June. Here is what that took, next to what the industry publishes for the same kind of work.

Months to go-live

Didriksons vs. published ERP programme timelines
Didriksons3 months
Single-site, extended modules912 mo
Multi-site international1524 mo
Didriksons' scope maps to the bottom band. Scale: 0–24 months.

ERP integration hours

Integration development line item only
Didriksons — ERP integration37.25 h
Didriksons — entire backend scope112.25 h
Industry benchmark200600 h
Bars are to scale. Our whole backend scope sits under the low end of the benchmark for integration work alone.

Where the 112.25 hours went

37.25 h ERP integration
15.75 h Checkout, delivery & payments
13.25 h Discovery, workshops & training
12.25 h Launch & aftercare
12.00 h Pricing, products & promotions
10.25 h Customer, CRM & marketing
8.50 h Inventory & stock
3.00 h Order lifecycle & environments

12% of the project was meetings. We publish that because it's true.

Training & change management

The line item nobody expects to be this small
Didriksons~24 h
Benchmark — training + change mgmt200700 h
We trained a handful of super users; Didriksons handled the rest internally. The best training budget is the one you don't need to spend.

About these numbers

  • 18 of the 37.25 ERP integration hours were logged in January–February, before the official window, to align with Didriksons' Business Central upgrade. We count them anyway.
  • We integrated Business Central. We did not implement it. The comparison is integration line item to integration line item.
  • Didriksons is multi-market — 8 markets, 5 currencies, 4 fulfilment locations — which maps to the upper end of the source's complexity bands. We compare against the conservative band regardless.
  • Our training figure is approximate and mostly unlogged, because we don't charge for training.
  • See exactly how we built the benchmark →
10 March → 9 June 2026

Week by week.

March
Kickoff and workshops
Mar–Apr
ERP integration, price imports
April
Delivery, tracking, order flow
May
Stock, checkout, payments
Late May
Promotions, production setup
9 June
Go-live
In their words

Two teams, two different reasons to care.

Marketing
“We've consistently gotten the right solution at the right time, with high quality. Now we have a campaign engine where we own pricing and campaign speed ourselves, which has completely changed how fast and agile we can move.”
David Roy, E-commerce Manager · Didriksons
Operations
“Hantera_ brings our systems into one platform so the team can find information and support customers faster. The cooperation is easy and forward-thinking. Things we want to implement are taken care of quickly, always with the full scope in mind.”
Kilian Leister, Head of Sales Operations · Didriksons
Why it was possible

Four things that made the timeline real.

/ 01

Apps are versioned units

Integrations ship as installable apps with their own release cadence. There is no shared release window to queue behind — which is what turned three to six weeks into the same day.

/ 02

Components are small and declarative

Rules, jobs and promotions are Filtrera components. A new promotion type is a file, not a project — declarative enough that a model can write most of it from the specification.

/ 03

One source of price and cart truth

The storefront asks Hantera_ for prices and never computes them. One authority means nothing to reconcile, and nothing to disagree about.

/ 04

The portal is extensible

Operational tooling — payment actions, debug views, delivery toolbars — lives inside the order view, where the work actually happens.

Your replatform has a date.

Tell us the scope and the deadline. We'll tell you honestly whether it's realistic — and what we'd do differently.