# CRITICAL GAPS: Signup to Site Creation Workflow
**Analysis Date:** December 8, 2025  
**Status:** 🔴 BLOCKING ISSUES FOUND

---

## Executive Summary

**CRITICAL FINDING:** The registration flow for paid plans is **COMPLETELY BROKEN** due to missing model definition and multiple architectural inconsistencies. Free trial signups work but have significant gaps.

**Impact:**
- ❌ Paid plan signups (starter/growth/scale) **FAIL** on registration
- ⚠️ Free trial signups work but create incomplete data structures  
- ⚠️ Site creation has validation issues and missing relationships
- ⚠️ Duplicate fields cause data inconsistency risks

---

## 🔴 CRITICAL ISSUES (Must Fix Immediately)

### 1. MISSING MODEL: `billing.Subscription` Does Not Exist

**Problem:**  
`RegisterSerializer.create()` imports and tries to use `billing.Subscription` which **DOES NOT EXIST**:

```python
# In auth/serializers.py line 291
from igny8_core.business.billing.models import Subscription  # ❌ IMPORT FAILS
```

**Evidence:**
```python
# Python shell test:
>>> from igny8_core.business.billing.models import Subscription
ImportError: cannot import name 'Subscription' from 'igny8_core.business.billing.models'
```

**What Actually Exists:**
- `auth.Subscription` model at `igny8_core/auth/models.py` line 218
- Database table: `igny8_subscriptions` (created by `auth.Subscription`)

**Impact:**
- Registration with paid plans (`starter`, `growth`, `scale`) **FAILS IMMEDIATELY**
- Line 403-412 in `RegisterSerializer.create()` crashes on paid signups:
  ```python
  subscription = Subscription.objects.create(...)  # ❌ CRASHES
  ```

**Root Cause:**
Documentation and code assume `billing.Subscription` was created but it was never implemented.

**Fix Required:**
1. **Option A (Recommended):** Use existing `auth.Subscription`
   ```python
   # Change line 291 in auth/serializers.py
   from igny8_core.auth.models import Subscription
   ```

2. **Option B:** Create `billing.Subscription` and migrate
   - Create model in `billing/models.py`
   - Create migration to point Invoice FK to new model
   - Data migration to copy existing records
   - Update all imports

---

### 2. MISSING FIELD: `Subscription.plan` Does Not Exist

**Problem:**  
The `Subscription` model in `auth/models.py` has **NO `plan` field**, but `InvoiceService` and documentation assume it exists.

**Evidence:**
```python
# Database inspection shows:
class Igny8Subscriptions(models.Model):
    tenant = models.OneToOneField('Igny8Tenants')
    stripe_subscription_id = CharField
    payment_method = CharField
    external_payment_id = CharField
    status = CharField
    current_period_start = DateTimeField
    current_period_end = DateTimeField
    cancel_at_period_end = BooleanField
    # ❌ NO PLAN FIELD
```

**Code Expects:**
```python
# In InvoiceService.create_subscription_invoice()
subscription.plan  # ❌ AttributeError
```

**Impact:**
- Invoice creation for subscriptions **WILL FAIL**
- Cannot determine which plan a subscription belongs to
- Credit allocation logic broken

**Fix Required:**
Add `plan` field to `Subscription` model:
```python
class Subscription(models.Model):
    # ... existing fields ...
    plan = models.ForeignKey('igny8_core_auth.Plan', on_delete=models.PROTECT, related_name='subscriptions')
```

Migration required to add column and populate from `Account.plan`.

---

### 3. Account.owner Circular Dependency Race Condition

**Problem:**  
Registration creates User and Account in 3 separate steps, causing temporary invalid state:

```python
# Step 1: User created WITHOUT account
user = User.objects.create_user(account=None)  # ⚠️ User has no tenant

# Step 2: Account created WITH user as owner
account = Account.objects.create(owner=user)

# Step 3: User updated to link to account
user.account = account
user.save()  # ⚠️ Three DB writes for one logical operation
```

**Impact:**
- Between steps 1-3, user exists without `account` (tenant isolation broken)
- If step 2 or 3 fails, orphaned user exists
- Race condition: if another request hits during steps 1-3, middleware fails
- Triple database writes for single logical operation

**Fix Required:**
Use single transaction or remove `Account.owner` FK entirely and derive from role:

```python
# Option 1: Single transaction (already wrapped, but still 3 writes)
# Option 2: Remove Account.owner and use property
@property
def owner(self):
    return self.users.filter(role='owner').first()
```

---

