Return to projects
Project deep dive· Active development

TIQRI Assets

Enterprise IT Asset Management System

A centralized TIQRI platform for managing asset custody, lifecycle operations, financial value, maintenance, software licensing, and compliant disposal.

Next.js
React
TypeScript
PostgreSQL
Tailwind
Next.jsReactTypeScriptPostgreSQLTailwind
TIQRI Assets project cover
Project frame

Overview

EITAMS is a centralized enterprise platform designed for TIQRI Corporation to replace disconnected spreadsheets and legacy asset trackers with one accountable system of record.

The product manages more than a list of devices. It connects the complete operational history of hardware, software licenses, office electronics, and furniture: how an asset entered the company, where it is located, who accepted custody, what it cost, whether it was repaired, and how it was eventually retired or disposed of.

Earlier design documents refer to the system as IDAMS. The current repository and product documentation use EITAMS — Enterprise IT Asset Management System.

What the system covers

AreaImplemented responsibility
Asset registryHardware, furniture, office electronics, category-specific attributes, documents, QR tags, and bulk onboarding
Custody and operationsEmployee/location assignments, digital acknowledgement, returns, issue reporting, and maintenance history
Financial intelligenceAcquisition cost, exchange rates, straight-line depreciation, net book value, TCO, salvage value, and write-offs
Software assetsLicense terms, expiry dates, seat counts, and employee allocations
Disposal and complianceApproval queues, data-wipe and tag-removal checks, certificates, archival, and audit events
AutomationWarranty, overdue return, repair, acceptance, and renewal notifications
ReportingRegistry, assignment, maintenance, disposal, financial, audit, and configurable report templates

The application is single-tenant and built specifically around TIQRI's internal roles and operating processes.

Architecture

EITAMS is a full-stack Next.js 16 App Router application written in TypeScript and React 19. Its UI uses Tailwind CSS 4, Shadcn/Radix primitives, TanStack Table for dense operational grids, and Recharts for dashboard reporting.

Business operations are implemented through Server Actions and route handlers rather than a separate Express service. Input schemas and permission checks sit at those mutation boundaries, keeping validation and authorization close to the database operation they protect.

The persistence layer uses Neon Serverless PostgreSQL with Drizzle ORM. The relational schema separates core concerns into assets, purchases, assignments, maintenance tickets, disposals, audit events, notification queues, software licenses, allocations, report templates, integrations, API keys, webhooks, and linked devices.

Next.js pages and feature components
              |
Server Actions + authenticated route handlers
              |
Drizzle repositories and transactional business rules
              |
Neon PostgreSQL + Vercel Blob + Upstash services

Supporting integrations include Vercel Blob for invoices and compliance documents, Upstash QStash for scheduled work, Redis-backed rate limiting, Pusher and webhooks for integration events, and generated PDF/Excel reports.

Identity and authorization

The current implementation uses NextAuth with Keycloak OIDC and JWT sessions. Users are provisioned into the application database and assigned one of four business roles:

  • GlobalAdmin
  • ITOperator
  • FinancialAuditor
  • Employee

The Next.js proxy applies top-level route access rules, while Server Actions and route handlers repeat authorization checks for the operation being performed. Employees are directed to the self-service My Assets experience; management areas are restricted according to financial, operational, and administrative responsibility.

One of the most interesting authentication problems was refresh-token rotation across multiple Next.js workers. An in-memory mutex cannot coordinate separate processes, and concurrent refresh attempts can cause Keycloak to revoke a rotated token.

The solution uses a PostgreSQL advisory transaction lock derived from the user's UUID:

request arrives
  -> acquire per-user Postgres advisory lock
  -> re-read the authoritative stored token
  -> skip refresh if another worker already completed it
  -> otherwise refresh with Keycloak and persist the result
  -> commit transaction and release the lock

This turns the shared database into a cross-process coordination point without introducing a second locking service.

Asset onboarding and bulk import

Assets can be registered individually through a multi-step workflow or imported in bulk. Categories define their own custom schema, allowing a laptop, chair, monitor, or network appliance to collect different model and instance attributes without creating a separate table for every category.

