Back to Portfolio
Case Study · Freelance Project

Industrial Warehouse &
Operations Portal

A full-stack, production-grade management platform for industrial warehouse operations, delivered solo from requirements gathering to on-site deployment on a client touch-screen kiosk.

2024–2025 Solo Developer C# · ASP.NET Core · SQL Server Docker · SQL Server 2022 View on GitHub

The Problem & The Solution

An industrial warehouse client needed to replace a manual, paper-based system for tracking maintenance tasks and inventory. The platform runs on-premise on a touch-screen kiosk in the warehouse floor, used daily by operators and supervisors.

Problem
  • No centralized system for maintenance task scheduling
  • Inventory tracked on paper — no critical stock alerts
  • No visibility into who did what and when
  • No recurring task support for periodic maintenance
  • Admin and operators needed separate access levels
Solution
  • Centralized web portal with real-time task tracking
  • Inventory module with configurable critical stock thresholds
  • Full audit trail — assigned operators, dates, status changes
  • Recurring task engine (daily / weekly / custom intervals)
  • Cookie-based auth with pin-protected admin actions

Interface Walkthrough

The interface was designed for warehouse floor use — large targets, clear status colours, and pin-gated actions to prevent accidental edits by operators.

Dashboard 4 screens
Task Management 3 screens
Warehouse Inventory 2 screens
Calendar & Touch UX 2 screens
Configuration 3 screens

Architecture & Technology Decisions

The application follows a strict layered architecture deployed entirely on-premise inside Docker containers. Every technology choice was driven by the on-premise kiosk constraint and client familiarity requirements.

🏭 On-Premise · Client's Machine
Kiosk on the warehouse floor — touch-screen browser, no keyboard
📱
Touch Screen
Warehouse Kiosk
Razor Pages chosen over SPA: server-rendering is more reliable on-premise with no CDN
🖥️
Razor Pages
ASP.NET Core · Virtual Keyboard
Business Logic
5 scoped DI services — all business logic isolated from pages; no logic in .cshtml.cs
⚙️
Service Layer
InterventiSvc · MagazzinoSvc
CalendarSvc · AdminSvc
Auth API
Custom cookie auth instead of ASP.NET Identity — operators don't need email/registration; full control over session logic
🔐
Custom Auth
Cookie-based · SHA256
Background
Nightly raw SQL batch update — EF Core would add unnecessary overhead for a single-table status update job
Background Job
Nightly · 00:01 · Raw SQL
EF Core provides type-safe CRUD. 2-step query pattern avoids cartesian explosion with multiple .Include()
🗂️
Entity Framework Core 8
12 entities · Transactions · DTOs
SQL Server 2022: relational structure fits the task/inventory domain perfectly; client already had SQL Server
🗄️
SQL Server 2022
Containerized
Docker Compose: portable deployment — entire stack runs with one command on any Windows/Linux machine
🐳
Docker Compose
App + DB + Volumes

Hover each node to see the design rationale

Why These Choices?

🖥️ Razor Pages over SPA Key Decision

The app runs on a kiosk with no internet. Razor Pages serve complete HTML from the server — no CDN, no JS bundle loading issues, no hydration problems. Simpler mental model for a CRUD-heavy workflow. The only JS needed is the virtual keyboard for touch input.

🔐 Custom Auth (No Identity) Key Decision

ASP.NET Identity brings email verification, password resets, and claim tables — all useless for warehouse operators who are assigned by an admin. Custom cookie auth with SHA256 + timing-safe comparison gives full control in ~100 lines of code.

🐳 Docker Compose Key Decision

The client has limited IT expertise. Docker means the entire stack — ASP.NET Core app + SQL Server 2022 — deploys with docker compose up. No manual SQL Server installation. Works identically on Windows or Linux. Health checks ensure the DB is ready before the app starts.

📦 Service + DTO Pattern Architecture

Pages call services, services return DTOs — never EF entities directly. This means the UI is decoupled from the database schema. Adding a computed property (like "next occurrence for recurring task") happens in the service, not scattered across page handlers.

