# Master-Template-Program Question & Section System - Complete Guide

**Version**: 4.0.0 (Consolidated)  
**Last Updated**: 2026-03-13  
**Status**: ✅ Final - Ready for Implementation

---

## 📊 Executive Summary

This document defines the **Master → Template → Program** architecture for question and section management in the registration system. It combines the complete specification, implementation guide, and migration procedures.

### What This System Enables

- ✅ **Reusable master questions and sections** (single source of truth)
- ✅ **Template-based program creation** (clone once, customize per program)
- ✅ **Hierarchical subsections** (nested form structure)
- ✅ **Version tracking and audit trail** (know what changed and when)
- ✅ **Program isolation** (changes don't propagate back)
- ✅ **Backward compatibility** (existing programs continue working)

---

# PART 1: ARCHITECTURE & DESIGN

## 🏗️ Architecture Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                        MASTER LAYER                             │
│  (NEW TABLES: master_question, master_form_section)             │
│  • Authoritative definitions                                     │
│  • Standalone master data tables                                │
│  • Categorized (PERSONAL, TRAVEL, DIETARY, etc.)                │
│  • Hierarchical sections (parent_section_id)                    │
│  • Options stored as config in master questions                 │
└─────────────────────┬───────────────────────────────────────────┘
                      │ Clone ↓
┌─────────────────────┴───────────────────────────────────────────┐
│                       TEMPLATE LAYER                            │
│  (NEW TABLES: template_question, template_form_section)         │
│  • Associated with program_template                             │
│  • Publishable (DRAFT → PUBLISHED → ARCHIVED)                   │
│  • Immutable after publish                                      │
└─────────────────────┬───────────────────────────────────────────┘
                      │ Clone ↓
┌─────────────────────┴───────────────────────────────────────────┐
│                        PROGRAM LAYER                            │
│  (EXISTING: program_question, program_form_section)             │
│  • Snapshot at program creation                                 │
│  • Can be customized per program                                │
│  • References source (master/template) for traceability         │
└─────────────────────┬───────────────────────────────────────────┘
                      │ Runtime ↓
┌─────────────────────┴───────────────────────────────────────────┐
│                        RUNTIME LAYER                            │
│  (Registration Operations - registration_question_answer)       │
│  • Uses program-level question snapshot                         │
│  • Frozen schema during registration lifecycle                  │
│  • No schema changes mid-registration                           │
└─────────────────────────────────────────────────────────────────┘
```

---

## 🏢 Hierarchical Subsection Support

### Overview

The system supports **nested/hierarchical sections** with a configurable maximum depth. This allows complex form structures like:

```
📁 Personal Information (Level 0 - Root)
   ├─ 📁 Basic Details (Level 1)
   └─ 📁 Contact Information (Level 1)

📁 Travel Details (Level 0 - Root)
   ├─ 📁 Arrival Information (Level 1)
   └─ 📁 Departure Information (Level 1)
```

### Configuration

**Recommended Depth Levels:**
- **Level 0**: Root sections (e.g., "Personal Information", "Travel Details")
- **Level 1**: Primary subsections (e.g., "Basic Details", "Contact Information")
- **Level 2**: Secondary subsections (e.g., "Current Address", "Permanent Address")

**Max Depth:** 3 levels (0, 1, 2) - **Configurable per entity layer**

```typescript
// System-level configuration
const SECTION_DEPTH_CONFIG = {
  MAX_DEPTH: 3, // 0, 1, 2 allowed
  ENFORCE_DEPTH_LIMITS: true,
};
```

### Database Schema for Hierarchy

All section entities include:
- `parent_section_id`: Self-referencing FK (nullable for root sections)

### Validation Rules

1. **Depth Validation**: Cannot create section deeper than `MAX_DEPTH - 1`
2. **Parent Validation**: Parent section must exist and not be deleted

### Benefits

✅ **Logical Grouping**: Group related questions hierarchically
✅ **UI Flexibility**: Render as tabs, accordions, or nested cards
✅ **Conditional Logic**: Show/hide entire subsection trees
✅ **Repeatable Sections**: Support for multiple addresses, travelers, etc.
✅ **Clear Navigation**: Breadcrumb trails and tree views
✅ **Progressive Disclosure**: Hide complexity until needed

---

# PART 2: DATABASE SCHEMA

## 📋 Core Entities

### MASTER LAYER

#### Table: `master_form_section`

| Column             | Type         | Constraints                 |
|--------------------|--------------|----------------------------|
| id                 | BIGSERIAL    | PK                         |
| section_key        | VARCHAR(100) | UNIQUE NOT NULL            |
| name               | VARCHAR(255) | NOT NULL                   |
| description        | TEXT         | NULL                       |
| parent_section_id  | BIGINT       | FK → master_form_section.id|
| conditional_config | JSONB        | NULL                       |
| display_order      | INTEGER      | NOT NULL                   |
| is_active          | BOOLEAN      | NOT NULL                   |
| created_at         | TIMESTAMP    | NOT NULL                   |
| updated_at         | TIMESTAMP    | NOT NULL                   |
| deleted_at         | TIMESTAMP    | NULL                       |
| created_by         | INTEGER      | NULL                       |
| updated_by         | INTEGER      | NULL                       |

**Indexes:**
```sql
CREATE UNIQUE INDEX idx_master_section_key ON master_form_section(section_key) WHERE deleted_at IS NULL;
CREATE INDEX idx_master_section_parent ON master_form_section(parent_section_id);
CREATE INDEX idx_master_section_active ON master_form_section(is_active) WHERE is_active = true;
CREATE INDEX idx_master_section_display_order ON master_form_section(display_order);
```

**TypeScript Entity:**
```typescript
@Entity('master_form_section')
export class MasterFormSection extends BaseEntity {
  @PrimaryGeneratedColumn('increment', { type: 'bigint' })
  id: string;

  @Column({ name: 'section_key', type: 'varchar', length: 100, unique: true })
  sectionKey: string;

  @Column({ type: 'varchar', length: 255 })
  name: string;

  @Column({ type: 'text', nullable: true })
  description: string;

  @Column({ name: 'parent_section_id', type: 'bigint', nullable: true })
  parentSectionId: string;

  @ManyToOne(() => MasterFormSection, { nullable: true })
  @JoinColumn({ name: 'parent_section_id' })
  parentSection: MasterFormSection;

  @Column({ name: 'conditional_config', type: 'jsonb', nullable: true })
  conditionalConfig: object;

  @Column({ name: 'display_order', type: 'integer' })
  displayOrder: number;

  @Column({ name: 'is_active', type: 'boolean', default: true })
  isActive: boolean;

  @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone' })
  updatedAt: Date;

  @DeleteDateColumn({ name: 'deleted_at', type: 'timestamp with time zone', nullable: true })
  deletedAt: Date;

  @Column({ name: 'created_by', type: 'integer', nullable: true })
  createdBy: number;

  @Column({ name: 'updated_by', type: 'integer', nullable: true })
  updatedBy: number;
}
```

---

#### Table: `master_question`

| Column                 | Type         | Constraints                 |
|------------------------|--------------|----------------------------|
| id                     | BIGSERIAL    | PK                         |
| question_code          | VARCHAR(100) | UNIQUE NOT NULL            |
| binding_key            | VARCHAR(100) | NOT NULL                   |
| master_form_section_id | BIGINT       | FK → master_form_section.id|
| question_text          | TEXT         | NOT NULL                   |
| question_type          | ENUM         | NOT NULL                   |
| answer_type            | ENUM         | NOT NULL                   |
| answer_location        | VARCHAR(1024)| NULL                       |
| option_config          | JSONB        | NULL                       |
| config                 | JSONB        | NOT NULL                   |
| is_active              | BOOLEAN      | NOT NULL                   |
| created_at             | TIMESTAMP    | NOT NULL                   |
| updated_at             | TIMESTAMP    | NOT NULL                   |
| deleted_at             | TIMESTAMP    | NULL                       |
| created_by             | INTEGER      | NULL                       |
| updated_by             | INTEGER      | NULL                       |

**Indexes:**
```sql
CREATE UNIQUE INDEX idx_master_question_code ON master_question(question_code) WHERE deleted_at IS NULL;
CREATE INDEX idx_master_question_section ON master_question(master_form_section_id);
CREATE INDEX idx_master_question_binding ON master_question(binding_key);
CREATE INDEX idx_master_question_active ON master_question(is_active) WHERE is_active = true;
```

**Enums:**

```sql
-- question_type enum (UI control type)
CREATE TYPE question_type_enum AS ENUM (
  'text', 'number', 'radio', 'checkbox', 'multiselect',
  'email', 'tel', 'textarea', 'select', 'date',
  'dateandtime', 'time', 'year', 'yearRange', 'file',
  'draganddrop', 'apicall', 'Address', 'address',
  'boolean', 'button', 'multiQuestion'
);

-- answer_type enum (submitted value data type)
CREATE TYPE answer_type AS ENUM (
  'string',   -- text, email, tel, textarea, radio, select, year, yearRange, file, checkbox, multiselect, Address, draganddrop, apicall
  'number',   -- number
  'boolean',  -- boolean
  'date'      -- date, dateandtime
);
```

**TypeScript Entity:**
```typescript
export enum QuestionType {
  TEXT = 'text',
  NUMBER = 'number',
  RADIO = 'radio',
  CHECKBOX = 'checkbox',
  MULTISELECT = 'multiselect',
  EMAIL = 'email',
  TEL = 'tel',
  TEXTAREA = 'textarea',
  SELECT = 'select',
  DATE = 'date',
  DATEANDTIME = 'dateandtime',
  TIME = 'time',
  YEAR = 'year',
  YEAR_RANGE = 'yearRange',
  FILE = 'file',
  DRAGANDDROP = 'draganddrop',
  APICALL = 'apicall',
  ADDRESS = 'Address',
  ADDRESS_LOWER = 'address',
  BOOLEAN = 'boolean',
  BUTTON = 'button',
  MULTI_QUESTION = 'multiQuestion'
}

export enum AnswerType {
  STRING = 'string',
  NUMBER = 'number',
  BOOLEAN = 'boolean',
  DATE = 'date'
}

@Entity('master_question')
export class MasterQuestion extends BaseEntity {
  @PrimaryGeneratedColumn('increment', { type: 'bigint' })
  id: string;

  @Column({ name: 'question_code', type: 'varchar', length: 100, unique: true })
  questionCode: string;

  @Column({ name: 'binding_key', type: 'varchar', length: 100 })
  bindingKey: string;

  @Column({ name: 'master_form_section_id', type: 'bigint' })
  masterFormSectionId: string;

  @ManyToOne(() => MasterFormSection)
  @JoinColumn({ name: 'master_form_section_id' })
  masterFormSection: MasterFormSection;

  @Column({ name: 'question_text', type: 'text' })
  questionText: string;

  @Column({ name: 'question_type', type: 'enum', enum: QuestionType })
  questionType: QuestionType;

  @Column({ name: 'answer_type', type: 'enum', enum: AnswerType })
  answerType: AnswerType;

  @Column({ name: 'answer_location', type: 'varchar', length: 1024, nullable: true })
  answerLocation: string;

  @Column({ name: 'option_config', type: 'jsonb', nullable: true })
  optionConfig: object;

  @Column({ name: 'config', type: 'jsonb' })
  config: object;

  @Column({ name: 'is_active', type: 'boolean', default: true })
  isActive: boolean;

  @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone' })
  updatedAt: Date;

  @DeleteDateColumn({ name: 'deleted_at', type: 'timestamp with time zone', nullable: true })
  deletedAt: Date;

  @Column({ name: 'created_by', type: 'integer', nullable: true })
  createdBy: number;

  @Column({ name: 'updated_by', type: 'integer', nullable: true })
  updatedBy: number;
}
```

**option_config Schema:**
```json
[
  {
    "id": 1,
    "name": "Option Name",
    "value": "Option Name",
    "type": "string",
    "order": 1
  }
]
```

**config Schema:**
```json
{
  "placeholder": "Enter value",
  "min": 0,
  "max": 100,
  "is_required": true,
  "validation_regex": "^[A-Za-z]+$",
  "help_text": "Additional guidance"
}
```

**Required field:** `is_required` (boolean)

**conditional_config Schema:**
```json
{
  "show_when": {
    "question_code": "q_0000000001",
    "operator": "equals",
    "value": "yes"
  }
}
```

---

### TEMPLATE LAYER

#### Table: `program_template`

**Table:** `program_template`

| Column             | Type         | Constraints                   |
|--------------------|--------------|------------------------------|
| id                 | BIGSERIAL    | PK                           |
| program_type_id    | INTEGER      | FK → program_type_v1.id      |
| name               | VARCHAR(255) | NOT NULL                     |
| description        | TEXT         | NULL                         |
| version            | INTEGER      | NOT NULL                     |
| status             | ENUM         | NOT NULL                     |
| is_active          | BOOLEAN      | NOT NULL                     |
| created_at         | TIMESTAMP    | NOT NULL                     |
| updated_at         | TIMESTAMP    | NOT NULL                     |
| deleted_at         | TIMESTAMP    | NULL                         |
| created_by         | INTEGER      | NULL                         |
| updated_by         | INTEGER      | NULL                         |

**template_status ENUM:**
```sql
CREATE TYPE template_status AS ENUM (
  'DRAFT',
  'PUBLISHED',
  'ARCHIVED'
);
```

**TypeScript Entity:**
```typescript
export enum TemplateStatus {
  DRAFT = 'DRAFT',
  PUBLISHED = 'PUBLISHED',
  ARCHIVED = 'ARCHIVED'
}

@Entity('program_template')
export class ProgramTemplate extends BaseEntity {
  @PrimaryGeneratedColumn('increment', { type: 'bigint' })
  id: string;

  @Column({ name: 'program_type_id', type: 'integer' })
  programTypeId: number;

  @Column({ type: 'varchar', length: 255 })
  name: string;

  @Column({ type: 'text', nullable: true })
  description: string;

  @Column({ type: 'integer', default: 1 })
  version: number;

  @Column({ name: 'status', type: 'enum', enum: TemplateStatus, default: TemplateStatus.DRAFT })
  status: TemplateStatus;

  @Column({ name: 'is_active', type: 'boolean', default: true })
  isActive: boolean;

  @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone' })
  updatedAt: Date;

  @DeleteDateColumn({ name: 'deleted_at', type: 'timestamp with time zone', nullable: true })
  deletedAt: Date;

  @Column({ name: 'created_by', type: 'integer', nullable: true })
  createdBy: number;

  @Column({ name: 'updated_by', type: 'integer', nullable: true })
  updatedBy: number;
}
```

---

#### Table: `template_form_section`

| Column                   | Type         | Constraints                   |
|--------------------------|--------------|------------------------------|
| id                       | BIGSERIAL    | PK                           |
| program_template_id      | BIGINT       | FK → program_template.id     |
| master_form_section_id   | BIGINT       | FK → master_form_section.id  |
| section_key              | VARCHAR(100) | NOT NULL                     |
| name                     | VARCHAR(255) | NOT NULL                     |
| description              | TEXT         | NULL                         |
| parent_section_id        | BIGINT       | FK → template_form_section.id|
| conditional_config       | JSONB        | NULL                         |
| display_order            | INTEGER      | NOT NULL                     |
| created_at               | TIMESTAMP    | NOT NULL                     |
| updated_at               | TIMESTAMP    | NOT NULL                     |
| deleted_at               | TIMESTAMP    | NULL                         |
| created_by               | INTEGER      | NULL                         |
| updated_by               | INTEGER      | NULL                         |

**Indexes:**
```sql
CREATE INDEX idx_template_section_template ON template_form_section(program_template_id);
CREATE INDEX idx_template_section_master ON template_form_section(master_form_section_id);
CREATE INDEX idx_template_section_parent ON template_form_section(parent_section_id);
CREATE INDEX idx_template_section_display_order ON template_form_section(program_template_id, display_order);
```

**TypeScript Entity:**
```typescript
@Entity('template_form_section')
export class TemplateFormSection extends BaseEntity {
  @PrimaryGeneratedColumn('increment', { type: 'bigint' })
  id: string;

  @Column({ name: 'program_template_id', type: 'bigint' })
  programTemplateId: string;

  @ManyToOne(() => ProgramTemplate)
  @JoinColumn({ name: 'program_template_id' })
  programTemplate: ProgramTemplate;

  @Column({ name: 'master_form_section_id', type: 'bigint', nullable: true })
  masterFormSectionId: string;

  @ManyToOne(() => MasterFormSection, { nullable: true })
  @JoinColumn({ name: 'master_form_section_id' })
  masterFormSection: MasterFormSection;

  @Column({ name: 'section_key', type: 'varchar', length: 100 })
  sectionKey: string;

  @Column({ type: 'varchar', length: 255 })
  name: string;

  @Column({ type: 'text', nullable: true })
  description: string;

  @Column({ name: 'parent_section_id', type: 'bigint', nullable: true })
  parentSectionId: string;

  @ManyToOne(() => TemplateFormSection, { nullable: true })
  @JoinColumn({ name: 'parent_section_id' })
  parentSection: TemplateFormSection;

  @Column({ name: 'conditional_config', type: 'jsonb', nullable: true })
  conditionalConfig: object;

  @Column({ name: 'display_order', type: 'integer' })
  displayOrder: number;

  @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone' })
  updatedAt: Date;

  @DeleteDateColumn({ name: 'deleted_at', type: 'timestamp with time zone', nullable: true })
  deletedAt: Date;

  @Column({ name: 'created_by', type: 'integer', nullable: true })
  createdBy: number;

  @Column({ name: 'updated_by', type: 'integer', nullable: true })
  updatedBy: number;
}
```

---

#### Table: `template_question`

| Column                   | Type         | Constraints                   |
|--------------------------|--------------|------------------------------|
| id                       | BIGSERIAL    | PK                           |
| template_form_section_id | BIGINT       | FK → template_form_section.id|
| master_question_id       | BIGINT       | FK → master_question.id      |
| question_code            | VARCHAR(100) | NOT NULL                     |
| binding_key              | VARCHAR(100) | NOT NULL                     |
| question_text            | TEXT         | NOT NULL                     |
| question_type            | ENUM         | NOT NULL                     |
| answer_type              | ENUM         | NOT NULL                     |
| answer_location          | VARCHAR(1024)| NULL                         |
| option_config            | JSONB        | NULL                         |
| config                   | JSONB        | NOT NULL                     |
| display_order            | INTEGER      | NOT NULL                     |
| created_at               | TIMESTAMP    | NOT NULL                     |
| updated_at               | TIMESTAMP    | NOT NULL                     |
| deleted_at               | TIMESTAMP    | NULL                         |
| created_by               | INTEGER      | NULL                         |
| updated_by               | INTEGER      | NULL                         |

**Indexes:**
```sql
CREATE INDEX idx_template_question_section ON template_question(template_form_section_id);
CREATE INDEX idx_template_question_master ON template_question(master_question_id);
CREATE INDEX idx_template_question_code ON template_question(question_code);
CREATE INDEX idx_template_question_binding ON template_question(binding_key);
CREATE INDEX idx_template_question_display_order ON template_question(template_form_section_id, display_order);
```

**TypeScript Entity:**
```typescript
@Entity('template_question')
export class TemplateQuestion extends BaseEntity {
  @PrimaryGeneratedColumn('increment', { type: 'bigint' })
  id: string;

  @Column({ name: 'template_form_section_id', type: 'bigint' })
  templateFormSectionId: string;

  @ManyToOne(() => TemplateFormSection)
  @JoinColumn({ name: 'template_form_section_id' })
  templateFormSection: TemplateFormSection;

  @Column({ name: 'master_question_id', type: 'bigint', nullable: true })
  masterQuestionId: string;

  @ManyToOne(() => MasterQuestion, { nullable: true })
  @JoinColumn({ name: 'master_question_id' })
  masterQuestion: MasterQuestion;

  @Column({ name: 'question_code', type: 'varchar', length: 100 })
  questionCode: string;

  @Column({ name: 'binding_key', type: 'varchar', length: 100 })
  bindingKey: string;

  @Column({ name: 'question_text', type: 'text' })
  questionText: string;

  @Column({ name: 'question_type', type: 'enum', enum: QuestionType })
  questionType: QuestionType;

  @Column({ name: 'answer_type', type: 'enum', enum: AnswerType })
  answerType: AnswerType;

  @Column({ name: 'answer_location', type: 'varchar', length: 1024, nullable: true })
  answerLocation: string;

  @Column({ name: 'option_config', type: 'jsonb', nullable: true })
  optionConfig: object;

  @Column({ name: 'config', type: 'jsonb' })
  config: object;

  @Column({ name: 'display_order', type: 'integer' })
  displayOrder: number;

  @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone' })
  updatedAt: Date;

  @DeleteDateColumn({ name: 'deleted_at', type: 'timestamp with time zone', nullable: true })
  deletedAt: Date;

  @Column({ name: 'created_by', type: 'integer', nullable: true })
  createdBy: number;

  @Column({ name: 'updated_by', type: 'integer', nullable: true })
  updatedBy: number;
}
```

---

### PROGRAM LAYER

#### Extend: `hdb_form_section`

**Add columns:**

| Column                   | Type         | Constraints                       |
|--------------------------|--------------|----------------------------------|
| template_form_section_id | BIGINT       | FK → template_form_section.id  |
| parent_section_id        | INTEGER      | FK → hdb_form_section.id       |
| conditional_config       | JSONB        | NULL                            |

**Indexes:**
```sql
CREATE INDEX idx_hdb_section_template ON hdb_form_section(template_form_section_id);
CREATE INDEX idx_hdb_section_parent ON hdb_form_section(parent_section_id);
```

---

#### Extend: `hdb_question`

**Add columns:**

| Column               | Type         | Constraints                |
|----------------------|--------------|---------------------------|
| template_question_id | BIGINT       | FK → template_question.id |
| question_code        | VARCHAR(100) | NULL                      |
| option_config        | JSONB        | NULL                      |

**Note:** `binding_key` and `config` columns already exist in `hdb_question`

**Indexes:**
```sql
CREATE INDEX idx_hdb_question_template ON hdb_question(template_question_id);
CREATE INDEX idx_hdb_question_code ON hdb_question(question_code);
```

---

#### Extend: `program_v1`

**Add columns:**

| Column               | Type   | Constraints               |
|----------------------|--------|---------------------------|
| program_template_id  | BIGINT | FK → program_template.id  |

**Indexes:**
```sql
CREATE INDEX idx_program_template ON program_v1(program_template_id);
```

---

# PART 3: DATA OPERATIONS

## 🔄 Clone Operations

### Clone Master → Template

**Endpoint:** `POST /templates/:templateId/clone-from-master`

**Request Body:**
```json
{
  "masterSectionIds": [1, 2, 3]
}
```

**Process:**
1. Copy `master_form_section` → `template_form_section`
2. Copy `master_question` → `template_question`
3. Preserve: `section_key`, `question_code`, `binding_key`, `option_config`, `config`, `display_order`
4. Set references: `master_form_section_id`, `master_question_id`
5. Set: `status = DRAFT`

---

### Clone Template → Program

**Endpoint:** `POST /programs/:programId/clone-from-template`

**Request Body:**
```json
{
  "templateId": 1
}
```

**Validation:**
- Template must have `status = PUBLISHED`

**Process:**
1. Copy `template_form_section` → `hdb_form_section`
2. Copy `template_question` → `hdb_question`
3. Preserve: `section_key`, `question_code`, `binding_key`, `option_config`, `config`, `display_order`, `conditional_config`
4. Set references: `template_form_section_id`, `template_question_id`

---

## 📊 Data Mappings

### Answer Type Mapping Logic

| question_type (UI Control) | answer_type (Data Type) | Example Submitted Value |
|---------------------------|------------------------|-------------------------|
| text, email, tel, textarea, radio, select, year, yearRange, file | string | "John Doe", "2024", "https://s3.../file.jpg" |
| checkbox, multiselect | string | "[\"option1\", \"option2\"]" (JSON-encoded array) |
| Address, draganddrop, apicall | string | "{\"street\": \"...\", \"city\": \"...\"}" (JSON-encoded object) |
| number | number | 42, 3.14 |
| boolean | boolean | true, false |
| date, dateandtime | date | "2024-01-15T10:30:00Z" |

### Question Code Convention

Questions are assigned **unique, sequential codes** using a dedicated database sequence:
- Format: `Q_XXXXXXXXXX` (10 digits with leading zeros)
- Generated by: `nextval('master_question_code_seq')`
- Example: First question → `Q_0000000001`, Second → `Q_0000000002`, etc.
- **Guaranteed unique** even if question IDs have gaps or are deleted

---

## 🔧 Template Lifecycle

### Publish Template

**Endpoint:** `POST /templates/:templateId/publish`

**Validation:**
- Template must have at least 1 section
- Template must have at least 1 question

**Updates:**
```sql
UPDATE program_template SET status = 'PUBLISHED' WHERE id = :templateId;
```

---

### Archive Template

**Endpoint:** `POST /templates/:templateId/archive`

**Transition:** `PUBLISHED → ARCHIVED`

**Updates:**
```sql
UPDATE program_template SET status = 'ARCHIVED' WHERE id = :templateId;
```

---

# PART 4: API ENDPOINTS

## 📡 Complete API Reference

### Master Section APIs

```
GET    /api/v1/master-sections
POST   /api/v1/master-sections
GET    /api/v1/master-sections/:id
PUT    /api/v1/master-sections/:id
DELETE /api/v1/master-sections/:id
GET    /api/v1/master-sections/hierarchy
GET    /api/v1/master-sections/:id/children
GET    /api/v1/master-sections/:id/ancestors
POST   /api/v1/master-sections/:parentId/subsections
```

---

### Master Question APIs

```
GET    /api/v1/master-questions
POST   /api/v1/master-questions
GET    /api/v1/master-questions/:id
PUT    /api/v1/master-questions/:id
DELETE /api/v1/master-questions/:id
```

---

### Template APIs

```
GET  /api/v1/templates
POST /api/v1/templates
GET  /api/v1/templates/:id
GET  /api/v1/templates/:templateId/sections
POST /api/v1/templates/:templateId/clone-from-master
PUT  /api/v1/template-sections/:id
PUT  /api/v1/template-questions/:id
POST /api/v1/templates/:templateId/publish
POST /api/v1/templates/:templateId/archive
POST /api/v1/templates/:id/version
```

---

### Program APIs

```
POST /api/v1/programs/:programId/clone-from-template
POST /api/v1/programs/from-template/:templateId
GET  /api/v1/programs/:programId/sections
GET  /api/v1/programs/:programId/questions
GET  /api/v1/programs/:programId/questions/:questionId
PUT  /api/v1/program-questions/:id
PUT  /api/v1/program-sections/:id
```

---

# PART 5: VALIDATION RULES

## ✅ Validation Rules

### 1. option_config Validation

For `answer_type IN ('single_choice', 'multi_choice')`:
- `option_config.options` must exist
- `option_config.options.length > 0`

---

### 2. Publish Validation

Cannot publish template if:
- No sections exist
- No questions exist

---

### 3. Clone Validation

Template cloning to program allowed only if:
- `template.status = 'PUBLISHED'`

---

### 4. Section Nesting Validation

Reject when:
- `parent_section_id = id` (self-reference)
- Circular reference detected (recursive check)

---

### 5. config.is_required

- Must be present in `config` JSONB
- Type: `boolean`

---

# PART 6: MIGRATION GUIDE

## 📦 Migration Files

```
database/program-configuration/
├── 00_master-template-schema-migration.sql     # Creates tables, indexes, enums
├── 01_master-data-migration.sql                 # Migrates existing data
├── 02_clone-functions.sql                       # Clone scripts
└── rollback-master-template-system.sql          # Complete rollback
```

## ⚠️ Pre-Migration Checklist

- ✅ **Database Backup**: Take a full backup
- ✅ **Database Connection**: Verify connection
- ✅ **Permissions**: Ensure CREATE, ALTER, INSERT privileges
- ✅ **Existing Tables**: Verify `hdb_form_section` and `hdb_question` exist
- ✅ **Check Structure**: Ensure tables have required columns
- ✅ **Review Scripts**: Review all SQL scripts

---

## 🚀 Migration Steps

### Step 1: Backup Database

```bash
pg_dump -h <host> -U <username> -d <database_name> -F c -b -v -f backup_before_migration_$(date +%Y%m%d).dump
```

### Step 2: Run Schema Migration

```bash
psql -h <host> -U <username> -d <database_name> -f program-configuration/00_master-template-schema-migration.sql
```

### Step 3: Run Data Migration

```bash
psql -h <host> -U <username> -d <database_name> -f program-configuration/01_master-data-migration.sql
```

### Step 4: Use Clone Scripts (Optional)

1. Open `02_clone-functions.sql`
2. Run SCRIPT 0 to create program_template
3. Run SCRIPT 1 to clone master data to template

### Step 5: Verify Migration

```sql
-- Check section count
SELECT COUNT(*) as total_sections 
FROM master_form_section 
WHERE deleted_at IS NULL;

-- Check question count
SELECT COUNT(*) as total_questions 
FROM master_question 
WHERE deleted_at IS NULL;

-- Check questions by section
SELECT 
    mfs.section_key,
    mfs.name as section_name,
    COUNT(mq.id) as question_count
FROM master_form_section mfs
LEFT JOIN master_question mq 
    ON mq.master_form_section_id = mfs.id 
    AND mq.deleted_at IS NULL
WHERE mfs.deleted_at IS NULL
GROUP BY mfs.id, mfs.section_key, mfs.name
ORDER BY mfs.display_order;
```

---

## 🔧 Rollback Procedure

```sql
-- Drop new tables
DROP TABLE IF EXISTS template_question CASCADE;
DROP TABLE IF EXISTS template_form_section CASCADE;
DROP TABLE IF EXISTS master_question CASCADE;
DROP TABLE IF EXISTS master_form_section CASCADE;

-- Remove extended columns
ALTER TABLE hdb_question 
    DROP COLUMN IF EXISTS template_question_id,
    DROP COLUMN IF EXISTS question_code,
    DROP COLUMN IF EXISTS option_config;

ALTER TABLE hdb_form_section 
    DROP COLUMN IF EXISTS template_form_section_id,
    DROP COLUMN IF EXISTS conditional_config;

-- Drop enums
DROP TYPE IF EXISTS template_status CASCADE;
DROP TYPE IF EXISTS answer_type CASCADE;
DROP TYPE IF EXISTS question_type_enum CASCADE;

-- Drop sequences
DROP SEQUENCE IF EXISTS master_question_code_seq CASCADE;
```

---

# PART 7: IMPLEMENTATION TASKS

## 📝 Implementation Roadmap

### **Phase 1: Master Data Setup** (Week 1-2)

#### Database Schema Tasks
- [ ] Create `master_question` table
- [ ] Create `master_form_section` table
- [ ] Add FK constraints
- [ ] Create indexes

#### Code Implementation Tasks
- [ ] Create `master-question.entity.ts`
- [ ] Create `master-form-section.entity.ts`
- [ ] Create DTOs
- [ ] Extend repositories
- [ ] Update services
- [ ] Create controller endpoints

#### Testing Tasks
- [ ] Unit tests
- [ ] Integration tests

---

### **Phase 2: Template Layer** (Week 3-4)

#### Database Schema Tasks
- [ ] Create `program_template` table
- [ ] Create `template_question` table
- [ ] Create `template_form_section` table
- [ ] Add FK constraints
- [ ] Create indexes

#### Code Implementation Tasks
- [ ] Create entities
- [ ] Create DTOs
- [ ] Create repositories
- [ ] Create services
- [ ] Create controllers

#### Testing Tasks
- [ ] Unit tests
- [ ] Integration tests

---

### **Phase 3: Program Cloning** (Week 5-7)

#### Database Schema Tasks
- [ ] Add columns to `program_v1` table
- [ ] Add columns to `hdb_question` table
- [ ] Update `hdb_form_section` table
- [ ] Create indexes

#### Code Implementation Tasks
- [ ] Update entities
- [ ] Create clone services
- [ ] Update repositories
- [ ] Update controllers

#### Testing Tasks
- [ ] Unit tests
- [ ] Integration tests

---

### **Phase 4: Registration Service Updates** (Week 8-9)

#### Code Updates
- [ ] Update registration service
- [ ] Update question retrieval endpoints
- [ ] Maintain backwards compatibility

#### Testing Tasks (CRITICAL)
- [ ] Regression tests
- [ ] Integration tests

---

## ✨ Success Metrics

- ✅ All tables created: 4 new tables
- ✅ All APIs implemented: 20+ endpoints
- ✅ Code coverage: >80%
- ✅ All tests passing
- ✅ Master data seeded
- ✅ Zero data loss in cloning operations

---

## 📞 Support

For issues or questions:
1. Check the error message in PostgreSQL logs
2. Review the verification queries output
3. Consult the coding standards: `AGENTS.md`

---

## 📋 Notes

- **Idempotent Scripts**: Scripts are safe to run multiple times
- **ON CONFLICT**: Uses `ON CONFLICT DO UPDATE`
- **BIGSERIAL IDs**: All primary keys use BIGSERIAL
- **Dynamic Lookups**: Section IDs resolved via `section_key`
- **Soft Deletes**: All tables support `deleted_at`
- **Audit Fields**: All tables include audit columns
- **Auto Timestamps**: Triggers update `updated_at`
- **Entity Defaults**: Default values handled in TypeScript entities, not database

---
# 📘 Template Form Builder API - Complete Guide

## 🎯 Overview

The **Template Form Builder API** is a unified endpoint for building program template forms with maximum flexibility. It allows you to:

- **Clone sections and questions** from master templates
- **Create brand new custom sections and questions**
- **Mix cloned and custom content** in the same request
- **Create hierarchical nested subsections**
- **Reference and link existing sections**
- **Do everything in ONE API call**

---

## 🔗 API Endpoint

```
POST /v1/program-templates/:id/form
```

**Parameters:**
- `:id` - Program Template ID (also can be provided in request body)

**Authentication:** Required
- Bearer Token
- User ID Header
- Active Role Header

---

## 🧩 Key Concepts

### 1. **Three Types of Sections**

| Type | Description | When to Use |
|------|-------------|-------------|
| **Clone from Master** | Copy a master section with all its questions | Reusing standard sections across templates |
| **Create New** | Build custom section from scratch | Template-specific unique sections |
| **Reference Existing** | Link to already created template section | Building hierarchies, reorganizing structure |

### 2. **Hierarchical Subsections**

- Sections can contain **unlimited nested subsections**
- Nesting depth controlled by `MAX_SECTION_NESTING_DEPTH` constant
- Use `subsections` array to define child sections
- Mix any combination of new/cloned/existing sections at any level

### 3. **Questions**

- Every **new or cloned section** must have questions
- **Existing sections** don't need questions (already have them)
- Questions can be:
  - **Cloned from master** (with optional overrides)
  - **Created brand new**

---

## 📋 Request Structure

### **Basic Request Schema**

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      // Section definition (clone/create/existing)
      "questions": [...],        // Required for new/cloned
      "subsections": [...]       // Optional nested sections
    }
  ],
  "createdBy": 1
}
```

### **Section Input Options**

**Must specify EXACTLY ONE of:**

1. **`masterFormSectionId`** - Clone from master
2. **`sectionName` + `sectionKey`** - Create new section
3. **`templateFormSectionId`** - Reference existing section

---

## 📖 All Supported Scenarios

### ✅ **Scenario 1: Clone Section from Master**

Clone a master section with all its questions.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "masterFormSectionId": 1,
      "questions": [
        { "masterQuestionId": 5 },
        { "masterQuestionId": 12 },
        { "masterQuestionId": 14 }
      ]
    }
  ],
  "createdBy": 1
}
```

---

### ✅ **Scenario 2: Clone with Overrides**

Clone and customize section/question properties.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "masterFormSectionId": 1,
      "sectionOverride": {
        "sectionName": "Participant Information (HDB 2025)",
        "displayOrder": 1
      },
      "questions": [
        {
          "masterQuestionId": 5,
          "override": {
            "label": "Full Name (as per ID)",
            "config": {
              "isRequired": true,
              "maxCharacters": 100
            }
          }
        }
      ]
    }
  ],
  "createdBy": 1
}
```

