Skoolsewa - School Management System Docs
Developer ResourcesAssets

Assets Features and Flows

Complete feature list, actor journeys, state flows, business rules, edge cases, and diagrams for the asset management module.

Assets Features and Flows

1. Documentation Evidence

Source TypeFiles or DocsWhat Was Extracted
Schemapackages/db/src/schema/school/assets.ts, packages/db/src/schema/school/asset-contract.tsEvery table, closed vocabulary, and CHECK constraint behind the status machine
Controllersapps/api/src/modules/assets/**/*.controller.ts (13 files)Every admin and portal route, its permission, and its purpose
Permission catalogpackages/db/src/authorization/permission-catalog.ts (lines 671-688)The 9 asset permission modules and their SUPERADMIN_ONLY default
Notification matrixpackages/db/src/schema/school/asset-contract.tsThe 8 asset notification events, their audiences, and default channels
Notifierapps/api/src/modules/assets/notify/asset-notifier.service.tsHow an event is turned into a notification row
Inbound syncapps/api/src/modules/assets/inbound/asset-sync.controller.tsThe accounting application's write path and conflict handling
Portalapps/api/src/modules/assets/portal/asset-portal.controller.tsThe staff self-service "my assets" screen
People integrationapps/api/src/modules/people/shared/people-deletion.service.ts, apps/api/src/modules/people/shared/people-code.service.tsThe deletion refusal and asset code allocation
Error codesapps/api/src/common/types/error-codes.ts (ASSET_*, STAFF_HAS_ACTIVE_PAY_OR_ASSETS)Every asset-specific failure

2. Feature Summary

FieldValue
Moduleassets
Submodulescatalog, register (including attachments), inbound, portal, reports, settings, notify, shared
Primary user valueThe school keeps one authoritative register of the physical property it owns — computers, furniture, lab equipment — knowing who holds each item, where it sits, its condition, and its accounting status, and lets a linked accounting application keep its own book value and depreciation state in sync without a second data entry.
ActorsAdmin/office staff (holding Asset*/Assets* codes, all SUPERADMIN_ONLY by default), any staff member reading their own held assets, the accounting application (via integration)
Main entry points/api/assets/** and /api/asset-catalog/**//api/asset-settings/**//api/asset-reports/** admin routes, /api/mobile/staff/assets portal route, /api/asset-sync/** inbound accounting routes
Main outputsAsset records and their event timeline, maintenance history, attachments, the asset change feed (consumed by integration), in-app/push/email/SMS notifications
Related docsBackend, API

3. Actor Matrix

ActorCan DoCannot DoAuth RequirementNotes
Office/asset administrator (granted Asset*/Assets* codes)Manage the catalogue (types, categories, locations), register assets, assign/transfer/return/install/uninstall, change condition, open/close maintenance, mark damaged/lost, retire/dispose/archive, resolve accounting conflicts, export the register, configure settings and the notification matrixAnything a code they were not granted coversJWT + RoleGuard + the specific Asset*_ACTION/Assets_ACTION codeEvery asset module except MyAssets is on SUPERADMIN_ONLY_MODULES; a school grants them to a named role deliberately
Any staff member (MyAssets_READ)Read the list of assets currently assigned to themSee another staff member's assets, change anythingJWT + RoleGuard, no @Permissions() on the handler — AssetPortalService.resolveOwnStaffId is the controlGET /api/mobile/staff/assets
Accounting application (via an integration client holding assets:read/assets:write)Read asset events through the change feed, report accounting status/book value/depreciation, raise a conflict when its own field disagrees with what the school recordedChange custody, location, or operational statusAPI-key auth on integration's own guard, scoped to the granted scopesSee the integration module's docs for the client-facing contract; granting or revoking assets:* needs AssetSync_UPDATE on the granting admin — INTEGRATION_SCOPE_GRANT_FORBIDDEN otherwise

4. The Asset Status Machine

