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 |
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/NameSecondaryDescriptionPrimary/DescriptionSecondaryFullNamePrimary/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→ writeEntityConfiguration : BaseEntityConfiguration<T>→ addDbSet<T>toAppDbContext→ add a code prefix toCodeGeneratorService._codeMap→ writeCreate*CommandHandler(setId = Guid.NewGuid()on every child) → write*Dtoand mapping → addRequirePermissionattributes on the controller endpoints → register the permission keys inPermissionSeeder→ write the EF migration withdotnet 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 toDatabaseSeeder.obsoleteKeys. - Adding a new notification template: insert into
DatabaseSeeder._notificationTemplateswith InApp + (optional) Email channel rows → reference merge fields by{{ namespace.fieldName }}→ ensure the publishing handler pushes those fields into the merge dict;linkanduser.*are auto-injected byNotificationService. - Adding a new module-level translation: append to
frontend/src/app/core/i18n/translations.tsunder the English block (others fall back viai18n.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 |