00 — System Overview

One-page entry point. Read this first; deeper docs link from here.

What this system is

The Asset Tracking System registers, monitors, transfers, maintains, and audits physical assets. It supports per-asset identification by QR code or manual code entry. The product is bilingual (English / Arabic primary, plus 9 secondary UI languages) with full RTL support.

This docs/ folder is the up-to-date reference for the system as implemented.

Top-level architecture

flowchart LR
  subgraph Clients
    A[Angular SPA<br/>desktop shell]
    M[Angular SPA<br/>mobile shell<br/>/mobile/*]
  end
  A -- REST/JSON over HTTPS --> API
  M -- REST/JSON --> API
  subgraph "ASP.NET Core 8 Web API"
    API[Controllers /api/v1/*] --> APP[Application layer<br/>MediatR handlers]
    APP --> DOM[Domain entities + interfaces]
    APP --> INFRA[Infrastructure<br/>JWT, password hash,<br/>permission resolver,<br/>code generator,<br/>file storage,<br/>email]
    APP --> PERS[Persistence<br/>EF Core + SQL Server]
  end
  PERS --> DB[(SQL Server)]
  INFRA --> FS[(AppData/Documents)]
  INFRA --> SMTP[(SMTP / configured<br/>email provider)]

The backend is a modular monolith: one deployable API process, internal module boundaries (Identity, Master Data, Assets, Audits, Transfers, Custody, Maintenance, Notifications, Documents, Reporting, Settings, System). Each module owns its domain folder, its EF configurations, and its Application/Features/<Module>/ handlers.

Solution layout

AssetTrackingSystem/
├── AssetTracking.slnx                ← solution file
├── docs/                             ← THIS DOCUMENTATION
├── src/
│   ├── AssetTracking.Domain/         ← entities, enums, base classes, interfaces (zero deps)
│   ├── AssetTracking.Application/    ← MediatR commands/queries, DTOs, validators, mappings, pipeline behaviors
│   ├── AssetTracking.Persistence/    ← EF Core DbContext, configurations, interceptors, migrations, seeders
│   ├── AssetTracking.Infrastructure/ ← JWT, password hashing, permission resolver, code generator, file storage, email
│   └── AssetTracking.API/            ← Controllers, middleware, RequirePermission attribute, Program.cs
├── tests/
│   ├── AssetTracking.Domain.Tests/
│   ├── AssetTracking.Application.Tests/
│   └── AssetTracking.API.Tests/
└── frontend/                         ← Angular 18 + PrimeNG 18 SPA
    └── src/app/{core,shared,layouts,features}/...

Tech stack

Concern Choice Notes
Runtime (backend) .NET 8 <TargetFramework>net8.0</TargetFramework> in every csproj
Web framework ASP.NET Core 8 Web API Controllers + Swagger UI in dev
ORM Entity Framework Core 8 SQL Server provider; forward-only migrations
Database SQL Server (default) Connection string in appsettings.json:3
CQRS / mediator MediatR Two pipeline behaviors: ValidationBehavior, LoggingBehavior
Validation FluentValidation Auto-discovered per assembly via DI
Auth Custom JWT + bcrypt-style hash 15-min access, 30-day refresh w/ rotation
Permission cache In-memory (InMemoryCacheService) Keyed by (UserId, SecurityStamp); 30-min TTL
Frontend Angular 18 (standalone components, signals) package.json
UI kit PrimeNG 18 + PrimeIcons + @primeng/themes Dark mode via html.p-dark selector
QR scanning html5-qrcode 2.3.8 Mobile shell /mobile/*
Rich-text editor Quill 2 (PrimeNG <p-editor>) Used by notification template editor
QR/PDF rendering qrcode (frontend), QuestPDF (backend) QuestPDF set to Community license at Program.cs:13
Logging Serilog → console (dev) Configured at Program.cs:17-23
API docs Swashbuckle / Swagger UI Mounted only in Development
Storage Local filesystem AppData/Documents appsettings.json:25-27
Email Configurable (SMTP / SendGrid) via EmailProviderSettings table One row per environment

Core domain conventions

These apply to almost every entity. Detailed treatment in 01-backend-architecture.

BaseEntity

Every persisted entity inherits BaseEntity. Fields:

Field Type Source of value
Id Guid Caller (handlers always set explicitly)
Code string ICodeGenerator per-entity strategies (e.g., ASSET-2026-000001)
CreatedAt / CreatedBy DateTime / string AuditInterceptor on insert
ModifiedAt / ModifiedBy DateTime? / string? AuditInterceptor on update
IsDeleted / DeletedAt / DeletedBy bool / DateTime? / string? Set by interceptor when Remove(...) called → soft delete
RowVersion byte[] EF concurrency token (rowversion in SQL Server)

A global EF query filter on IsDeleted == false is applied to every entity in BaseEntityConfiguration. Use IgnoreQueryFilters() in admin/seed paths only.

Bilingual fields

User-facing strings come in pairs:

  • NamePrimary / NameSecondary
  • DescriptionPrimary / DescriptionSecondary
  • FullNamePrimary / FullNameSecondary (User)
  • SubjectPrimary / BodyPrimary + SubjectSecondary / BodySecondary (NotificationTemplate)

The "primary" / "secondary" terminology is language-agnostic — admins configure which language is which via HierarchyConfig and the App Languages page. Frontend uses the LocalizePipe to pick the right one for the active UI language. Bilingual data input fields use the appLangDir="primary|secondary" directive so each input picks the right text direction independent of the surrounding UI language.

Code generation

Auto-generated, human-readable codes via ICodeGenerator, implemented by CodeGeneratorService. Per-entity prefix table lives in that service; row-level locking (UPDLOCK, ROWLOCK) on the CodeSequences table prevents duplicates.

Prefix Entity Example
USR User USR-000003
ROLE Role ROLE-abc12345 (system roles get GUID suffix)
PRM Permission PRM-000017
ORG Organization ORG-000001
LOC Location LOC-000042
CLS Classification CLS-000007
VND Vendor VND-000005
MFR Manufacturer MFR-000003
ASSET Asset ASSET-2026-000123
AS AssetStatus AS-000001
AP AuditPlan AP-000003
APS AuditPlanScope APS-000004
AA AuditAssignment AA-000001
AAA AuditAssignmentAsset AAA-...
AR AuditResult AR-000001
ARL AuditResultLine ARL-...
ARA AuditReviewAction ARA-...
XFER AssetTransfer XFER-000001
XFL AssetTransferLine XFL-...
CO CheckOut CO-000003
MP MaintenancePlan MP-000001
MPA MaintenancePlanAsset MPA-... (added 2026-05-03)
MR MaintenanceRequest MR-000004
WO WorkOrder WO-000010
NT NotificationTemplate NT-...
NOTIF Notification NOTIF-...
ND NotificationDelivery ND-...
NP NotificationPreference NP-...
DOC Document DOC-...
DL DocumentLink DL-...

(Full source-of-truth: CodeGeneratorService._codeMap.)

RBAC — permission-based, never role-based

  • Roles are cosmetic collections of permissions; business code checks permission keys, never role names.
  • Resolution: effective = (∪ role permissions) ∪ (direct grants) − (direct denies). Deny wins.
  • Endpoint enforcement: [RequirePermission("resource.action")] attribute on every controller action (or [AllowAnonymous]). Verified by a build-time reflection test.
  • Frontend gates UI affordances with auth.hasPermission('...').
  • Permission cache key: (UserId, SecurityStamp). SecurityStamp regenerates when role/permission grants change.

Granular keys live in PermissionSeeder.cs; obsolete keys are hard-deleted via DatabaseSeeder.obsoleteKeys. The system favors granular per-action permissions (e.g., report.audit-history.export-excel rather than a generic report.export).

Modules at a glance

flowchart TB
  IDENT[Identity & Access<br/>User, Role, Permission,<br/>UserRole, RolePermission, UserPermission,<br/>RefreshToken, LoginAudit]
  MD[Master Data<br/>Organization, Location, Classification,<br/>Vendor, Manufacturer]
  ASSET[Assets<br/>Asset, AssetDetails, AssetStatus,<br/>+ four history tables]
  AUDIT[Audits<br/>AuditPlan, AuditPlanScope,<br/>AuditAssignment, AuditAssignmentAsset,<br/>AuditResult, AuditResultLine, AuditReviewAction]
  XFER[Transfers<br/>AssetTransfer, AssetTransferLine]
  CUST[Custody<br/>CheckOut]
  MAINT[Maintenance<br/>MaintenancePlan + MaintenancePlanAsset,<br/>MaintenanceRequest, WorkOrder]
  NOTIF[Notifications<br/>NotificationTemplate, Notification,<br/>NotificationDelivery, NotificationPreference]
  DOC[Documents<br/>Document, DocumentLink]
  RPT[Reporting<br/>per-page filterable reports<br/>with XLSX/PDF export]
  SETT[Settings<br/>HierarchyConfig, HierarchyLevel,<br/>AppSettings, Translation, EmailProviderSettings]
  SYS[System<br/>AuditLogEntry, RequestLog]

  IDENT --> ASSET
  MD --> ASSET
  ASSET --> AUDIT
  ASSET --> XFER
  ASSET --> CUST
  ASSET --> MAINT
  AUDIT --> NOTIF
  XFER --> NOTIF
  MAINT --> NOTIF
  ASSET --> DOC
  AUDIT --> DOC
Module Purpose Detailed in
Identity Auth (login, refresh, logout), users, roles, permissions, sessions 02 §Identity
Master Data Hierarchical org/location/classification trees + flat vendor/manufacturer 02 §MasterData
Assets Asset CRUD, current state + immutable history tables 02 §Assets
Audits Plan → Assignment → Mobile execution → Submission → Review → Write-back 02 §Audits
Transfers Draft → Submitted → Approved → InTransit → Completed (with rejection / cancel) 02 §Transfers
Custody Per-asset check-out / check-in, late tracking 02 §Custody
Maintenance Plan → Request → WorkOrder; multi-asset plans 02 §Maintenance
Notifications Bilingual template engine; in-app + email channels; per-user preferences 02 §Notifications
Documents Photo/file uploads with polymorphic owners (asset, audit-result-line, etc.) 02 §Documents
Reporting Per-page filterable reports with XLSX/PDF export 02 §Reporting
Settings Hierarchy depth/labels, UI translations, email provider, app languages 02 §Settings
System Audit log, request log, login audit 02 §System

Frontend at a glance

frontend/src/app/
├── core/             ← singletons: services, interceptors, models, i18n, theme
├── shared/           ← reusable components, directives, pipes
├── layouts/
│   ├── main-layout/  ← desktop shell (top bar + side nav)
│   └── mobile-layout/← mobile shell (top bar only, simplified menus)
└── features/
    ├── auth/login/
    ├── dashboard/
    ├── users/{user-list, user-detail}
    ├── roles/{role-list, role-detail}
    ├── permissions/   ← read-only browser
    ├── master-data/{organizations, locations, classifications, vendors, manufacturers}
    ├── assets/{asset-list, asset-detail}
    ├── audits/{audit-plans, audit-plan-create, audit-plan-detail,
    │           my-audits, assignment-detail,
    │           pending-reviews, result-review}
    ├── transfers/{transfer-list, transfer-detail}
    ├── checkouts/
    ├── maintenance/{maintenance-plans, maintenance-plan-detail,
    │                maintenance-requests,
    │                work-orders, work-order-detail}
    ├── notifications/{notifications-inbox, preferences}
    ├── reports/{reports-hub, asset-inventory, audit-history,
    │            audit-results, transfer-history, maintenance-history,
    │            checkout-activity}
    ├── settings/{hierarchy-config, app-languages, translations,
    │             notification-templates, email-provider}
    ├── audit-log/, login-audit/, profile/
    └── mobile/{mobile-home, mobile-assignment, scan-audit}

Routing (app.routes.ts) gates each route with a permission guard except the login page and the mobile shell entry. The mobile shell auto-redirects desktop-typed users back to /, and MainLayoutComponent redirects userType === 'Mobile' users to /mobile. Cross-shell navigation pitfalls are documented in 04-frontend-architecture.

Communication patterns

Pattern Where Notes
Synchronous REST All CRUD and lifecycle endpoints RFC 7807 problem+json errors with stable errorCode
Idempotency keys SubmitResultCommand.ClientSubmissionId (mobile audit submission) Prevents duplicate results on retry
Polling NotificationBellComponent polls unread count every 60 s Drives the top-bar bell badge
Server-side draft AuditAssignment.DraftPayload (JSON column) Mobile scanner debounce-saves progress
Outbound email NotificationDeliveryWorker (background) Reads NotificationDelivery queue

Lifecycle conventions cheat sheet

  • Adding a new entity: derive from BaseEntity → write EntityConfiguration : BaseEntityConfiguration<T> → add DbSet<T> to AppDbContext → add a code prefix to CodeGeneratorService._codeMap → write Create*CommandHandler (set Id = Guid.NewGuid() on every child) → write *Dto and mapping → add RequirePermission attributes on the controller endpoints → register the permission keys in PermissionSeeder → write the EF migration with dotnet ef migrations add.
  • Adding a new permission: append to PermissionSeeder._permissions (with module + label + isDangerous + sortOrder) → if it replaces an old key, add the old key to DatabaseSeeder.obsoleteKeys.
  • Adding a new notification template: insert into DatabaseSeeder._notificationTemplates with InApp + (optional) Email channel rows → reference merge fields by {{ namespace.fieldName }} → ensure the publishing handler pushes those fields into the merge dict; link and user.* are auto-injected by NotificationService.
  • Adding a new module-level translation: append to frontend/src/app/core/i18n/translations.ts under the English block (others fall back via i18n.service.ts:59).

Where to go next

If you want to… Read
Understand DI wiring, MediatR pipeline, RBAC plumbing 01 — Backend Architecture
Drill into one module's entities + handlers + business rules 02 — Backend Modules
Look up a column / index / migration 03 — Database
Understand the Angular shell, routing, services 04 — Frontend Architecture
Find which component drives the screen you're looking at 05 — Frontend Features
Build, run, deploy, configure 06 — Operations
Look up a single endpoint quickly 07 — API Reference