An enterprise-style inventory and procurement system: an ASP.NET Core 8
API built on Clean Architecture, paired with a React + TypeScript SPA. Built to mirror how
a real internal business system is structured — layered architecture, an auditable
inventory ledger, a multi-step purchasing approval workflow, and the operational hardening
a system needs before real traffic hits it.
Inventiq is a full operational tool for tracking products, suppliers, purchase orders,
and stock movements — with role-based access across three permission levels (Admin, Manager,
Clerk). It's built as two independent, purpose-built services: a REST API and a React SPA
that consumes it, each modeled after how a real internal business system is actually
structured rather than a single-file CRUD demo.
Problem
Small operations tracking inventory in spreadsheets lose visibility fast: stock levels
drift out of sync, reorder points get missed, purchasing has no approval trail, and there's
no single source of truth for what's actually on hand versus what the paperwork says.
Solution
Inventiq centralizes that operational data behind role-gated screens: live stock counts
computed from an append-only transaction ledger (never a stored, driftable number), a
purchase-order lifecycle with a real approval chain, and reporting with CSV export — backed
by a rate-limited, hardened API rather than an open CRUD surface.
Authenticated access — JWT-backed sign in
Demo Loginadmin@inventiq.com/Admin@12345
Key Features
Dashboard: live counts of products, categories, suppliers, and pending
orders, plus total inventory value and low-stock alerts.
Catalog: categories with parent/child hierarchy, suppliers, and
SKU-level product detail — full CRUD with search, filtering, and pagination.
Inventory ledger: stock in / stock out / manual adjustment, each
recorded as an append-only transaction — current stock is always computed, never stored.
Purchase orders: a real approval lifecycle — Draft → Submitted →
Approved → (Partially) Received → Received — with partial-receiving support.
Reports: date-ranged inventory and purchase reports with CSV export.
Users & roles (Admin only): role assignment and account
activation/deactivation, with two safety rules baked in — an Admin can't strip their own
Admin role, and the system always keeps at least one active Admin.
Role-aware UI: every screen shows different available actions for
Clerk / Manager / Admin, mirroring the backend's own authorization rules.
Purchase orders — Draft / Approved / Received lifecycleReports — date-ranged, exportable to CSVInventory ledger & stock movementAdmin-only user & role management
Architecture
React + TypeScript SPA
↓
ASP.NET Core Web API
↓
Application Layer — DTOs, Services, Validation
↓
Domain Layer — Entities, Business Rules
↓
Infrastructure / Persistence — EF Core, JWT, Repositories
↓
SQL Server
The backend follows Clean Architecture with dependencies pointing strictly inward:
Inventiq.Domain — entities, enums, domain exceptions; zero external dependencies.
Inventiq.Application — DTOs, service interfaces/implementations, FluentValidation validators.
Inventiq.Tests — xUnit + Moq unit tests against Application services, with no database or HTTP host required to run.
The React frontend mirrors this structure on the client: a types/ folder maps
the backend's DTOs field-for-field (a backend change surfaces as a compile error, not a
silent runtime bug), an Axios client with interceptor-based token refresh, and one
api/ file per backend controller area.
Stock as a computed value, not a stored column. Product has no
Quantity field — current stock is always SUM(StockIn) − SUM(StockOut) + SUM(Adjustment)
over the transaction ledger. This rules out an entire class of race-condition bugs and
gives a full audit trail of why a number changed, not just what it is now.
Projected reads instead of mapped entities. Read paths project
directly into DTOs inside the LINQ query (.Select(p => new ProductDto(...)))
rather than loading an entity and mapping it afterward — EF Core doesn't load related data
unless the query explicitly asks for it, so this avoids silently-blank fields and only
pulls the columns actually needed.
Explicit transactional commit. Purchase-order receiving writes to the
inventory ledger inside an explicit database transaction that rolls back by default and
only commits as the last line of the success path — a partially-failed receive can never
leave stock and PO status out of sync.
Rotating refresh tokens. Every refresh marks the old token revoked and
chains it via ReplacedByToken, which makes token theft detectable — a revoked
token being presented again is a strong compromise signal.
Soft deletes throughout (an IsActive flag) on Products,
Categories, and Suppliers, since inventory and purchase-order history reference them and a
hard delete would corrupt the ledger.
Operational hardening: per-endpoint rate limiting (tighter on
login/register to blunt brute-force attempts), HSTS, standard security headers, and
request size limits.
Client-side validation mirrors server-side rules. React Hook Form +
Zod validation on the frontend mirrors the backend's FluentValidation rules, so obviously
invalid input never reaches the API.
Results
Inventiq is deployed and publicly reachable as a working demo, seeded with a realistic
dataset (18 products across 5 categories, 4 suppliers, 3 purchase orders in different
lifecycle stages). No production usage metrics (real customer volume, uptime history) are
published here — this is a live, fully functional build, not a claim of commercial scale.