A Concur expenses supplier updater for Unit 4 ERPx

HawkBS · Writing

Managing corporate expenses across a large organisation involves multiple systems working in concert. When employees submit travel and entertainment expenses through SAP Concur, those transactions must ultimately arrive in the company's ERP for accounting, reporting, and payment processing. A critical prerequisite sits in the middle of that workflow: every employee who submits an expense report must already exist as a supplier inside Unit 4 ERPx before their expense data can be imported. Without that supplier record, the financial transaction cannot be posted — and the import fails, delaying reimbursements and disrupting month-end close.

The Concur Expenses Supplier Updater is a .NET 8 microservice that solves this dependency problem. It runs as a stateless batch process, deployed as a containerised application on AWS ECS, that extracts expense data from SAP Concur's API, identifies employees who do not yet exist as suppliers in Unit 4 ERPx, and automatically creates those supplier records before the expense data is written to shared storage for downstream processing. The finance pipeline never encounters a missing supplier reference when importing Concur expense transactions into the accounting ledger.

Business context

Haven operates a multi-entity structure with several companies registered within Unit 4 ERPx, including PUK (Haven UK), WUK, and GUK. When employees travel or incur business expenses, they record those through SAP Concur. The finance team periodically extracts the expense reports and posts them as registered incoming invoices within ERPx, where each expense line references a supplier identifier corresponding to the employee who incurred the cost.

The fundamental challenge is that employees exist in Concur as expense submitters but not necessarily as suppliers within ERPx. New starters, transferred employees, or individuals submitting expenses for the first time will not have a corresponding supplier record. If the expense import proceeds without first verifying and creating these supplier records, the ERPx API rejects the transactions, creating a bottleneck that requires manual intervention. The Supplier Updater removes that bottleneck through automated pre-validation and creation.

Architecture and stack

The service is built on .NET 8, the long-term support framework release, using modern C# language features including primary constructors, required members, and implicit usings. The application uses the Generic Host model via Host.CreateApplicationBuilder, giving a consistent approach to dependency injection, configuration, and hosted service lifecycle management.

The solution is structured across three projects. The main executable, erp.Concur.Extract, contains the orchestration logic, Concur API integration services, and the hosted service implementation. ErpxSupplierClient is a dedicated HTTP client library encapsulating all communication with the Unit 4 ERPx Supplier API — OAuth2 authentication, token caching, and CRUD operations against the supplier endpoint. erp.Extract.Common provides shared configuration models for file storage abstraction, allowing the service to write output to local disk during development or AWS S3 in production.

Key dependencies: Polly for transient fault handling with exponential backoff on HTTP calls; Serilog for structured logging with sinks targeting both console output and Datadog; the AWS SDK for S3 and security token management; the Concur Platform SDK for the base HTTP layer against SAP Concur's REST APIs; and an internal Haven.Shared.Email package for notification dispatch through an internal email queue.

Data flow and processing pipeline

The service executes as a single-run batch process — it starts up, performs its work, and shuts down. The processing pipeline follows a strict sequential flow to ensure suppliers exist before expense data is persisted for downstream consumption.

The process begins with OAuth2 authentication against SAP Concur's token endpoint, using a client-credentials flow with a refresh token to obtain a time-limited access token. With authentication complete, the service queries Concur's Extract Definition API to locate the configured expense extract definition for the target environment, then either retrieves a previously generated extract by job identifier or initiates a new extract generation — which can involve polling the Concur server for up to several hours while the extract compiles.

Once the expense extract data is retrieved as a pipe-delimited text payload, the service parses individual expense lines to identify unique employees. Each line carries employee data at specific field positions: the employee identifier at index 4, last name at index 5, first name at index 6, site code at index 10, and email address at index 264. The service extracts these fields and assembles a list of employee records requiring supplier verification.

The critical supplier verification phase iterates through each identified employee. For each one, the service calls the Unit 4 ERPx Supplier API to determine whether a supplier record with that employee identifier already exists within the configured company. A 404 means the employee needs a new supplier record. A 200 with supplier data means no action is needed. An HTTP error during the check causes the service to conservatively skip that employee to prevent duplicate creation, logging a warning for operational review.

