> ## Documentation Index
> Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Organization Document Templates - Architecture Document

> Module: Platform (PF) Feature ID: PF-64 Version: 1.0.0 Last Updated: 2026-01-30 Status: Production Ready

**Module:** Platform (PF)\
**Feature ID:** PF-64\
**Version:** 1.0.0\
**Last Updated:** 2026-01-30\
**Status:** Production Ready

***

## Executive Summary

The Organization Document Templates module provides centralized management of letterheads, document templates, and approval chain templates for cross-core document generation. This enables consistent, professional document output across GR (Governance & Risk), HR, and other modules.

***

## Architecture Overview

### Module Purpose and Scope

PF-64 provides:

* **Letterhead Management**: Organization branding for document headers/footers
* **Document Templates**: Structure templates for policies, procedures, letters
* **Approval Chain Templates**: Reusable approval workflows
* **PDF Generation**: Server-side document rendering with letterhead integration

### Key Design Decisions

1. **Multi-tenant with System Templates**: Organizations can create custom templates while also accessing system-provided templates
2. **Version Control**: Document templates maintain immutable version history
3. **Status-based Deprecation**: Templates use status fields instead of soft-delete to preserve audit trails
4. **Dynamic Approver Resolution**: Approval chains integrate with HR module for manager/department head lookup

***

## Database Design

### Entity Relationship Diagram

```
┌─────────────────────┐     ┌─────────────────────────┐
│   pf_letterheads    │     │  pf_document_templates  │
├─────────────────────┤     ├─────────────────────────┤
│ id (PK)             │     │ id (PK)                 │
│ organization_id (FK)│◄────┤ letterhead_id (FK)      │
│ name                │     │ organization_id (FK)    │
│ logo_url            │     │ name                    │
│ logo_position       │     │ template_type           │
│ organization_name   │     │ status                  │
│ address_*           │     │ sections (JSONB)        │
│ primary_color       │     │ config (JSONB)          │
│ footer_text         │     │ version                 │
│ is_default          │     │ usage_count             │
│ is_system           │     │ is_default              │
└─────────────────────┘     │ is_system               │
                            └───────────┬─────────────┘
                                        │
                                        ▼
                    ┌───────────────────────────────────┐
                    │ pf_document_template_versions     │
                    ├───────────────────────────────────┤
                    │ id (PK)                           │
                    │ template_id (FK)                  │
                    │ version_number                    │
                    │ sections (JSONB)                  │
                    │ config (JSONB)                    │
                    │ change_notes                      │
                    │ created_by                        │
                    │ created_at                        │
                    └───────────────────────────────────┘

┌─────────────────────────────┐
│ pf_approval_chain_templates │
├─────────────────────────────┤
│ id (PK)                     │
│ organization_id (FK)        │
│ name                        │
│ category                    │
│ steps (JSONB)               │
│ usage_count                 │
│ is_default                  │
│ is_system                   │
└─────────────────────────────┘
```

### Table Specifications

#### pf\_letterheads

| Column             | Type    | Constraints                             | Description                 |
| ------------------ | ------- | --------------------------------------- | --------------------------- |
| id                 | UUID    | PK, DEFAULT gen\_random\_uuid()         | Unique identifier           |
| organization\_id   | UUID    | FK → pf\_organizations, NULL for system | Owner organization          |
| name               | TEXT    | NOT NULL                                | Display name                |
| logo\_url          | TEXT    | -                                       | Logo image URL              |
| logo\_position     | TEXT    | 'left', 'center', 'right'               | Logo alignment              |
| organization\_name | TEXT    | -                                       | Org name for header         |
| address\_line\_1   | TEXT    | -                                       | Street address              |
| city               | TEXT    | -                                       | City                        |
| state              | TEXT    | -                                       | State/Province              |
| zip\_code          | TEXT    | -                                       | Postal code                 |
| phone              | TEXT    | -                                       | Contact phone               |
| email              | TEXT    | -                                       | Contact email               |
| primary\_color     | TEXT    | -                                       | Brand color (hex)           |
| secondary\_color   | TEXT    | -                                       | Accent color (hex)          |
| footer\_text       | TEXT    | -                                       | Footer/confidentiality text |
| is\_default        | BOOLEAN | DEFAULT false                           | Default for org             |
| is\_system         | BOOLEAN | DEFAULT false                           | System-provided             |

