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.
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
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.
Why was this order charged that price?
Answering it 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.
Can we run this promotion by Friday?
Not if the discount logic didn't already exist. That meant a development ticket, then a sprint, then a release window — so the idea never got proposed in the first place.
What did that campaign actually earn?
The coupon, the order it was used on and the refund that came later lived in three separate systems. Revenue was countable. What survived the returns was a manual export, so nobody ran it.
Has this payment been refunded?
Payment calls failed, so agents refunded in the PayPal or Kustom dashboard instead. The money moved; the order didn't know. Two systems, two answers.
Why does publishing a page cost so much?
Slow and expensive to get anything on the site. Solved by the new headless CMS.
Not our workWhy is the site slow?
A real complaint, and a real fix — but it belongs to the rebuilt storefront.
Not our workTwo 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.
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
Hantera_
Surfaces
One order record. Every system writes to it; every team reads from it.
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.
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 thisDerived 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 thisPromotions
Cart-level logic: targeting, spend thresholds in each currency, and the “add €20 more” message that comes with it.
Marketing owns thisThen 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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
This isn't about having fewer tabs open. It's that the systems can no longer disagree about whether money moved.
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.
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
ERP integration hours
Where the 112.25 hours went
12% of the project was meetings. We publish that because it's true.
Training & change management
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 →
Week by week.
Two teams, two different reasons to care.
“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.”
“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.”
Four things that made the timeline real.
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.
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.
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.
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.