refactor: Reorganize Django apps into domain-based structure
Restructured 13 Django apps from flat/mixed organization into 5 logical
domain packages following cookiecutter-django conventions:
- identity/: core (tenant/domain models, middleware, mixins), users
- scheduling/: schedule, contracts, analytics
- communication/: notifications, credits, mobile, messaging
- commerce/: payments, tickets
- platform/: admin, api
Key changes:
- Moved all apps to smoothschedule/smoothschedule/{domain}/{app}/
- Updated all import paths across the codebase
- Updated settings (base.py, multitenancy.py, test.py)
- Updated URL configuration in config/urls.py
- Updated middleware and permission paths
- Preserved app_label in AppConfig for migration compatibility
- Updated CLAUDE.md documentation with new structure
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
52
CLAUDE.md
52
CLAUDE.md
@@ -62,25 +62,51 @@ docker compose -f docker-compose.local.yml exec django python manage.py <command
|
||||
| `frontend/src/types.ts` | TypeScript interfaces |
|
||||
| `frontend/src/i18n/locales/en.json` | Translations |
|
||||
|
||||
## Key Django Apps
|
||||
## Django App Organization (Domain-Based)
|
||||
|
||||
Apps are organized into domain packages under `smoothschedule/smoothschedule/`:
|
||||
|
||||
### Identity Domain
|
||||
| App | Location | Purpose |
|
||||
|-----|----------|---------|
|
||||
| `schedule` | `smoothschedule/smoothschedule/schedule/` | Resources, Events, Services |
|
||||
| `users` | `smoothschedule/smoothschedule/users/` | Authentication, User model |
|
||||
| `tenants` | `smoothschedule/smoothschedule/tenants/` | Multi-tenancy (Business model) |
|
||||
| `core` | `smoothschedule/core/` | Shared mixins, permissions, middleware |
|
||||
| `payments` | `smoothschedule/payments/` | Stripe integration, subscriptions |
|
||||
| `platform_admin` | `smoothschedule/platform_admin/` | Platform administration |
|
||||
| `core` | `identity/core/` | Tenant, Domain, PermissionGrant, middleware, mixins |
|
||||
| `users` | `identity/users/` | User model, authentication, MFA |
|
||||
|
||||
### Scheduling Domain
|
||||
| App | Location | Purpose |
|
||||
|-----|----------|---------|
|
||||
| `schedule` | `scheduling/schedule/` | Resources, Events, Services, Participants |
|
||||
| `contracts` | `scheduling/contracts/` | Contract/e-signature system |
|
||||
| `analytics` | `scheduling/analytics/` | Business analytics and reporting |
|
||||
|
||||
### Communication Domain
|
||||
| App | Location | Purpose |
|
||||
|-----|----------|---------|
|
||||
| `notifications` | `communication/notifications/` | Notification system |
|
||||
| `credits` | `communication/credits/` | SMS/calling credits |
|
||||
| `mobile` | `communication/mobile/` | Field employee mobile app |
|
||||
| `messaging` | `communication/messaging/` | Email templates and messaging |
|
||||
|
||||
### Commerce Domain
|
||||
| App | Location | Purpose |
|
||||
|-----|----------|---------|
|
||||
| `payments` | `commerce/payments/` | Stripe Connect payments bridge |
|
||||
| `tickets` | `commerce/tickets/` | Support ticket system |
|
||||
|
||||
### Platform Domain
|
||||
| App | Location | Purpose |
|
||||
|-----|----------|---------|
|
||||
| `admin` | `platform/admin/` | Platform administration, subscriptions |
|
||||
| `api` | `platform/api/` | Public API v1 for third-party integrations |
|
||||
|
||||
## Core Mixins & Base Classes
|
||||
|
||||
Located in `smoothschedule/core/mixins.py`. Use these to avoid code duplication.
|
||||
Located in `smoothschedule/smoothschedule/identity/core/mixins.py`. Use these to avoid code duplication.
|
||||
|
||||
### Permission Classes
|
||||
|
||||
```python
|
||||
from core.mixins import DenyStaffWritePermission, DenyStaffAllAccessPermission, DenyStaffListPermission
|
||||
from smoothschedule.identity.core.mixins import DenyStaffWritePermission, DenyStaffAllAccessPermission, DenyStaffListPermission
|
||||
|
||||
class MyViewSet(ModelViewSet):
|
||||
# Block write operations for staff (GET allowed)
|
||||
@@ -120,7 +146,7 @@ docker compose -f docker-compose.local.yml exec django python manage.py shell
|
||||
```
|
||||
|
||||
```python
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
# Find the staff member
|
||||
staff = User.objects.get(email='john@example.com')
|
||||
@@ -147,7 +173,7 @@ Then grant via: `staff.permissions['can_manage_equipment'] = True`
|
||||
### QuerySet Mixins
|
||||
|
||||
```python
|
||||
from core.mixins import TenantFilteredQuerySetMixin, UserTenantFilteredMixin
|
||||
from smoothschedule.identity.core.mixins import TenantFilteredQuerySetMixin, UserTenantFilteredMixin
|
||||
|
||||
# For tenant-scoped models (automatic django-tenants filtering)
|
||||
class ResourceViewSet(TenantFilteredQuerySetMixin, ModelViewSet):
|
||||
@@ -166,7 +192,7 @@ class CustomerViewSet(UserTenantFilteredMixin, ModelViewSet):
|
||||
### Feature Permission Mixins
|
||||
|
||||
```python
|
||||
from core.mixins import PluginFeatureRequiredMixin, TaskFeatureRequiredMixin
|
||||
from smoothschedule.identity.core.mixins import PluginFeatureRequiredMixin, TaskFeatureRequiredMixin
|
||||
|
||||
# Checks can_use_plugins feature on list/retrieve/create
|
||||
class PluginViewSet(PluginFeatureRequiredMixin, ModelViewSet):
|
||||
@@ -181,7 +207,7 @@ class ScheduledTaskViewSet(TaskFeatureRequiredMixin, TenantFilteredQuerySetMixin
|
||||
|
||||
```python
|
||||
from rest_framework.views import APIView
|
||||
from core.mixins import TenantAPIView, TenantRequiredAPIView
|
||||
from smoothschedule.identity.core.mixins import TenantAPIView, TenantRequiredAPIView
|
||||
|
||||
# Optional tenant - use self.get_tenant()
|
||||
class MyView(TenantAPIView, APIView):
|
||||
|
||||
383
PLAN_APP_REORGANIZATION.md
Normal file
383
PLAN_APP_REORGANIZATION.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# Django App Reorganization Plan - Option C (Domain-Based)
|
||||
|
||||
## Overview
|
||||
|
||||
Reorganize Django apps from their current scattered locations into a clean domain-based structure within `smoothschedule/smoothschedule/`.
|
||||
|
||||
**Branch:** `refactor/organize-django-apps`
|
||||
**Risk Level:** Medium-High (migration history must be preserved)
|
||||
**Estimated Parallel Agents:** 6-8
|
||||
|
||||
---
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### Current App Locations (Inconsistent)
|
||||
|
||||
| App | Current Location | Registered As |
|
||||
|-----|-----------------|---------------|
|
||||
| core | `smoothschedule/core/` | `"core"` |
|
||||
| schedule | `smoothschedule/schedule/` | `"schedule"` |
|
||||
| payments | `smoothschedule/payments/` | `"payments"` |
|
||||
| platform_admin | `smoothschedule/platform_admin/` | `"platform_admin.apps.PlatformAdminConfig"` |
|
||||
| analytics | `smoothschedule/analytics/` | `"analytics"` |
|
||||
| notifications | `smoothschedule/notifications/` | `"notifications"` |
|
||||
| tickets | `smoothschedule/tickets/` | `"tickets"` |
|
||||
| contracts | `smoothschedule/contracts/` | **NOT REGISTERED** |
|
||||
| communication | `smoothschedule/communication/` | **NOT REGISTERED** |
|
||||
| users | `smoothschedule/smoothschedule/users/` | `"smoothschedule.users"` |
|
||||
| comms_credits | `smoothschedule/smoothschedule/comms_credits/` | `"smoothschedule.comms_credits"` |
|
||||
| field_mobile | `smoothschedule/smoothschedule/field_mobile/` | `"smoothschedule.field_mobile"` |
|
||||
| public_api | `smoothschedule/smoothschedule/public_api/` | `"smoothschedule.public_api"` |
|
||||
|
||||
### Migration Counts by App
|
||||
|
||||
| App | Migrations | Complexity |
|
||||
|-----|------------|------------|
|
||||
| core | 22 | High (Tenant model) |
|
||||
| schedule | 30 | High (main business logic) |
|
||||
| payments | 1 | Low |
|
||||
| platform_admin | 12 | Medium |
|
||||
| users | 10 | Medium |
|
||||
| tickets | 13 | Medium |
|
||||
| contracts | 1 | Low |
|
||||
| notifications | 1 | Low |
|
||||
| comms_credits | 2 | Low |
|
||||
| field_mobile | 1 | Low |
|
||||
| public_api | 3 | Low |
|
||||
| analytics | 0 | None |
|
||||
| communication | 1 | Low |
|
||||
|
||||
---
|
||||
|
||||
## Target Structure (Option C - Domain-Based)
|
||||
|
||||
```
|
||||
smoothschedule/smoothschedule/
|
||||
├── __init__.py
|
||||
│
|
||||
├── identity/ # User & Tenant Management
|
||||
│ ├── __init__.py
|
||||
│ ├── core/ # Multi-tenancy, permissions, OAuth
|
||||
│ │ └── (moved from smoothschedule/core/)
|
||||
│ └── users/ # User model, auth, invitations
|
||||
│ └── (keep at current location, just move parent)
|
||||
│
|
||||
├── scheduling/ # Core Business Logic
|
||||
│ ├── __init__.py
|
||||
│ ├── schedule/ # Resources, Events, Services, Plugins
|
||||
│ │ └── (moved from smoothschedule/schedule/)
|
||||
│ ├── contracts/ # E-signatures, legal documents
|
||||
│ │ └── (moved from smoothschedule/contracts/)
|
||||
│ └── analytics/ # Reporting, dashboards
|
||||
│ └── (moved from smoothschedule/analytics/)
|
||||
│
|
||||
├── communication/ # Messaging & Notifications
|
||||
│ ├── __init__.py
|
||||
│ ├── notifications/ # In-app notifications
|
||||
│ │ └── (moved from smoothschedule/notifications/)
|
||||
│ ├── credits/ # SMS/voice credits (renamed from comms_credits)
|
||||
│ │ └── (moved from smoothschedule/smoothschedule/comms_credits/)
|
||||
│ ├── mobile/ # Field employee app (renamed from field_mobile)
|
||||
│ │ └── (moved from smoothschedule/smoothschedule/field_mobile/)
|
||||
│ └── messaging/ # Twilio conversations (renamed from communication)
|
||||
│ └── (moved from smoothschedule/communication/)
|
||||
│
|
||||
├── commerce/ # Payments & Support
|
||||
│ ├── __init__.py
|
||||
│ ├── payments/ # Stripe Connect, transactions
|
||||
│ │ └── (moved from smoothschedule/payments/)
|
||||
│ └── tickets/ # Support tickets, email integration
|
||||
│ └── (moved from smoothschedule/tickets/)
|
||||
│
|
||||
└── platform/ # Platform Administration
|
||||
├── __init__.py
|
||||
├── admin/ # Platform settings, subscriptions (renamed)
|
||||
│ └── (moved from smoothschedule/platform_admin/)
|
||||
└── api/ # Public API v1 (renamed from public_api)
|
||||
└── (moved from smoothschedule/smoothschedule/public_api/)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Critical Constraints
|
||||
|
||||
### 1. Migration History Preservation
|
||||
|
||||
Django migrations contain the app label in their `dependencies` and `app_label` references. We MUST:
|
||||
|
||||
- **Keep `app_label` unchanged** in each app's `Meta` class
|
||||
- Update `AppConfig.name` to the new dotted path
|
||||
- Django will use the `app_label` (not the path) for migration tracking
|
||||
|
||||
### 2. Foreign Key String References
|
||||
|
||||
Models use string references like `'users.User'` and `'core.Tenant'`. These reference `app_label`, not the module path, so they remain valid.
|
||||
|
||||
### 3. Import Path Updates
|
||||
|
||||
All imports across the codebase must be updated:
|
||||
- `from core.models import Tenant` → `from smoothschedule.identity.core.models import Tenant`
|
||||
- `from schedule.models import Event` → `from smoothschedule.scheduling.schedule.models import Event`
|
||||
|
||||
### 4. URL Configuration
|
||||
|
||||
`config/urls.py` imports views directly - all import paths must be updated.
|
||||
|
||||
### 5. Settings Files
|
||||
|
||||
- `config/settings/base.py` - `LOCAL_APPS`
|
||||
- `config/settings/multitenancy.py` - `SHARED_APPS`, `TENANT_APPS`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### Phase 1: Preparation (Serial)
|
||||
|
||||
**Agent 1: Setup & Verification**
|
||||
1. Create all domain package directories with `__init__.py` files
|
||||
2. Verify Docker is running and database is accessible
|
||||
3. Run existing tests to establish baseline
|
||||
4. Create backup of current migration state
|
||||
|
||||
```bash
|
||||
# Create domain packages
|
||||
mkdir -p smoothschedule/smoothschedule/identity
|
||||
mkdir -p smoothschedule/smoothschedule/scheduling
|
||||
mkdir -p smoothschedule/smoothschedule/communication
|
||||
mkdir -p smoothschedule/smoothschedule/commerce
|
||||
mkdir -p smoothschedule/smoothschedule/platform
|
||||
|
||||
# Create __init__.py files
|
||||
touch smoothschedule/smoothschedule/identity/__init__.py
|
||||
touch smoothschedule/smoothschedule/scheduling/__init__.py
|
||||
touch smoothschedule/smoothschedule/communication/__init__.py
|
||||
touch smoothschedule/smoothschedule/commerce/__init__.py
|
||||
touch smoothschedule/smoothschedule/platform/__init__.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Move Apps (Parallel - 5 Agents)
|
||||
|
||||
Each agent handles one domain. For each app move:
|
||||
|
||||
1. **Move directory** to new location
|
||||
2. **Update `apps.py`** - change `name` to new dotted path, keep `label` same
|
||||
3. **Update internal imports** within the app
|
||||
4. **Add explicit `app_label`** to all model Meta classes (if not present)
|
||||
|
||||
#### Agent 2: Identity Domain
|
||||
Move and update:
|
||||
- `smoothschedule/core/` → `smoothschedule/smoothschedule/identity/core/`
|
||||
- `smoothschedule/smoothschedule/users/` → `smoothschedule/smoothschedule/identity/users/`
|
||||
|
||||
**apps.py changes:**
|
||||
```python
|
||||
# identity/core/apps.py
|
||||
class CoreConfig(AppConfig):
|
||||
name = "smoothschedule.identity.core" # NEW
|
||||
label = "core" # KEEP SAME
|
||||
verbose_name = "Core"
|
||||
|
||||
# identity/users/apps.py
|
||||
class UsersConfig(AppConfig):
|
||||
name = "smoothschedule.identity.users" # NEW
|
||||
label = "users" # KEEP SAME
|
||||
```
|
||||
|
||||
#### Agent 3: Scheduling Domain
|
||||
Move and update:
|
||||
- `smoothschedule/schedule/` → `smoothschedule/smoothschedule/scheduling/schedule/`
|
||||
- `smoothschedule/contracts/` → `smoothschedule/smoothschedule/scheduling/contracts/`
|
||||
- `smoothschedule/analytics/` → `smoothschedule/smoothschedule/scheduling/analytics/`
|
||||
|
||||
#### Agent 4: Communication Domain
|
||||
Move and update:
|
||||
- `smoothschedule/notifications/` → `smoothschedule/smoothschedule/communication/notifications/`
|
||||
- `smoothschedule/smoothschedule/comms_credits/` → `smoothschedule/smoothschedule/communication/credits/`
|
||||
- `smoothschedule/smoothschedule/field_mobile/` → `smoothschedule/smoothschedule/communication/mobile/`
|
||||
- `smoothschedule/communication/` → `smoothschedule/smoothschedule/communication/messaging/`
|
||||
|
||||
**Note:** Rename apps for clarity:
|
||||
- `comms_credits` label stays same, path changes
|
||||
- `field_mobile` label stays same, path changes
|
||||
- `communication` label stays same, path changes
|
||||
|
||||
#### Agent 5: Commerce Domain
|
||||
Move and update:
|
||||
- `smoothschedule/payments/` → `smoothschedule/smoothschedule/commerce/payments/`
|
||||
- `smoothschedule/tickets/` → `smoothschedule/smoothschedule/commerce/tickets/`
|
||||
|
||||
#### Agent 6: Platform Domain
|
||||
Move and update:
|
||||
- `smoothschedule/platform_admin/` → `smoothschedule/smoothschedule/platform/admin/`
|
||||
- `smoothschedule/smoothschedule/public_api/` → `smoothschedule/smoothschedule/platform/api/`
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Update Settings (Serial)
|
||||
|
||||
**Agent 7: Settings Configuration**
|
||||
|
||||
Update `config/settings/base.py`:
|
||||
```python
|
||||
LOCAL_APPS = [
|
||||
# Identity
|
||||
"smoothschedule.identity.users",
|
||||
"smoothschedule.identity.core",
|
||||
|
||||
# Scheduling
|
||||
"smoothschedule.scheduling.schedule",
|
||||
"smoothschedule.scheduling.contracts",
|
||||
"smoothschedule.scheduling.analytics",
|
||||
|
||||
# Communication
|
||||
"smoothschedule.communication.notifications",
|
||||
"smoothschedule.communication.credits",
|
||||
"smoothschedule.communication.mobile",
|
||||
"smoothschedule.communication.messaging",
|
||||
|
||||
# Commerce
|
||||
"smoothschedule.commerce.payments",
|
||||
"smoothschedule.commerce.tickets",
|
||||
|
||||
# Platform
|
||||
"smoothschedule.platform.admin",
|
||||
"smoothschedule.platform.api",
|
||||
]
|
||||
```
|
||||
|
||||
Update `config/settings/multitenancy.py`:
|
||||
```python
|
||||
SHARED_APPS = [
|
||||
'django_tenants',
|
||||
'smoothschedule.identity.core',
|
||||
'smoothschedule.platform.admin',
|
||||
# ... rest of shared apps with new paths
|
||||
]
|
||||
|
||||
TENANT_APPS = [
|
||||
'django.contrib.contenttypes',
|
||||
'smoothschedule.scheduling.schedule',
|
||||
'smoothschedule.commerce.payments',
|
||||
'smoothschedule.scheduling.contracts',
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 4: Update All Import Paths (Parallel - Multiple Agents)
|
||||
|
||||
**This is the largest task.** Each agent handles specific import patterns:
|
||||
|
||||
#### Agent 8: Core Imports
|
||||
Find and replace across entire codebase:
|
||||
- `from core.models import` → `from smoothschedule.identity.core.models import`
|
||||
- `from core.` → `from smoothschedule.identity.core.`
|
||||
- `import core` → `import smoothschedule.identity.core as core`
|
||||
|
||||
#### Agent 9: Schedule Imports
|
||||
- `from schedule.models import` → `from smoothschedule.scheduling.schedule.models import`
|
||||
- `from schedule.` → `from smoothschedule.scheduling.schedule.`
|
||||
|
||||
#### Agent 10: Users/Auth Imports
|
||||
- `from smoothschedule.users.` → `from smoothschedule.identity.users.`
|
||||
- `from users.` → `from smoothschedule.identity.users.`
|
||||
|
||||
#### Agent 11: Other App Imports
|
||||
Handle remaining apps:
|
||||
- payments, tickets, notifications, contracts, analytics
|
||||
- platform_admin, public_api, comms_credits, field_mobile, communication
|
||||
|
||||
---
|
||||
|
||||
### Phase 5: URL Configuration Updates (Serial)
|
||||
|
||||
**Agent 12: URL Updates**
|
||||
|
||||
Update `config/urls.py` with new import paths:
|
||||
```python
|
||||
# Old
|
||||
from schedule.views import ResourceViewSet, EventViewSet
|
||||
from core.api_views import business_current
|
||||
|
||||
# New
|
||||
from smoothschedule.scheduling.schedule.views import ResourceViewSet, EventViewSet
|
||||
from smoothschedule.identity.core.api_views import business_current
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 6: Cleanup & Verification (Serial)
|
||||
|
||||
**Agent 13: Cleanup**
|
||||
1. Remove old empty directories at top level
|
||||
2. Remove deprecated `smoothschedule/smoothschedule/schedule/` directory
|
||||
3. Update `CLAUDE.md` documentation
|
||||
4. Update any remaining references
|
||||
|
||||
**Agent 14: Verification**
|
||||
1. Run `docker compose exec django python manage.py check`
|
||||
2. Run `docker compose exec django python manage.py makemigrations --check`
|
||||
3. Run `docker compose exec django python manage.py migrate --check`
|
||||
4. Run test suite
|
||||
5. Manual smoke test of key endpoints
|
||||
|
||||
---
|
||||
|
||||
## App Label Mapping Reference
|
||||
|
||||
| Old Import Path | New Import Path | app_label (unchanged) |
|
||||
|----------------|-----------------|----------------------|
|
||||
| `core` | `smoothschedule.identity.core` | `core` |
|
||||
| `smoothschedule.users` | `smoothschedule.identity.users` | `users` |
|
||||
| `schedule` | `smoothschedule.scheduling.schedule` | `schedule` |
|
||||
| `contracts` | `smoothschedule.scheduling.contracts` | `contracts` |
|
||||
| `analytics` | `smoothschedule.scheduling.analytics` | `analytics` |
|
||||
| `notifications` | `smoothschedule.communication.notifications` | `notifications` |
|
||||
| `smoothschedule.comms_credits` | `smoothschedule.communication.credits` | `comms_credits` |
|
||||
| `smoothschedule.field_mobile` | `smoothschedule.communication.mobile` | `field_mobile` |
|
||||
| `communication` | `smoothschedule.communication.messaging` | `communication` |
|
||||
| `payments` | `smoothschedule.commerce.payments` | `payments` |
|
||||
| `tickets` | `smoothschedule.commerce.tickets` | `tickets` |
|
||||
| `platform_admin` | `smoothschedule.platform.admin` | `platform_admin` |
|
||||
| `smoothschedule.public_api` | `smoothschedule.platform.api` | `public_api` |
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues are encountered:
|
||||
|
||||
1. **Git Reset:** `git checkout main` and delete branch
|
||||
2. **Database:** No migration changes, database remains intact
|
||||
3. **Docker:** Rebuild containers if needed
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- [ ] All apps moved to domain-based structure
|
||||
- [ ] `python manage.py check` passes
|
||||
- [ ] `python manage.py makemigrations --check` shows no changes
|
||||
- [ ] All existing tests pass
|
||||
- [ ] Frontend can communicate with API
|
||||
- [ ] Mobile app can communicate with API
|
||||
- [ ] CLAUDE.md updated with new structure
|
||||
|
||||
---
|
||||
|
||||
## Execution Order
|
||||
|
||||
```
|
||||
Phase 1 (Serial): Agent 1 - Setup
|
||||
Phase 2 (Parallel): Agents 2-6 - Move apps by domain
|
||||
Phase 3 (Serial): Agent 7 - Update settings
|
||||
Phase 4 (Parallel): Agents 8-11 - Update imports
|
||||
Phase 5 (Serial): Agent 12 - URL updates
|
||||
Phase 6 (Serial): Agents 13-14 - Cleanup & verify
|
||||
```
|
||||
|
||||
**Total Agents:** 14 (8 can run in parallel at peak)
|
||||
@@ -289,7 +289,7 @@ docker compose -f docker-compose.local.yml exec django python manage.py migrate
|
||||
from django.test import TestCase, RequestFactory
|
||||
from rest_framework.test import APITestCase
|
||||
from core.models import Tenant
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
class CalendarSyncTests(APITestCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -95,23 +95,38 @@ smoothschedule/
|
||||
│ └── traefik/
|
||||
```
|
||||
|
||||
### Django Apps
|
||||
### Django Apps (Domain-Based Organization)
|
||||
|
||||
```
|
||||
smoothschedule/smoothschedule/
|
||||
├── users/ # User management, authentication
|
||||
│ ├── models.py # User model with roles
|
||||
│ ├── api_views.py # Auth endpoints, user API
|
||||
│ └── migrations/
|
||||
├── schedule/ # Core scheduling functionality
|
||||
│ ├── models.py # Resource, Event, Service, Participant
|
||||
│ ├── serializers.py # DRF serializers
|
||||
│ ├── views.py # ViewSets for API
|
||||
│ ├── services.py # AvailabilityService
|
||||
│ └── migrations/
|
||||
├── tenants/ # Multi-tenancy (Business/Tenant models)
|
||||
│ ├── models.py # Tenant, Domain models
|
||||
│ └── migrations/
|
||||
├── identity/ # Identity Domain
|
||||
│ ├── core/ # Tenant, Domain, middleware, mixins
|
||||
│ │ ├── models.py # Tenant, Domain, PermissionGrant
|
||||
│ │ ├── middleware.py # TenantHeader, Sandbox, Masquerade
|
||||
│ │ └── mixins.py # Base classes for views/viewsets
|
||||
│ └── users/ # User management, authentication
|
||||
│ ├── models.py # User model with roles
|
||||
│ ├── api_views.py # Auth endpoints
|
||||
│ └── mfa_api_views.py # MFA endpoints
|
||||
├── scheduling/ # Scheduling Domain
|
||||
│ ├── schedule/ # Core scheduling functionality
|
||||
│ │ ├── models.py # Resource, Event, Service, Participant
|
||||
│ │ ├── serializers.py # DRF serializers
|
||||
│ │ ├── views.py # ViewSets for API
|
||||
│ │ └── services.py # AvailabilityService
|
||||
│ ├── contracts/ # E-signature system
|
||||
│ └── analytics/ # Business analytics
|
||||
├── communication/ # Communication Domain
|
||||
│ ├── notifications/ # Notification system
|
||||
│ ├── credits/ # SMS/calling credits
|
||||
│ ├── mobile/ # Field employee mobile app
|
||||
│ └── messaging/ # Email templates
|
||||
├── commerce/ # Commerce Domain
|
||||
│ ├── payments/ # Stripe Connect integration
|
||||
│ └── tickets/ # Support tickets
|
||||
└── platform/ # Platform Domain
|
||||
├── admin/ # Platform administration
|
||||
└── api/ # Public API v1
|
||||
```
|
||||
|
||||
### API Endpoints
|
||||
@@ -160,16 +175,20 @@ You're trying to run Python directly instead of through Docker. Use `docker comp
|
||||
|
||||
## Key Models
|
||||
|
||||
### Resource (schedule/models.py)
|
||||
### Resource (scheduling/schedule/models.py)
|
||||
- `name`, `type` (STAFF/ROOM/EQUIPMENT)
|
||||
- `max_concurrent_events` - concurrency limit (1=exclusive, >1=multilane, 0=unlimited)
|
||||
- `saved_lane_count` - remembers lane count when multilane disabled
|
||||
- `buffer_duration` - time between events
|
||||
|
||||
### Event (schedule/models.py)
|
||||
### Event (scheduling/schedule/models.py)
|
||||
- `title`, `start_time`, `end_time`, `status`
|
||||
- Links to resources/customers via `Participant` model
|
||||
|
||||
### User (users/models.py)
|
||||
### User (identity/users/models.py)
|
||||
- Roles: `superuser`, `platform_manager`, `platform_support`, `owner`, `manager`, `staff`, `resource`, `customer`
|
||||
- `business_subdomain` - which tenant they belong to
|
||||
|
||||
### Tenant (identity/core/models.py)
|
||||
- `name`, `subdomain`, `schema_name`
|
||||
- Multi-tenancy via django-tenants
|
||||
|
||||
@@ -159,7 +159,7 @@ docker-compose -f docker-compose.local.yml run --rm django python manage.py migr
|
||||
In Django shell or admin, create users with different roles:
|
||||
|
||||
```python
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
from core.models import Tenant
|
||||
|
||||
# Get the tenant
|
||||
|
||||
@@ -2,7 +2,7 @@ from django.conf import settings
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.routers import SimpleRouter
|
||||
|
||||
from smoothschedule.users.api.views import UserViewSet
|
||||
from smoothschedule.identity.users.api.views import UserViewSet
|
||||
|
||||
router = DefaultRouter() if settings.DEBUG else SimpleRouter()
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ django_asgi_app = get_asgi_application()
|
||||
from channels.auth import AuthMiddlewareStack
|
||||
from channels.routing import ProtocolTypeRouter, URLRouter
|
||||
|
||||
from tickets import routing as tickets_routing
|
||||
from schedule import routing as schedule_routing
|
||||
from tickets.middleware import TokenAuthMiddleware
|
||||
from smoothschedule.commerce.tickets import routing as tickets_routing
|
||||
from smoothschedule.scheduling.schedule import routing as schedule_routing
|
||||
from smoothschedule.commerce.tickets.middleware import TokenAuthMiddleware
|
||||
|
||||
|
||||
application = ProtocolTypeRouter(
|
||||
|
||||
@@ -97,17 +97,28 @@ THIRD_PARTY_APPS = [
|
||||
]
|
||||
|
||||
LOCAL_APPS = [
|
||||
"smoothschedule.users",
|
||||
"core",
|
||||
"schedule",
|
||||
"analytics",
|
||||
"payments",
|
||||
"platform_admin.apps.PlatformAdminConfig",
|
||||
"notifications", # New: Generic notification app
|
||||
"tickets", # New: Support tickets app
|
||||
"smoothschedule.comms_credits", # Communication credits and SMS/calling
|
||||
"smoothschedule.field_mobile", # Field employee mobile app
|
||||
# Your stuff: custom apps go here
|
||||
# Identity Domain
|
||||
"smoothschedule.identity.users",
|
||||
"smoothschedule.identity.core",
|
||||
|
||||
# Scheduling Domain
|
||||
"smoothschedule.scheduling.schedule",
|
||||
"smoothschedule.scheduling.contracts",
|
||||
"smoothschedule.scheduling.analytics",
|
||||
|
||||
# Communication Domain
|
||||
"smoothschedule.communication.notifications",
|
||||
"smoothschedule.communication.credits", # SMS/calling credits (was comms_credits)
|
||||
"smoothschedule.communication.mobile", # Field employee app (was field_mobile)
|
||||
"smoothschedule.communication.messaging", # Twilio conversations (was communication)
|
||||
|
||||
# Commerce Domain
|
||||
"smoothschedule.commerce.payments",
|
||||
"smoothschedule.commerce.tickets",
|
||||
|
||||
# Platform Domain
|
||||
"smoothschedule.platform.admin", # Platform settings (was platform_admin)
|
||||
"smoothschedule.platform.api", # Public API v1 (was public_api)
|
||||
]
|
||||
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
|
||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||
@@ -183,7 +194,7 @@ TEMPLATES = [
|
||||
"django.template.context_processors.media",
|
||||
"django.template.context_processors.static",
|
||||
"django.template.context_processors.tz",
|
||||
"smoothschedule.users.context_processors.allauth_settings",
|
||||
"smoothschedule.identity.users.context_processors.allauth_settings",
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -318,13 +329,13 @@ ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
|
||||
# https://docs.allauth.org/en/latest/account/configuration.html
|
||||
ACCOUNT_ADAPTER = "smoothschedule.users.adapters.AccountAdapter"
|
||||
ACCOUNT_ADAPTER = "smoothschedule.identity.users.adapters.AccountAdapter"
|
||||
# https://docs.allauth.org/en/latest/account/forms.html
|
||||
ACCOUNT_FORMS = {"signup": "smoothschedule.users.forms.UserSignupForm"}
|
||||
ACCOUNT_FORMS = {"signup": "smoothschedule.identity.users.forms.UserSignupForm"}
|
||||
# https://docs.allauth.org/en/latest/socialaccount/configuration.html
|
||||
SOCIALACCOUNT_ADAPTER = "smoothschedule.users.adapters.SocialAccountAdapter"
|
||||
SOCIALACCOUNT_ADAPTER = "smoothschedule.identity.users.adapters.SocialAccountAdapter"
|
||||
# https://docs.allauth.org/en/latest/socialaccount/configuration.html
|
||||
SOCIALACCOUNT_FORMS = {"signup": "smoothschedule.users.forms.UserSocialSignupForm"}
|
||||
SOCIALACCOUNT_FORMS = {"signup": "smoothschedule.identity.users.forms.UserSocialSignupForm"}
|
||||
|
||||
# django-rest-framework
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
@@ -13,10 +13,16 @@ from .base import INSTALLED_APPS, MIDDLEWARE, DATABASES, LOGGING, env
|
||||
# Shared apps - Available to all tenants (stored in 'public' schema)
|
||||
SHARED_APPS = [
|
||||
'django_tenants', # Must be first
|
||||
'core', # Core models (Tenant, Domain, PermissionGrant)
|
||||
'platform_admin.apps.PlatformAdminConfig', # Platform management (TenantInvitation, etc.)
|
||||
|
||||
# Django built-ins (must be in shared
|
||||
# Identity Domain (shared)
|
||||
'smoothschedule.identity.core', # Core models (Tenant, Domain, PermissionGrant)
|
||||
'smoothschedule.identity.users', # Users app (shared across tenants)
|
||||
|
||||
# Platform Domain (shared)
|
||||
'smoothschedule.platform.admin', # Platform management (TenantInvitation, etc.)
|
||||
'smoothschedule.platform.api', # Public API v1 for third-party integrations
|
||||
|
||||
# Django built-ins (must be in shared)
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.sessions',
|
||||
@@ -25,15 +31,12 @@ SHARED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.admin',
|
||||
|
||||
# Users app (shared across tenants)
|
||||
'smoothschedule.users',
|
||||
|
||||
# Third-party apps that should be shared
|
||||
'rest_framework',
|
||||
'rest_framework.authtoken',
|
||||
'corsheaders',
|
||||
'drf_spectacular',
|
||||
'channels', # WebSockets
|
||||
'channels', # WebSockets
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.mfa',
|
||||
@@ -45,23 +48,26 @@ SHARED_APPS = [
|
||||
'crispy_bootstrap5',
|
||||
'csp',
|
||||
'djstripe', # Stripe integration
|
||||
'tickets', # Ticket system - shared for platform support access
|
||||
'notifications', # Notification system - shared for platform to notify tenants
|
||||
'smoothschedule.public_api', # Public API v1 for third-party integrations
|
||||
'smoothschedule.comms_credits', # Communication credits (SMS/calling) - shared for billing
|
||||
'smoothschedule.field_mobile', # Field employee mobile app - shared for location tracking
|
||||
|
||||
# Commerce Domain (shared for platform support)
|
||||
'smoothschedule.commerce.tickets', # Ticket system - shared for platform support access
|
||||
|
||||
# Communication Domain (shared)
|
||||
'smoothschedule.communication.notifications', # Notification system - shared for platform
|
||||
'smoothschedule.communication.credits', # Communication credits (SMS/calling) - shared for billing
|
||||
'smoothschedule.communication.mobile', # Field employee mobile app - shared for location tracking
|
||||
]
|
||||
|
||||
# Tenant-specific apps - Each tenant gets isolated data in their own schema
|
||||
TENANT_APPS = [
|
||||
'django.contrib.contenttypes', # Needed for tenant schemas
|
||||
'schedule', # Resource scheduling with configurable concurrency
|
||||
'payments', # Stripe Connect payments bridge
|
||||
'contracts', # Contract/e-signature system
|
||||
# Add your tenant-scoped business logic apps here:
|
||||
# 'appointments',
|
||||
# 'customers',
|
||||
# 'analytics',
|
||||
|
||||
# Scheduling Domain (tenant-isolated)
|
||||
'smoothschedule.scheduling.schedule', # Resource scheduling with configurable concurrency
|
||||
'smoothschedule.scheduling.contracts', # Contract/e-signature system
|
||||
|
||||
# Commerce Domain (tenant-isolated)
|
||||
'smoothschedule.commerce.payments', # Stripe Connect payments bridge
|
||||
]
|
||||
|
||||
|
||||
@@ -96,7 +102,7 @@ MIDDLEWARE = [
|
||||
|
||||
# 1. Tenant resolution
|
||||
'django_tenants.middleware.main.TenantMainMiddleware',
|
||||
'core.middleware.TenantHeaderMiddleware', # Support tenant switching via header
|
||||
'smoothschedule.identity.core.middleware.TenantHeaderMiddleware', # Support tenant switching via header
|
||||
|
||||
# 2. Security middleware
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
@@ -108,7 +114,7 @@ MIDDLEWARE = [
|
||||
|
||||
# 4. Sandbox mode - switches to sandbox schema if requested
|
||||
# MUST come after TenantMainMiddleware and SessionMiddleware
|
||||
'core.middleware.SandboxModeMiddleware',
|
||||
'smoothschedule.identity.core.middleware.SandboxModeMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
@@ -120,7 +126,7 @@ MIDDLEWARE = [
|
||||
'hijack.middleware.HijackUserMiddleware',
|
||||
|
||||
# 6. MASQUERADE AUDIT - MUST come AFTER HijackUserMiddleware
|
||||
'core.middleware.MasqueradeAuditMiddleware',
|
||||
'smoothschedule.identity.core.middleware.MasqueradeAuditMiddleware',
|
||||
|
||||
# 7. Messages, Clickjacking, and Allauth
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
@@ -176,7 +182,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
# HIJACK (MASQUERADING) CONFIGURATION
|
||||
# =============================================================================
|
||||
|
||||
HIJACK_AUTHORIZATION_CHECK = 'core.permissions.can_hijack'
|
||||
HIJACK_AUTHORIZATION_CHECK = 'smoothschedule.identity.core.permissions.can_hijack'
|
||||
HIJACK_DISPLAY_ADMIN_BUTTON = True
|
||||
HIJACK_USE_BOOTSTRAP = True
|
||||
HIJACK_ALLOW_GET_REQUESTS = False # Security: require POST
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
With these settings, tests run faster.
|
||||
"""
|
||||
|
||||
from .base import * # noqa: F403
|
||||
from .base import TEMPLATES
|
||||
from .base import env
|
||||
from .multitenancy import * # noqa: F403
|
||||
from .multitenancy import TEMPLATES, env
|
||||
|
||||
# GENERAL
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
@@ -10,33 +10,33 @@ from drf_spectacular.views import SpectacularAPIView
|
||||
from drf_spectacular.views import SpectacularSwaggerView
|
||||
from rest_framework.authtoken.views import obtain_auth_token
|
||||
|
||||
from smoothschedule.users.api_views import (
|
||||
from smoothschedule.identity.users.api_views import (
|
||||
login_view, current_user_view, logout_view, send_verification_email, verify_email,
|
||||
hijack_acquire_view, hijack_release_view,
|
||||
staff_invitations_view, cancel_invitation_view, resend_invitation_view,
|
||||
invitation_details_view, accept_invitation_view, decline_invitation_view,
|
||||
check_subdomain_view, signup_view
|
||||
)
|
||||
from smoothschedule.users.mfa_api_views import (
|
||||
from smoothschedule.identity.users.mfa_api_views import (
|
||||
mfa_status, send_phone_verification, verify_phone, enable_sms_mfa,
|
||||
setup_totp, verify_totp_setup, generate_backup_codes, backup_codes_status,
|
||||
disable_mfa, mfa_login_send_code, mfa_login_verify,
|
||||
list_trusted_devices, revoke_trusted_device, revoke_all_trusted_devices
|
||||
)
|
||||
from schedule.api_views import (
|
||||
from smoothschedule.scheduling.schedule.api_views import (
|
||||
current_business_view, update_business_view,
|
||||
oauth_settings_view, oauth_credentials_view,
|
||||
custom_domains_view, custom_domain_detail_view,
|
||||
custom_domain_verify_view, custom_domain_set_primary_view,
|
||||
sandbox_status_view, sandbox_toggle_view, sandbox_reset_view
|
||||
)
|
||||
from core.email_autoconfig import (
|
||||
from smoothschedule.identity.core.email_autoconfig import (
|
||||
MozillaAutoconfigView,
|
||||
MicrosoftAutodiscoverView,
|
||||
AppleConfigProfileView,
|
||||
WellKnownAutoconfigView,
|
||||
)
|
||||
from core.api_views import (
|
||||
from smoothschedule.identity.core.api_views import (
|
||||
quota_status_view,
|
||||
quota_resources_view,
|
||||
quota_archive_view,
|
||||
@@ -48,7 +48,7 @@ urlpatterns = [
|
||||
# Django Admin, use {% url 'admin:index' %}
|
||||
path(settings.ADMIN_URL, admin.site.urls),
|
||||
# User management
|
||||
path("users/", include("smoothschedule.users.urls", namespace="users")),
|
||||
path("users/", include("smoothschedule.identity.users.urls", namespace="users")),
|
||||
path("accounts/", include("allauth.urls")),
|
||||
# Django Hijack (masquerade) - for admin interface
|
||||
path("hijack/", include("hijack.urls")),
|
||||
@@ -78,28 +78,28 @@ urlpatterns += [
|
||||
# Stripe Webhooks (dj-stripe built-in handler)
|
||||
path("stripe/", include("djstripe.urls", namespace="djstripe")),
|
||||
# Public API v1 (for third-party integrations)
|
||||
path("v1/", include("smoothschedule.public_api.urls", namespace="public_api")),
|
||||
path("v1/", include("smoothschedule.platform.api.urls", namespace="public_api")),
|
||||
# Schedule API (internal)
|
||||
path("", include("schedule.urls")),
|
||||
path("", include("smoothschedule.scheduling.schedule.urls")),
|
||||
# Analytics API
|
||||
path("", include("analytics.urls")),
|
||||
path("", include("smoothschedule.scheduling.analytics.urls")),
|
||||
# Payments API
|
||||
path("payments/", include("payments.urls")),
|
||||
path("payments/", include("smoothschedule.commerce.payments.urls")),
|
||||
# Contracts API
|
||||
path("contracts/", include("contracts.urls")),
|
||||
path("contracts/", include("smoothschedule.scheduling.contracts.urls")),
|
||||
# Communication Credits API
|
||||
path("communication-credits/", include("smoothschedule.comms_credits.urls", namespace="comms_credits")),
|
||||
path("communication-credits/", include("smoothschedule.communication.credits.urls", namespace="comms_credits")),
|
||||
# Field Mobile API (for field employee mobile app)
|
||||
path("mobile/", include("smoothschedule.field_mobile.urls", namespace="field_mobile")),
|
||||
path("mobile/", include("smoothschedule.communication.mobile.urls", namespace="field_mobile")),
|
||||
# Tickets API
|
||||
path("tickets/", include("tickets.urls")),
|
||||
path("tickets/", include("smoothschedule.commerce.tickets.urls")),
|
||||
# Notifications API
|
||||
path("notifications/", include("notifications.urls")),
|
||||
path("notifications/", include("smoothschedule.communication.notifications.urls")),
|
||||
# Platform API
|
||||
path("platform/", include("platform_admin.urls", namespace="platform")),
|
||||
path("platform/", include("smoothschedule.platform.admin.urls", namespace="platform")),
|
||||
# OAuth Email Integration API
|
||||
path("oauth/", include("core.oauth_urls", namespace="oauth")),
|
||||
path("auth/oauth/", include("core.oauth_urls", namespace="auth_oauth")),
|
||||
path("oauth/", include("smoothschedule.identity.core.oauth_urls", namespace="oauth")),
|
||||
path("auth/oauth/", include("smoothschedule.identity.core.oauth_urls", namespace="auth_oauth")),
|
||||
# Auth API
|
||||
path("auth-token/", csrf_exempt(obtain_auth_token), name="obtain_auth_token"),
|
||||
path("auth/signup/check-subdomain/", check_subdomain_view, name="check_subdomain"),
|
||||
|
||||
@@ -6,7 +6,7 @@ import django
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
|
||||
django.setup()
|
||||
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
from rest_framework.authtoken.models import Token
|
||||
|
||||
# Create or get a superuser with platform admin role
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
Create a default tenant for local development
|
||||
"""
|
||||
from core.models import Tenant, Domain
|
||||
from smoothschedule.identity.core.models import Tenant, Domain
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
import django
|
||||
from django.conf import settings
|
||||
from django_tenants.utils import tenant_context
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
|
||||
# Setup Django
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.local")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Script to ensure production domain exists in the database.
|
||||
Run with: python manage.py shell < scripts/ensure_production_domain.py
|
||||
"""
|
||||
from core.models import Tenant, Domain
|
||||
from smoothschedule.identity.core.models import Tenant, Domain
|
||||
from django.conf import settings
|
||||
|
||||
def ensure_production_domain():
|
||||
|
||||
@@ -3,4 +3,5 @@ from django.apps import AppConfig
|
||||
|
||||
class PaymentsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'payments'
|
||||
name = 'smoothschedule.commerce.payments'
|
||||
label = 'payments'
|
||||
@@ -92,6 +92,7 @@ class TransactionLink(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'payments'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['status', 'created_at']),
|
||||
@@ -10,13 +10,13 @@ from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework import status
|
||||
from core.permissions import HasFeaturePermission
|
||||
from core.mixins import TenantAPIView, TenantRequiredAPIView
|
||||
from smoothschedule.identity.core.permissions import HasFeaturePermission
|
||||
from smoothschedule.identity.core.mixins import TenantAPIView, TenantRequiredAPIView
|
||||
from decimal import Decimal
|
||||
from .services import get_stripe_service_for_tenant
|
||||
from .models import TransactionLink
|
||||
from schedule.models import Event
|
||||
from platform_admin.models import SubscriptionPlan
|
||||
from smoothschedule.scheduling.schedule.models import Event
|
||||
from smoothschedule.platform.admin.models import SubscriptionPlan
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -1532,8 +1532,8 @@ class CustomerBillingView(APIView):
|
||||
def get(self, request):
|
||||
"""Get customer billing data."""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from schedule.models import Participant
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.scheduling.schedule.models import Participant
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
user = request.user
|
||||
|
||||
@@ -1653,7 +1653,7 @@ class CustomerPaymentMethodsView(APIView):
|
||||
|
||||
def get(self, request):
|
||||
"""Get customer's saved payment methods from Stripe."""
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
user = request.user
|
||||
|
||||
@@ -1726,7 +1726,7 @@ class CustomerSetupIntentView(APIView):
|
||||
"""Create a SetupIntent for the customer."""
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
user = request.user
|
||||
tenant = request.tenant
|
||||
@@ -1841,7 +1841,7 @@ class CustomerPaymentMethodDeleteView(APIView):
|
||||
|
||||
def delete(self, request, payment_method_id):
|
||||
"""Delete a payment method."""
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
user = request.user
|
||||
|
||||
@@ -1904,7 +1904,7 @@ class CustomerPaymentMethodDefaultView(APIView):
|
||||
|
||||
def post(self, request, payment_method_id):
|
||||
"""Set payment method as default."""
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
user = request.user
|
||||
|
||||
@@ -1989,8 +1989,8 @@ class SetFinalPriceView(APIView):
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from schedule.models import Participant
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.scheduling.schedule.models import Participant
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
final_price = request.data.get('final_price')
|
||||
charge_now = request.data.get('charge_now', True)
|
||||
@@ -7,7 +7,7 @@ from django.dispatch import receiver
|
||||
from djstripe import signals
|
||||
from django.utils import timezone
|
||||
from .models import TransactionLink
|
||||
from schedule.models import Event
|
||||
from smoothschedule.scheduling.schedule.models import Event
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -3,7 +3,8 @@ from django.apps import AppConfig
|
||||
|
||||
class TicketsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'tickets'
|
||||
name = 'smoothschedule.commerce.tickets'
|
||||
label = 'tickets'
|
||||
|
||||
def ready(self):
|
||||
import tickets.signals # noqa
|
||||
import smoothschedule.commerce.tickets.signals # noqa
|
||||
@@ -2,7 +2,7 @@ import json
|
||||
from channels.generic.websocket import AsyncWebsocketConsumer
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
from .models import Ticket, TicketComment
|
||||
from .serializers import TicketSerializer, TicketCommentSerializer # Import your serializers
|
||||
|
||||
@@ -31,7 +31,7 @@ def get_default_platform_email():
|
||||
Returns None if no default is configured.
|
||||
"""
|
||||
try:
|
||||
from platform_admin.models import PlatformEmailAddress
|
||||
from smoothschedule.platform.admin.models import PlatformEmailAddress
|
||||
return PlatformEmailAddress.objects.filter(
|
||||
is_default=True,
|
||||
is_active=True,
|
||||
@@ -75,7 +75,7 @@ class TicketEmailService:
|
||||
Returns None if template not found.
|
||||
"""
|
||||
try:
|
||||
from schedule.models import EmailTemplate
|
||||
from smoothschedule.scheduling.schedule.models import EmailTemplate
|
||||
return EmailTemplate.objects.filter(
|
||||
name=template_name,
|
||||
scope=EmailTemplate.Scope.BUSINESS
|
||||
@@ -37,7 +37,7 @@ from .models import (
|
||||
TicketEmailAddress,
|
||||
IncomingTicketEmail
|
||||
)
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -713,7 +713,7 @@ class PlatformEmailReceiver:
|
||||
|
||||
def __init__(self, email_address):
|
||||
"""Initialize with a PlatformEmailAddress instance."""
|
||||
from platform_admin.models import PlatformEmailAddress
|
||||
from smoothschedule.platform.admin.models import PlatformEmailAddress
|
||||
self.email_address = email_address
|
||||
self.connection = None
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from core.models import Tenant
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
|
||||
class Ticket(models.Model):
|
||||
@@ -160,6 +160,7 @@ class Ticket(models.Model):
|
||||
resolved_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'tickets'
|
||||
ordering = ['-priority', '-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['tenant', 'status']),
|
||||
@@ -247,6 +248,7 @@ class TicketTemplate(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'tickets'
|
||||
ordering = ['ticket_type', 'name']
|
||||
|
||||
def __str__(self):
|
||||
@@ -285,6 +287,7 @@ class CannedResponse(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'tickets'
|
||||
ordering = ['-use_count', 'title']
|
||||
|
||||
def __str__(self):
|
||||
@@ -349,6 +352,7 @@ class TicketComment(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'tickets'
|
||||
ordering = ['created_at']
|
||||
|
||||
@property
|
||||
@@ -495,6 +499,7 @@ class IncomingTicketEmail(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'tickets'
|
||||
ordering = ['-received_at']
|
||||
indexes = [
|
||||
models.Index(fields=['message_id']),
|
||||
@@ -640,6 +645,7 @@ class TicketEmailAddress(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'tickets'
|
||||
ordering = ['-is_default', 'display_name']
|
||||
unique_together = [['tenant', 'email_address']]
|
||||
indexes = [
|
||||
@@ -1,7 +1,7 @@
|
||||
from rest_framework import serializers
|
||||
from .models import Ticket, TicketComment, TicketTemplate, CannedResponse, IncomingTicketEmail, TicketEmailAddress
|
||||
from smoothschedule.users.models import User
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.users.models import User
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
|
||||
class TicketCommentSerializer(serializers.ModelSerializer):
|
||||
author_email = serializers.ReadOnlyField(source='author.email')
|
||||
@@ -8,7 +8,7 @@ from channels.layers import get_channel_layer
|
||||
from asgiref.sync import async_to_sync
|
||||
|
||||
from .models import Ticket, TicketComment
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -25,7 +25,7 @@ def is_notifications_available():
|
||||
global _notifications_available
|
||||
if _notifications_available is None:
|
||||
try:
|
||||
from notifications.models import Notification
|
||||
from smoothschedule.communication.notifications.models import Notification
|
||||
# Check if the table exists by doing a simple query
|
||||
Notification.objects.exists()
|
||||
_notifications_available = True
|
||||
@@ -60,7 +60,7 @@ def create_notification(recipient, actor, verb, action_object, target, data):
|
||||
return
|
||||
|
||||
try:
|
||||
from notifications.models import Notification
|
||||
from smoothschedule.communication.notifications.models import Notification
|
||||
Notification.objects.create(
|
||||
recipient=recipient,
|
||||
actor=actor,
|
||||
@@ -33,7 +33,7 @@ def fetch_incoming_emails(self):
|
||||
"""
|
||||
from .email_receiver import TicketEmailReceiver, PlatformEmailReceiver
|
||||
from .models import TicketEmailAddress
|
||||
from platform_admin.models import PlatformEmailAddress
|
||||
from smoothschedule.platform.admin.models import PlatformEmailAddress
|
||||
|
||||
total_processed = 0
|
||||
results = []
|
||||
@@ -7,8 +7,8 @@ from rest_framework.views import APIView
|
||||
from django.db.models import Q
|
||||
from rest_framework.filters import OrderingFilter, SearchFilter
|
||||
|
||||
from core.models import Tenant
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from smoothschedule.identity.users.models import User
|
||||
from .models import Ticket, TicketComment, TicketTemplate, CannedResponse, IncomingTicketEmail, TicketEmailAddress
|
||||
from .serializers import (
|
||||
TicketSerializer, TicketListSerializer, TicketCommentSerializer,
|
||||
@@ -941,7 +941,7 @@ class RefreshTicketEmailsView(APIView):
|
||||
)
|
||||
|
||||
from .email_receiver import PlatformEmailReceiver
|
||||
from platform_admin.models import PlatformEmailAddress
|
||||
from smoothschedule.platform.admin.models import PlatformEmailAddress
|
||||
|
||||
results = []
|
||||
total_processed = 0
|
||||
@@ -3,5 +3,6 @@ from django.apps import AppConfig
|
||||
|
||||
class CommsCreditsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'smoothschedule.comms_credits'
|
||||
name = 'smoothschedule.communication.credits'
|
||||
label = 'comms_credits'
|
||||
verbose_name = 'Communication Credits'
|
||||
@@ -109,6 +109,7 @@ class CommunicationCredits(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'comms_credits'
|
||||
verbose_name = 'Communication Credits'
|
||||
verbose_name_plural = 'Communication Credits'
|
||||
|
||||
@@ -210,7 +211,7 @@ class CommunicationCredits(models.Model):
|
||||
|
||||
def _send_low_balance_warning(self):
|
||||
"""Send low balance warning email."""
|
||||
from smoothschedule.comms_credits.tasks import send_low_balance_warning
|
||||
from smoothschedule.communication.credits.tasks import send_low_balance_warning
|
||||
send_low_balance_warning.delay(self.id)
|
||||
|
||||
self.low_balance_warning_sent = True
|
||||
@@ -219,7 +220,7 @@ class CommunicationCredits(models.Model):
|
||||
|
||||
def _trigger_auto_reload(self):
|
||||
"""Trigger auto-reload of credits."""
|
||||
from smoothschedule.comms_credits.tasks import process_auto_reload
|
||||
from smoothschedule.communication.credits.tasks import process_auto_reload
|
||||
process_auto_reload.delay(self.id)
|
||||
|
||||
|
||||
@@ -291,6 +292,7 @@ class CreditTransaction(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'comms_credits'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['credits', '-created_at']),
|
||||
@@ -383,6 +385,7 @@ class ProxyPhoneNumber(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'comms_credits'
|
||||
ordering = ['phone_number']
|
||||
verbose_name = 'Proxy Phone Number'
|
||||
verbose_name_plural = 'Proxy Phone Numbers'
|
||||
@@ -495,6 +498,7 @@ class MaskedSession(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'comms_credits'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['tenant', 'status']),
|
||||
@@ -20,7 +20,7 @@ def sync_twilio_usage_all_tenants():
|
||||
2. Calculate charges with markup
|
||||
3. Deduct from tenant credits
|
||||
"""
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
|
||||
tenants = Tenant.objects.exclude(twilio_subaccount_sid='')
|
||||
|
||||
@@ -46,7 +46,7 @@ def sync_twilio_usage_for_tenant(tenant_id):
|
||||
|
||||
Fetches usage from Twilio API and deducts from credits.
|
||||
"""
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from .models import CommunicationCredits
|
||||
|
||||
try:
|
||||
@@ -219,7 +219,7 @@ def process_auto_reload(credits_id):
|
||||
|
||||
try:
|
||||
# Get Stripe API key from platform settings
|
||||
from platform_admin.models import PlatformSettings
|
||||
from smoothschedule.platform.admin.models import PlatformSettings
|
||||
platform_settings = PlatformSettings.get_instance()
|
||||
stripe.api_key = platform_settings.get_stripe_secret_key()
|
||||
|
||||
@@ -373,7 +373,7 @@ def create_twilio_subaccount(tenant_id):
|
||||
|
||||
Called when SMS/calling is first enabled for a tenant.
|
||||
"""
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from twilio.rest import Client
|
||||
|
||||
try:
|
||||
@@ -3,4 +3,5 @@ from django.apps import AppConfig
|
||||
|
||||
class CommunicationConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'communication'
|
||||
name = 'smoothschedule.communication.messaging'
|
||||
label = 'communication'
|
||||
@@ -83,6 +83,7 @@ class CommunicationSession(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'communication'
|
||||
ordering = ['-created_at']
|
||||
indexes = [
|
||||
models.Index(fields=['is_active', 'created_at']),
|
||||
@@ -57,7 +57,7 @@ class TwilioService:
|
||||
PermissionError: If tenant doesn't have masked calling feature
|
||||
"""
|
||||
from django.db import connection
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
# Check feature permission
|
||||
@@ -3,7 +3,7 @@ from django.apps import AppConfig
|
||||
|
||||
class FieldMobileConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'smoothschedule.field_mobile'
|
||||
name = 'smoothschedule.communication.mobile'
|
||||
label = 'field_mobile'
|
||||
verbose_name = 'Field Mobile App'
|
||||
|
||||
@@ -81,6 +81,7 @@ class EventStatusHistory(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'field_mobile'
|
||||
ordering = ['-changed_at']
|
||||
indexes = [
|
||||
models.Index(fields=['tenant', 'event_id']),
|
||||
@@ -174,6 +175,7 @@ class EmployeeLocationUpdate(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'field_mobile'
|
||||
ordering = ['-timestamp']
|
||||
indexes = [
|
||||
models.Index(fields=['tenant', 'event_id', '-timestamp']),
|
||||
@@ -315,6 +317,7 @@ class FieldCallLog(models.Model):
|
||||
ended_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
app_label = 'field_mobile'
|
||||
ordering = ['-initiated_at']
|
||||
indexes = [
|
||||
models.Index(fields=['tenant', 'event_id']),
|
||||
@@ -6,8 +6,8 @@ Serializers for the field employee mobile app API.
|
||||
from rest_framework import serializers
|
||||
from django.utils import timezone
|
||||
|
||||
from schedule.models import Event, Service, Participant
|
||||
from smoothschedule.field_mobile.models import (
|
||||
from smoothschedule.scheduling.schedule.models import Event, Service, Participant
|
||||
from smoothschedule.communication.mobile.models import (
|
||||
EventStatusHistory,
|
||||
EmployeeLocationUpdate,
|
||||
FieldCallLog,
|
||||
@@ -110,7 +110,7 @@ class JobListSerializer(serializers.ModelSerializer):
|
||||
|
||||
def get_allowed_transitions(self, obj):
|
||||
"""Get list of statuses this job can transition to."""
|
||||
from smoothschedule.field_mobile.services import StatusMachine
|
||||
from smoothschedule.communication.mobile.services import StatusMachine
|
||||
|
||||
# Get the valid transitions without needing user context
|
||||
return StatusMachine.VALID_TRANSITIONS.get(obj.status, [])
|
||||
@@ -118,7 +118,7 @@ class JobListSerializer(serializers.ModelSerializer):
|
||||
def _get_customer_participant(self, obj):
|
||||
"""Get the customer User from participants."""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
if not hasattr(self, '_customer_cache'):
|
||||
self._customer_cache = {}
|
||||
@@ -196,8 +196,8 @@ class JobDetailSerializer(serializers.ModelSerializer):
|
||||
def get_assigned_staff(self, obj):
|
||||
"""Get list of assigned staff members."""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from smoothschedule.users.models import User
|
||||
from schedule.models import Resource
|
||||
from smoothschedule.identity.users.models import User
|
||||
from smoothschedule.scheduling.schedule.models import Resource
|
||||
|
||||
staff = []
|
||||
|
||||
@@ -239,12 +239,12 @@ class JobDetailSerializer(serializers.ModelSerializer):
|
||||
return None
|
||||
|
||||
def get_allowed_transitions(self, obj):
|
||||
from smoothschedule.field_mobile.services import StatusMachine
|
||||
from smoothschedule.communication.mobile.services import StatusMachine
|
||||
return StatusMachine.VALID_TRANSITIONS.get(obj.status, [])
|
||||
|
||||
def get_can_track_location(self, obj):
|
||||
"""Check if location tracking is allowed for current status."""
|
||||
from smoothschedule.field_mobile.services import StatusMachine
|
||||
from smoothschedule.communication.mobile.services import StatusMachine
|
||||
return obj.status in StatusMachine.TRACKING_STATUSES
|
||||
|
||||
def get_has_active_call_session(self, obj):
|
||||
@@ -253,7 +253,7 @@ class JobDetailSerializer(serializers.ModelSerializer):
|
||||
if not tenant:
|
||||
return False
|
||||
|
||||
from smoothschedule.comms_credits.models import MaskedSession
|
||||
from smoothschedule.communication.credits.models import MaskedSession
|
||||
return MaskedSession.objects.filter(
|
||||
tenant=tenant,
|
||||
event_id=obj.id,
|
||||
@@ -305,7 +305,7 @@ class JobDetailSerializer(serializers.ModelSerializer):
|
||||
|
||||
def _get_customer_participant(self, obj):
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
try:
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
@@ -324,7 +324,7 @@ class JobDetailSerializer(serializers.ModelSerializer):
|
||||
Returns True if the user's linked resource has user_can_edit_schedule=True.
|
||||
"""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from schedule.models import Resource
|
||||
from smoothschedule.scheduling.schedule.models import Resource
|
||||
|
||||
# Get the current user from context
|
||||
request = self.context.get('request')
|
||||
@@ -8,8 +8,8 @@ from django.db import transaction
|
||||
from typing import Optional, Tuple
|
||||
from decimal import Decimal
|
||||
|
||||
from schedule.models import Event
|
||||
from smoothschedule.field_mobile.models import EventStatusHistory, EmployeeLocationUpdate
|
||||
from smoothschedule.scheduling.schedule.models import Event
|
||||
from smoothschedule.communication.mobile.models import EventStatusHistory, EmployeeLocationUpdate
|
||||
|
||||
|
||||
class StatusTransitionError(Exception):
|
||||
@@ -124,8 +124,8 @@ class StatusMachine:
|
||||
- User is linked to a Resource that is a participant
|
||||
"""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from schedule.models import Participant, Resource
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.scheduling.schedule.models import Participant, Resource
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
# Check if user is directly a participant
|
||||
user_ct = ContentType.objects.get_for_model(User)
|
||||
@@ -158,7 +158,7 @@ class StatusMachine:
|
||||
Returns:
|
||||
Tuple of (is_allowed, reason_if_not)
|
||||
"""
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
# Owners and managers can always change status
|
||||
if self.user.role in [User.Role.TENANT_OWNER, User.Role.TENANT_MANAGER]:
|
||||
@@ -235,7 +235,7 @@ class StatusMachine:
|
||||
self._stop_location_tracking(event)
|
||||
|
||||
# Emit status change signal (triggers notifications and plugin hooks)
|
||||
from schedule.signals import emit_status_change
|
||||
from smoothschedule.scheduling.schedule.signals import emit_status_change
|
||||
emit_status_change(
|
||||
event=event,
|
||||
old_status=old_status,
|
||||
@@ -90,7 +90,7 @@ class TwilioFieldCallService:
|
||||
Returns:
|
||||
MaskedSession instance
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import MaskedSession, ProxyPhoneNumber
|
||||
from smoothschedule.communication.credits.models import MaskedSession, ProxyPhoneNumber
|
||||
|
||||
# Check for existing active session
|
||||
existing = MaskedSession.objects.filter(
|
||||
@@ -145,7 +145,7 @@ class TwilioFieldCallService:
|
||||
1. Numbers already assigned to this tenant
|
||||
2. Numbers in the shared pool (AVAILABLE status)
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import ProxyPhoneNumber
|
||||
from smoothschedule.communication.credits.models import ProxyPhoneNumber
|
||||
|
||||
# First, try tenant's assigned numbers
|
||||
tenant_number = ProxyPhoneNumber.objects.filter(
|
||||
@@ -180,7 +180,7 @@ class TwilioFieldCallService:
|
||||
Args:
|
||||
estimated_cost_cents: Estimated cost of the call/SMS
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import CommunicationCredits
|
||||
from smoothschedule.communication.credits.models import CommunicationCredits
|
||||
|
||||
try:
|
||||
credits = CommunicationCredits.objects.get(tenant=self.tenant)
|
||||
@@ -202,8 +202,8 @@ class TwilioFieldCallService:
|
||||
Looks up the customer participant and returns their phone.
|
||||
"""
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from schedule.models import Event, Participant
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.scheduling.schedule.models import Event, Participant
|
||||
from smoothschedule.identity.users.models import User
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
with schema_context(self.tenant.schema_name):
|
||||
@@ -248,7 +248,7 @@ class TwilioFieldCallService:
|
||||
Returns:
|
||||
Dict with call_sid, proxy_number, status
|
||||
"""
|
||||
from smoothschedule.field_mobile.models import FieldCallLog
|
||||
from smoothschedule.communication.mobile.models import FieldCallLog
|
||||
|
||||
# Check permissions and credits
|
||||
self._check_feature_permission()
|
||||
@@ -344,7 +344,7 @@ class TwilioFieldCallService:
|
||||
Returns:
|
||||
Dict with message_sid, status
|
||||
"""
|
||||
from smoothschedule.field_mobile.models import FieldCallLog
|
||||
from smoothschedule.communication.mobile.models import FieldCallLog
|
||||
|
||||
# Check permissions and credits
|
||||
self._check_feature_permission()
|
||||
@@ -425,7 +425,7 @@ class TwilioFieldCallService:
|
||||
|
||||
Returns None if no active session exists.
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import MaskedSession
|
||||
from smoothschedule.communication.credits.models import MaskedSession
|
||||
|
||||
return MaskedSession.objects.filter(
|
||||
tenant=self.tenant,
|
||||
@@ -440,7 +440,7 @@ class TwilioFieldCallService:
|
||||
|
||||
Called when a job is completed to stop allowing calls/SMS.
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import MaskedSession
|
||||
from smoothschedule.communication.credits.models import MaskedSession
|
||||
|
||||
session = self.get_session_for_event(event_id)
|
||||
if session:
|
||||
@@ -458,7 +458,7 @@ class TwilioFieldCallService:
|
||||
Returns:
|
||||
List of FieldCallLog records
|
||||
"""
|
||||
from smoothschedule.field_mobile.models import FieldCallLog
|
||||
from smoothschedule.communication.mobile.models import FieldCallLog
|
||||
|
||||
return list(
|
||||
FieldCallLog.objects.filter(
|
||||
@@ -507,7 +507,7 @@ def handle_incoming_call(session_id: int, from_number: str) -> str:
|
||||
Returns:
|
||||
TwiML response string
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import MaskedSession
|
||||
from smoothschedule.communication.credits.models import MaskedSession
|
||||
from twilio.twiml.voice_response import VoiceResponse
|
||||
|
||||
response = VoiceResponse()
|
||||
@@ -557,7 +557,7 @@ def handle_incoming_sms(session_id: int, from_number: str, body: str) -> str:
|
||||
Returns:
|
||||
TwiML response string (empty for SMS)
|
||||
"""
|
||||
from smoothschedule.comms_credits.models import MaskedSession
|
||||
from smoothschedule.communication.credits.models import MaskedSession
|
||||
from twilio.rest import Client
|
||||
from django.conf import settings as django_settings
|
||||
|
||||
@@ -21,11 +21,11 @@ def send_customer_status_notification(tenant_id, event_id, notification_type):
|
||||
event_id: The event/job ID
|
||||
notification_type: One of 'en_route_notification', 'arrived_notification', 'completed_notification'
|
||||
"""
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from django_tenants.utils import schema_context
|
||||
from schedule.models import Event, Participant
|
||||
from smoothschedule.scheduling.schedule.models import Event, Participant
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
try:
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
@@ -118,8 +118,8 @@ def send_sms_notification(tenant_id, phone_number, message):
|
||||
phone_number: Recipient phone number
|
||||
message: SMS message body
|
||||
"""
|
||||
from core.models import Tenant
|
||||
from smoothschedule.comms_credits.models import CommunicationCredits
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from smoothschedule.communication.credits.models import CommunicationCredits
|
||||
|
||||
try:
|
||||
tenant = Tenant.objects.get(id=tenant_id)
|
||||
@@ -190,7 +190,7 @@ def send_email_notification(tenant_id, email, subject, message, customer_name='C
|
||||
message: Email body
|
||||
customer_name: Customer's name for personalization
|
||||
"""
|
||||
from core.models import Tenant
|
||||
from smoothschedule.identity.core.models import Tenant
|
||||
from django.core.mail import send_mail
|
||||
|
||||
try:
|
||||
@@ -237,7 +237,7 @@ def cleanup_old_location_data(days_to_keep=30):
|
||||
Args:
|
||||
days_to_keep: Number of days of data to retain (default 30)
|
||||
"""
|
||||
from smoothschedule.field_mobile.models import EmployeeLocationUpdate
|
||||
from smoothschedule.communication.mobile.models import EmployeeLocationUpdate
|
||||
|
||||
cutoff = timezone.now() - timezone.timedelta(days=days_to_keep)
|
||||
|
||||
@@ -259,7 +259,7 @@ def cleanup_old_status_history(days_to_keep=365):
|
||||
Args:
|
||||
days_to_keep: Number of days of data to retain (default 365)
|
||||
"""
|
||||
from smoothschedule.field_mobile.models import EventStatusHistory
|
||||
from smoothschedule.communication.mobile.models import EventStatusHistory
|
||||
|
||||
cutoff = timezone.now() - timezone.timedelta(days=days_to_keep)
|
||||
|
||||
@@ -19,14 +19,14 @@ from rest_framework.response import Response
|
||||
|
||||
from django_tenants.utils import schema_context
|
||||
|
||||
from schedule.models import Event, Participant, Resource
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.field_mobile.models import (
|
||||
from smoothschedule.scheduling.schedule.models import Event, Participant, Resource
|
||||
from smoothschedule.identity.users.models import User
|
||||
from smoothschedule.communication.mobile.models import (
|
||||
EventStatusHistory,
|
||||
EmployeeLocationUpdate,
|
||||
FieldCallLog,
|
||||
)
|
||||
from smoothschedule.field_mobile.serializers import (
|
||||
from smoothschedule.communication.mobile.serializers import (
|
||||
JobListSerializer,
|
||||
JobDetailSerializer,
|
||||
SetStatusSerializer,
|
||||
@@ -41,9 +41,9 @@ from smoothschedule.field_mobile.serializers import (
|
||||
CallHistorySerializer,
|
||||
EmployeeProfileSerializer,
|
||||
)
|
||||
from smoothschedule.field_mobile.services import StatusMachine, TwilioFieldCallService
|
||||
from smoothschedule.field_mobile.services.status_machine import StatusTransitionError
|
||||
from smoothschedule.field_mobile.services.twilio_calls import TwilioFieldCallError
|
||||
from smoothschedule.communication.mobile.services import StatusMachine, TwilioFieldCallService
|
||||
from smoothschedule.communication.mobile.services.status_machine import StatusTransitionError
|
||||
from smoothschedule.communication.mobile.services.twilio_calls import TwilioFieldCallError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -467,8 +467,8 @@ def location_update_view(request, job_id):
|
||||
|
||||
# Broadcast location update via WebSocket
|
||||
# Find the resource linked to this user and broadcast to watchers
|
||||
from schedule.models import Resource
|
||||
from schedule.consumers import broadcast_resource_location_update
|
||||
from smoothschedule.scheduling.schedule.models import Resource
|
||||
from smoothschedule.scheduling.schedule.consumers import broadcast_resource_location_update
|
||||
from asgiref.sync import async_to_sync
|
||||
|
||||
user_resources = Resource.objects.filter(user=user)
|
||||
@@ -702,7 +702,7 @@ def twilio_voice_webhook(request, session_id):
|
||||
Called by Twilio when a call is initiated or received.
|
||||
Returns TwiML to route the call.
|
||||
"""
|
||||
from smoothschedule.field_mobile.services.twilio_calls import handle_incoming_call
|
||||
from smoothschedule.communication.mobile.services.twilio_calls import handle_incoming_call
|
||||
|
||||
from_number = request.data.get('From', '')
|
||||
twiml = handle_incoming_call(session_id, from_number)
|
||||
@@ -774,7 +774,7 @@ def twilio_sms_webhook(request, session_id):
|
||||
|
||||
Forwards the SMS to the appropriate party.
|
||||
"""
|
||||
from smoothschedule.field_mobile.services.twilio_calls import handle_incoming_sms
|
||||
from smoothschedule.communication.mobile.services.twilio_calls import handle_incoming_sms
|
||||
|
||||
from_number = request.data.get('From', '')
|
||||
body = request.data.get('Body', '')
|
||||
@@ -3,4 +3,5 @@ from django.apps import AppConfig
|
||||
|
||||
class NotificationsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'notifications'
|
||||
name = 'smoothschedule.communication.notifications'
|
||||
label = 'notifications'
|
||||
@@ -2,7 +2,7 @@ from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from smoothschedule.users.models import User
|
||||
from smoothschedule.identity.users.models import User
|
||||
|
||||
class Notification(models.Model):
|
||||
"""
|
||||
@@ -57,6 +57,7 @@ class Notification(models.Model):
|
||||
)
|
||||
|
||||
class Meta:
|
||||
app_label = 'notifications'
|
||||
ordering = ['-timestamp']
|
||||
indexes = [
|
||||
models.Index(fields=['recipient', 'read', 'timestamp']),
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user