Supplier creation logic

When a missing supplier is identified, the service constructs a complete ERPx supplier record from the employee data. The supplier identifier mirrors the Concur employee ID, establishing a direct cross-reference between the systems. The supplier name combines the employee's first and last names. An alias name is generated from the first seven characters of the surname plus the first two characters of the first name, constrained to a maximum of ten characters as required by ERPx.

The supplier record carries default values appropriate for employee expense suppliers: a supplier group of "EXP" (Expenses), country code "GB", language code "EN", and a default bank account placeholder. Invoice settings specify GBP currency, an "IP" (Internal Payment) payment method, and "N01" payment terms. The contact point address is derived from the employee's site code, mapped through a configurable site dictionary to resolve the physical office location and postcode.

Before submission to ERPx, the supplier record passes through a validation layer that verifies required fields are populated, the alias name fits within the character limit, email addresses are well-formed, street addresses respect the 160-character maximum, and payment details are complete. Any validation failure is logged and the service continues to the next employee — one invalid record does not halt processing for the rest.

The validated supplier is serialised to JSON using camelCase naming and posted to the ERPx Supplier API with an OAuth2 bearer token. The ERPx client manages its own authentication lifecycle, obtaining tokens from the ERPx identity provider using client credentials and caching them with a thirty-second safety buffer before expiry.

Notification and observability

On successful supplier creation, the service dispatches an email notification to the finance team via an internal email queue. The notification includes the employee ID, full name, site code, site name, alias, and creation timestamp in a formatted HTML table. This gives the finance team immediate visibility of new supplier records created during each execution, enabling them to verify the records and update bank details or other fields that cannot be automatically populated from Concur data.

The service distinguishes between production and non-production environments in notification subjects, prepending "TEST:" to emails generated outside production. Notification failures are handled gracefully with error logging but do not halt the main processing pipeline.

Structured logging through Serilog captures the entire execution lifecycle — from configuration dump at startup, through each supplier check and creation, to final completion status. Datadog integration provides real-time dashboards and alerting for operational monitoring.

Deployment and orchestration

The service is containerised using Docker with a Linux target and deployed to AWS Elastic Container Service. Deployment follows a tag-based CI/CD pattern: semantic version tags trigger GitHub Actions workflows that build, test, publish, and push container images to AWS Elastic Container Registry in eu-west-1.

AWS Step Functions orchestrate the service's execution within the broader finance pipeline, passing runtime parameters including execution identifiers for log correlation and date ranges for extract targeting. AWS EventBridge schedules trigger the Step Function at configured intervals, ensuring regular expense data extraction without manual intervention. The Step Function orchestration guarantees the Concur extract service completes, including all supplier creation, before any downstream service attempts to import the expense data into ERPx as financial transactions.

Resilience and error handling

The service uses multiple resilience patterns. HTTP clients for both Concur and ERPx APIs are configured with Polly retry policies that handle transient HTTP errors and 404 responses with exponential backoff across two retry attempts. Token caching with thread-safe semaphore locking prevents redundant authentication calls during concurrent operations. The three-state supplier existence check (exists, does not exist, or unknown due to error) ensures the service never creates duplicate suppliers when API communication is degraded. Petty cash lines identified by employee IDs starting with "9999" are automatically excluded from supplier processing, avoiding unnecessary API calls for system-generated expense entries.

Why this shape of integration matters

The Supplier Updater is a focused piece of integration plumbing that bridges expense management and enterprise accounting. By ensuring supplier records exist before expense data progresses through the finance pipeline, it eliminates a whole class of import failures that would otherwise require manual intervention, shortens the path to employee reimbursement, and maintains data integrity across the Haven financial technology estate.

This is the kind of work that doesn't make slide decks but quietly removes hours from someone's week, every week. .NET 8, containerised, observable, and orchestrated by the same Step Functions that drive the rest of the finance pipeline — the boring infrastructure that keeps month-end close on schedule.

← Back to Writing · See the Consultancy engagements →