Delivery and Field-Service Platform: orders, couriers, technicians and stock in one system

Between May 2025 and February 2026 I built a platform for a business that sells products which need delivery and on-site installation. A customer orders and pays, the warehouse allocates serial-numbered stock, a courier delivers, a technician installs, and the back office follows the money and the stock at every step. I wrote the large majority of the code; one other developer contributed reports and smaller features.

Context

Delivery with installation involves more people than an ordinary online order. Customers place and track requests, call-centre staff take orders by phone, warehouse staff receive and issue stock, couriers carry the goods, and technicians install them, often on a later day. Finance pays couriers and technicians and handles refunds. Each group needs a different view of the same order.

The engineering problem

  • One order, many endings. A request moves from awaiting payment to assigned, awaiting courier confirmation, with the courier, delivered and installed. It can also be cancelled, fail at delivery or installation, find the customer away for either visit, or come back as a return. Each of those states has consequences for stock, money and notifications.
  • Money follows the order. Online payment, coupons and surcharges, refunds, and what each courier and technician has earned all hang off the same request.
  • Stock is physical. Items carry serial numbers, are allocated when a request is paid, and have their serial numbers captured at delivery. Delivery time slots have a capacity.
  • Field staff work from phones, in traffic. Couriers and technicians need short, clear APIs and sensible routes.

What I built

  • Back office. A DevExpress XAF Blazor application on XPO and SQL Server, with a Persian user interface: views for every business object, role-based permissions, dashboards per role, a delivery tracking map, a dispatch console, and reports such as daily operations, the daily finance summary, settlements due, failed payments and customer RFM analysis. XAF's audit trail records data changes.
  • Role-based Web APIs. About 90 ASP.NET Core endpoints for mobile and web clients, grouped by role: customer (requests for products and their services, delivery time slots, rescheduling, payment, invoices, returns, technician ratings), courier (daily, past and upcoming jobs, delivery with serial-number capture, customer not present, route optimisation, earnings), technician (accepting or rejecting assignments, completing jobs with photos, service areas, earnings), warehouse (receipts and inventory) and call centre (customers, requests and status changes).
  • Sign-in. Users register and sign in with a mobile number and a one-time code (couriers use a password) and receive a JWT. Each role endpoint works only with the courier, technician or customer record that the token's user name points to.
  • Technician auto-assignment. A hosted service runs every night. It takes delivered requests that have no installer yet, ordered by installation priority and then age, and scores every active technician on distance (haversine), the request's age and priority, the technician's rating and current workload. The best three candidates for each request get an assignment they can accept or reject, and customers are informed by SMS.
  • Payments. One provider interface over three Iranian payment gateways. The service checks whether the chosen gateway is available and falls back to another if not; HTTP calls to the gateways retry through Polly policies.
  • Maps. Reverse geocoding, distance with traffic, and multi-stop route optimisation through an Iranian maps API.
  • Notifications. One-time codes and templated SMS messages, recorded in a separate notification database through EF Core.

Design notes

Why XAF for the back office

The back office has dozens of business objects, and each needs lists, forms, filters, permissions and often a report. XAF generates those from the data model, so most of the effort could go into the workflows and the APIs instead of screens. The cost is that the framework's conventions shape the code, and business logic that lives inside persistent objects is harder to unit test.

Keeping the request entity manageable

The request is the centre of the model, so it grew quickly. I split it into partial classes by concern (entity, pricing, financial, state and persistence, timestamps) and moved inventory allocation into a separate domain service. Status changes are handled in one place, which records the timestamp, updates the status of the allocated stock items and sends the notification for that transition. A capacity check on the delivery slot runs when a paid request first moves on.

A nightly batch for assignment

Assignment runs as a batch at night rather than as each request arrives. A batch can compare all open requests with all technicians at once, and the result is ready before the working day starts. The weights make the trade-offs explicit: priority counts most, then the request's age, then rating, workload and distance. The cost is that the batch does not react to changes during the day.

Gateways fail

Payment gateways are outside the system's control and are sometimes unavailable. Hiding them behind one interface made a fallback possible: if the chosen gateway is down, the payment starts on another one, and verification goes to the gateway recorded for that transaction.

Testing, and the next step

Automated tests are the thin part of this codebase. The solution has one end-to-end smoke test, written with DevExpress EasyTest over Selenium, that starts the back office on a fresh database, signs in and opens the main navigation items.

In January 2026 I started a modular redesign. I wrote a module map that splits the platform into bounded contexts (customer, courier, technician, warehouse, pickup, store, administration, finance, identity, messaging, pricing rules, maps, support, risk and analytics) and created a clean-architecture skeleton for each, with API, application, domain and EF Core infrastructure projects. The customer module then moved to its own repository, where another developer extended it with the cart, orders and service requests. This is the start of a migration, not a finished one.

Stack

  • .NET 8, ASP.NET Core Web API, JWT
  • DevExpress XAF 25.1: Blazor UI, XPO, Web API, reports, dashboards, audit trail
  • SQL Server, EF Core for the notification store
  • Polly, Swagger
  • xUnit, DevExpress EasyTest, Selenium

Status

  • A platform that covers ordering, payment, stock, delivery, installation, settlements and reporting, with a back office and role-based APIs for five kinds of users.
  • The main repository's last commit is from February 2026. The modular redesign exists as documents and project skeletons, with the customer module furthest along.

Related

Field-service work orders also appear in the multi-tenant microservice platform, which is built as separate services from the start. If you need a back office or a field-service system on .NET, see Work with me.

Frequently asked questions

What does the delivery and field-service platform do?

It runs orders for products that need delivery and on-site installation. Customers order and pay, the warehouse allocates serial-numbered stock, a courier delivers, a technician installs, and the back office tracks payments, refunds, settlements and reports.

How are technicians assigned to jobs?

A background service runs every night. It ranks delivered requests that still need an installer by priority and age, scores each active technician on distance, the request's age and priority, the technician's rating and current workload, and offers the job to the best three candidates, who can accept or reject it.

Which technologies does the platform use?

It is built on .NET 8 with a DevExpress XAF Blazor back office on XPO and SQL Server, ASP.NET Core Web APIs secured with JWT, EF Core for the notification store, and Polly for retries to payment gateways.

What was Mahdi Molavi's role?

He built the platform and wrote the large majority of its code between May 2025 and February 2026. One other developer contributed reports and smaller features.

More in this section

Projects