### 4. Site.industry Should Be Required But Is Nullable

**Problem:**  
`Site.industry` is `null=True, blank=True` but sector creation **REQUIRES** site to have industry:

```python
# In Sector.save() line 541
if self.industry_sector.industry != self.site.industry:
    raise ValidationError("Sector must belong to site's industry")
```

**Impact:**
- Sites can be created without industry
- When user tries to add sector: **VALIDATION FAILS** (industry is None)
- No sectors can ever be added to sites without industry
- Confusing UX: "Why can't I add sectors?"

**Evidence:**
```python
# Database schema:
industry = models.ForeignKey(Igny8Industries, blank=True, null=True)  # ❌ NULLABLE
```

**Fix Required:**
1. Make field required: `Site.industry` → `null=False, blank=False`
2. Migration: Set default industry for existing NULL sites
3. Update serializers to require industry during site creation

---

### 5. Free Plan Credits Mismatch

**Problem:**  
Documentation says free plan gives 1000 credits, but actual database has only 100:

**Documentation:**
```
Free Trial | free | 0.00 | 1000 credits | 1 site | 1 user
```

**Actual Database:**
```
Free Plan | free | 0.00 | 100 credits | 1 site | 1 user
```

**Impact:**
- New users get 10x fewer credits than documented
- Sales/marketing materials may be wrong
- User expectations mismatched

**Fix Required:**
Update Plan record or documentation to match:
```sql
UPDATE igny8_plans SET included_credits = 1000 WHERE slug = 'free';
```

---

## 🟡 MEDIUM PRIORITY ISSUES (Fix Soon)

### 6. Duplicate Billing Email Fields

**Problem:**  
`billing_email` exists in **TWO** models:

1. `Account.billing_email` - Primary billing contact
2. `Invoice.billing_email` - Email on invoice (snapshot)

**Current Code:**
```python
# Account model line 106
billing_email = models.EmailField(blank=True, null=True)

# Invoice model line 213  
billing_email = models.EmailField(null=True, blank=True)
```

**Impact:**
- Which is source of truth?
- Data can become inconsistent
- Invoice should snapshot billing info at creation time

**Fix Required:**
- Keep `Account.billing_email` as primary
- Invoice should copy from Account at creation time
- OR: Store full billing snapshot in `Invoice.metadata`:
  ```json
  {
    "billing_snapshot": {
      "email": "john@example.com",
      "address_line1": "123 Main St",
      ...
    }
  }
  ```

---

### 7. Plan.max_industries Misnamed Field

**Problem:**  
Field is called `max_industries` but controls **sectors per site**, not industries:

```python
# Plan model line 177
max_industries = models.IntegerField(help_text="Optional limit for industries/sectors")

# Site model line 371
def get_max_sectors_limit(self):
    return self.account.plan.max_industries  # ❌ Confusing name
```

**Evidence from Database:**
```
Free Plan: max_industries = 1  (means 1 sector per site, NOT 1 industry)
```

**Impact:**
- Misleading field name (developers confused)
- Documentation uses "max_industries" and "max_sectors_per_site" inconsistently
- No way to configure unlimited sectors (NULL means fallback to 5)

**Fix Required:**
1. Rename field: `max_industries` → `max_sectors_per_site`
2. Migration to rename column
3. Update all references in code
4. Use `0` to mean unlimited

---

### 8. Duplicate Subscription Payment Method

**Problem:**  
Payment method stored in **THREE** places:

1. `Account.payment_method` - Account default
2. `Subscription.payment_method` - Subscription payment method
3. `AccountPaymentMethod.type` - Saved payment methods

**Current State:**
```python
# Account line 87
payment_method = models.CharField(default='stripe')

# Subscription line 231
payment_method = models.CharField(default='stripe')  

# AccountPaymentMethod line 476
type = models.CharField(PAYMENT_METHOD_CHOICES)
```

**Impact:**
- Three fields that can be out of sync
- Which one is used for billing?
- AccountPaymentMethod is proper design, others redundant

**Fix Required:**
- Use `AccountPaymentMethod` as single source of truth
- Deprecate `Account.payment_method` and `Subscription.payment_method`
- Add migration to create AccountPaymentMethod records from existing data

---

### 9. tenant_id vs account Field Name Confusion

**Problem:**  
Django field name is `account`, database column name is `tenant_id`:

```python
class AccountBaseModel(models.Model):
    account = models.ForeignKey(db_column='tenant_id')  # ❌ Confusing
```