---

### ✅ **Scenario 3: Create Custom Section**

Build a completely new section from scratch.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "sectionName": "Emergency Contact",
      "sectionKey": "EMERGENCY_CONTACT",
      "displayOrder": 1,
      "questions": [
        {
          "label": "Contact Name",
          "type": "text",
          "answerType": "string",
          "config": { "isRequired": true }
        },
        {
          "label": "Relationship",
          "type": "dropdown",
          "answerType": "string",
          "optionConfig": [
            { "value": "parent", "label": "Parent" },
            { "value": "spouse", "label": "Spouse" }
          ]
        },
        {
          "label": "Phone Number",
          "type": "tel",
          "answerType": "string",
          "config": { "isRequired": true }
        }
      ]
    }
  ],
  "createdBy": 1
}
```

---

### ✅ **Scenario 4: Mix Cloned + Custom**

Combine cloned sections with custom sections in one request.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "masterFormSectionId": 1,
      "questions": [
        { "masterQuestionId": 5 },
        { "masterQuestionId": 12 }
      ]
    },
    {
      "sectionName": "Custom Section",
      "sectionKey": "CUSTOM_SECTION",
      "questions": [
        {
          "label": "Custom Question",
          "type": "text",
          "answerType": "string"
        }
      ]
    }
  ],
  "createdBy": 1
}
```

