Construction ERP for a Mid-Sized German Bau Contractor
Client Context
Two ERPs. Both cancelled in pilot. That was the first thing the Head of Operations told us on the discovery call — before he'd asked a single question about our process. He wasn't hostile. He was tired. A Hamburg-based general contractor, ~180 staff, running 14–18 active sites across Northern Germany. After two failed ERP rollouts the site managers had gone back to spreadsheets on a shared drive. The first ERP had correct modules but wrong workflow assumptions — it had been designed for large contractors; site managers were expected to open a laptop and navigate a 12-tab form to log a delivery. The second had been built for manufacturing and retrofitted. In both cases site managers stopped using it within 6 weeks. 'If you want to understand why,' he said, 'you need to spend a week on a site.' We did. What we found wasn't a UI problem. It was a fundamental mismatch: both systems treated the site manager as an office worker who happened to visit sites. The actual job is 80% outside, hands dirty, managing three parallel conversations at once. Any system that requires both hands and a stable network is dead on arrival.
The Challenge
Business Challenge
Cost overruns averaged 6–9% per project, discovered only at month-end consolidation — by which point it was too late to act. Subcontractor invoice approvals averaged 5.2 working days, delaying payment and damaging subcontractor relationships. Quarterly bank forecasting required weekend overtime from the controller team every quarter. Two failed ERP attempts had created deep organizational scepticism: site managers would game any system they didn't believe in.
Technical Challenge
14+ different spreadsheet templates in active use across 18 sites, with no shared schema. DATEV accounting maintained in parallel with no automated reconciliation path. The specific technical constraints the previous ERPs had missed: (1) sites often have 30-minute connectivity gaps — any system requiring a live connection loses data; (2) the same subcontractor invoice can require approval from the site manager, the controller, and the MD depending on amount — the chain is not static; (3) DATEV export must map to the client's specific chart of accounts, not a generic construction template.
Signals Before We Started
Average 5.2 working days from subcontractor invoice receipt to approval
6–9% cost overruns detected at month-end — too late to intervene on active projects
~€140K of unnecessary material stock at peak from duplicated cross-site orders
Site managers spending ~6h/week on reporting (14% of productive time)
Two prior ERP rollouts cancelled in pilot due to adoption failure — strong institutional distrust
No structured DATEV reconciliation path; controllers reconciling manually every quarter
Our Solution
Overview
We built a construction ERP from first principles, starting with the site manager's daily flow rather than the accounting system's needs. The key architectural decisions: (1) offline-first with IndexedDB as the local write-ahead log, syncing opportunistically — site managers don't notice the system when connectivity is good, and don't lose data when it isn't; (2) modular monolith rather than microservices — a 180-person company doesn't benefit from the operational overhead of distributed systems, and a monolith is far easier to deploy on Azure App Service without an SRE team; (3) amount-based dynamic approval chains — the chain is evaluated at runtime from a configuration table, not hardcoded; (4) event-sourced cost projection — every domain event (delivery confirmed, invoice approved, labor logged) is immediately projected onto the cost ledger, giving the MD near-real-time budget visibility without waiting for accounting reconciliation. The DATEV adapter was the last mile: we mapped the client's existing chart of accounts into the export rather than asking them to adopt a generic construction COA.
Architecture
.NET 8 modular monolith deployed on Azure App Service in the Germany region. Why modular monolith and not microservices? Three reasons: (1) the team that would maintain this is 2 developers, not 8 — microservices would create operational complexity they can't sustain; (2) the approval and cost-controlling domains are tightly coupled through shared project state — message-passing between services adds latency and failure modes for no gain; (3) Azure App Service with deployment slots handles blue-green deploys at a fraction of the cost and complexity of AKS. PostgreSQL 15 with row-level partitioning per project (each project is its own logical partition — queries never cross project boundaries by default, and purging old projects is a partition drop). Angular 17 web app with a dedicated gesture-first mobile view for site managers (separate route, separate component tree — not responsive, purpose-built). Service Worker + IndexedDB for offline capture. Azure AD with role-based access: site manager, controller, MD, tax advisor (read-only). DATEV adapter as a scheduled job: nightly projection of the prior day's approved transactions into the client's chart of accounts.
Approach
- 1
Two weeks shadowing three site managers and one controller before any wireframe or architecture decision
- 2
Offline-first from day one: IndexedDB write-ahead log, sync on reconnect, explicit conflict resolution
- 3
Amount-based dynamic approval chains: evaluated at runtime from configuration, not hardcoded
- 4
Event-sourced cost ledger: every domain event immediately projected onto budget vs actual
- 5
Separate mobile UX (gesture-first capture) and desktop UX (rich controllers view) — not responsive
- 6
DATEV adapter mapped to client's existing chart of accounts (not a generic construction template)
- 7
Per-site feature flags for gradual rollout — lessons from each site applied before the next
Platform Modules
The system was delivered as the following modules — each with its own owner, integration contract and rollout plan.
Offline-First Capture
Service Worker + IndexedDB write-ahead log on the mobile view. Every tap writes locally first; sync to the API happens opportunistically on reconnect. Conflict detection uses a vector-clock approach: if two offline writes target the same entity, the system flags the conflict in the audit log rather than silently choosing — the site manager sees a one-tap resolution screen on next open.
Dynamic Approval Chains
Multi-step approval chains evaluated at runtime from a configuration table: amount threshold, approver role, delegation rule, escalation timer. The same invoice routes through 1, 2, or 3 approvers depending on its amount and category. Hardcoding these chains was how the previous ERP failed — any org-change required a developer.
Event-Sourced Cost Ledger
Every domain event (delivery confirmed, invoice approved, labor logged, PO issued) is immediately projected onto a denormalized cost ledger table. Budget vs actual is visible within seconds of an approval, not the next morning. The ledger is append-only; corrections are new events, not updates.
Cross-Site Material Catalog
Shared material catalog with per-site inventory positions. Duplicate-order detection runs at purchase-order creation: if the same material is already on order at another site within the same week, the site manager sees a warning and the cross-site inventory balance before confirming.
DATEV Adapter
Nightly projection of the prior day's approved transactions into the client's specific DATEV chart of accounts. The mapping is held in a configuration table (account code → cost domain category) certified by the client's tax advisor. The adapter produces DATEV XML and a human-readable reconciliation report.
Executive Dashboard
Near-real-time budget vs actual at site, project, and company level — powered by the event-sourced cost ledger. The dashboard was deliberately withheld until week 18 when data quality had stabilized. Showing unreliable numbers to a managing director earlier would have eroded trust before it was earned.
Data Flow
Site managers open the mobile app (Angular PWA) and capture material deliveries, labor hours, and equipment use. Every tap writes to a local IndexedDB queue first — the UI is optimistic and never blocks on network. When connectivity returns, the Service Worker drains the queue to the ERP API in order. The API writes each event to PostgreSQL and emits a domain event (e.g., `delivery.confirmed`). The Cost Controlling service subscribes to every cost-affecting domain event and updates the denormalized cost ledger in the same database transaction — no eventual consistency, no separate projection lag. The Approval service evaluates the dynamic chain for each invoice, emits notification events (push via Microsoft 365, email as fallback), and records each approval step in the append-only audit log. Nightly at 01:00, the DATEV Adapter reads all approved transactions since the last run, projects them into the client's chart of accounts, and writes a DATEV XML export and reconciliation CSV to a secure Azure File Share accessible to the tax advisor.
Integrations
DATEV (CSV + DATEV XML export, mapped to client's existing chart of accounts)
Microsoft 365 — SSO via Azure AD; push + email approval notifications
Time-clock terminals (legacy, 12 sites) — CSV import on a 30-minute schedule
PDF generation for purchase orders and approval evidence bundles
Delivery Timeline
Phased delivery — each phase had explicit goals, measurable outcomes and a checkpoint before progression.
Phase 1 — Site shadowing & discovery
Week 1–3Goals- ·Shadow three site managers (residential, commercial, fit-out) through a full week each
- ·Shadow the controller through a full month-end close
- ·Inventory every spreadsheet template, approval chain, and failure mode
Outcomes- ✓Two non-negotiables surfaced: must work offline on a phone; must export to DATEV in the client's existing chart of accounts
- ✓Documented 11 approval chains and 14 spreadsheet templates across 18 sites
- ✓Root cause of the two prior ERP failures confirmed: both assumed live connectivity and a desktop-first workflow
- ✓Pilot site selected: a mid-rise residential project starting week 6
Phase 2 — Core domain & offline capture
Week 4–10Goals- ·Project, Site, Subcontractor, Material, Labor, Approval domain model in code
- ·Offline-first mobile capture: IndexedDB write-ahead log, optimistic sync, conflict resolution
- ·Amount-based dynamic approval workflow with delegation and escalation timers
Outcomes- ✓Pilot site live week 10 with material delivery + labor capture
- ✓Approval lead time on pilot site: 5.2d → 2.4d within 3 weeks
- ✓First production incident in week 11: two foremen logged the same delivery simultaneously (offline); resolved with a last-write-wins policy with mandatory audit flag — the right call for this domain
Phase 3 — Procurement & cost controlling
Week 9–18Goals- ·Cross-site material catalog with duplicate-order detection
- ·Event-sourced cost ledger: budget vs actual projected from domain events in near-real-time
- ·Executive dashboard for the MD (weekly, then daily, then live)
Outcomes- ✓€140K of duplicated material orders detected in the first month after cross-site visibility went live
- ✓Cost overrun detection: month-end → weekly → near-real-time as data quality improved per site
- ✓MD adopted the dashboard as the standard Friday review artefact by week 16; the previous Friday report was a 2-hour manual exercise
Phase 4 — DATEV integration
Week 15–22Goals- ·Map the client's existing DATEV chart of accounts into the cost domain model
- ·Build the nightly export adapter; run parallel with existing accounting for 6 weeks
- ·Certify the export with the client's tax advisor
Outcomes- ✓DATEV export reconciled to the cent across 6 weeks of parallel run
- ✓Controller team weekend overtime eliminated by week 22 — the previous quarterly close required 3 weekends
- ✓Tax advisor certified the export format; no adjustments required at the following quarterly submission
Phase 5 — Site-by-site rollout
Week 18–32Goals- ·Roll out 2 sites per fortnight using per-site feature flags
- ·Apply learnings from each site before the next; track bug-category emergence rate
- ·Decommission spreadsheet templates; certify backup site managers
Outcomes- ✓All 18 active sites migrated by week 30; new bug categories stopped appearing after site 6
- ✓Approval lead time stabilized at 1.4 working days company-wide
- ✓First two projects fully delivered on the new ERP: cost overruns 6–9% → 2–4%
Technology Stack
The Results
Measurable impact delivered within 8 months (Phase 1–5).
Security & Compliance
- ✓All data hosted in Azure Germany region (Germany West Central); no cross-region replication
- ✓GDPR: site worker personal data minimized; payroll-relevant data retained only for legal retention period
- ✓Role-based access aligned to position: site manager, controller, MD, tax advisor (read-only), biomedical engineer
- ✓Full append-only audit trail of every approval, cost-affecting change, and DATEV export
- ✓Encrypted backups with 30-day point-in-time restore; quarterly restore drills
- ✓Azure AD Conditional Access: MFA required for controller and MD roles
Delivery & Operations
- ✓GitHub Actions CI: domain model unit tests, DATEV export reconciliation tests (golden-file comparison), offline-sync replay tests
- ✓Azure App Service deployment slots (staging → swap to production); per-site feature flags for gradual rollout
- ✓Staging environment refreshed nightly from anonymized production data
- ✓Per-site rollout retrospective after each wave; bug-category tracking by site number
- ✓Weekly office hours with site managers for the first 90 days post-rollout
- ✓Documented runbooks: sync-conflict resolution, DATEV export failure, approval-chain misconfiguration
Key Learnings
Two weeks on site before the first wireframe was the single highest-ROI activity of the entire project. It surfaced the offline and DATEV non-negotiables that had killed the two previous ERP attempts — and would have killed ours too.
Offline-first is a different programming model, not just a caching layer. You need a local write-ahead log, a deterministic sync protocol, and an explicit conflict-resolution policy before you write a single business rule. Every conflict-resolution case must be a first-class UX decision, not a technical fallback.
A modular monolith deployed on App Service is the correct architecture for a 180-person company with a 2-developer maintenance team. The operational overhead of AKS + Kafka would have exceeded the maintenance budget within 18 months.
Cost dashboards only become trustworthy once data quality crosses a threshold. Showing early, unreliable numbers to a managing director erodes trust before it's earned. Delaying the dashboard until week 18 was the right call — but it needed to be communicated in writing from the start.
What We'd Do Differently
Every project teaches us something we hadn't anticipated. Here's what we'd change if we were starting again.
We built the approval engine to support any configurable chain — any number of steps, any threshold, any delegation rule. In production, Bau Nord used exactly 3 of the 11 chain types we modelled. We spent 6 weeks on flexibility that wasn't needed. Ship the top 3 patterns first; build configurability as an explicit phase 2 only if demand materializes.
We let the executive dashboard slip to week 18 intentionally (the data quality wasn't ready), but didn't put that decision in writing at the start of phase 3. The MD asked twice about consolidated numbers. We explained the data quality reasoning verbally each time. We should have set the launch criteria in a written document at the phase 3 kickoff — 'dashboard ships when X% of sites have 4 weeks of clean data' — so the delay was a predictable milestone, not an unexplained absence.
Similar Projects
Other high-stakes systems we've shipped.
EU Auto Parts Group
Auto Parts Data Platform for a European B2B Marketplace
−45% Wrong-fit returns
Regional supermarket chain (APAC)
Inventory & Promotion Platform for a 120-Store Supermarket Chain
3 days → ~2h Promotion go-live time
Let's Discuss Your Project
Schedule a free consultation to explore how we can help you achieve your goals.