Raw SQL for Background Job Intentional Tradeoff

The nightly job marks hundreds of overdue tasks as expired. Loading each entity through EF Core, checking the condition, and saving back would generate N round-trips. A single bulk UPDATE does it in one query, with no ORM overhead.

🌳 TaxonomyNode Self-Reference Architecture

Categories needed unlimited depth for future growth. A self-referencing table avoids separate tables per category level. Descendant traversal is done in-memory after loading the full tree once — avoids recursive SQL CTEs that are harder to maintain.

Problems Solved

Three non-trivial engineering problems encountered during development and how they were resolved.

CHALLENGE 01

Recurring Task Expansion & "Imminent" Filtering

Problem

Tasks could repeat daily, on specific weekdays, or at custom intervals. The "imminent tasks" filter needed to show only tasks due in the next N hours — including the next occurrence of any recurring task — without storing each occurrence in the database.

Solution

Recurring tasks are stored as a single parent row with a linked InterventoRipetizione record. At query time, InterventiService calculates the next occurrence in-memory using Italian day-of-week parsing, then applies the imminent window filter before returning paged results.

CHALLENGE 02

Hierarchical Taxonomy Filtering with Descendant Traversal

Problem

The inventory category system is a multi-level tree (TaxonomyNode with self-referencing ParentId). Filtering products by a parent category needed to include all descendants, not just direct children — without recursive SQL.

Solution

MagazzinoService loads the full taxonomy tree once, then recursively collects all descendant node IDs in-memory. The product query then uses an IN clause against this pre-computed ID set. This avoids N+1 and complex recursive CTEs while keeping the query simple.

CHALLENGE 03

Cartesian Explosion in Paginated Queries with Multiple Includes

Problem

Products have multiple many-to-many relationships (brands, suppliers, machinery, images). Loading all of them in a single EF Core query with .Include() produced cartesian explosions — the same product row multiplied across all related rows, breaking pagination count.

Solution

Adopted a 2-step query pattern: first fetch the paginated product IDs (simple, fast), then load the full object graph for only those IDs in a second query. This eliminates cartesian explosion and keeps pagination accurate while still loading all related data.

Docker-Based On-Premise Deployment

The application runs entirely on the client's local machine with no cloud dependency. A Docker Compose stack manages both the ASP.NET Core web app and SQL Server 2022, with health checks ensuring the database is ready before the app starts.

services:
  sqlserver:
    image: mcr.microsoft.com/mssql/server:2022-latest
    healthcheck: # TCP check on 1433
    volumes: ./sqlbackup → /var/opt/mssql/backup

  webapp:
    build: Dockerfile # multi-stage: SDK build → Runtime image
    ports: 8080:8080
    depends_on: sqlserver (condition: healthy)
    environment: ConnectionStrings__DefaultConnection

Delivery & Impact

Shipped and deployed to a real warehouse client — from first requirement meeting to running on a touch-screen kiosk on the factory floor.

📦
12
Database entities
⚙️
5
Business services
📄
23
Razor pages
🐳
2
Docker services
🚀
v1.2
Stable release shipped
🤝
Solo
End-to-end delivery

What the Client Said

Feedback received after delivery — from initial requirements gathering to running in production on the warehouse floor.

Ho avuto il piacere di collaborare con Marco per lo sviluppo di un software gestionale dedicato alla mia azienda e il risultato ha superato le aspettative. Marco non solo ha realizzato il prodotto rispettando al millimetro tutte le caratteristiche e le funzionalità che gli avevo richiesto, ma ha anche dimostrato una disponibilità eccezionale. Ha accolto ogni richiesta di confronto con prontezza e professionalità, rendendo l'intero processo fluido e senza stress. Lo raccomando caldamente a chiunque cerchi uno sviluppatore affidabile, competente e orientato alle soluzioni.

Interested in working together?

Whether it's a similar industrial solution or something completely different — let's talk.