---

### ✅ **Scenario 5: New Section Inside New Section**

Create hierarchical nested sections (all new).

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "sectionName": "Main Section",
      "sectionKey": "MAIN_SECTION",
      "displayOrder": 1,
      "questions": [
        {
          "label": "Main Question",
          "type": "text",
          "answerType": "string",
          "config": { "isRequired": true }
        }
      ],
      "subsections": [
        {
          "sectionName": "Child Section",
          "sectionKey": "CHILD_SECTION",
          "displayOrder": 1,
          "questions": [
            {
              "label": "Child Question",
              "type": "text",
              "answerType": "string"
            }
          ],
          "subsections": [
            {
              "sectionName": "Grandchild Section",
              "sectionKey": "GRANDCHILD_SECTION",
              "questions": [
                {
                  "label": "Deeply Nested Question",
                  "type": "text",
                  "answerType": "string"
                }
              ]
            }
          ]
        }
      ]
    }
  ],
  "createdBy": 1
}
```

---

### ✅ **Scenario 6: Existing Section Inside New Section**

Create a new parent section and link existing sections as children.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "sectionName": "New Parent Section",
      "sectionKey": "NEW_PARENT",
      "displayOrder": 1,
      "questions": [
        {
          "label": "Parent Question",
          "type": "text",
          "answerType": "string"
        }
      ],
      "subsections": [
        {
          "templateFormSectionId": 33
        },
        {
          "templateFormSectionId": 34
        }
      ]
    }
  ],
  "createdBy": 1
}
```