**Impact:**
- ORM uses `account`, raw SQL uses `tenant_id`
- Debugging confusion: "Why isn't `account=1` working in SQL?"
- Must use: `SELECT * FROM igny8_sites WHERE tenant_id=1` (not `account=1`)

**Fix Required:**
- **Option A:** Rename column to `account_id` (requires migration, data untouched)
- **Option B:** Keep as-is, document clearly (current approach)

Recommend Option A for consistency.

---

### 10. SiteUserAccess Never Created

**Problem:**  
`SiteUserAccess` model exists for granular site permissions but is **NEVER CREATED**:

**Expected Flow:**
```python
# During site creation
SiteUserAccess.objects.create(user=owner, site=site)
```

**Actual Flow:**
```python
# Site created, NO SiteUserAccess record
site = Site.objects.create(...)  # ✓ Site exists
# ❌ No SiteUserAccess created
```

**Impact:**
- Granular site permissions not enforced
- Model exists but unused (dead code)
- User.get_accessible_sites() checks SiteUserAccess but it's always empty
- Only role-based access works (owner/admin see all)

**Fix Required:**
Auto-create SiteUserAccess on site creation:
```python
# In SiteViewSet.perform_create()
site = serializer.save()
if self.request.user.role in ['owner', 'admin']:
    SiteUserAccess.objects.create(
        user=self.request.user,
        site=site,
        granted_by=self.request.user
    )
```

---

### 11. Credits Auto-Update Missing

**Problem:**  
Account credits manually updated, no service layer:

```python
# Current approach (scattered throughout codebase)
account.credits += 1000
account.save()

# Separate transaction log (can be forgotten)
CreditTransaction.objects.create(...)
```

**Impact:**
- Easy to forget logging transaction
- Balance can become inconsistent
- No atomic updates
- No single source of truth

**Fix Required:**
Create `CreditService` for atomic operations:
```python
class CreditService:
    @staticmethod
    @transaction.atomic
    def add_credits(account, amount, description, metadata=None):
        account.credits += amount
        account.save()
        
        CreditTransaction.objects.create(
            account=account,
            transaction_type='purchase',
            amount=amount,
            balance_after=account.credits,
            description=description,
            metadata=metadata or {}
        )
        return account.credits
```

---

## 🟢 LOW PRIORITY (Technical Debt)

### 12. Legacy WordPress Fields Unused

**Problem:**  
Site model has 4 WordPress fields but new `SiteIntegration` model exists:

```python
# Site model (legacy)
wp_url = models.URLField(help_text="legacy - use SiteIntegration")
wp_username = CharField
wp_app_password = CharField  
wp_api_key = CharField

# Newer approach
SiteIntegration.platform = 'wordpress'
SiteIntegration.credentials = {...}
```

**Fix Required:**
- Mark fields as deprecated
- Migrate to SiteIntegration
- Remove legacy fields in future release

---

### 13. Plan.credits_per_month Deprecated

**Problem:**  
Two fields for same purpose:

```python
credits_per_month = IntegerField(default=0)  # ❌ Deprecated
included_credits = IntegerField(default=0)   # ✓ Use this

def get_effective_credits_per_month(self):
    return self.included_credits if self.included_credits > 0 else self.credits_per_month
```

**Fix Required:**
- Data migration: Copy to `included_credits`
- Remove `credits_per_month` field
- Update method to just return `included_credits`

---

### 14. No Slug Generation Utility

**Problem:**  
Slug generation duplicated in Account, Site, Sector serializers:

```python
# Duplicated 3+ times:
base_slug = name.lower().replace(' ', '-')[:50]
slug = base_slug
counter = 1
while Model.objects.filter(slug=slug).exists():
    slug = f"{base_slug}-{counter}"
    counter += 1
```

**Fix Required:**
Create utility function:
```python
def generate_unique_slug(model_class, base_name, filters=None, max_length=50):
    ...
```

---

## 📊 Summary by Severity

### 🔴 CRITICAL (Blocking - Fix Now)
1. ❌ **billing.Subscription does not exist** - Paid signups FAIL
2. ❌ **Subscription.plan field missing** - Invoice creation broken  
3. ⚠️ **Account.owner circular dependency** - Race condition risk
4. ⚠️ **Site.industry is nullable** - Sector creation fails
5. ⚠️ **Free plan credits mismatch** - 100 vs 1000 credits

**IMMEDIATE ACTION REQUIRED:**
```bash
# Fix #1: Update import in auth/serializers.py line 291
from igny8_core.auth.models import Subscription

# Fix #2: Add migration for Subscription.plan field
# Fix #4: Make Site.industry required
# Fix #5: Update Plan.included_credits to 1000
```