#### pf\_document\_templates

| Column           | Type    | Constraints          | Description                                     |
| ---------------- | ------- | -------------------- | ----------------------------------------------- |
| id               | UUID    | PK                   | Unique identifier                               |
| organization\_id | UUID    | FK, NULL for system  | Owner organization                              |
| letterhead\_id   | UUID    | FK → pf\_letterheads | Associated letterhead                           |
| name             | TEXT    | NOT NULL             | Template name                                   |
| description      | TEXT    | -                    | Template description                            |
| template\_type   | TEXT    | NOT NULL             | policy, procedure, letter, report\_cover, other |
| status           | TEXT    | DEFAULT 'draft'      | draft, active, deprecated                       |
| sections         | JSONB   | NOT NULL             | Section definitions                             |
| config           | JSONB   | DEFAULT '{}'         | Template configuration                          |
| version          | INTEGER | DEFAULT 1            | Current version number                          |
| usage\_count     | INTEGER | DEFAULT 0            | Times template used                             |
| is\_default      | BOOLEAN | DEFAULT false        | Default for type                                |
| is\_system       | BOOLEAN | DEFAULT false        | System-provided                                 |

#### pf\_document\_template\_versions

| Column          | Type        | Constraints                  | Description                |
| --------------- | ----------- | ---------------------------- | -------------------------- |
| id              | UUID        | PK                           | Version record ID          |
| template\_id    | UUID        | FK → pf\_document\_templates | Parent template            |
| version\_number | INTEGER     | NOT NULL                     | Sequential version         |
| sections        | JSONB       | NOT NULL                     | Sections at this version   |
| config          | JSONB       | NOT NULL                     | Config at this version     |
| change\_notes   | TEXT        | -                            | Version change description |
| created\_by     | UUID        | FK → pf\_profiles            | Version creator            |
| created\_at     | TIMESTAMPTZ | NOT NULL                     | Version timestamp          |

#### pf\_approval\_chain\_templates

| Column           | Type    | Constraints         | Description                                          |
| ---------------- | ------- | ------------------- | ---------------------------------------------------- |
| id               | UUID    | PK                  | Unique identifier                                    |
| organization\_id | UUID    | FK, NULL for system | Owner organization                                   |
| name             | TEXT    | NOT NULL            | Chain name                                           |
| description      | TEXT    | -                   | Chain description                                    |
| category         | TEXT    | NOT NULL            | policy, document, financial, hr, compliance, general |
| steps            | JSONB   | NOT NULL            | Approval step definitions                            |
| usage\_count     | INTEGER | DEFAULT 0           | Times chain applied                                  |
| is\_default      | BOOLEAN | DEFAULT false       | Default for category                                 |
| is\_system       | BOOLEAN | DEFAULT false       | System-provided                                      |

### JSONB Schemas

#### Section Definition

```typescript theme={null}
interface TemplateSection {
  name: string;
  help_text?: string;
  required?: boolean;
  placeholder?: string;
}
```

#### Template Config

```typescript theme={null}
interface TemplateConfig {
  numbering_style?: 'decimal' | 'alpha' | 'roman' | 'none';
  show_approval_block?: boolean;
  default_watermark?: string;
}
```

#### Approval Step

```typescript theme={null}
interface ApprovalStep {
  step_order: number;
  name: string;
  approver_type: 'user' | 'role' | 'dynamic' | 'field';
  approver_config: {
    userId?: string;
    role?: string;
    resolver?: 'manager' | 'department_head';
    fieldPath?: string;
  };
  timeout_hours?: number;
  reminder_hours?: number;
  allow_delegate?: boolean;
}
```