**Result:** 
- Section ID 33 and 34 will have their `parentSectionId` updated to the new parent section's ID

---

### ✅ **Scenario 7: New Section Inside Existing Section**

Add new subsections to an existing section.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "templateFormSectionId": 33,
      "subsections": [
        {
          "sectionName": "New Child Section",
          "sectionKey": "NEW_CHILD",
          "displayOrder": 1,
          "questions": [
            {
              "label": "Child Question",
              "type": "email",
              "answerType": "string",
              "config": { "isRequired": true }
            }
          ]
        }
      ]
    }
  ],
  "createdBy": 1
}
```

**Result:**
- Existing section 33 is not modified
- New section is created with `parentSectionId = 33`

---

### ✅ **Scenario 8: Existing Section Inside Existing Section**

Link existing sections to form parent-child relationships.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "templateFormSectionId": 33,
      "subsections": [
        {
          "templateFormSectionId": 34
        },
        {
          "templateFormSectionId": 35
        }
      ]
    }
  ],
  "createdBy": 1
}
```

**Result:**
- Section 34's `parentSectionId` → 33
- Section 35's `parentSectionId` → 33

---

### ✅ **Scenario 9: Complete Production Form**

Full HDB MSD 2025 template with all sections.

```json
{
  "programTemplateId": 5,
  "sections": [
    {
      "masterFormSectionId": 1,
      "sectionOverride": {
        "sectionName": "Participant Basic Details",
        "displayOrder": 1
      },
      "questions": [
        {
          "masterQuestionId": 5,
          "override": {
            "label": "Full Name (As per ID)",
            "displayOrder": 1
          }
        },
        { "masterQuestionId": 4, "override": { "displayOrder": 2 } },
        { "masterQuestionId": 13, "override": { "displayOrder": 3 } },
        { "masterQuestionId": 12, "override": { "displayOrder": 4 } },
        { "masterQuestionId": 14, "override": { "displayOrder": 5 } }
      ]
    },
    {
      "masterFormSectionId": 2,
      "sectionOverride": {
        "sectionName": "Payment & Invoice Details",
        "displayOrder": 2
      },
      "questions": [
        { "masterQuestionId": 35 },
        { "masterQuestionId": 26 },
        { "masterQuestionId": 30 }
      ]
    },
    {
      "masterFormSectionId": 3,
      "sectionOverride": {
        "sectionName": "Travel Information",
        "displayOrder": 3
      },
      "questions": [
        { "masterQuestionId": 50 },
        { "masterQuestionId": 78 },
        { "masterQuestionId": 80 }
      ]
    }
  ],
  "createdBy": 1
}
```

