= ({ plan, onSave, onClose, isLoading
)}
+ {/* Contracts Feature */}
+
+
+
+
Contracts
+
Allow tenants to create and manage contracts with customers
+
+
+
+
+
{/* Default Credit Settings */}
Default Credit Settings
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index 009da10..0dc8ce3 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -46,6 +46,7 @@ export interface PlanPermissions {
masked_calling: boolean;
pos_system: boolean;
mobile_app: boolean;
+ contracts: boolean;
}
export interface Business {
diff --git a/smoothschedule/contracts/views.py b/smoothschedule/contracts/views.py
index b9b4123..4c52554 100644
--- a/smoothschedule/contracts/views.py
+++ b/smoothschedule/contracts/views.py
@@ -9,9 +9,37 @@ from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.views import APIView
from rest_framework.response import Response
-from rest_framework.permissions import IsAuthenticated, AllowAny
+from rest_framework.permissions import IsAuthenticated, AllowAny, BasePermission
from .models import ContractTemplate, ServiceContractRequirement, Contract, ContractSignature
+
+
+class HasContractsPermission(BasePermission):
+ """
+ Permission class to check if the tenant has contracts feature enabled.
+ """
+ message = "Contracts feature is not enabled for your subscription plan."
+
+ def has_permission(self, request, view):
+ user = request.user
+ if not user or not user.is_authenticated:
+ return False
+
+ # Platform users (superuser, platform_manager, etc.) can access
+ if hasattr(user, 'role') and user.role in ['superuser', 'platform_manager', 'platform_support']:
+ return True
+
+ # Get tenant from user
+ tenant = getattr(user, 'tenant', None)
+ if not tenant:
+ return False
+
+ # Check if tenant's subscription plan has contracts enabled
+ subscription_plan = getattr(tenant, 'subscription_plan', None)
+ if subscription_plan:
+ return getattr(subscription_plan, 'contracts_enabled', False)
+
+ return False
from .serializers import (
ContractTemplateSerializer, ContractTemplateListSerializer,
ServiceContractRequirementSerializer, ContractSerializer, ContractListSerializer,
@@ -32,10 +60,10 @@ def get_client_ip(request):
class ContractTemplateViewSet(viewsets.ModelViewSet):
"""
CRUD for contract templates.
- Permissions: owner/manager only
+ Permissions: owner/manager only, must have contracts feature enabled
"""
queryset = ContractTemplate.objects.all()
- permission_classes = [IsAuthenticated]
+ permission_classes = [IsAuthenticated, HasContractsPermission]
def get_serializer_class(self):
if self.action == "list":
@@ -136,7 +164,7 @@ class ServiceContractRequirementViewSet(viewsets.ModelViewSet):
"""Manage which contracts are required for which services"""
queryset = ServiceContractRequirement.objects.all()
serializer_class = ServiceContractRequirementSerializer
- permission_classes = [IsAuthenticated]
+ permission_classes = [IsAuthenticated, HasContractsPermission]
def get_queryset(self):
qs = super().get_queryset().select_related("service", "template")
@@ -155,7 +183,7 @@ class ContractViewSet(viewsets.ModelViewSet):
Includes sending, viewing, and PDF download.
"""
queryset = Contract.objects.all()
- permission_classes = [IsAuthenticated]
+ permission_classes = [IsAuthenticated, HasContractsPermission]
def get_serializer_class(self):
if self.action == "list":
diff --git a/smoothschedule/platform_admin/migrations/0012_add_contracts_enabled.py b/smoothschedule/platform_admin/migrations/0012_add_contracts_enabled.py
new file mode 100644
index 0000000..f414102
--- /dev/null
+++ b/smoothschedule/platform_admin/migrations/0012_add_contracts_enabled.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.2.8 on 2025-12-06 03:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('platform_admin', '0011_update_subscription_plan_business_tier'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='subscriptionplan',
+ name='contracts_enabled',
+ field=models.BooleanField(default=False, help_text='Whether tenants can use the contracts feature'),
+ ),
+ ]
diff --git a/smoothschedule/platform_admin/models.py b/smoothschedule/platform_admin/models.py
index f87d93e..6994b18 100644
--- a/smoothschedule/platform_admin/models.py
+++ b/smoothschedule/platform_admin/models.py
@@ -285,6 +285,12 @@ class SubscriptionPlan(models.Model):
help_text="Monthly fee per proxy number in cents"
)
+ # Contracts Feature
+ contracts_enabled = models.BooleanField(
+ default=False,
+ help_text="Whether tenants can use the contracts feature"
+ )
+
# Default Credit Settings (for new tenants on this tier)
default_auto_reload_enabled = models.BooleanField(
default=False,
diff --git a/smoothschedule/platform_admin/serializers.py b/smoothschedule/platform_admin/serializers.py
index 4630dd7..9efdc30 100644
--- a/smoothschedule/platform_admin/serializers.py
+++ b/smoothschedule/platform_admin/serializers.py
@@ -117,6 +117,8 @@ class SubscriptionPlanSerializer(serializers.ModelSerializer):
'masked_calling_enabled', 'masked_calling_price_per_minute_cents',
# Proxy Number Settings
'proxy_number_enabled', 'proxy_number_monthly_fee_cents',
+ # Contracts Feature
+ 'contracts_enabled',
# Default Credit Settings
'default_auto_reload_enabled', 'default_auto_reload_threshold_cents',
'default_auto_reload_amount_cents',
@@ -145,6 +147,8 @@ class SubscriptionPlanCreateSerializer(serializers.ModelSerializer):
'masked_calling_enabled', 'masked_calling_price_per_minute_cents',
# Proxy Number Settings
'proxy_number_enabled', 'proxy_number_monthly_fee_cents',
+ # Contracts Feature
+ 'contracts_enabled',
# Default Credit Settings
'default_auto_reload_enabled', 'default_auto_reload_threshold_cents',
'default_auto_reload_amount_cents',
diff --git a/smoothschedule/schedule/api_views.py b/smoothschedule/schedule/api_views.py
index c26d2b2..e5f8332 100644
--- a/smoothschedule/schedule/api_views.py
+++ b/smoothschedule/schedule/api_views.py
@@ -184,6 +184,7 @@ def current_business_view(request):
'masked_calling': tenant.can_use_masked_phone_numbers or plan_permissions.get('masked_calling', False),
'pos_system': tenant.can_use_pos or plan_permissions.get('pos_system', False),
'mobile_app': tenant.can_use_mobile_app or plan_permissions.get('mobile_app', False),
+ 'contracts': getattr(tenant.subscription_plan, 'contracts_enabled', False) if tenant.subscription_plan else False,
}
business_data = {