### Index Strategy

```sql theme={null}
-- Letterheads
CREATE INDEX idx_pf_letterheads_org ON pf_letterheads(organization_id);
CREATE INDEX idx_pf_letterheads_default ON pf_letterheads(organization_id, is_default) WHERE is_default = true;

-- Document Templates
CREATE INDEX idx_pf_document_templates_org ON pf_document_templates(organization_id);
CREATE INDEX idx_pf_document_templates_type ON pf_document_templates(organization_id, template_type);
CREATE INDEX idx_pf_document_templates_status ON pf_document_templates(organization_id, status);
CREATE INDEX idx_pf_document_templates_default ON pf_document_templates(organization_id, template_type, is_default) WHERE is_default = true;

-- Template Versions
CREATE INDEX idx_pf_template_versions_template ON pf_document_template_versions(template_id);
CREATE INDEX idx_pf_template_versions_number ON pf_document_template_versions(template_id, version_number DESC);

-- Approval Chain Templates
CREATE INDEX idx_pf_approval_chains_org ON pf_approval_chain_templates(organization_id);
CREATE INDEX idx_pf_approval_chains_category ON pf_approval_chain_templates(organization_id, category);
CREATE INDEX idx_pf_approval_chains_default ON pf_approval_chain_templates(organization_id, category, is_default) WHERE is_default = true;
```

***

## RLS Policy Summary

### Multi-Tenant Isolation

All tables implement organization-based RLS:

```sql theme={null}
-- Pattern for org-scoped SELECT
CREATE POLICY "select_org_or_system" ON table_name
FOR SELECT USING (
  organization_id IS NULL  -- System templates visible to all
  OR organization_id IN (
    SELECT organization_id FROM pf_user_organizations 
    WHERE profile_id = auth.uid()
  )
);
```

### System Template Protection

System templates (is\_system = true) are read-only:

```sql theme={null}
-- Prevent modification of system templates
CREATE POLICY "update_non_system" ON table_name
FOR UPDATE USING (
  is_system = false
  AND organization_id IN (SELECT ...)
) WITH CHECK (
  is_system = false
  AND organization_id = OLD.organization_id  -- Prevent org_id change
);
```

### Version History Immutability

Template versions are append-only:

```sql theme={null}
-- No UPDATE or DELETE policies on pf_document_template_versions
CREATE POLICY "versions_insert" ON pf_document_template_versions
FOR INSERT WITH CHECK (
  EXISTS (
    SELECT 1 FROM pf_document_templates t
    WHERE t.id = template_id
    AND t.organization_id IN (SELECT ...)
  )
);

CREATE POLICY "versions_select" ON pf_document_template_versions
FOR SELECT USING (
  EXISTS (
    SELECT 1 FROM pf_document_templates t
    WHERE t.id = template_id
    AND (t.organization_id IS NULL OR t.organization_id IN (SELECT ...))
  )
);
```

***

## Hook Architecture

### Query Key Factory Pattern

```typescript theme={null}
// src/platform/templates/hooks/queryKeys.ts
export const templateKeys = {
  all: ['templates'] as const,
  
  letterheads: {
    all: () => [...templateKeys.all, 'letterheads'] as const,
    list: (orgId: string, filters?: LetterheadFilters) => 
      [...templateKeys.letterheads.all(), orgId, filters] as const,
    detail: (id: string) => 
      [...templateKeys.letterheads.all(), 'detail', id] as const,
    default: (orgId: string) => 
      [...templateKeys.letterheads.all(), 'default', orgId] as const,
  },
  
  documents: {
    all: () => [...templateKeys.all, 'documents'] as const,
    list: (orgId: string, filters?: DocumentTemplateFilters) => 
      [...templateKeys.documents.all(), orgId, filters] as const,
    detail: (id: string) => 
      [...templateKeys.documents.all(), 'detail', id] as const,
    versions: (templateId: string) => 
      [...templateKeys.documents.all(), 'versions', templateId] as const,
  },
  
  approvalChains: {
    all: () => [...templateKeys.all, 'approvalChains'] as const,
    list: (orgId: string, filters?: ApprovalChainFilters) => 
      [...templateKeys.approvalChains.all(), orgId, filters] as const,
    detail: (id: string) => 
      [...templateKeys.approvalChains.all(), 'detail', id] as const,
    default: (orgId: string, category: string) => 
      [...templateKeys.approvalChains.all(), 'default', orgId, category] as const,
  },
};
```