---

## 🔑 Field Reference

### **Section Fields**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `masterFormSectionId` | number | Option 1 | Master section ID to clone from |
| `sectionName` | string | Option 2 | Name for new section |
| `sectionKey` | string | Option 2 | Unique key for new section |
| `templateFormSectionId` | number | Option 3 | Existing template section ID |
| `sectionDescription` | string | Optional | Section description |
| `displayOrder` | number | Optional | Display order (default: 1) |
| `conditionalConfig` | object | Optional | Conditional display config |
| `sectionOverride` | object | Optional | Override properties when cloning |
| `questions` | array | Conditional | Required for new/cloned; optional for existing |
| `subsections` | array | Optional | Nested child sections |

### **Question Fields (New)**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `label` | string | Yes | Question text |
| `type` | enum | Yes | Question type (text, email, tel, etc.) |
| `answerType` | enum | Yes | Answer type (string, number, date) |
| `config` | object | Optional | Validation config (isRequired, etc.) |
| `placeholder` | string | Optional | Placeholder text |
| `helpText` | string | Optional | Help text |
| `displayOrder` | number | Optional | Display order |
| `optionConfig` | array | Conditional | Required for dropdown/radio/checkbox |

### **Question Fields (Clone from Master)**

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `masterQuestionId` | number | Yes | Master question ID to clone |
| `override` | object | Optional | Override properties |

