An IT asset management system can look like a collection of forms: register a laptop, assign it to an employee, record a repair, and mark it as disposed. That view misses the real problem. The system has to preserve a trustworthy answer to three questions over time:
- What is this asset's current state?
- Who is responsible for it?
- How did the system arrive at that answer?
EITAMS taught me that enterprise software becomes difficult at the boundaries between workflows. The form is rarely the hard part. The hard part is making assignment, maintenance, depreciation, imports, reporting, and disposal agree with each other under real change.
Current state is not enough
Storing only assignedEmployeeId tells us who appears to hold an asset now. It does not explain whether custody was accepted, when a transfer happened, who initiated it, why it was rejected, or what condition was recorded on return.
EITAMS models assignment as a lifecycle with states such as pending acceptance, confirmed, overdue, returned, and rejected. This preserves the transition instead of overwriting the story.
available
-> assignment pending
-> custody accepted
-> returned or transferred
-> available / maintenance / disposal
That design supports employee acknowledgement, operator follow-up, overdue escalation, and an audit trail from the same underlying record. It also prevents the dashboard from presenting a request as a completed handover.
The principle applies widely: a business process deserves a state machine when intermediate states have different owners, permissions, or recovery actions.
One business rule needs one source of truth
Depreciation appears in asset detail pages, dashboards, ledgers, reports, disposal calculations, and external API responses. Reimplementing the formula in each surface creates drift even when every version initially looks correct.
The project centralizes the policy and provides equivalent logic for application calculations and PostgreSQL aggregate queries. The exact formula matters less than the ownership rule: every consumer should depend on the same documented assumptions about method, useful life, residual value, date boundaries, and rounding.
| Risk | Design response |
|---|---|
| UI and report disagree | Share policy inputs and test known examples |
| Aggregate query uses different rounding | Keep an equivalent SQL expression |
| Repair cost is ignored | Feed maintenance cost into total cost of ownership |
| Historical report changes unexpectedly | Make policy changes explicit and reviewable |
Financial-looking numbers create confidence quickly, so inconsistent numbers are especially damaging. A polished chart cannot compensate for an ambiguous calculation.
Bulk imports combine concurrency with partial failure
Imports look like a file-parsing feature until two users upload records at the same time. EITAMS generates asset tags from a prefix sequence. Counting existing rows and then inserting new tags without coordination allows concurrent imports to allocate the same range.
The import workflow acquires a PostgreSQL advisory lock around sequence allocation. Each accepted row then runs inside its own transaction, creating the asset, purchase data, currency information, and audit event together.
This produces a deliberate partial-success model:
- a malformed row does not erase every valid row;
- a valid row cannot leave half of its related records behind;
- concurrent imports cannot allocate overlapping identifiers;
- the result can explain which rows succeeded and why others failed.
“All or nothing” is not automatically the safest transaction boundary. The right boundary matches what the user can understand, correct, and retry.
Disposal should close history, not delete it
Disposal is the final lifecycle action, but it touches many connected facts: the request, asset eligibility, financial value, approval evidence, certificate data, audit history, and final status.
Treating it as DELETE FROM assets would remove exactly the information an accountable system should retain. EITAMS instead validates the batch, resolves financial values efficiently, and completes the connected records inside a transaction before archiving the assets as disposed.
validate every requested asset
-> load financial state as a batch
-> create disposal and certificate records
-> append audit events
-> mark assets disposed and archived
-> commit together
The transaction prevents a certificate from existing without a disposal or an asset from disappearing without an audit event. The batch read also avoids an N+1 lookup pattern as the disposal grows.
Authentication can become a distributed-systems problem
EITAMS uses NextAuth with Keycloak OIDC and JWT sessions. Refresh-token rotation exposed a problem that an in-memory mutex cannot solve: separate Next.js workers do not share process memory, and concurrent refresh attempts can invalidate a rotated token.
The response was to coordinate refresh state through the database so workers can observe the same lease and updated token result. This was a useful reminder that “server-side” does not mean “single process.” Any correctness mechanism stored only in memory needs to be reconsidered when an application scales horizontally.
Leading the project meant defining boundaries
My role expanded beyond implementing features. Team delivery required turning broad requirements into modules, documenting decisions, reviewing how schemas and workflows connected, and keeping incomplete capabilities from being presented as finished.
The most useful leadership work was often reducing ambiguity:
- name the owner of a calculation or state transition;
- distinguish implemented behavior from planned scope;
- define transaction boundaries before parallel feature work begins;
- preserve traceability between a requirement, schema, workflow, and test;
- make failure and recovery behavior part of the feature definition.
Those practices help a team move faster because fewer decisions remain hidden inside individual components.
What I would take into another enterprise build
I would begin with histories, not screens. For each domain object, I would ask which transitions matter, who can initiate them, what evidence must remain, which calculations depend on them, and what needs to be atomic.
Enterprise software earns trust by staying coherent while many people and processes change the same world. That coherence comes from explicit ownership, durable history, shared rules, and transactions that match the business event—not from adding more dashboard cards.