### Mutation Pattern with Cache Invalidation

```typescript theme={null}
export function useLetterheadMutation() {
  const queryClient = useQueryClient();
  const { currentOrganization } = useOrganization();

  const create = useMutation({
    mutationFn: async (data: CreateLetterheadInput) => {
      const { data: result, error } = await supabase
        .from('pf_letterheads')
        .insert({ ...data, organization_id: currentOrganization?.id })
        .select()
        .single();
      if (error) throw error;
      return result;
    },
    onSuccess: () => {
      // Invalidate all letterhead queries for this org
      queryClient.invalidateQueries({
        queryKey: templateKeys.letterheads.all(),
      });
    },
  });

  return { create, update, remove, setDefault };
}
```

### Error Handling Conventions

```typescript theme={null}
// Consistent error handling across all hooks
const { data, error, isLoading } = useQuery({
  queryKey: templateKeys.letterheads.detail(id),
  queryFn: async () => {
    const { data, error } = await supabase
      .from('pf_letterheads')
      .select('*')
      .eq('id', id)
      .maybeSingle();  // Use maybeSingle to handle not found
    
    if (error) throw error;
    return data;
  },
  enabled: !!id,  // Disable when ID missing
  retry: (failureCount, error) => {
    // Don't retry on 404/not found
    if (error.code === 'PGRST116') return false;
    return failureCount < 3;
  },
});
```

***

## Edge Function Design

### generate-templated-pdf Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                   Edge Function Request                      │
├─────────────────────────────────────────────────────────────┤
│ {                                                           │
│   organizationId: string,                                   │
│   templateId?: string,                                      │
│   letterheadId?: string,                                    │
│   content: { title, subtitle?, sections, metadata? },       │
│   options?: { showApprovalBlock, watermark, orientation }   │
│ }                                                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Request Validation                        │
│  - Validate required fields (organizationId, content.title) │
│  - Validate options schema                                   │
│  - Verify user org membership                                │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Fetch Dependencies                        │
│  - Fetch letterhead (if letterheadId or use default)        │
│  - Fetch template config (if templateId)                     │
│  - Fetch logo image (if letterhead.logo_url)                │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    PDF Generation (pdf-lib)                  │
│  1. Create PDFDocument                                       │
│  2. Add page(s) with orientation                             │
│  3. Render letterhead header (logo, org info)               │
│  4. Render document title/subtitle                           │
│  5. Render numbered sections with content                    │
│  6. Render approval block (if enabled)                       │
│  7. Render letterhead footer on all pages                   │
│  8. Add watermark (if specified)                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Storage & Record Creation                 │
│  1. Upload PDF to pf-documents bucket                        │
│  2. Create pf_documents record                               │
│  3. Generate signed URL (1 hour expiry)                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Response                                  │
│ {                                                           │
│   success: true,                                            │
│   documentId: string,                                       │
│   url: string (signed),                                     │
│   storagePath: string,                                      │
│   fileName: string,                                         │
│   fileSize: number,                                         │
│   pageCount: number,                                        │
│   generationTimeMs: number                                  │
│ }                                                           │
└─────────────────────────────────────────────────────────────┘
```

### PDF-lib Usage Patterns

```typescript theme={null}
import { PDFDocument, StandardFonts, rgb } from 'pdf-lib';