---

## 📊 Response Structure

```json
{
  "success": true,
  "message": "Template form built successfully",
  "data": {
    "templateFormSections": [
      {
        "id": 36,
        "programTemplateId": 5,
        "name": "Basic Details",
        "sectionKey": "FS_BASICDETAILS",
        "displayOrder": 1,
        "parentSectionId": null,
        "createdAt": "2026-03-18T10:30:00Z"
      }
    ],
    "totalSectionsCreated": 5,
    "totalQuestionsCreated": 25
  }
}
```

---

## ⚙️ Configuration

### **Nesting Depth Control**

Set in `src/common/constants/constants.ts`:

```typescript
export const MAX_SECTION_NESTING_DEPTH = 5; // Maximum nesting levels (null = unlimited)
export const MAX_LIMIT_FOR_NESTED_SUBSECTIONS = 10; // Safety limit
```

---

## 🚨 Validation Rules

1. **Section Source:** Must specify EXACTLY ONE of: `masterFormSectionId`, `sectionName`, or `templateFormSectionId`
2. **Questions:** Required for new/cloned sections; optional for existing sections
3. **Nesting Depth:** Cannot exceed `MAX_SECTION_NESTING_DEPTH`
4. **Template Ownership:** Existing sections must belong to the same template
5. **Parent-Child:** Cannot create circular references

