Skip to content

Notes & Item Lifecycle Dates

Date: 2026-06-13

Problem / motivation

Users need a lightweight, free-form way to attach contextual remarks to records in the system. The first consumer is the Item catalog, but the note should not be item-specific — it should be a generic, polymorphic resource that can hang off any entity in the future (clients, suppliers, orders, …) without a schema change. Separately, the Item catalog needs to track two lifecycle dates that the business already records on paper: when an item was manufactured and when it expires.

In scope

  • A new top-level notes resource (model → repo → service → graphql → RBAC → migration → tests).
  • Notes link polymorphically to any entity via (entity_type, entity_id), where entity_type is the existing SourceType enum (the same enum file_entities uses). For this feature the frontend only wires notes to items (SourceType.ITEMS), but nothing in the model restricts it.
  • CRUD over notes: create, update, list-for-entity, get-by-id, delete.
  • Two new nullable columns on items: manufacture_date and expiration_date.

Out of scope

  • Wiring notes to entities other than items (clients/suppliers/orders/etc.) — the model supports it, but no frontend contract is defined here.
  • Per-lot / per-serial expiration tracking. The two dates live on the item row itself, not on inventory lots.
  • Mentions, attachments, rich text, threading, or note reactions.

Data model changes

  • New table notes (migration add_notes_table):
  • id UUID PK
  • content Text, not null
  • entity_type SmallInteger (IntEnum(SourceType)), not null
  • entity_id UUID, not null
  • created_by_id UUID FK → users.id, not null
  • created_at timestamptz, not null, default now()
  • Composite index notes_entity_type_entity_id_idx on (entity_type, entity_id) — the only query pattern is "list notes for this entity".
  • items table gains two nullable date columns (migration add_dates_to_items):
  • manufacture_date — date the item was manufactured.
  • expiration_date — date the item expires.

Migration chain tip at authoring time: add_parent_id_to_exp_categories. add_notes_tabledown_revision = add_parent_id_to_exp_categories; add_dates_to_itemsdown_revision = add_notes_table.

GraphQL surface

Queries:

  • notesForEntity(entityType: SourceType!, entityId: UUID!): [Note!]!
  • note(id: UUID!): Note

Mutations:

  • createNote(note: NoteInput!): Note!
  • updateNote(note: NoteInput!): Note!
  • deleteNote(id: UUID!): Boolean!

Types:

  • input NoteInput { id, content, entityType, entityId } — shared create/update input following the quotes/orders to_orm_model() convention.
  • type Note { id, content, entityType, entityId, createdById, createdAt }

RBAC

  • New Path.NOTES and Resource.NOTES enum members in app/graphql/rbac/models/models.py.
  • Mutations are guarded by PathPermissionAccess(Path.NOTES).
  • Backfill: Path.NOTES added to scripts/backfill_new_path_permissions.py NEW_PATHS so existing tenants get a PathPermission(can_view=True) row.

Background tasks / cron

None.

Frontend contract

  • The item detail screen renders a notes panel: call notesForEntity(entityType: ITEMS, entityId: <itemId>), and createNote / updateNote / deleteNote with NoteInput.entityType = ITEMS and entityId = <itemId>.
  • ItemInput accepts optional manufactureDate and expirationDate (ISO Date). Item / ItemLite expose both fields (nullable). The items landing page (ItemLandingPage) also returns manufactureDate and expirationDate so they can be shown/filtered in the list view.

Open questions

None — defaults chosen: notes are tenant-visible, editable and deletable by any user with the NOTES path; created_by_id is retained for audit only.

What shipped

  • New notes module: Note model, NoteRepository, NoteService, NoteInput / Note GraphQL types, and NoteQueries / NoteMutations (auto-discovered into the schema). Files under app/graphql/notes/.
  • Note registered in load_models.py.
  • Path.NOTES + Resource.NOTES added to rbac/models/models.py; NOTES added to the medical tenant path list in tenant_type.py (commercial already grants all non-health paths); Path.NOTES appended to NEW_PATHS in backfill_new_path_permissions.py.
  • items.manufacture_date and items.expiration_date columns added to the Item model, ItemInput.to_orm_model, and ItemLiteResponse (so both Item and ItemLite expose them).
  • Migrations: add_notes_table then add_dates_to_items.
  • Tests under tests/graphql/notes/: repository (real-DB CRUD) + service (mapping/delegation).

Future additions

  • Wire notes to more entity types — the model is already polymorphic; only the item frontend is wired. Adding clients/suppliers/orders is frontend-only (pass a different SourceType). Deferred until product asks for it.
  • Per-lot expiration tracking — the two dates currently live on the item row. If lot/serial tracking needs its own expiry, that belongs on the inventory lot, not the item. Deferred; no current demand.