# Organization Model Pattern v1.0.0

Standard pattern for Organization models across Django applications. Each app implements its own Organization model following this pattern to ensure interoperability and consistent field names.

## 🐾 Red Panda Approval™

This pattern follows Red Panda Approval standards.

---

## Why a Pattern, Not a Shared Model

Organization requirements vary by domain. A financial app needs stock symbols and ISIN codes. A healthcare app needs provider IDs. An education app needs accreditation fields. Shipping a monolithic Organization model with 40+ fields forces every app to carry fields it does not need.

Instead, this pattern defines:

- **Required fields** every Organization model must have
- **Recommended fields** most apps should include
- **Extension guidelines** for domain-specific needs
- **Standard choice values** for interoperability

---

## Required Fields

Every Organization model must include these fields:

```python
import uuid
from django.conf import settings
from django.db import models


class Organization(models.Model):
    # Primary key
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    # Core identity
    name = models.CharField(max_length=255, db_index=True,
                            help_text="Organization display name")
    slug = models.SlugField(max_length=255, unique=True,
                            help_text="URL-friendly identifier")

    # Classification
    type = models.CharField(max_length=20, choices=TYPE_CHOICES,
                            help_text="Organization type")
    status = models.CharField(max_length=20, choices=STATUS_CHOICES,
                              default="active", help_text="Current status")

    # Location
    country = models.CharField(max_length=2, help_text="ISO 3166-1 alpha-2 country code")

    # Audit
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
                                   null=True, blank=True, related_name="created_organizations")
    updated_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
                                   null=True, blank=True, related_name="updated_organizations")

    class Meta:
        verbose_name = "Organization"
        verbose_name_plural = "Organizations"

    def __str__(self):
        return self.name

    def get_absolute_url(self):
        from django.urls import reverse
        return reverse("organization-detail", kwargs={"slug": self.slug})
```

---

## Standard Choice Values

Use these exact values for interoperability between apps:

### TYPE_CHOICES

```python
TYPE_CHOICES = [
    ("for-profit", "For-Profit"),
    ("non-profit", "Non-Profit"),
    ("government", "Government"),
    ("ngo", "NGO"),
    ("educational", "Educational"),
    ("healthcare", "Healthcare"),
    ("cooperative", "Cooperative"),
]
```

### STATUS_CHOICES

```python
STATUS_CHOICES = [
    ("active", "Active"),
    ("inactive", "Inactive"),
    ("pending", "Pending"),
    ("suspended", "Suspended"),
    ("dissolved", "Dissolved"),
    ("merged", "Merged"),
]
```

### SIZE_CHOICES (recommended)

```python
SIZE_CHOICES = [
    ("micro", "Micro (1-9)"),
    ("small", "Small (10-49)"),
    ("medium", "Medium (50-249)"),
    ("large", "Large (250-999)"),
    ("enterprise", "Enterprise (1000+)"),
]
```

### PARENT_RELATIONSHIP_CHOICES (if using hierarchy)

```python
PARENT_RELATIONSHIP_CHOICES = [
    ("subsidiary", "Subsidiary"),
    ("division", "Division"),
    ("branch", "Branch"),
    ("franchise", "Franchise"),
    ("joint-venture", "Joint Venture"),
    ("department", "Department"),
]
```

---

## Recommended Fields

Most apps should include these fields:

```python
# Extended identity
legal_name = models.CharField(max_length=255, blank=True, default="",
                              help_text="Full legal entity name")
abbreviated_name = models.CharField(max_length=50, blank=True, default="",
                                    db_index=True, help_text="Short name/acronym")

# Classification
size = models.CharField(max_length=20, choices=SIZE_CHOICES, blank=True, default="",
                        help_text="Organization size")

# Contact
primary_email = models.EmailField(blank=True, default="", help_text="Primary contact email")
primary_phone = models.CharField(max_length=20, blank=True, default="", help_text="Primary phone")
website = models.URLField(blank=True, default="", help_text="Organization website")

# Address
address_line1 = models.CharField(max_length=255, blank=True, default="")
address_line2 = models.CharField(max_length=255, blank=True, default="")
city = models.CharField(max_length=100, blank=True, default="")
state_province = models.CharField(max_length=100, blank=True, default="")
postal_code = models.CharField(max_length=20, blank=True, default="")

# Content
overview = models.TextField(blank=True, default="", help_text="Organization description")

# Metadata
is_active = models.BooleanField(default=True, help_text="Soft delete flag")
tags = models.JSONField(default=list, blank=True, help_text="Flexible tags")
```

---

## Hierarchy Pattern

For apps that need parent-child organization relationships:

```python
# Hierarchical relationships
parent_organization = models.ForeignKey(
    "self",
    on_delete=models.SET_NULL,
    null=True,
    blank=True,
    related_name="subsidiaries",
    help_text="Parent organization",
)
parent_relationship_type = models.CharField(
    max_length=20,
    choices=PARENT_RELATIONSHIP_CHOICES,
    blank=True,
    default="",
    help_text="Type of relationship with parent",
)
```

### Hierarchy Utility Functions

```python
def get_ancestors(org):
    """Walk up the parent chain. Returns list of Organization instances."""
    ancestors = []
    current = org.parent_organization
    while current:
        ancestors.append(current)
        current = current.parent_organization
    return ancestors


def get_descendants(org):
    """Recursively collect all child organizations."""
    descendants = []
    for child in org.subsidiaries.all():
        descendants.append(child)
        descendants.extend(get_descendants(child))
    return descendants
```

⚠️ **Warning:** Recursive queries can be expensive. For deep hierarchies, consider using `django-mptt` or `django-treebeard`, or store a materialized path.

---

## Domain Extension Examples

### Financial App

```python
class Organization(BaseOrganization):
    revenue = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True)
    revenue_year = models.PositiveIntegerField(null=True, blank=True)
    employee_count = models.PositiveIntegerField(null=True, blank=True)
    stock_symbol = models.CharField(max_length=10, blank=True, default="", db_index=True)
    fiscal_year_end_month = models.PositiveSmallIntegerField(null=True, blank=True)
```

### Healthcare App

```python
class Organization(BaseOrganization):
    npi_number = models.CharField(max_length=10, blank=True, default="")
    facility_type = models.CharField(max_length=30, choices=FACILITY_CHOICES)
    bed_count = models.PositiveIntegerField(null=True, blank=True)
    accreditation = models.JSONField(default=list, blank=True)
```

### Education App

```python
class Organization(BaseOrganization):
    institution_type = models.CharField(max_length=30, choices=INSTITUTION_CHOICES)
    student_count = models.PositiveIntegerField(null=True, blank=True)
    accreditation_body = models.CharField(max_length=100, blank=True, default="")
```

---

## Anti-Patterns

- ❌ Don't use `null=True` on CharField/TextField — use `blank=True, default=""`
- ❌ Don't put all possible fields in a single model — extend per domain
- ❌ Don't use `Meta.ordering` on Organization — specify in queries
- ❌ Don't override `save()` for hierarchy calculation — use signals or service functions
- ❌ Don't expose sequential IDs in URLs — use slug or short UUID

---

## Indexing Recommendations

```python
class Meta:
    indexes = [
        models.Index(fields=["name"], name="org_name_idx"),
        models.Index(fields=["status"], name="org_status_idx"),
        models.Index(fields=["type"], name="org_type_idx"),
        models.Index(fields=["country"], name="org_country_idx"),
    ]
```

Add domain-specific indexes as needed (e.g., `stock_symbol` for financial apps).