The bulk importer generates category-aware spreadsheet templates, parses CSV/XLSX input, resolves master-data references, validates rows, and previews failures before execution. The execution path currently accepts up to 5,000 resolved rows in one run.

To prevent two imports from generating overlapping asset-tag sequences, the action acquires a PostgreSQL advisory lock before counting the current prefix and allocating new tags. Each accepted row then runs inside its own transaction, creating the asset, purchase record, currency conversion data, and audit event together.

This produces a useful partial-success model: one malformed row does not erase every valid asset, but no individual row can leave behind half of a record.

Lifecycle, custody, and maintenance

The operational model treats status changes as business events instead of cosmetic labels. Assets move through states such as Available, Assigned, Requested, Defective, In Repair, Pending Disposal, and Disposed.

Assignments record whether custody is pending acceptance, confirmed, overdue, returned, or rejected. Employees can see their own equipment and acknowledge handovers, while operators manage check-outs, returns, condition changes, and maintenance tickets.

Repair costs feed back into Total Cost of Ownership. That connection matters because financial reporting should represent the actual cost of keeping an asset in service, not only its purchase price.

Financial calculations as one source of truth

Straight-line depreciation is isolated in a dedicated domain module rather than repeated across pages. The calculation accounts for acquisition cost, salvage value, purchase date, and useful life in months, then clamps net book value between the original cost and residual value.

The same module also exposes an equivalent PostgreSQL expression for aggregate queries. Dashboard KPIs, financial ledgers, standard reports, asset details, external API responses, and disposal calculations therefore share one depreciation policy.

NBV = cost - ((cost - salvage value) / useful life) x elapsed months

Keeping both the application calculation and SQL fragment together reduces the risk of a dashboard showing a different book value from a disposal record or exported report.

Compliance-focused disposal workflow

Disposal is implemented as a guarded workflow because it changes both operational and financial history. Execution requires an authorized administrator and validated checks such as data wiping, tag removal, disposal method, date, and supporting certificate URLs.

The final execution runs atomically:

  1. Verify every disposal request and its asset mapping.
  2. Reject requests that are not in an eligible state.
  3. Batch-load purchase data and calculate book value at disposal.
  4. Complete the disposal records and distribute actual salvage value.
  5. mark the assets as Disposed and archived.
  6. Attach disposal certificates.
  7. Write detailed audit entries containing the before/after state.
  8. Dispatch the resulting integration event.

The batch read avoids an N+1 financial lookup, while the surrounding transaction prevents a certificate, audit entry, disposal record, and asset status from drifting apart.

Notifications and integrations

A signed Upstash QStash route drives scheduled checks for warranty expiry, overdue returns, overdue repairs, upcoming returns, and custody acceptance escalations.

Pending handovers use staged reminders at 24 and 48 hours, followed by an administrator escalation at 72 hours. A notification queue and conflict-safe inserts prevent the scheduler from producing duplicate reminders when the same threshold is checked more than once.

The project also exposes authenticated API routes, generated OpenAPI documentation, API-key management, webhook subscriptions, email/Teams dispatch paths, and device-linking endpoints used by the separate React Native mobile companion.

Verification strategy

The codebase uses Vitest and React Testing Library for domain logic, validations, permissions, Server Actions, repositories, and feature components. Playwright covers browser-level flows, with a Docker Compose PostgreSQL environment available for isolated end-to-end and integration tests.

Testing concentrates on the expensive failure points: authorization boundaries, concurrent operations, financial calculations, bulk-import validation, assignment transitions, disposal rules, notification deduplication, and report generation.

What I learned

EITAMS reinforced that enterprise software is mostly about preserving trustworthy state across many connected workflows.

The difficult parts were not individual forms or tables. They were the boundaries between them: coordinating rotated tokens across workers, keeping financial calculations consistent between TypeScript and SQL, preventing concurrent imports from allocating duplicate identifiers, recording custody without losing history, and making disposal an auditable transaction instead of a destructive update.

The result is an evolving operational system with a documented domain model, explicit role boundaries, transaction-backed workflows, automation paths, and a test suite designed around business risk.