Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

331 lines
19 KiB
Markdown

# PRD: Documents System Improvements
- Slug: `2026-02-27-documents-system-improvements`
- Date: `2026-02-27`
- Status: Draft
## Summary
A 5-phase overhaul of the Alga PSA documents system to add entity-scoped folders, admin-configurable folder templates, client portal visibility controls, shareable document URLs, and a knowledge base foundation. The system is designed so that each phase builds on the previous ones, with the entire document infrastructure serving as the foundation for a full MSP knowledge base.
## Problem
The current documents system has several gaps that limit its value for MSPs:
1. **Flat folders per tenant** — Folders are global to the tenant. A client, project, or ticket cannot have its own private folder tree. This means all users see one giant folder structure that mixes documents from every entity.
2. **No visibility control for clients** — There is no `is_client_visible` flag. MSPs cannot control which documents clients see on the portal. The client portal only shows documents embedded in individual tickets and project tasks — no central Documents page.
3. **No folder templates** — Every folder is manually created. There is no way for an admin to define a standard structure (e.g., "Contracts / Invoices / Meeting Notes") that auto-applies to new clients.
4. **No auto-filing** — Uploading a ticket attachment doesn't place it in any folder. Documents accumulate without organization.
5. **No shareable links** — There is no way to generate a URL that allows external access to a document (public or authenticated).
6. **No knowledge base** — Documents exist as files and rich-text notes, but there is no article concept, publishing workflow, review cycle, or audience targeting. MSPs must use separate tools (IT Glue, Hudu) for KB, losing the integrated advantage.
## Goals
1. **Entity-scoped folders** — Each client, project, ticket, and contract gets its own folder tree, isolated from others.
2. **Visibility toggle** — MSP users can mark documents/folders as client-visible with a single click. Clients never see internal-only documents.
3. **Folder templates** — Admins define folder structures per entity type. Templates are applied lazily on first document access.
4. **Auto-filing** — Uploads are automatically placed in the correct folder based on entity type and context.
5. **Client portal Documents hub** — A dedicated Documents page in the client portal aggregating all client-visible documents with folder browsing, search, and filtering.
6. **Share URLs** — MSP users can generate public, portal-authenticated, or password-protected share links for any document.
7. **Knowledge base foundation** — KB articles built as a document subtype with audience targeting, publishing workflow, review cycles, and category/tag taxonomy. Both internal and client-facing KB supported.
## Non-Goals
- AI-powered article generation or smart suggestions (future work, Phase 5 lays the foundation)
- Real-time collaborative editing of KB articles (existing Hocuspocus/Yjs infrastructure handles this for documents already)
- Document import from IT Glue / Hudu / Confluence (separate migration initiative)
- Credential / password vault (separate feature)
- Document retention policies / auto-archival
- E-signature integration
- Full-text search engine (e.g., Elasticsearch) — uses PostgreSQL text search for now
## Users and Primary Flows
### Personas
| Persona | Description |
|---------|-------------|
| **MSP Admin** | Configures folder templates, manages visibility settings, creates KB articles |
| **MSP Technician** | Uploads documents to entities, uses KB articles for troubleshooting, generates share links |
| **Client Contact (Portal)** | Views shared documents, browses external KB articles, downloads files via share links |
| **External Recipient** | Accesses documents via public share URLs without any account |
### Primary Flows
**Flow 1: MSP Admin configures folder template**
1. Admin navigates to Settings → Document Templates
2. Creates template "MSP Client Default" for entity type "client"
3. Defines folder tree: /Contracts, /Contracts/SLAs, /Invoices, /Meeting Notes, /Technical Documentation
4. Sets /Contracts and /Invoices as client-visible
5. Marks template as default for "client"
**Flow 2: Technician opens client Documents tab (lazy folder init)**
1. Technician navigates to Client → Documents tab
2. System checks `document_entity_folder_init` — no record found
3. System applies default "client" template → creates entity-scoped folders
4. Folder tree renders with pre-created structure
5. Technician uploads a contract → auto-filed to /Contracts
**Flow 3: MSP user toggles document visibility**
1. In Documents list, user sees eye icon next to each document
2. Clicks to toggle `is_client_visible` → eye icon changes state
3. Document is now visible in client portal Documents hub
4. Bulk select + toggle also supported
**Flow 4: Client views Documents hub in portal**
1. Client contact logs into portal
2. Navigates to "Documents" tab in top nav
3. Sees folder tree of only client-visible folders
4. Browses by folder, searches by name, filters by source (tickets/projects/contracts)
5. Views/downloads documents
**Flow 5: MSP user generates share link**
1. Right-clicks document → "Share"
2. Dialog opens with share type selector (Public / Portal Auth / Password Protected)
3. Sets optional expiry and max downloads
4. Clicks "Generate" → URL with copy button appears
5. Shares URL with recipient
**Flow 6: MSP user creates KB article**
1. Navigates to Knowledge Base section
2. Clicks "New Article" → selects template (e.g., "Troubleshooting Guide")
3. Editor opens with template structure pre-filled
4. Sets audience to "client", category to "FAQs"
5. Writes content using BlockNote editor
6. Submits for review → reviewer approves → article published
7. Published article automatically appears in client portal KB section
## UX / UI Notes
### Phase 1 — Entity Mode Folder Tree
- Documents component in entity mode gains a collapsible folder sidebar (same as global folder mode, but scoped to entity)
- Eye icon (visibility toggle) appears on each document row/card and each folder in the tree
- Eye icon only rendered for MSP users, not in client portal context
### Phase 3 — Client Portal Documents Hub
- New top-level nav item "Documents" between "Projects" and "Appointments"
- Left sidebar: folder tree (read-only, no create/delete)
- Main area: document grid/list with cards showing name, type icon, date, source entity badge
- Filter bar: search, source type dropdown (Tickets / Projects / Contracts / All), date range
### Phase 4 — Share Link Dialog
- Modal dialog launched from document context menu
- Three share type cards with radio selection
- Password input, date picker, number input for max downloads
- Generated URL shown in copyable input field
- List of existing share links below with status, copy, and revoke actions
### Phase 4 — Public Share Landing Page
- Minimal standalone page (not wrapped in MSP or portal layout)
- Shows: document name, file type icon, file size, expiry countdown
- Password input if password-protected
- Large "Download" button
- Tenant branding (logo) if available
### Phase 5 — KB Article Editor
- Same BlockNote editor as regular documents
- Metadata sidebar panel: audience selector, article type, category picker, review cycle, related articles
- Status bar at top: Draft / In Review / Published / Archived with transition buttons
## Requirements
### Functional Requirements
#### Phase 1: Entity-Scoped Folders + Visibility
| ID | Requirement |
|----|-------------|
| FR-1.1 | `document_folders` gains `entity_id` and `entity_type` columns. When set, folder is scoped to that entity. When NULL, folder is global (current behavior). |
| FR-1.2 | Same `folder_path` can exist for different entities (unique constraint includes entity scope). |
| FR-1.3 | `documents` and `document_folders` gain `is_client_visible` boolean column, default `false`. |
| FR-1.4 | `getFolderTree()` accepts optional `entityId`/`entityType` params and returns only matching folders. |
| FR-1.5 | `getDocumentsByFolder()` respects entity scope. |
| FR-1.6 | `createFolder()` accepts `entityId`, `entityType`, `isClientVisible`. |
| FR-1.7 | `toggleDocumentVisibility(documentIds, isClientVisible)` bulk-toggles visibility. |
| FR-1.8 | `toggleFolderVisibility(folderId, isClientVisible, cascade?)` toggles folder and optionally cascades to contained documents. |
| FR-1.9 | Documents component in entity mode shows folder tree sidebar. |
| FR-1.10 | Visibility toggle (eye icon) shown on documents and folders in MSP context only. |
| FR-1.11 | `document_folders` has RLS tenant isolation policies. |
| FR-1.12 | `ensureEntityFolders(entityId, entityType)` stub returns empty tree (Phase 2 fills in logic). |
#### Phase 2: Folder Templates + Auto-Filing
| ID | Requirement |
|----|-------------|
| FR-2.1 | `document_folder_templates` table stores template name, entity type, and default flag per tenant. |
| FR-2.2 | `document_folder_template_items` table stores the folder tree structure for each template. |
| FR-2.3 | `document_entity_folder_init` table tracks which entities have had folders initialized. |
| FR-2.4 | At most one template per entity type per tenant can be marked as default (partial unique index). |
| FR-2.5 | Admin can CRUD folder templates via Settings → Document Templates. |
| FR-2.6 | `ensureEntityFolders()` checks init tracker, applies default template if uninitialized, records init. |
| FR-2.7 | Template application is idempotent — skips folders that already exist. |
| FR-2.8 | `uploadDocument()` auto-files: if entity has a matching folder, sets `folder_path`. Best-effort — never fails the upload. |
| FR-2.9 | Template editor supports drag-and-drop reorder, add/remove folders, client-visibility toggles per folder. |
| FR-2.10 | Documents component in entity mode calls `ensureEntityFolders()` on mount. |
#### Phase 3: Client Portal Documents Hub
| ID | Requirement |
|----|-------------|
| FR-3.1 | New "Documents" page in client portal at `/client-portal/documents`. |
| FR-3.2 | "Documents" link added to client portal top navigation. |
| FR-3.3 | `getClientDocuments()` returns paginated docs where `is_client_visible = true` AND associated with authenticated user's client. |
| FR-3.4 | Aggregates documents across: direct client associations, client's tickets, client's project tasks, client's contracts. |
| FR-3.5 | `getClientDocumentFolders()` returns folder tree for client-visible folders only. |
| FR-3.6 | Client portal documents page shows folder tree (read-only), document grid/list, search, and filters. |
| FR-3.7 | Client A can NEVER see client B's documents (enforced at query level via client_id filter). |
| FR-3.8 | `downloadClientDocument()` verifies both `is_client_visible` and client ownership before serving. |
| FR-3.9 | File view API route extended to check `is_client_visible` for client users. |
| FR-3.10 | Existing inline ticket document display continues working unchanged (does not require `is_client_visible`). |
#### Phase 4: Document Share URLs
| ID | Requirement |
|----|-------------|
| FR-4.1 | `document_share_links` table stores share token, type, password hash, expiry, max downloads, revocation status. |
| FR-4.2 | `document_share_access_log` table records every access with IP, user agent, timestamp. |
| FR-4.3 | `createShareLink()` generates 256-bit cryptographically random token. |
| FR-4.4 | Three share types: `public` (no auth), `portal_authenticated` (portal login required), `password_protected`. |
| FR-4.5 | Share links support optional expiry date and max download count. |
| FR-4.6 | Public share route (`/api/share/[token]`) works without session. |
| FR-4.7 | Password-protected links require password verification before download. |
| FR-4.8 | Portal-authenticated links require active client portal session and verify client access. |
| FR-4.9 | Share info route (`/api/share/[token]/info`) returns document metadata without downloading. |
| FR-4.10 | Public landing page at `/share/[token]` shows document info and download button. |
| FR-4.11 | `ShareLinkDialog` component allows creating/listing/revoking share links from document context menu. |
| FR-4.12 | Access is logged and download count incremented on each access. |
| FR-4.13 | MSP users can revoke any share link at any time. |
#### Phase 5: Knowledge Base Foundation
| ID | Requirement |
|----|-------------|
| FR-5.1 | `kb_articles` table extends `documents` — every article has a parent document (inherits content, versions, associations). |
| FR-5.2 | Article types: sop, runbook, troubleshooting, faq, how_to, reference, policy. |
| FR-5.3 | Audience targeting: internal, client, public. |
| FR-5.4 | Publishing workflow: draft → in_review → published → archived. |
| FR-5.5 | Publishing an article with `audience = 'client'` auto-sets parent document's `is_client_visible = true`. |
| FR-5.6 | Archiving clears `is_client_visible` on parent document. |
| FR-5.7 | Review cycle: configurable review_cycle_days, next_review_due, staleness indicators. |
| FR-5.8 | Review assignment: submit for review to specific users, reviewers approve or request changes. |
| FR-5.9 | `kb_article_relations` for related/prerequisite/supersedes linking. |
| FR-5.10 | `kb_article_templates` for pre-built article structures (BlockNote JSON). |
| FR-5.11 | MSP KB section: article list with filters, article editor with metadata sidebar, publishing controls, review dashboard. |
| FR-5.12 | Client portal KB section: published client-audience articles with category browsing, search, "was this helpful?" feedback. |
| FR-5.13 | Articles can be tagged via existing tag system (`tagged_type = 'knowledge_base_article'`). |
| FR-5.14 | `createArticleFromTicket()` pre-populates article from ticket data (foundation for AI enhancement). |
| FR-5.15 | View count and helpfulness tracking (helpful_count, not_helpful_count). |
| FR-5.16 | URL-friendly slug per article, unique per tenant. |
### Non-functional Requirements
| ID | Requirement |
|----|-------------|
| NFR-1 | All new tables use composite keys with `tenant` and RLS tenant isolation policies. |
| NFR-2 | All new tables include inline `distributeIfCitus()` calls within the same migration file (not separate Citus migrations). Pattern: define helper at top of migration, call after each `createTable`. See `server/migrations/20260219000001_create_sla_policies.cjs` for reference. |
| NFR-3 | No database triggers (Citus constraint). |
| NFR-4 | Share token validation must not use tenant-scoped connection (uses admin connection). |
| NFR-5 | Client portal queries must always filter by authenticated user's client_id — no exceptions. |
| NFR-6 | Existing document flows (upload, download, entity mode, folder mode) must not regress. |
## Data / API / Integrations
### New Database Tables (by phase)
**Phase 1**: Columns added to `document_folders` (entity_id, entity_type) and `documents`/`document_folders` (is_client_visible).
**Phase 2**: `document_folder_templates`, `document_folder_template_items`, `document_entity_folder_init`
**Phase 4**: `document_share_links`, `document_share_access_log`
**Phase 5**: `kb_articles`, `kb_article_relations`, `kb_article_templates`, `kb_article_reviewers`
### New API Routes
| Route | Phase | Auth | Description |
|-------|-------|------|-------------|
| `GET /api/share/[token]` | 4 | None/Conditional | Download shared document |
| `GET /api/share/[token]/info` | 4 | None | Get share metadata |
| `GET /share/[token]` (page) | 4 | None | Public share landing page |
### New Server Action Files
| File | Phase | Description |
|------|-------|-------------|
| `packages/documents/src/actions/folderTemplateActions.ts` | 2 | Template CRUD, apply template |
| `packages/documents/src/actions/shareActions.ts` | 4 | Share link CRUD, token validation |
| `packages/documents/src/actions/kbActions.ts` | 5 | Article CRUD, publishing, review |
| `packages/client-portal/src/actions/client-portal-actions/client-documents.ts` | 3 | Client portal document queries |
## Security / Permissions
- **Tenant isolation**: All tables use RLS. All queries include tenant filter.
- **Client isolation**: Client portal queries derive client_id from authenticated user → contact → client chain. Never trust client-provided IDs.
- **Share URL security**: Tokens are 256-bit cryptographically random. Public routes use admin connection for lookup, then set tenant context. Password-protected links use bcrypt hashing.
- **Visibility enforcement**: `is_client_visible = false` by default. MSP explicitly controls what clients see.
- **RBAC**: Existing `document:read/create/update` permissions enforced for client portal users. MSP users require corresponding entity permissions.
## Rollout / Migration
### Phase Order and Dependencies
```
Phase 1 (Entity-Scoped Folders + Visibility)
├──→ Phase 2 (Folder Templates + Auto-Filing)
├──→ Phase 3 (Client Portal Documents Hub)
├──→ Phase 4 (Share URLs) — can parallel with P2/P3
└──→ Phase 5 (Knowledge Base) — needs P1 + P3
```
### Migration Safety
- Phase 1 migrations add nullable columns and a new default-false boolean — **no data transformation needed**.
- Unique constraint change on `document_folders` must handle existing NULL entity_id rows correctly (uses COALESCE).
- `is_client_visible` defaults to `false` — all existing documents remain invisible to clients until explicitly toggled. This is intentionally conservative.
## Open Questions
1. Should the client portal Documents page be behind a feature flag initially?
2. Should folder templates be seeded with defaults (e.g., "MSP Client Default") or start empty?
3. For KB articles, should `audience = 'public'` articles be accessible without any login at all?
## Acceptance Criteria (Definition of Done)
### Phase 1
- [ ] Entity-scoped folders can be created for clients, projects, tickets, contracts
- [ ] Two different clients can have folders with the same path
- [ ] `is_client_visible` toggle works on documents and folders
- [ ] Existing global folders still work (regression test)
- [ ] Documents component in entity mode shows folder tree sidebar
### Phase 2
- [ ] Admin can create/edit/delete folder templates
- [ ] Default template is applied lazily on first entity document access
- [ ] Template application is idempotent
- [ ] Document uploads auto-file into matching entity folders
- [ ] Admin UI for template management is functional
### Phase 3
- [ ] Client portal has "Documents" nav item and page
- [ ] Only `is_client_visible = true` documents appear
- [ ] Client A cannot see client B's documents
- [ ] Folder tree, search, and source filters work
- [ ] Download works with proper permission checks
### Phase 4
- [ ] Public share link works in incognito (no auth)
- [ ] Portal-authenticated link requires login
- [ ] Password-protected link requires password
- [ ] Expiry and max download limits enforced
- [ ] Revocation immediately invalidates the link
- [ ] Access logging records every download
### Phase 5
- [ ] KB article created as document subtype (dual identity)
- [ ] Publishing with audience='client' makes article visible in portal
- [ ] Archiving removes portal visibility
- [ ] Review cycle and staleness indicators work
- [ ] Article templates populate editor with structure
- [ ] Tags and categories are functional
- [ ] Client portal KB section shows published client articles with feedback