draft is a registered-but-not-yet-active record: it may hold none of custodian, room, location, or disposal fields, and it is the only status a soft delete may target (assets_deleted_only_draft). available means unheld and unplaced (assets_available_unheld); in_use means placed in a room or location and unheld (assets_in_use_placed); assigned means held by a staff member and/or a department (assets_assigned_held); retired, disposed and archived hold nothing and sit nowhere (assets_retired_unheld, assets_gone_nowhere). disposed additionally requires the disposal date, method, reason and authorizing admin to already be recorded (assets_disposed_recorded); disposal fields may only be set once the asset is disposed or archived (assets_disposal_only_when_disposed). "Active" in the product vocabulary is available, in_use, or assigned together (ASSET_ACTIVE_STATUSES); "transferred" is an event on the timeline, not a status of its own.

5. Custody and Placement

An asset is in exactly one of three places at a time, enforced by the status CHECKs above:

  • Held by a staff member (custodianStaffId) and/or a department (custodianDepartmentId) — status assigned.
  • Placed in a room (roomId) or a location (assetLocations.id) — status in_use. A room and a location are mutually exclusive (assets_place_exclusive).
  • Neither — status available, retired, disposed, or archived.

Every custody or placement change is one of five actions on AssetAssignmentsController (assets/register/asset-assignments.controller.ts), each behind AssetAssignments_UPDATE:

ActionRouteEffect
AssignPOST /api/assets/:id/assignHand the asset to a staff member and/or department; drops any room/location
InstallPOST /api/assets/:id/installPlace the asset in a room or location; drops any custodian
TransferPOST /api/assets/:id/transferMove custody or placement without an intervening return
ReturnPOST /api/assets/:id/returnCustodian hands the asset back; becomes available
UninstallPOST /api/assets/:id/uninstallRemove the asset from its room/location; becomes available

Every transition appends an asset_events row (created, assigned, transferred, returned, status_changed, condition_changed, ...) carrying a human-readable *_holder_label alongside the id, so the timeline survives the referenced room, location, staff row, or department being deleted or renamed later.

The room field on every custody/placement action reuses the school's own room structure rather than a separate asset-only list. The picker (GET /api/asset-catalog/rooms) shows each active room's floor and the classes currently timetabled into it — the ACTIVE classes of the CURRENT academic session whose home room it is — so a clerk placing a projector can see at a glance whether a room is a classroom in active use this year. The picker is searchable across room number, name and building, and filterable to one building at a time; GET /api/asset-catalog/rooms/buildings lists the distinct building names to populate that filter. The register list and its CSV export can themselves be narrowed to one building, or to the assets currently placed in one class's home room, with both filters applying identically to the export so a filtered screen and its download always agree. None of this reads or writes the school structure — it is read-only reuse of rooms/grades/sections/classes.

6. Maintenance and Condition

AssetMaintenanceController gates condition changes and the maintenance lifecycle behind AssetMaintenance_UPDATE/AssetMaintenance_CREATE:

  • POST /:id/condition — record a new AssetCondition (new, good, fair, poor, damaged, critical) without opening a maintenance ticket.
  • POST /:id/mark-damaged / POST /:id/mark-lost — move the status to damaged/lost (available from available, in_use, or assigned).
  • POST /:id/return-to-service — bring a damaged (or maintained) asset back to available.
  • POST /:id/maintenance — open a ticket (assetMaintenance row, status open); ASSET_MAINTENANCE_OPEN_EXISTS refuses a second open ticket on the same asset.
  • POST /:id/maintenance/:maintenanceId/close / .../cancel — close (completed) or cancel a ticket; ASSET_MAINTENANCE_NOT_OPEN guards both.
  • GET /:id/maintenance — the asset's maintenance history, behind plain Assets_READ.

7. Disposal

AssetDisposalController (AssetDisposal_UPDATE) offers retire, dispose, and archive as the terminal path. Disposal records the date, an AssetDisposalMethod (sold, scrapped, donated, written_off, returned_to_vendor, other), a reason, the authorizing admin, proceeds in paisa, and an optional reference — all four of date/method/reason/authorizer are mandatory once the status becomes disposed (assets_disposed_recorded). Archiving is the final state; neither disposal nor archival may be reversed through this surface.