// Document creation
const pdfDoc = await PDFDocument.create();
const page = pdfDoc.addPage([612, 792]); // Letter size

// Font embedding
const helvetica = await pdfDoc.embedFont(StandardFonts.Helvetica);
const helveticaBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold);

// Logo embedding (PNG/JPEG only)
const logoBytes = await fetch(logoUrl).then(r => r.arrayBuffer());
const logo = logoUrl.endsWith('.png') 
  ? await pdfDoc.embedPng(logoBytes)
  : await pdfDoc.embedJpg(logoBytes);

// Text rendering with wrapping
const drawWrappedText = (text: string, x: number, y: number, maxWidth: number) => {
  const lines = wrapText(text, maxWidth, fontSize);
  let currentY = y;
  for (const line of lines) {
    page.drawText(line, { x, y: currentY, font, size: fontSize });
    currentY -= lineHeight;
  }
  return currentY;
};
```

### Performance Considerations

| Aspect              | Target       | Implementation                        |
| ------------------- | ------------ | ------------------------------------- |
| Generation Time     | \< 3 seconds | Parallel fetches, efficient rendering |
| Memory              | \< 128MB     | Stream large images, cleanup          |
| Concurrent Requests | 10+          | Stateless function, no shared state   |
| Large Documents     | 50+ pages    | Pagination logic, memory management   |

***

## Security Model

### Permission Requirements

| Permission          | Description                  | Default Roles              |
| ------------------- | ---------------------------- | -------------------------- |
| pf.templates.view   | View templates               | org\_admin, manager, staff |
| pf.templates.manage | Create/edit/delete templates | org\_admin                 |

### Multi-Tenant Isolation

1. **RLS on all tables**: Every query filtered by organization\_id
2. **System template protection**: is\_system templates read-only
3. **Edge function validation**: Verify org membership before processing
4. **Storage bucket policies**: Organization-scoped paths

### Data Protection

1. **No PHI in templates**: Templates contain structure, not patient data
2. **Audit trail**: Version history preserved, no hard deletes
3. **Signed URLs**: Time-limited access to generated documents

***

## Consumer Integration Guide

### GR-01 Policy Integration

```typescript theme={null}
import { 
  useDocumentTemplate, 
  useGenerateTemplatedPdf,
  PolicyExportDialog 
} from '@/platform/templates';

function PolicyDetailPage({ policy }) {
  const { data: template } = useDocumentTemplate(undefined, undefined, 'policy');
  const { generatePdf, isGenerating } = useGenerateTemplatedPdf();

  const handleExport = async (letterheadId: string, templateId?: string) => {
    const result = await generatePdf({
      letterheadId,
      templateId,
      content: {
        title: policy.title,
        sections: [
          { name: 'Purpose', content: policy.purpose },
          { name: 'Scope', content: policy.scope },
          { name: 'Policy Statement', content: policy.statement },
        ],
        metadata: {
          documentNumber: policy.policy_number,
          version: policy.version,
          effectiveDate: policy.effective_date,
        },
      },
      options: {
        showApprovalBlock: true,
      },
    });
    
    if (result.url) {
      window.open(result.url, '_blank');
    }
  };

  return (
    <PolicyExportDialog
      policy={policy}
      onExport={handleExport}
      isExporting={isGenerating}
    />
  );
}
```

### GR-11 Procedure Integration

```typescript theme={null}
import { useDocumentTemplate, useGenerateTemplatedPdf } from '@/platform/templates';

function ProcedureExport({ procedure }) {
  const { generatePdf } = useGenerateTemplatedPdf();

  const handleExport = async () => {
    await generatePdf({
      content: {
        title: procedure.title,
        subtitle: 'Standard Operating Procedure',
        sections: procedure.steps.map((step, idx) => ({
          name: `Step ${idx + 1}: ${step.name}`,
          content: step.description,
        })),
      },
    });
  };
}
```

### HR Offer Letter Integration

```typescript theme={null}
import { useLetterhead, useGenerateTemplatedPdf } from '@/platform/templates';

