fina use and signup process
This commit is contained in:
676
CRITICAL-GAPS-SIGNUP-TO-SITE-WORKFLOW.md
Normal file
676
CRITICAL-GAPS-SIGNUP-TO-SITE-WORKFLOW.md
Normal 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
|
||||||
2488
IMPLEMENTATION-PLAN-SIGNUP-TO-PAYMENT-WORKFLOW.md
Normal file
2488
IMPLEMENTATION-PLAN-SIGNUP-TO-PAYMENT-WORKFLOW.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user