Skip to main content

Documentation Index

Fetch the complete documentation index at: https://grantmaster.dev/llms.txt

Use this file to discover all available pages before exploring further.

Engineering reference: For service contracts, EventBus events, and data-layer details see src/features/organizations/organizations.md.

Organizations Feature

Overview

The organizations feature manages tenant data and organizational classification in GrantMaster. It bridges legacy organization profiles with a modern structured classification system, enabling understanding of organizational identity across six dimensions: role, sector, geography, scale, legal/fiscal form, and operational model. Core Capabilities:
  • Organization profile creation, reading, and updates (name, legal entity, contact, subscription)
  • Tenant classification system with six structured dimensions
  • Lazy migration of legacy loose fields into the classification schema
  • Completeness scoring and audit trails with up to 50-item change history
  • Multi-step wizards for onboarding and ongoing updates

Data Model

Firestore Collections

CollectionDocument TypeDescription
organizationsOrganizationTenant metadata (name, legal entity, contact, logoUrl, subscription). Nested classification field contains TenantClassification

Key TypeScript Types

Organization (legacy profile):
  • name, slug, logoUrl, website — Branding
  • legalEntity — Legal structure (visitingAddress, postalAddress, statutoryName, kvkNumber, rsinNumber, vatNumber, legalRepresentative)
  • primaryContact — Primary person (name, email, phone, avatarUrl)
  • subscription — Subscription tier and billing data
  • annualBudgetRange — Budget classification (deprecated; migrates to classification.budgetTier)
  • Deprecated loose fields: targetSectors[], operationalScope, foundationYear, sdgs[], missionStatement, targetBeneficiaries
TenantClassification (nested on organizations document):
  • Dimension 1 (Role): organizationalRole: ORG_ROLE | null, missionStatement: string (≤500 chars), targetBeneficiaries: string (≤300 chars)
  • Dimension 2 (Sector): primarySector: SectorCode | null, secondarySectors: SectorCode[] (max 3)
  • Dimension 3 (Geography): geographicScope: GeographicScope | null, operatingCountries: string[] (ISO 3166-1 alpha-3), primaryCountry: string | null
  • Dimension 4 (Scale): budgetTier: BudgetTier | null
  • Dimension 5 (Legal/Fiscal): legalFiscal: { legalForm: LegalFormCode | null, fiscalStatus: FiscalStatusCode | null }
  • Dimension 6 (Operational): operationalModels: OperationalModelCode[] (max 3)
  • Goals & Foundation: foundationYear: number | null, sdgs: number[] (1-17, max 5)
  • Audit Trail: completenessScore: number (0-100), classificationHistory: ChangeRecord[] (max 50), lastUpdatedAt: ISO string, lastUpdatedBy: string
Completeness is 100% only when all six dimensions are filled (non-null and non-empty for arrays). Score = (filledDimensions / 6) * 100.

Key Behaviors

Lazy Migration

When getClassification() is called on an organization with no classification field but legacy loose fields present:
  1. Automatically creates classification from loose fields using dimension mappings
  2. Persists migration (fire-and-forget) to avoid repeated work
  3. Emits TENANT_CLASSIFICATION_UPDATED event with isMigration: true
Legacy Mappings:
  • targetSectors[]primarySector (first) + secondarySectors (rest, up to 3)
  • operationalScopegeographicScope
  • annualBudgetRangebudgetTier
  • sdgs[]sdgs[] (string-to-number conversion)
  • missionStatementmissionStatement (truncated to 500 chars)
  • targetBeneficiariestargetBeneficiaries

Partial Updates

Classification supports partial updates:
  • Only set fields you change; unset fields preserve existing values
  • Automatically diffs old vs. new to track changed fields
  • Maintains 50-item circular audit trail (classificationHistory)
  • Recomputes completeness score after each update
  • All changes validated via Zod schema before persistence

Completeness Scoring

Binary scoring across six dimensions:
  • Filled: When required field is non-null (or non-empty array for operational models)
  • Score: (filledDimensions / 6) * 100
  • UI Feedback: ClassificationCompletenessCard displays progress and lists missing dimensions

Organization Switching (Multi-Tenancy)

Via TenantContext:
  • useCurrentTenant() provides active organization context
  • switchOrganization(orgId) switches tenant; emits EXTENSION_TENANT_SWITCHED event
  • Cached in localStorage as selectedOrganizationId
  • Super Admin role sees all organizations; regular users see only their own

Service Contract

TenantClassificationService

Extends BaseService<TenantClassification>. Access via getTenantClassificationService() singleton.
ServiceOwnsKey Methods
TenantClassificationServiceClassification read/update on organizations docgetClassification(orgId), updateClassification(orgId, partial, userId, source)
getClassification(organizationId: string)Promise<ServiceResult<TenantClassification>>
  • Fetches classification from organizations document
  • Performs lazy migration if missing but loose fields exist
  • Emits migration event (fire-and-forget; doesn’t block)
  • Returns { success: true, data: TenantClassification } or { success: false, error: string }
updateClassification(organizationId, partial, userId, source)Promise<ServiceResult<TenantClassification>>
  • Loads current classification (triggers lazy migration if needed)
  • Merges partial updates and diffs for audit trail
  • Validates via Zod classificationPartialSchema
  • Persists to Firestore and emits TENANT_CLASSIFICATION_UPDATED event
  • Maintains up to 50 change records in history
  • Returns { success: true, data: updated } or { success: false, error }