---

## ✨ Key Features

### ✅ **Transaction Safety**
- All operations in a single database transaction
- Rollback on any error
- Data consistency guaranteed

### ✅ **Recursive Processing**
- Handles unlimited nesting levels
- Automatic parent-child relationship management
- Depth validation

### ✅ **Audit Trail**
- All entities track `createdBy`, `updatedBy`
- Automatic timestamps (`createdAt`, `updatedAt`)
- Soft delete support

### ✅ **Flexibility**
- Mix and match any combination of cloning/creating/referencing
- Override any property when cloning
- Add new content to existing structures

---

## 🎯 Use Cases

### **Use Case 1: Initial Template Setup**
Clone all standard sections from master template, customize labels for specific program.

### **Use Case 2: Template Variation**
Create a new template based on existing one, add custom sections for special requirements.

### **Use Case 3: Incremental Building**
Start with basic sections, add more sections/subsections over time as requirements evolve.

### **Use Case 4: Organization Restructuring**
Reorganize existing sections into new hierarchies without recreating content.

### **Use Case 5: Reusable Components**
Build library of reusable sections, mix and match for different programs.

---

## 📝 Best Practices

1. **Plan Your Structure:** Design section hierarchy before implementation
2. **Use Display Order:** Set explicit display order for predictable UI rendering
3. **Meaningful Keys:** Use descriptive section keys (e.g., `EMERGENCY_CONTACT_2025`)
4. **Clone When Possible:** Leverage master templates for standardization
5. **Validate First:** Test with small payloads before full template creation
6. **Document Overrides:** Comment why you're overriding master properties

