Skip to content

M7 Database Schema

M7 uses PostgreSQL (version 17 in development, 12+ in production) managed via Prisma ORM (v7.2.0). The schema is defined in prisma/schema.prisma and is the single source of truth for the database structure.

All primary keys are cuid strings generated by Prisma. Timestamps (createdAt, updatedAt) are managed automatically.


erDiagram
Department {
string id PK
string name UK
datetime createdAt
datetime updatedAt
}
Employee {
string id PK
string name
string email
string phone
string role
string location
string avatarUrl
boolean isActive
datetime hireDate
datetime createdAt
datetime updatedAt
string departmentId FK
}
Asset {
string id PK
string assetId UK
string qrCode
int csvOriginalId
string assetTag
AssetCategory category
string name
string manufacturer
string modelNumber
string specifications
string operatingSystem
datetime purchaseDate
datetime warrantyExpiry
string serviceHistory
datetime lastMaintenance
datetime nextMaintenance
string supportContact
AssetCondition condition
datetime lastUsedDate
float purchaseCost
string networkPhone
string notes
AssetStatus status
BatteryCondition batteryCondition
float ramGb
float storageGb
float storageUsedGb
float screenSizeInch
string cpuInfo
string imei
string productLink
string photoUrl
string location
datetime createdAt
datetime updatedAt
string assignedToId FK
string departmentId FK
}
TransferHistory {
string id PK
datetime transferDate
TransferReason reason
string notes
string assetId FK
string fromEmployeeId FK
string toEmployeeId FK
string fromLocation
string toLocation
datetime createdAt
}
CheckoutForm {
string id PK
datetime checkoutDate
datetime returnDate
datetime expectedReturn
CheckoutStatus status
datetime acknowledgedAt
string acknowledgedNote
string conditionOnCheckout
string conditionOnReturn
string notes
string assetId FK
string employeeId FK
datetime createdAt
datetime updatedAt
}
ActivityLog {
string id PK
ActivityAction action
string entityType
string entityId
string description
json metadata
datetime createdAt
}
Department ||--o{ Employee : "has"
Department ||--o{ Asset : "owns"
Employee ||--o{ Asset : "assigned_to"
Employee ||--o{ TransferHistory : "transferred_from"
Employee ||--o{ TransferHistory : "transferred_to"
Employee ||--o{ CheckoutForm : "checks_out"
Asset ||--o{ TransferHistory : "has_history"
Asset ||--o{ CheckoutForm : "checked_out_via"

Represents an organisational unit (e.g. Sales, Operations, Development).

FieldTypeConstraintsDescription
idStringPK, @default(cuid())Internal identifier
nameString@uniqueDepartment name
createdAtDateTime@default(now())Record creation timestamp
updatedAtDateTime@updatedAtLast update timestamp

Relations:

  • employeesEmployee[] — all staff in this department
  • assetsAsset[] — all assets assigned to this department

Represents a company staff member who can be assigned hardware assets.

FieldTypeConstraintsDescription
idStringPK, @default(cuid())Internal identifier
nameStringrequiredFull name
emailString?Work email
phoneString?Phone number
roleString?Job title
locationString?Office or remote location
avatarUrlString?Profile photo URL
isActiveBoolean@default(true)Soft-delete flag
hireDateDateTime?Employment start date
createdAtDateTime@default(now())
updatedAtDateTime@updatedAt
departmentIdString?FK → Department.idOwning department

Database indexes: departmentId, name

Relations:

  • departmentDepartment?
  • assetsAsset[] (assets currently assigned to this employee)
  • transfersFromTransferHistory[] (transfers initiated from this employee)
  • transfersToTransferHistory[] (transfers received by this employee)
  • checkoutFormsCheckoutForm[]

The core entity. Represents a single physical hardware item.

FieldTypeConstraintsDescription
idStringPK, @default(cuid())Internal identifier
assetIdString@uniqueHuman-readable ID (format: HW-00001)
qrCodeString?QR code value (reserved, not yet generated)
csvOriginalIdInt?Row ID from original CSV import
assetTagString?Physical label / serial number
categoryAssetCategory@default(OTHER)See enum below
nameStringrequiredAsset name / model name
manufacturerString?Brand (e.g. Apple, Dell, Samsung)
modelNumberString?Manufacturer model number
FieldTypeDescription
specificationsString?Free-text spec summary
operatingSystemString?OS installed
ramGbFloat?RAM in GB (parsed from specs)
storageGbFloat?Total storage in GB
storageUsedGbFloat?Used storage in GB
screenSizeInchFloat?Display size in inches
cpuInfoString?Processor description
imeiString?IMEI number (mobile devices)
networkPhoneString?Carrier / SIM info (phones)
FieldTypeConstraintsDescription
statusAssetStatus@default(WORKING)Operational status
conditionAssetCondition@default(NORMAL)Physical condition
batteryConditionBatteryCondition@default(UNKNOWN)Battery health
purchaseDateDateTime?Date of purchase
purchaseCostFloat?Purchase price
warrantyExpiryDateTime?Warranty end date
lastMaintenanceDateTime?Last service date
nextMaintenanceDateTime?Scheduled next service
serviceHistoryString?Free-text service notes
supportContactString?Vendor support contact
lastUsedDateDateTime?Last recorded usage date
FieldTypeConstraintsDescription
assignedToIdString?FK → Employee.idAssigned employee
departmentIdString?FK → Department.idOwning department
locationString?Physical location
FieldTypeDescription
photoUrlString?Condition photo URL (storage not yet connected)
productLinkString?Product page / spec sheet URL
notesString?Free-text notes
FieldType
createdAtDateTime @default(now())
updatedAtDateTime @updatedAt

Database indexes: assignedToId, departmentId, category, status, warrantyExpiry, purchaseDate, manufacturer

Relations:

  • assignedToEmployee?
  • departmentDepartment?
  • transferHistoryTransferHistory[]
  • checkoutFormsCheckoutForm[]

Immutable audit record of every asset movement between employees or locations.

FieldTypeConstraintsDescription
idStringPK
transferDateDateTime@default(now())When the transfer occurred
reasonTransferReason@default(OTHER)Reason for the transfer
notesString?Optional notes
assetIdStringFK → Asset.id, cascade deleteThe asset being transferred
fromEmployeeIdString?FK → Employee.idEmployee the asset came from (null = from pool)
toEmployeeIdString?FK → Employee.idEmployee receiving the asset (null = returned to pool)
fromLocationString?Origin location
toLocationString?Destination location
createdAtDateTime@default(now())Record creation

Database indexes: assetId, fromEmployeeId, toEmployeeId, transferDate

Transfer records are never updated — they are append-only. Deleting an asset cascades to its transfer history.


Tracks temporary loans of equipment to employees (as opposed to permanent assignments).

FieldTypeConstraintsDescription
idStringPK
checkoutDateDateTime@default(now())When equipment was checked out
returnDateDateTime?When it was actually returned
expectedReturnDateTime?Originally planned return date
statusCheckoutStatus@default(PENDING)Current state of the checkout
acknowledgedAtDateTime?When employee acknowledged receipt
acknowledgedNoteString?Note added at acknowledgement
conditionOnCheckoutString?Condition description at checkout
conditionOnReturnString?Condition description on return
notesString?General notes
assetIdStringFK → Asset.id, cascade deleteThe asset being checked out
employeeIdStringFK → Employee.idThe employee checking out
createdAtDateTime@default(now())
updatedAtDateTime@updatedAt

Database indexes: assetId, employeeId, status

Checkout lifecycle:

stateDiagram-v2
[*] --> PENDING : createCheckout()
PENDING --> ACKNOWLEDGED : acknowledgeCheckout()
PENDING --> CANCELLED : (manual cancel)
ACKNOWLEDGED --> RETURNED : returnCheckout()
RETURNED --> [*]
CANCELLED --> [*]

Append-only audit trail. Every create, update, delete, and assign operation writes a log entry. There is no foreign-key relation to other tables — entityType and entityId are stored as strings to allow logging of deleted records.

FieldTypeConstraintsDescription
idStringPK
actionActivityActionrequiredThe action performed
entityTypeStringrequiredModel name (e.g. "Asset", "Employee")
entityIdStringrequiredID of the affected record
descriptionStringrequiredHuman-readable description
metadataJson?Structured extra context (e.g. changed fields)
createdAtDateTime@default(now())Timestamp

Database indexes: (entityType, entityId), action, createdAt


ValueDescription
LAPTOPLaptops and notebooks
PHONEMobile phones
MONITORDisplay screens
PERIPHERALKeyboards, mice, webcams, headsets, etc.
STORAGEExternal drives, USB sticks
DOCKING_STATIONUSB-C / Thunderbolt hubs
OTHERAnything not categorised above
ValueDescription
WORKINGFully operational
BROKENNon-functional
IN_REPAIRCurrently being serviced
DECOMMISSIONEDRetired from service
LOSTCannot be located
UNASSIGNEDIn the pool, not assigned
ValueDescription
GOODLike new / excellent
NORMALNormal wear and tear
FAIRVisible wear, functional
POORHeavy wear or cosmetic damage
ABNORMALUnusual condition requiring review
ValueDescription
NORMALHealthy battery
LOWBelow recommended capacity
ABNORMALSwelling or irregular behaviour
SERVICE_RECOMMENDEDShould be serviced
UNKNOWNNot assessed
ValueDescription
NEW_ASSIGNMENTFirst assignment to a new employee
ROLE_CHANGEEmployee changed role or team
EMPLOYEE_DEPARTUREEmployee left the company
REASSIGNMENTMoved to a different employee
REPAIRSent for repair
RETURN_TO_POOLReturned to unassigned pool
BULK_IMPORTCreated during CSV data import
OTHERGeneral or unspecified reason
ValueDescription
PENDINGCheckout created, not yet acknowledged
ACKNOWLEDGEDEmployee confirmed receipt
RETURNEDEquipment returned
CANCELLEDCheckout cancelled
ValueTrigger
ASSET_CREATEDNew asset added
ASSET_UPDATEDAsset fields changed
ASSET_DELETEDAsset deleted
ASSET_ASSIGNEDAsset assigned to an employee
ASSET_UNASSIGNEDAsset returned to pool
ASSET_TRANSFERREDAsset moved between employees
EMPLOYEE_CREATEDNew employee added
EMPLOYEE_UPDATEDEmployee fields changed
EMPLOYEE_DEACTIVATEDEmployee soft-deleted
CHECKOUT_CREATEDCheckout form created
CHECKOUT_ACKNOWLEDGEDCheckout acknowledged by employee
CHECKOUT_RETURNEDEquipment returned
BULK_IMPORTData imported via CSV seed script

The prisma/seed.ts script imports hardware data from a CSV file (Hardware_update_11112025(Hardware).csv). The seed script performs:

  1. CSV parsing with quoted field support
  2. Department normalisation (maps freeform text to known department names)
  3. Employee name and department mapping
  4. Asset category auto-detection from device names
  5. Date parsing (handles multiple formats: DD/MM/YYYY, Month Year, MFG:MONTH,YEAR)
  6. Hardware spec extraction (RAM, storage, screen size, IMEI) from free-text fields
  7. Enum value normalisation (status, condition, battery)
  8. Initial TransferHistory record creation (BULK_IMPORT reason)
  9. ActivityLog entries for all imported records

To run: npm run db:seed

The seed is a one-time import. Re-running it will attempt to create duplicate records unless the database is cleared first.


The project currently uses prisma db push (schema push without migration files) for local development. To introduce tracked migrations:

Terminal window
# Create a named migration
npm run db:migrate
# Apply pending migrations (production)
npx prisma migrate deploy

Note: No migration files currently exist in the repository. The schema has been applied directly via db push. Requires manual verification before enabling migration-based deployments.