data-model-specification.md
CODITECT Multi-Tenant SaaS Platform - Complete Data Model Specification
Generated: 2025-12-30 Purpose: Comprehensive schema specification with recommended complete data model Status: Production + Recommended Additions for Pilot Launch
Table of Contents
- Overview
- Entity Relationship Diagram (ERD)
- Complete Schema by Domain
- Business Rules and Constraints
- Multi-Tenant Architecture
- Frontend Type Requirements
- Database Indexes Strategy
- Implementation Roadmap
Overview
Current State (Existing Models)
- Total Models: 40
- Total Tables: 31
- Apps: 10 (with models) + 1 (core, no models)
- Domain Coverage: Partial (User, Tenant, Commerce, Subscriptions, Permissions, Context, Licenses, Workstations, Repositories)
Recommended State (Complete System)
- Total Models: 75 (+35 new)
- Total Tables: 66 (+35 new)
- Apps: 16 (+6 new apps)
- Domain Coverage: Complete (adds PM, DMS, Workflows, Audit, Contacts)
New Apps Recommended
- pm - Project Management (Task, Sprint, Milestone, TaskComment, TaskAttachment, TaskActivity, Board)
- dms - Document Management System (Document, DocumentVersion, Folder, DocumentPermission, DocumentActivity, DocumentShare)
- workflows - Workflow Analysis (Workflow, WorkflowExecution, WorkflowStep, WorkflowApproval)
- audit - Audit & Compliance (AuditLog, AuditReview, ComplianceReport)
- billing - Enhanced Billing (Payment, Refund, TaxRate)
- contacts - Contact Management (Address, PhoneNumber, EmailAddress)
Entity Relationship Diagram (ERD)
Core Multi-Tenant Hierarchy
Project Management Domain ERD
DMS Domain ERD
Workflow Analysis Domain ERD
Commerce & Billing ERD
Complete Schema by Domain
Domain 1: User & Tenant Management
TENANT (Existing - Root Multi-Tenant Model)
class Tenant(TenantModel):
id = UUIDField(primary_key=True)
name = CharField(255) # Organization name
slug = SlugField(100, unique=True) # URL-safe identifier
email = EmailField() # Primary contact
license_tier = CharField(20) # trial, starter, professional, enterprise
seats_total = PositiveIntegerField(default=1)
is_active = BooleanField(default=True)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
stripe_customer_id = CharField(255, null=True)
stripe_subscription_id = CharField(255, null=True)
# RECOMMENDED ADDITIONS:
billing_email = EmailField(null=True) # Separate billing contact
tax_id = CharField(100, null=True) # VAT/EIN for invoicing
phone_number = CharField(50, null=True)
website = URLField(null=True)
industry = CharField(100, null=True)
company_size = CharField(20, null=True) # small, medium, large, enterprise
Indexes: slug, email, is_active Constraints: slug unique
USER (Existing - Custom User Model)
class User(AbstractUser): # Note: Does NOT use TenantModel (MRO conflicts)
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE) # Multi-tenant isolation via FK
email = EmailField(unique=True)
role = CharField(20) # owner, admin, member, guest
oauth_provider = CharField(50, null=True) # google, github
oauth_sub = CharField(255, null=True)
updated_at = DateTimeField(auto_now=True)
# RECOMMENDED ADDITIONS:
phone_number = CharField(50, null=True)
avatar_url = URLField(null=True)
timezone = CharField(50, default='UTC')
language = CharField(10, default='en')
notification_preferences = JSONField(default=dict)
Indexes: (tenant, email), (oauth_provider, oauth_sub), role Constraints: (tenant, email) unique
Domain 2: Organization Structure
MEMBERSHIP (Existing)
class Membership(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
user = TenantForeignKey(User, on_delete=CASCADE)
role = CharField(20) # owner, admin, member, viewer
joined_at = DateTimeField(default=now)
revoked_at = DateTimeField(null=True)
Constraints: (user, tenant) unique
TEAM (Existing)
class Team(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
name = CharField(255)
description = TextField()
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
Constraints: (tenant, name) unique
PROJECT (Existing)
class Project(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
name = CharField(255)
description = TextField()
metadata = JSONField(default=dict)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
deleted_at = DateTimeField(null=True) # Soft delete
Indexes: (tenant, -created_at) WHERE deleted_at IS NULL Constraints: (tenant, name) unique WHERE deleted_at IS NULL
Domain 3: Project Management (NEW)
TASK (NEW - CRITICAL)
class Task(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
project = TenantForeignKey(Project, on_delete=CASCADE)
title = CharField(500)
description = TextField()
task_type = CharField(20) # task, bug, feature, epic
status = CharField(20) # backlog, todo, in_progress, in_review, done, cancelled
priority = CharField(20) # low, medium, high, urgent
assignee = TenantForeignKey(User, on_delete=SET_NULL, null=True, related_name='assigned_tasks')
reporter = TenantForeignKey(User, on_delete=SET_NULL, null=True, related_name='reported_tasks')
sprint = TenantForeignKey(Sprint, on_delete=SET_NULL, null=True)
milestone = TenantForeignKey(Milestone, on_delete=SET_NULL, null=True)
parent_task = ForeignKey(self, on_delete=SET_NULL, null=True) # Subtasks
story_points = IntegerField(null=True)
due_date = DateTimeField(null=True)
started_at = DateTimeField(null=True)
completed_at = DateTimeField(null=True)
tags = JSONField(default=list)
custom_fields = JSONField(default=dict)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
deleted_at = DateTimeField(null=True)
Indexes: (tenant, project, status), assignee, sprint, due_date
SPRINT (NEW - HIGH)
class Sprint(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
project = TenantForeignKey(Project, on_delete=CASCADE)
name = CharField(255)
goal = TextField()
status = CharField(20) # planned, active, completed, cancelled
start_date = DateField()
end_date = DateField()
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
completed_at = DateTimeField(null=True)
Indexes: (tenant, project, status), (start_date, end_date) Constraints: (tenant, project, name) unique
MILESTONE (NEW - MEDIUM)
class Milestone(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
project = TenantForeignKey(Project, on_delete=CASCADE)
title = CharField(255)
description = TextField()
status = CharField(20) # open, in_progress, completed, cancelled
due_date = DateField(null=True)
progress_percent = IntegerField(default=0) # Calculated
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
completed_at = DateTimeField(null=True)
Domain 4: Document Management System (NEW)
DOCUMENT (NEW - CRITICAL)
class Document(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
project = TenantForeignKey(Project, on_delete=SET_NULL, null=True)
folder = TenantForeignKey(Folder, on_delete=SET_NULL, null=True)
title = CharField(500)
description = TextField()
file_path = CharField(1000) # GCS path
file_name = CharField(255)
file_size_bytes = BigIntegerField()
mime_type = CharField(100)
checksum = CharField(64) # SHA256 (deduplication)
version = IntegerField(default=1)
status = CharField(20) # draft, published, archived, deleted
owner = TenantForeignKey(User, on_delete=SET_NULL, null=True, related_name='owned_documents')
created_by = TenantForeignKey(User, on_delete=SET_NULL, null=True, related_name='created_documents')
updated_by = TenantForeignKey(User, on_delete=SET_NULL, null=True, related_name='updated_documents')
tags = JSONField(default=list)
metadata = JSONField(default=dict)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
deleted_at = DateTimeField(null=True)
Indexes: (tenant, folder), (tenant, owner), checksum, (tenant, status, -updated_at)
DOCUMENT_VERSION (NEW - CRITICAL)
class DocumentVersion(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
document = TenantForeignKey(Document, on_delete=CASCADE)
version_number = IntegerField()
file_path = CharField(1000)
file_size_bytes = BigIntegerField()
checksum = CharField(64)
change_summary = TextField()
created_by = TenantForeignKey(User, on_delete=SET_NULL, null=True)
created_at = DateTimeField(auto_now_add=True)
Indexes: (document, -version_number) Constraints: (document, version_number) unique
Domain 5: Workflow Analysis (NEW)
WORKFLOW (NEW - CRITICAL)
class Workflow(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
project = TenantForeignKey(Project, on_delete=SET_NULL, null=True)
name = CharField(255)
description = TextField()
workflow_type = CharField(50) # approval, automation, review, deployment, custom
version = IntegerField(default=1)
status = CharField(20) # draft, active, paused, archived
definition = JSONField() # State machine (nodes, edges, conditions)
triggers = JSONField(default=list)
created_by = TenantForeignKey(User, on_delete=SET_NULL, null=True)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
published_at = DateTimeField(null=True)
Indexes: (tenant, status), (tenant, workflow_type)
WORKFLOW_EXECUTION (NEW - CRITICAL)
class WorkflowExecution(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
workflow = TenantForeignKey(Workflow, on_delete=CASCADE)
triggered_by = TenantForeignKey(User, on_delete=SET_NULL, null=True)
trigger_type = CharField(50) # manual, scheduled, webhook, event
status = CharField(20) # pending, running, paused, completed, failed, cancelled
current_step = CharField(100, null=True)
started_at = DateTimeField(auto_now_add=True)
completed_at = DateTimeField(null=True)
error_message = TextField(null=True)
context_data = JSONField(default=dict)
created_at = DateTimeField(auto_now_add=True)
Indexes: (workflow, -started_at), (tenant, status)
Domain 6: Audit & Compliance (NEW)
AUDIT_LOG (NEW - HIGH)
class AuditLog(models.Model): # Global model (not tenant-scoped for immutability)
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
user = ForeignKey(User, on_delete=SET_NULL, null=True)
action = CharField(100) # 'user.login', 'document.delete', etc.
resource_type = CharField(100) # 'User', 'Document', 'Project'
resource_id = CharField(255, null=True)
ip_address = GenericIPAddressField(null=True)
user_agent = CharField(500)
request_method = CharField(10)
request_path = CharField(1000)
changes = JSONField(default=dict) # Before/after values
metadata = JSONField(default=dict)
severity = CharField(20) # info, warning, error, critical
created_at = DateTimeField(auto_now_add=True)
Indexes: (tenant, -created_at), (user, -created_at), (resource_type, resource_id), action, severity Business Rules: Immutable (no updates/deletes), 1 year retention
Domain 7: Commerce & Billing
ORDER (Existing - Enhanced)
class Order(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=SET_NULL, null=True)
user = ForeignKey(User, on_delete=RESTRICT)
stripe_payment_intent_id = CharField(255, unique=True, null=True)
stripe_checkout_session_id = CharField(255, unique=True, null=True)
payment_method = CharField(50)
total_cents = IntegerField()
currency = CharField(3, default='usd')
status = CharField(20)
line_items = JSONField(default=list)
billing_details = JSONField(null=True)
paid_at = DateTimeField(null=True)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
# RECOMMENDED ADDITIONS:
tax_cents = IntegerField(default=0)
discount_cents = IntegerField(default=0)
discount_code = CharField(50, null=True)
shipping_address = JSONField(null=True)
PAYMENT (NEW - HIGH)
class Payment(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
order = TenantForeignKey(Order, on_delete=SET_NULL, null=True)
invoice = TenantForeignKey(Invoice, on_delete=SET_NULL, null=True)
stripe_payment_intent_id = CharField(255, unique=True)
stripe_charge_id = CharField(255, null=True)
amount_cents = IntegerField()
currency = CharField(3, default='usd')
status = CharField(20) # pending, processing, succeeded, failed, refunded, cancelled
payment_method = CharField(50) # card, bank_account, google_pay, apple_pay
card_last4 = CharField(4, null=True)
card_brand = CharField(20, null=True)
failure_code = CharField(50, null=True)
failure_message = CharField(500, null=True)
created_at = DateTimeField(auto_now_add=True)
succeeded_at = DateTimeField(null=True)
refunded_at = DateTimeField(null=True)
Indexes: (tenant, -created_at), stripe_payment_intent_id, status
Domain 8: Contact Management (NEW)
ADDRESS (NEW - HIGH)
class Address(TenantModel):
id = UUIDField(primary_key=True)
tenant = ForeignKey(Tenant, on_delete=CASCADE)
entity_type = CharField(50) # 'tenant', 'user', 'order'
entity_id = UUIDField() # GenericForeignKey pattern
address_type = CharField(20) # billing, shipping, office, home
label = CharField(100, null=True)
full_name = CharField(255, null=True)
company_name = CharField(255, null=True)
address_line1 = CharField(255)
address_line2 = CharField(255, null=True)
city = CharField(100)
state = CharField(100, null=True)
postal_code = CharField(20)
country_code = CharField(2) # ISO 3166-1 alpha-2
latitude = DecimalField(9, 6, null=True)
longitude = DecimalField(9, 6, null=True)
is_default = BooleanField(default=False)
is_verified = BooleanField(default=False)
created_at = DateTimeField(auto_now_add=True)
updated_at = DateTimeField(auto_now=True)
Indexes: (tenant, entity_type, entity_id), (tenant, is_default)
Business Rules and Constraints
Global Business Rules
-
Multi-Tenant Isolation
- ALL tenant-scoped models MUST inherit from TenantModel OR have ForeignKey(Tenant)
- Query filtering by tenant is AUTOMATIC via django-multitenant middleware
- Exception: Global catalog models (Product, Permission, WorkstationConfig, TaxRate)
-
Soft Deletes
- Models with user-facing data use
deleted_attimestamp (no hard deletes) - Applies to: Project, Task, Document, Folder
- Unique constraints MUST include
WHERE deleted_at IS NULL
- Models with user-facing data use
-
Audit Trail
- All state changes MUST be logged to AuditLog
- Includes: who, what, when, IP, user agent, before/after values
- AuditLog is IMMUTABLE (no updates, no deletes)
-
UUID Primary Keys
- ALL models use UUID primary keys (distributed system requirement)
- Enables sharding, prevents ID enumeration attacks
-
Timestamps
- ALL models have
created_at(auto_now_add) andupdated_at(auto_now) - Exceptions: Immutable models (AuditLog has created_at only)
- ALL models have
Domain-Specific Business Rules
Project Management
- Task Hierarchy: Tasks can have parent tasks (subtasks), max depth = 3
- Sprint Dates: end_date > start_date, no overlapping sprints per project
- Task Status Transitions: Enforce state machine (backlog → todo → in_progress → in_review → done)
- Story Points: Optional, positive integers only, typically Fibonacci (1, 2, 3, 5, 8, 13)
Document Management
- Versioning: DocumentVersion.version_number is auto-increment per document
- Checksums: Deduplication via SHA256, same file uploaded twice creates version, not duplicate
- Permissions: Inherited from folder if no document-specific permission
- Folder Paths: Auto-calculated, max depth = 10 levels
- File Size Limits: max_file_size_mb from subscription tier (Starter: 10MB, Pro: 100MB, Enterprise: 1GB)
Workflow Analysis
- Workflow Definitions: JSON schema validation required (state machine with nodes, edges, conditions)
- Execution State: WorkflowExecution.current_step must be valid step_id from workflow.definition
- Approvals: WorkflowApproval expires after 7 days default, configurable per workflow
Commerce & Billing
- Cart Expiration: Carts expire 24 hours after last update
- Order Immutability: Once status='paid', order cannot be modified (refund creates new Refund record)
- Entitlement Creation: Automatic on Order.status='paid' OR Subscription.status='active'
- Product Dependencies: Cart validation ensures all requires[] dependencies met before checkout
- Tax Calculation: Applied via TaxRate based on billing_address.country_code + state_code
Licenses
- Concurrent Seats: License.seats_total limits active sessions (Session.status='active')
- Heartbeat TTL: Sessions without heartbeat for 6 minutes → status='zombie' (auto cleanup)
- Hardware Fingerprinting: One active session per (license, hardware_id) - prevents seat sharing
Multi-Tenant Architecture
Tenant Isolation Strategy
Pattern 1: TenantModel (Automatic Filtering)
from django_multitenant.models import TenantModel
from django_multitenant.fields import TenantForeignKey
class MyModel(TenantModel):
tenant_id = 'tenant_id' # Tell django-multitenant which field
tenant = models.ForeignKey('tenants.Tenant', on_delete=CASCADE, db_column='tenant_id')
related = TenantForeignKey(RelatedModel, on_delete=CASCADE) # Use TenantForeignKey for tenant-scoped models
Pros: Automatic query filtering, enforced tenant isolation Use For: Most tenant-scoped models (organizations, commerce, subscriptions, context)
Pattern 2: ForeignKey(Tenant) Without TenantModel
class MyModel(models.Model):
tenant = models.ForeignKey('tenants.Tenant', on_delete=CASCADE)
Pros: Simpler, still has FK constraint Cons: Manual filtering required Use For: User (AbstractUser MRO conflict), licenses, sessions
Pattern 3: CharField(tenant_id) Without FK
class MyModel(models.Model):
tenant_id = models.CharField(max_length=63)
Pros: None (legacy pattern) Cons: No FK constraint, manual filtering, prone to errors RECOMMENDATION: Migrate workstations and repositories apps to Pattern 2
Middleware Setup
# settings.py
MIDDLEWARE = [
# ...
'django_multitenant.middleware.MultiTenantMiddleware', # Add after session middleware
# ...
]
Usage Example
from django_multitenant.utils import set_current_tenant
# In API view (automatic via middleware)
def my_view(request):
tenant = request.user.tenant
set_current_tenant(tenant)
# All subsequent TenantModel queries auto-filtered by tenant
projects = Project.objects.all() # Only returns current tenant's projects
Frontend Type Requirements
TypeScript Interfaces for All Models
CRITICAL: Every Django model MUST have corresponding TypeScript interface for frontend type safety.
Location: frontend/src/types/models/
Pattern:
// frontend/src/types/models/project.ts
export interface Project {
id: string; // UUID
tenant: string; // UUID (FK reference)
name: string;
description: string;
metadata: Record<string, any>;
created_at: string; // ISO 8601 timestamp
updated_at: string;
deleted_at: string | null;
}
export interface ProjectListResponse {
count: number;
next: string | null;
previous: string | null;
results: Project[];
}
export interface ProjectDetailResponse extends Project {
tasks_count?: number;
documents_count?: number;
}
Naming Conventions
| Django Model | TypeScript Interface | API Endpoint |
|---|---|---|
| Task | Task | /api/v1/tasks/ |
| TaskComment | TaskComment | /api/v1/tasks/:id/comments/ |
| Document | Document | /api/v1/documents/ |
| DocumentVersion | DocumentVersion | /api/v1/documents/:id/versions/ |
Field Type Mapping
| Django Field | TypeScript Type |
|---|---|
| UUIDField | string |
| CharField | string |
| TextField | string |
| IntegerField | number |
| BigIntegerField | number |
| BooleanField | boolean |
| DateTimeField | string (ISO 8601) |
| DateField | string (YYYY-MM-DD) |
| JSONField | Record<string, any> OR specific interface |
| ForeignKey | string (UUID) OR full object |
| EmailField | string |
| URLField | string |
Database Indexes Strategy
Index Types
1. B-Tree Indexes (Default)
- Used for equality and range queries
- Applies to: Primary keys, foreign keys, unique constraints
2. Partial Indexes (Filtered)
- Only index rows matching condition
- Smaller, faster, less disk space
- Example:
WHERE deleted_at IS NULL,WHERE status='active'
3. Composite Indexes
- Multiple columns in single index
- Order matters: most selective column first
- Example:
(tenant_id, status, -created_at)
4. GIN Indexes (for JSONField)
- Generalized Inverted Index
- Fast lookups in JSON data
- Example:
CREATE INDEX ON tasks USING GIN (tags)
Recommended Indexes by Model
High-Traffic Models (Query Hotspots)
Task:
CREATE INDEX idx_tasks_tenant_project_status ON tasks (tenant_id, project_id, status) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_assignee ON tasks (assignee_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_tasks_sprint ON tasks (sprint_id);
CREATE INDEX idx_tasks_due_date ON tasks (due_date) WHERE status != 'done' AND deleted_at IS NULL;
CREATE INDEX idx_tasks_tags ON tasks USING GIN (tags);
Document:
CREATE INDEX idx_documents_tenant_folder ON documents (tenant_id, folder_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_documents_checksum ON documents (checksum); -- Deduplication
CREATE INDEX idx_documents_owner ON documents (owner_id);
CREATE INDEX idx_documents_status_updated ON documents (tenant_id, status, updated_at DESC);
AuditLog:
CREATE INDEX idx_audit_tenant_created ON audit_logs (tenant_id, created_at DESC);
CREATE INDEX idx_audit_user ON audit_logs (user_id, created_at DESC);
CREATE INDEX idx_audit_resource ON audit_logs (resource_type, resource_id);
CREATE INDEX idx_audit_action ON audit_logs (action);
CREATE INDEX idx_audit_severity ON audit_logs (severity) WHERE severity IN ('error', 'critical');
Implementation Roadmap
Phase 1: CRITICAL Models (12-16 hours - BEFORE PILOT)
Target: December 22-23, 2025
| Model | App | Priority | Estimated Time |
|---|---|---|---|
| Task | pm | CRITICAL | 2-3 hours |
| Document | dms | CRITICAL | 2-3 hours |
| DocumentVersion | dms | CRITICAL | 1-2 hours |
| Workflow | workflows | CRITICAL | 2-3 hours |
| WorkflowExecution | workflows | CRITICAL | 2-3 hours |
Steps:
- Create Django apps:
pm,dms,workflows - Define models with TenantModel
- Create migrations
- Run migrations
- Create admin interfaces
- Create serializers (DRF)
- Create TypeScript interfaces
- Write unit tests
Phase 2: HIGH Models (15-23 hours - WEEK 1 POST-PILOT)
Target: December 26-30, 2025
| Model | App | Priority | Estimated Time |
|---|---|---|---|
| Sprint | pm | HIGH | 1-2 hours |
| TaskComment | pm | HIGH | 1 hour |
| Folder | dms | HIGH | 1-2 hours |
| DocumentPermission | dms | HIGH | 2-3 hours |
| WorkflowStep | workflows | HIGH | 1-2 hours |
| AuditLog | audit | HIGH | 2-3 hours |
| Payment | billing | HIGH | 2-3 hours |
| BillingAddress | contacts | HIGH | 1-2 hours |
| Address | contacts | HIGH | 2-3 hours |
Phase 3: MEDIUM Models (12-20 hours - WEEK 2-3 POST-PILOT)
Target: January 2026
| Model | App | Priority | Estimated Time |
|---|---|---|---|
| Milestone | pm | MEDIUM | 1-2 hours |
| TaskAttachment | pm | MEDIUM | 1-2 hours |
| TaskActivity | pm | MEDIUM | 1-2 hours |
| DocumentActivity | dms | MEDIUM | 1-2 hours |
| DocumentShare | dms | MEDIUM | 2-3 hours |
| WorkflowApproval | workflows | MEDIUM | 1-2 hours |
| AuditReview | audit | MEDIUM | 1-2 hours |
| Refund | billing | MEDIUM | 1-2 hours |
| TaxRate | billing | MEDIUM | 1-2 hours |
| PhoneNumber | contacts | MEDIUM | 1-2 hours |
Phase 4: Field Enhancements (2-3 hours)
Target: January 2026
Add recommended fields to existing models:
- Tenant: billing_email, tax_id, phone_number, website
- User: phone_number, avatar_url, timezone, language
- Order: tax_cents, discount_cents, discount_code
- Cart: discount_code, tax_cents
- Subscription: trial_start, trial_end
Phase 5: Multi-Tenant Compliance Fix (2-3 hours)
Target: January 2026
Migrate CharField(tenant_id) to ForeignKey(Tenant):
- workstations app (4 models)
- repositories app (GiteaOrganization)
Total Effort Summary
| Phase | Priority | Models | Hours | Target Date |
|---|---|---|---|---|
| Phase 1 | CRITICAL | 8 | 12-16 | Dec 22-23 |
| Phase 2 | HIGH | 15 | 15-23 | Dec 26-30 |
| Phase 3 | MEDIUM | 9 | 12-20 | Jan 2026 |
| Phase 4 | Field Gaps | N/A | 2-3 | Jan 2026 |
| Phase 5 | Compliance | 5 | 2-3 | Jan 2026 |
| TOTAL | 35+ | 43-65 | 4-6 weeks |
RECOMMENDATION FOR PILOT LAUNCH:
- Implement Phase 1 IMMEDIATELY (CRITICAL models) - 12-16 hours
- Consider DELAY if needed - DMS and Workflow Analyzer products CANNOT launch without their core models
- Alternative: REDUCE SCOPE - Launch with Core product only, delay DMS/Workflow addons to Week 2
End of data-model-specification.md