function OfferLetterGenerator({ candidate, offer }) {
  const { data: letterhead } = useLetterhead();
  const { generatePdf } = useGenerateTemplatedPdf();

  const handleGenerate = async () => {
    await generatePdf({
      letterheadId: letterhead?.id,
      content: {
        title: 'Offer of Employment',
        sections: [
          { name: 'Position', content: `We are pleased to offer you the position of ${offer.title}.` },
          { name: 'Compensation', content: `Annual salary: $${offer.salary.toLocaleString()}` },
          { name: 'Start Date', content: `Your anticipated start date is ${offer.startDate}.` },
        ],
        metadata: {
          author: 'Human Resources',
          effectiveDate: offer.startDate,
        },
      },
      options: {
        showApprovalBlock: true,
      },
    });
  };
}
```

### Custom Integration Pattern

```typescript theme={null}
// Create a custom export hook for your module
function useMyModuleExport() {
  const { generatePdf, isGenerating } = useGenerateTemplatedPdf();
  const { data: letterhead } = useLetterhead();
  const { data: template } = useDocumentTemplate(undefined, undefined, 'other');

  const exportDocument = async (entity: MyEntity) => {
    const sections = transformEntityToSections(entity);
    
    const result = await generatePdf({
      letterheadId: letterhead?.id,
      templateId: template?.id,
      content: {
        title: entity.name,
        sections,
        metadata: {
          documentNumber: entity.reference_number,
        },
      },
    });

    return result;
  };

  return { exportDocument, isExporting: isGenerating };
}
```

***

## Testing Strategy

### Test Files

| File                                                           | Type        | Coverage               |
| -------------------------------------------------------------- | ----------- | ---------------------- |
| tests/rls/pf-letterheads.rls.test.ts                           | RLS         | Multi-tenant isolation |
| tests/rls/pf-document-templates.rls.test.ts                    | RLS         | CRUD + versioning      |
| tests/rls/pf-approval-chain-templates.rls.test.ts              | RLS         | Chain lifecycle        |
| tests/unit/platform/templates/useLetterhead.test.ts            | Unit        | Query/mutation         |
| tests/unit/platform/templates/useDocumentTemplate.test.ts      | Unit        | Versioning             |
| tests/unit/platform/templates/useApprovalChainTemplate.test.ts | Unit        | Apply workflow         |
| tests/unit/platform/templates/components.test.tsx              | Unit        | UI components          |
| tests/integration/platform/templates-workflow\.test.ts         | Integration | E2E lifecycle          |
| tests/integration/platform/pdf-generation.test.ts              | Integration | PDF service            |
| supabase/functions/generate-templated-pdf/index.test.ts        | Edge        | PDF logic              |

### Coverage Targets

* RLS Tests: 100%
* Unit Tests: 80%+
* Integration Tests: Critical paths covered

***

## Appendix

### Feature Flag

Enable via organization settings:

```sql theme={null}
UPDATE pf_organizations 
SET settings = jsonb_set(settings, '{pf_templates_enabled}', 'true')
WHERE id = 'org-id';
```

### Related Modules

* **GR-01**: Policy Management (consumer)
* **GR-11**: Procedure Management (consumer)
* **HR**: Offer Letters (consumer)
* **FW**: Workflow Framework (approval integration)

### Migration Notes

PF-64 was implemented in phases:

* Phase 1: Database schema and types
* Phase 2: RLS policies
* Phase 3: React hooks and query layer
* Phase 4: PDF generation edge function
* Phase 5: Testing and documentation

***

**Document Version History**

| Version | Date       | Author | Changes                       |
| ------- | ---------- | ------ | ----------------------------- |
| 1.0.0   | 2026-01-30 | AI     | Initial architecture document |