8. Accounting Sync (Inbound)

The accounting application owns accountingStatus (unrecorded, capitalized, expensed, fully_depreciated, written_off, disposed), accountingReference, and accountingBookValuePaisa — SkoolSewa only stores what it reports, never computes depreciation itself. AssetSyncController (asset-sync.controller.ts) exposes:

  • GET /api/asset-sync/events (AssetSync_READ) — the inbound event log.
  • GET /api/asset-sync/events/:id (AssetSync_READ) — one event's detail.
  • POST /api/asset-sync/events/:id/resolve (AssetSync_UPDATE) — accept or dismiss a held_conflict.

An inbound event lands in one of three states (ASSET_INBOUND_STATES): applied (accepted and written), stale (a timestamp older than what is already recorded — refused, not stored as a failure), or held_conflict (the accounting application's own field disagrees with a school-recorded value and needs a human decision — asset_accounting_conflict fires an admin-only email+in-app alert). Resolving a conflict is accepted (apply the incoming value) or dismissed (keep the school's value); either way the resolution is recorded, never silently dropped.

9. Notifications

AssetNotifierService (assets/notify/asset-notifier.service.ts) turns an event into rows on the shared notification pipeline, using the matrix in asset-contract.ts:

EventAudienceDefault Channels
asset_assignedcustodian (admin optional)in-app, push
asset_transferredcustodian (admin optional)in-app, push
asset_returnedcustodian (admin optional)in-app, push
asset_damagedadminin-app
asset_lostadminin-app
asset_disposedadminin-app
asset_accounting_conflictadminin-app, email
asset_accounting_updatedadminin-app

No asset notification variable ever carries a monetary figure or an identity number — it names what happened and links to the register entry.

10. Reports and Export

AssetReportsController (asset-reports.controller.ts) exposes GET /api/asset-reports/dashboard (Assets_READ — counts by status/category/type) and GET /api/asset-reports/export (AssetExports_READ — a CSV of the register).

11. Cross-Module Effects

  • People: PeopleDeletionService.assertStaffHoldsNothing refuses to delete a staff member who is still custodian_staff_id on any assets row — STAFF_HAS_ACTIVE_PAY_OR_ASSETS, alongside the equivalent salary check. Return the assets first.
  • People code allocation: PeopleCodeService allocates the asset prefix (AST) using the same six-digit sequential allocator that issues student and staff codes, producing AST-<year>-<6 digits> (assets_code_format CHECK).
  • Integration: the integration module's assets:read/assets:write scopes gate the accounting application's visibility into the change feed and its write path into accountingStatus/accountingBookValuePaisa; granting either scope needs AssetSync_UPDATE on the granting admin (INTEGRATION_SCOPE_GRANT_FORBIDDEN otherwise).

12. Edge Cases

CaseBehavior
Serial number reused across two active (non-deleted) assetsRefused by a case-insensitive partial unique index (assets_serial_unique); a soft-deleted asset's serial is free to reuse
Register an asset with a quantity greater than 1 and a serial numberRefused — a serialized asset is always singular (assets_serial_single_quantity)
Assign an asset that is already assignedTreated as a transfer, not a second assignment; the previous custodian's hold ends and the timeline records transferred
Delete a non-draft assetRefused — soft delete only ever targets draft (assets_deleted_only_draft); a live asset is retired/disposed/archived instead
An inbound accounting event arrives with an older timestamp than what is recordedRejected as stale, not stored as a failure — no exception is raised to the caller beyond the recorded state
A field the accounting application is not allowed to touch appears in its payloadASSET_INBOUND_FIELD_NOT_ALLOWED
A room's class belongs to a non-current academic sessionIt never appears in the room picker's classes list, even if the class is still is_active
No academic session is currentEvery room in the picker returns classes: []
The register's classId filter names a class with no home roomMatches no assets — never falls back to an unfiltered list
The register's building filter is applied to an asset placed in a location, not a roomNever matches — the filter only ever reaches room-placed assets