fina use and signup process

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-08 16:47:27 +00:00
parent 73d7a6953b
commit 33ad6768ec
2 changed files with 3164 additions and 0 deletions

View File

@@ -0,0 +1,676 @@
# 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

File diff suppressed because it is too large Load Diff