---

### 🟡 MEDIUM (Fix This Sprint)
6. Duplicate billing_email fields
7. Plan.max_industries misnamed
8. Duplicate payment_method fields (3 places)
9. tenant_id vs account naming confusion
10. SiteUserAccess never created
11. No CreditService for atomic updates

---

### 🟢 LOW (Technical Debt)
12. Legacy WordPress fields
13. Plan.credits_per_month deprecated
14. No slug generation utility

---

## 🔧 Required Actions Before Production

### Phase 1: Emergency Fixes (Today)

```python
# 1. Fix Subscription import
# File: backend/igny8_core/auth/serializers.py line 291
from igny8_core.auth.models import Subscription  # Changed from billing.models

# 2. Add Subscription.plan field
# New migration:
class Migration:
    operations = [
        migrations.AddField(
            model_name='subscription',
            name='plan',
            field=models.ForeignKey(
                'igny8_core_auth.Plan',
                on_delete=models.PROTECT,
                related_name='subscriptions',
                null=True  # Temporarily nullable for data migration
            ),
        ),
        # Data migration: Copy plan from account
        migrations.RunPython(copy_plan_from_account),
        # Make non-nullable
        migrations.AlterField(
            model_name='subscription',
            name='plan',
            field=models.ForeignKey(
                'igny8_core_auth.Plan',
                on_delete=models.PROTECT,
                related_name='subscriptions'
            ),
        ),
    ]
```

### Phase 2: Data Integrity (This Week)

```sql
-- Fix free plan credits
UPDATE igny8_plans SET included_credits = 1000 WHERE slug = 'free';

-- Make Site.industry required (after setting defaults)
UPDATE igny8_sites SET industry_id = 2 WHERE industry_id IS NULL;  -- Technology
ALTER TABLE igny8_sites ALTER COLUMN industry_id SET NOT NULL;
```

### Phase 3: Architecture Improvements (Next Sprint)

1. Create CreditService
2. Auto-create SiteUserAccess
3. Rename max_industries → max_sectors_per_site
4. Consolidate payment method fields

---

## 🧪 Testing Required After Fixes

### Test 1: Free Trial Signup
```bash
POST /api/v1/auth/register/
{
  "email": "test@example.com",
  "password": "Test123!",
  "password_confirm": "Test123!",
  "account_name": "Test Account",
  "plan_slug": "free"
}

# Expected:
# ✓ User created
# ✓ Account created with 1000 credits (not 100)
# ✓ CreditTransaction logged
# ✓ No errors
```

### Test 2: Paid Plan Signup
```bash
POST /api/v1/auth/register/
{
  "email": "paid@example.com",
  "password": "Test123!",
  "password_confirm": "Test123!",
  "account_name": "Paid Account",
  "plan_slug": "starter",
  "payment_method": "bank_transfer"
}

# Expected:
# ✓ User created
# ✓ Account created with status='pending_payment'
# ✓ Subscription created with plan FK
# ✓ Invoice created
# ✓ AccountPaymentMethod created
# ✓ No errors (currently FAILS)
```

### Test 3: Site Creation
```bash
POST /api/v1/auth/sites/
{
  "name": "Test Site",
  "domain": "https://test.com",
  "industry": 2  # Must be required
}

# Expected:
# ✓ Site created
# ✓ SiteUserAccess created for owner
# ✓ Can add sectors
```

### Test 4: Sector Creation
```bash
POST /api/v1/auth/sectors/
{
  "site": 1,
  "name": "Web Development",
  "industry_sector": 4
}

# Expected:
# ✓ Sector created
# ✓ Validation: industry_sector.industry == site.industry
# ✓ Validation: sector count < plan.max_sectors_per_site
```

---

## 📝 Conclusion

**Current State:** Registration is **PARTIALLY BROKEN**
- ✅ Free trial signups work (with credit amount issue)
- ❌ Paid plan signups completely broken
- ⚠️ Site/sector creation has validation issues
- ⚠️ Data integrity risks from duplicate fields

**Estimated Fix Time:**
- Critical fixes: 2-4 hours
- Medium fixes: 1-2 days  
- Low priority: 1 week

**Recommended Approach:**
1. Fix critical import and field issues (Phase 1) - **URGENT**
2. Test all signup flows thoroughly
3. Address medium priority issues incrementally
4. Plan technical debt cleanup for next quarter

---

**Document Version:** 1.0  
**Next Review:** After Phase 1 fixes implemented  
**Owner:** Development Team