---

## 🐛 Error Handling

### **Common Errors**

| Error Code | Cause | Solution |
|------------|-------|----------|
| `MASTER_FORM_SECTION_NOT_FOUND` | Invalid master section ID | Verify master section exists |
| `TEMPLATE_FORM_SECTION_NOT_FOUND` | Invalid template section ID | Check template section ID |
| `FORM_SECTION_NESTING_DEPTH_EXCEEDED` | Too many nesting levels | Reduce nesting or increase limit |
| `TEMPLATE_FORM_SECTION_DUPLICATE_KEY` | Multiple source types specified | Use only one: clone/create/existing |

---

## 🔍 Example Queries to Get Data

### **Get Available Master Sections**

```sql
SELECT id, section_key, name, description 
FROM master_form_section 
WHERE deleted_at IS NULL;
```

### **Get Available Master Questions**

```sql
SELECT mq.id, mq.question_code, mq.question_text, mq.question_type, mq.answer_type
FROM master_question mq
WHERE mq.deleted_at IS NULL;
```

### **Get Existing Template Sections**

```sql
SELECT id, name, section_key, display_order, parent_section_id
FROM template_form_section
WHERE program_template_id = 5 AND deleted_at IS NULL;
```

---

## 🚀 Quick Start

### **1. Simple Clone**

```bash
curl -X POST http://localhost:9001/v1/program-templates/5/form \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "sections": [
      {
        "masterFormSectionId": 1,
        "questions": [
          { "masterQuestionId": 5 },
          { "masterQuestionId": 12 }
        ]
      }
    ],
    "createdBy": 1
  }'
```

### **2. Custom Section**

```bash
curl -X POST http://localhost:9001/v1/program-templates/5/form \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -d '{
    "sections": [
      {
        "sectionName": "Custom Section",
        "sectionKey": "CUSTOM_SECTION",
        "questions": [
          {
            "label": "Email",
            "type": "email",
            "answerType": "string",
            "config": { "isRequired": true }
          }
        ]
      }
    ],
    "createdBy": 1
  }'
```

---

## 📚 Related Documentation

- [AGENTS.md](./AGENTS.md) - Coding standards and project context
- [PROGRAM_FORM_BUILDER_API.md](./PROGRAM_FORM_BUILDER_API.md) - Program form builder (Template → Program)
- [PROGRAM_TEMPLATE_COMPLETE_GUIDE.md](./PROGRAM_TEMPLATE_COMPLETE_GUIDE.md) - Program templates overview

---

## 🎓 Summary

The **Template Form Builder API** is a powerful, flexible endpoint that supports:

✅ **All Creation Methods:** Clone, Create, Reference  
✅ **Unlimited Nesting:** Hierarchical subsections  
✅ **Mix & Match:** Any combination in one request  
✅ **Four Key Scenarios:**
  1. New inside New
  2. Existing inside New
  3. New inside Existing
  4. Existing inside Existing

✅ **Transaction Safe:** All-or-nothing operations  
✅ **Production Ready:** Validated, logged, audited

---

**Built with ❤️ for the HDB MSD 2025 Registration System**



**End of Complete Guide** 🎉