Error Handling: All operations wrapped in withErrorBoundary(). Non-critical operations (event emission) use executeNonCritical() to avoid blocking on failure.

Events

Emitted

EventTriggerSeverityPersisted
TENANT_CLASSIFICATION_UPDATEDAfter successful classification update or lazy migrationINFOYes (persisted via EventBus)
Payload (TenantClassificationUpdatedPayload):
{
  fieldsChanged: string[]           // Names of fields that changed
  completenessScore: number         // New score (0-100)
  previousScore: number             // Previous score
  source: 'ONBOARDING' | 'SETTINGS' | 'MIGRATION' | 'AI_SUGGESTION'
  isMigration: boolean              // true if triggered by lazy migration
}
Correlation: organizationId in event metadata for tracing.

Consumed

This feature does not currently consume system events. Future integration points (not yet implemented):
  • Organization deletion → Clean up classification history
  • Subscription changes → Enforce tier-based feature limits

Dependencies

Internal

  • @/contexts/AuthContext — Current user for audit trail (currentUser.uid)
  • @/contexts/TenantContext — Organization context via useCurrentTenant()
  • @/core/BaseService — Base service class with error handling and EventBus integration
  • @/core/firebase — Firestore db reference
  • @/core/eventBus — System event emission
  • @/hooks/useZodForm — Form state, validation, toast notifications
  • @/features/organizations/classification.contracts — Classification types and createEmptyClassification()
  • @/schemas/classification.schema — Zod validation schemas

External

  • firebase/firestore — Document operations (getDoc, updateDoc, doc)
  • react, react-hook-form — React context and form state
  • zod — Schema validation
  • lucide-react — Icons in components
  • react-i18next — Internationalization in UI components

File Structure

src/features/organizations/
├── components/
│   ├── classification/
│   │   ├── ClassificationWizard.tsx         # Main 4-step wizard (role → sector → geography → scale/legal/ops)
│   │   ├── ClassificationStep.tsx           # Onboarding integration wrapper
│   │   ├── EditClassificationModal.tsx      # Modal UI for editing
│   │   ├── QuickClassificationModal.tsx     # Condensed modal variant
│   │   ├── ClassificationCompletenessCard.tsx # Progress badge + missing dimensions
│   │   ├── RoleSelector.tsx                 # Dimension 1: Role picker
│   │   ├── SectorSelector.tsx               # Dimension 2: Primary + secondary sectors
│   │   ├── GeographySelector.tsx            # Dimension 3: Scope + countries
│   │   ├── ScaleSelector.tsx                # Dimension 4: Budget tier
│   │   ├── LegalFiscalSelector.tsx          # Dimension 5: Legal form + fiscal status
│   │   ├── OperationalModelSelector.tsx     # Dimension 6: Operational models
│   │   ├── SDGSelector.tsx                  # SDG goals picker (1-17, max 5)
│   │   ├── MissionInput.tsx                 # Mission statement text area
│   │   └── index.ts
│   ├── CreateOrganizationWizard/
│   │   ├── index.tsx                        # 4-step org creation flow
│   │   └── steps/
│   │       ├── BasicInfoStep.tsx
│   │       ├── LegalDetailsStep.tsx
│   │       ├── AddressStep.tsx
│   │       ├── SubscriptionStep.tsx
│   │       └── index.ts
│   ├── OrganizationEditModal/
│   │   ├── index.tsx                        # 6-tab org profile editor
│   │   ├── constants.ts
│   │   └── tabs/
│   │       ├── GeneralTab.tsx
│   │       ├── MissionTab.tsx
│   │       ├── LegalTab.tsx
│   │       ├── FinancialTab.tsx
│   │       ├── ContactTab.tsx
│   │       ├── SubscriptionTab.tsx
│   │       └── index.ts
│   ├── OrganizationCard.tsx                 # Org display card (legacy profile)
│   ├── OrganizationSkeleton.tsx             # Loading state placeholder
│   └── index.ts
├── hooks/
│   ├── useOrganizationForm.ts               # Form state for org profile edit (logo, sectors, SDGs)
│   ├── useOrganizationForm.test.ts
│   ├── useTenantClassificationForm.ts       # Classification form state with partial save support
│   └── useTenantClassificationForm.test.ts
├── services/
│   ├── TenantClassificationService.ts       # Main read/update service + lazy migration
│   └── TenantClassificationService.test.ts
├── utils/
│   ├── classificationUtils.ts               # Pure functions: completeness, migration, diffing
│   └── classificationUtils.test.ts
├── types.ts                                  # Local type definitions (form data, props)
├── index.ts                                  # Public API exports
├── public.ts                                 # External v1.0.0 API surface
├── organizations.md                          # This document
└── README.md                                 # Extended reference (legacy)

Integration Points

Onboarding: ClassificationStep integrates into TenantOnboardingWizard (source: ONBOARDING) Organization Settings: OrganizationEditModal with classification tabs; EditClassificationModal for standalone editing Dashboard: ClassificationCompletenessCard displays progress at-a-glance with missing dimensions Multi-Tenancy: TenantContext manages organization switching and organization list for Super Admin and regular users