Rename plugins feature to automations throughout codebase
- Update billing catalog feature codes: can_use_plugins → can_use_automations, can_create_plugins → can_create_automations - Update all backend permission checks to use new feature codes - Update API views to return automations permissions to frontend - Update frontend types and hooks to use automations terminology - Move Tasks to Extend section in Sidebar alongside Automations - Update all related tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -138,16 +138,6 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
isCollapsed={isCollapsed}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_tasks') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/tasks"
|
||||
icon={Clock}
|
||||
label={t('nav.tasks', 'Tasks')}
|
||||
isCollapsed={isCollapsed}
|
||||
locked={!canUse('automations') || !canUse('tasks')}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
{(isStaff && hasPermission('can_access_my_schedule')) && (
|
||||
<SidebarItem
|
||||
to="/dashboard/my-schedule"
|
||||
@@ -293,9 +283,20 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
</SidebarSection>
|
||||
)}
|
||||
|
||||
{/* Extend Section - Automations */}
|
||||
{hasPermission('can_access_automations') && (
|
||||
{/* Extend Section - Tasks & Automations */}
|
||||
{(hasPermission('can_access_tasks') || hasPermission('can_access_automations')) && (
|
||||
<SidebarSection title={t('nav.sections.extend', 'Extend')} isCollapsed={isCollapsed}>
|
||||
{hasPermission('can_access_tasks') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/tasks"
|
||||
icon={Clock}
|
||||
label={t('nav.tasks', 'Tasks')}
|
||||
isCollapsed={isCollapsed}
|
||||
locked={!canUse('automations') || !canUse('tasks')}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
{hasPermission('can_access_automations') && (
|
||||
<SidebarItem
|
||||
to="/dashboard/automations/my-automations"
|
||||
icon={Plug}
|
||||
@@ -304,6 +305,7 @@ const Sidebar: React.FC<SidebarProps> = ({ business, user, isCollapsed, toggleCo
|
||||
locked={!canUse('automations')}
|
||||
badgeElement={<UnfinishedBadge />}
|
||||
/>
|
||||
)}
|
||||
</SidebarSection>
|
||||
)}
|
||||
|
||||
|
||||
@@ -231,7 +231,8 @@ describe('usePlanFeatures', () => {
|
||||
custom_domain: true,
|
||||
white_label: true,
|
||||
custom_oauth: true,
|
||||
plugins: true,
|
||||
automations: true,
|
||||
can_create_automations: true,
|
||||
tasks: true,
|
||||
export_data: true,
|
||||
video_conferencing: true,
|
||||
@@ -259,7 +260,7 @@ describe('usePlanFeatures', () => {
|
||||
expect(result.current.canUse('custom_domain')).toBe(true);
|
||||
expect(result.current.canUse('white_label')).toBe(true);
|
||||
expect(result.current.canUse('custom_oauth')).toBe(true);
|
||||
expect(result.current.canUse('plugins')).toBe(true);
|
||||
expect(result.current.canUse('automations')).toBe(true);
|
||||
expect(result.current.canUse('tasks')).toBe(true);
|
||||
expect(result.current.canUse('export_data')).toBe(true);
|
||||
expect(result.current.canUse('video_conferencing')).toBe(true);
|
||||
@@ -780,7 +781,8 @@ describe('FEATURE_NAMES', () => {
|
||||
'custom_domain',
|
||||
'white_label',
|
||||
'custom_oauth',
|
||||
'plugins',
|
||||
'automations',
|
||||
'can_create_automations',
|
||||
'tasks',
|
||||
'export_data',
|
||||
'video_conferencing',
|
||||
@@ -805,7 +807,8 @@ describe('FEATURE_NAMES', () => {
|
||||
expect(FEATURE_NAMES.custom_domain).toBe('Custom Domain');
|
||||
expect(FEATURE_NAMES.white_label).toBe('White Label');
|
||||
expect(FEATURE_NAMES.custom_oauth).toBe('Custom OAuth');
|
||||
expect(FEATURE_NAMES.plugins).toBe('Plugins');
|
||||
expect(FEATURE_NAMES.automations).toBe('Automations');
|
||||
expect(FEATURE_NAMES.can_create_automations).toBe('Custom Automation Creation');
|
||||
expect(FEATURE_NAMES.tasks).toBe('Scheduled Tasks');
|
||||
expect(FEATURE_NAMES.export_data).toBe('Data Export');
|
||||
expect(FEATURE_NAMES.video_conferencing).toBe('Video Conferencing');
|
||||
@@ -826,7 +829,8 @@ describe('FEATURE_DESCRIPTIONS', () => {
|
||||
'custom_domain',
|
||||
'white_label',
|
||||
'custom_oauth',
|
||||
'plugins',
|
||||
'automations',
|
||||
'can_create_automations',
|
||||
'tasks',
|
||||
'export_data',
|
||||
'video_conferencing',
|
||||
@@ -851,7 +855,8 @@ describe('FEATURE_DESCRIPTIONS', () => {
|
||||
expect(FEATURE_DESCRIPTIONS.custom_domain).toContain('custom domain');
|
||||
expect(FEATURE_DESCRIPTIONS.white_label).toContain('branding');
|
||||
expect(FEATURE_DESCRIPTIONS.custom_oauth).toContain('OAuth');
|
||||
expect(FEATURE_DESCRIPTIONS.plugins).toContain('plugin');
|
||||
expect(FEATURE_DESCRIPTIONS.automations).toContain('Automate');
|
||||
expect(FEATURE_DESCRIPTIONS.can_create_automations).toContain('automations');
|
||||
expect(FEATURE_DESCRIPTIONS.tasks).toContain('task');
|
||||
expect(FEATURE_DESCRIPTIONS.export_data).toContain('Export');
|
||||
expect(FEATURE_DESCRIPTIONS.video_conferencing).toContain('video');
|
||||
|
||||
@@ -62,8 +62,8 @@ export const useCurrentBusiness = () => {
|
||||
custom_domain: false,
|
||||
white_label: false,
|
||||
custom_oauth: false,
|
||||
plugins: false,
|
||||
can_create_plugins: false,
|
||||
automations: false,
|
||||
can_create_automations: false,
|
||||
tasks: false,
|
||||
export_data: false,
|
||||
video_conferencing: false,
|
||||
|
||||
@@ -83,8 +83,8 @@ export const FEATURE_NAMES: Record<FeatureKey, string> = {
|
||||
custom_domain: 'Custom Domain',
|
||||
white_label: 'White Label',
|
||||
custom_oauth: 'Custom OAuth',
|
||||
plugins: 'Plugins',
|
||||
can_create_plugins: 'Custom Plugin Creation',
|
||||
automations: 'Automations',
|
||||
can_create_automations: 'Custom Automation Creation',
|
||||
tasks: 'Scheduled Tasks',
|
||||
export_data: 'Data Export',
|
||||
video_conferencing: 'Video Conferencing',
|
||||
@@ -106,9 +106,9 @@ export const FEATURE_DESCRIPTIONS: Record<FeatureKey, string> = {
|
||||
custom_domain: 'Use your own custom domain for your booking site',
|
||||
white_label: 'Remove SmoothSchedule branding and use your own',
|
||||
custom_oauth: 'Configure your own OAuth credentials for social login',
|
||||
plugins: 'Install and use plugins from the marketplace',
|
||||
can_create_plugins: 'Create custom plugins tailored to your business needs',
|
||||
tasks: 'Create scheduled tasks to automate plugin execution',
|
||||
automations: 'Automate repetitive tasks with custom workflows',
|
||||
can_create_automations: 'Create custom automations tailored to your business needs',
|
||||
tasks: 'Create scheduled tasks to automate execution',
|
||||
export_data: 'Export your data to CSV or other formats',
|
||||
video_conferencing: 'Add video conferencing links to appointments',
|
||||
two_factor_auth: 'Require two-factor authentication for enhanced security',
|
||||
|
||||
@@ -38,8 +38,8 @@ export interface PlanPermissions {
|
||||
custom_domain: boolean;
|
||||
white_label: boolean;
|
||||
custom_oauth: boolean;
|
||||
plugins: boolean;
|
||||
can_create_plugins: boolean;
|
||||
automations: boolean;
|
||||
can_create_automations: boolean;
|
||||
tasks: boolean;
|
||||
export_data: boolean;
|
||||
video_conferencing: boolean;
|
||||
|
||||
@@ -71,10 +71,10 @@ FEATURES = [
|
||||
{"code": "white_label", "name": "White Label", "description": "Remove all SmoothSchedule branding completely", "feature_type": "boolean", "category": "customization", "tenant_field_name": "can_white_label", "display_order": 50},
|
||||
{"code": "max_public_pages", "name": "Public Web Pages", "description": "Maximum number of public-facing web pages", "feature_type": "integer", "category": "customization", "tenant_field_name": "max_public_pages", "display_order": 55},
|
||||
|
||||
# --- Plugins & Automation ---
|
||||
{"code": "can_use_plugins", "name": "Use Plugins", "description": "Install and use marketplace plugins", "feature_type": "boolean", "category": "plugins", "tenant_field_name": "can_use_plugins", "display_order": 10},
|
||||
{"code": "can_use_tasks", "name": "Scheduled Tasks", "description": "Create automated scheduled tasks", "feature_type": "boolean", "category": "plugins", "tenant_field_name": "can_use_tasks", "display_order": 20, "depends_on": "can_use_plugins"},
|
||||
{"code": "can_create_plugins", "name": "Create Plugins", "description": "Build custom plugins", "feature_type": "boolean", "category": "plugins", "tenant_field_name": "can_create_plugins", "display_order": 30, "depends_on": "can_use_plugins"},
|
||||
# --- Automations ---
|
||||
{"code": "can_use_automations", "name": "Use Automations", "description": "Install and use marketplace automations", "feature_type": "boolean", "category": "automations", "tenant_field_name": "can_use_automations", "display_order": 10},
|
||||
{"code": "can_use_tasks", "name": "Scheduled Tasks", "description": "Create automated scheduled tasks", "feature_type": "boolean", "category": "automations", "tenant_field_name": "can_use_tasks", "display_order": 20, "depends_on": "can_use_automations"},
|
||||
{"code": "can_create_automations", "name": "Create Automations", "description": "Build custom automations", "feature_type": "boolean", "category": "automations", "tenant_field_name": "can_create_automations", "display_order": 30, "depends_on": "can_use_automations"},
|
||||
|
||||
# --- Advanced Features ---
|
||||
{"code": "api_access", "name": "API Access", "description": "Access the public API for integrations", "feature_type": "boolean", "category": "advanced", "tenant_field_name": "can_api_access", "display_order": 10},
|
||||
@@ -159,7 +159,7 @@ PLANS = [
|
||||
"payment_processing": True,
|
||||
"mobile_app_access": True,
|
||||
"can_use_email_templates": True,
|
||||
"can_use_plugins": True,
|
||||
"can_use_automations": True,
|
||||
"can_process_refunds": True,
|
||||
},
|
||||
"integer_features": {
|
||||
@@ -200,7 +200,7 @@ PLANS = [
|
||||
"custom_domain": True,
|
||||
"integrations_enabled": True,
|
||||
"can_use_email_templates": True,
|
||||
"can_use_plugins": True,
|
||||
"can_use_automations": True,
|
||||
"can_use_tasks": True,
|
||||
"can_process_refunds": True,
|
||||
"can_use_calendar_sync": True,
|
||||
@@ -252,9 +252,9 @@ PLANS = [
|
||||
"audit_logs": True,
|
||||
"custom_branding": True,
|
||||
"can_use_email_templates": True,
|
||||
"can_use_plugins": True,
|
||||
"can_use_automations": True,
|
||||
"can_use_tasks": True,
|
||||
"can_create_plugins": True,
|
||||
"can_create_automations": True,
|
||||
"can_process_refunds": True,
|
||||
"can_use_calendar_sync": True,
|
||||
"can_export_data": True,
|
||||
@@ -313,9 +313,9 @@ PLANS = [
|
||||
"dedicated_account_manager": True,
|
||||
"sla_guarantee": True,
|
||||
"can_use_email_templates": True,
|
||||
"can_use_plugins": True,
|
||||
"can_use_automations": True,
|
||||
"can_use_tasks": True,
|
||||
"can_create_plugins": True,
|
||||
"can_create_automations": True,
|
||||
"can_process_refunds": True,
|
||||
"can_use_calendar_sync": True,
|
||||
"can_export_data": True,
|
||||
|
||||
@@ -372,7 +372,7 @@ def HasFeaturePermission(permission_key):
|
||||
'can_use_masked_phone_numbers': 'Masked Calling',
|
||||
'can_use_custom_domain': 'Custom Domains',
|
||||
'can_white_label': 'White Labeling',
|
||||
'can_create_plugins': 'Plugin Creation',
|
||||
'can_create_automations': 'Automation Creation',
|
||||
'can_use_webhooks': 'Webhooks',
|
||||
'can_accept_payments': 'Payment Processing',
|
||||
'can_api_access': 'API Access',
|
||||
|
||||
@@ -672,7 +672,7 @@ class TestPermissionFactoryConfiguration:
|
||||
'can_use_masked_phone_numbers',
|
||||
'can_use_custom_domain',
|
||||
'can_white_label',
|
||||
'can_create_plugins',
|
||||
'can_create_automations',
|
||||
'can_use_webhooks',
|
||||
'can_accept_payments',
|
||||
'can_api_access',
|
||||
|
||||
@@ -267,7 +267,7 @@ class TenantSerializer(serializers.ModelSerializer):
|
||||
features = {}
|
||||
feature_keys = [
|
||||
'sms_reminders', 'webhooks', 'api_access', 'custom_domain',
|
||||
'white_label', 'custom_oauth', 'plugins', 'can_create_plugins',
|
||||
'white_label', 'custom_oauth', 'automations', 'can_create_automations',
|
||||
'tasks', 'export_data', 'video_conferencing', 'two_factor_auth',
|
||||
'masked_calling', 'pos_system', 'mobile_app', 'contracts',
|
||||
'multi_location',
|
||||
|
||||
@@ -265,11 +265,11 @@ def sync_subscription_plan_to_tenants(self, plan_id: int):
|
||||
# Advanced features - plan may use short names
|
||||
'export_data': 'can_export_data',
|
||||
'can_export_data': 'can_export_data',
|
||||
'plugins': 'can_use_plugins',
|
||||
'can_use_plugins': 'can_use_plugins',
|
||||
'automations': 'can_use_automations',
|
||||
'can_use_automations': 'can_use_automations',
|
||||
'tasks': 'can_use_tasks',
|
||||
'can_use_tasks': 'can_use_tasks',
|
||||
'can_create_plugins': 'can_create_plugins',
|
||||
'can_create_automations': 'can_create_automations',
|
||||
'webhooks': 'can_use_webhooks',
|
||||
'can_use_webhooks': 'can_use_webhooks',
|
||||
'calendar_sync': 'can_use_calendar_sync',
|
||||
|
||||
@@ -325,14 +325,14 @@ class TestSyncSubscriptionPlanToTenantsTask:
|
||||
mock_plan = Mock(
|
||||
id=1,
|
||||
name='Professional',
|
||||
permissions={'can_use_plugins': True},
|
||||
permissions={'can_use_automations': True},
|
||||
limits={}
|
||||
)
|
||||
MockPlan.objects.get.return_value = mock_plan
|
||||
|
||||
mock_tenant = Mock()
|
||||
mock_tenant.id = 1
|
||||
mock_tenant.can_use_plugins = False
|
||||
mock_tenant.can_use_automations = False
|
||||
mock_tenant.subscription_tier = 'FREE'
|
||||
mock_tenant.save.side_effect = Exception("DB error")
|
||||
|
||||
|
||||
@@ -1478,7 +1478,7 @@ class TestTenantViewSet:
|
||||
mock_tenant = Mock(id=1, schema_name='demo', name='Demo Business')
|
||||
mock_custom_tier = Mock(
|
||||
id=1,
|
||||
features={'max_users': 100, 'can_use_plugins': True},
|
||||
features={'max_users': 100, 'can_use_automations': True},
|
||||
notes='Custom tier for testing',
|
||||
is_active=True,
|
||||
days_until_expiry=None,
|
||||
@@ -1493,7 +1493,7 @@ class TestTenantViewSet:
|
||||
mock_serializer = Mock()
|
||||
mock_serializer.data = {
|
||||
'id': 1,
|
||||
'features': {'max_users': 100, 'can_use_plugins': True},
|
||||
'features': {'max_users': 100, 'can_use_automations': True},
|
||||
'notes': 'Custom tier for testing',
|
||||
'is_active': True,
|
||||
'days_until_expiry': None,
|
||||
|
||||
@@ -117,7 +117,7 @@ class AutomationTemplateViewSet(viewsets.ModelViewSet):
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant:
|
||||
# Check for new feature name, fall back to old name for compatibility
|
||||
return tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')
|
||||
return tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')
|
||||
return True # Allow if no tenant context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -183,14 +183,14 @@ class AutomationTemplateViewSet(viewsets.ModelViewSet):
|
||||
|
||||
# Check permission to use automations first
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Automation access. "
|
||||
"Please upgrade your subscription to use automations."
|
||||
)
|
||||
|
||||
# Check permission to create automations
|
||||
if tenant and not (tenant.has_feature('can_create_automations') or tenant.has_feature('can_create_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_create_automations') or tenant.has_feature('can_create_automations')):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Automation Creation. "
|
||||
"Please upgrade your subscription to create custom automations."
|
||||
@@ -269,7 +269,7 @@ class AutomationTemplateViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
# Check permission to use automations
|
||||
tenant = getattr(request, 'tenant', None)
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')):
|
||||
return Response(
|
||||
{'error': 'Your current plan does not include Automation access. Please upgrade your subscription to install automations.'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
@@ -473,7 +473,7 @@ class AutomationInstallationViewSet(viewsets.ModelViewSet):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Automation access. "
|
||||
"Please upgrade your subscription to use automations."
|
||||
@@ -505,7 +505,7 @@ class AutomationInstallationViewSet(viewsets.ModelViewSet):
|
||||
|
||||
# Check permission to use automations
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Automation access. "
|
||||
"Please upgrade your subscription to use automations."
|
||||
@@ -625,7 +625,7 @@ class EventAutomationViewSet(viewsets.ModelViewSet):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Automation access. "
|
||||
"Please upgrade your subscription to use automations."
|
||||
@@ -752,7 +752,7 @@ class GlobalEventAutomationViewSet(viewsets.ModelViewSet):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_plugins')):
|
||||
if tenant and not (tenant.has_feature('can_use_automations') or tenant.has_feature('can_use_automations')):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Automation access. "
|
||||
"Please upgrade your subscription to use automations."
|
||||
|
||||
@@ -171,8 +171,8 @@ def current_business_view(request):
|
||||
'custom_domain': tenant.has_feature('custom_domain'),
|
||||
'white_label': tenant.has_feature('white_label'),
|
||||
'custom_oauth': tenant.has_feature('can_manage_oauth'),
|
||||
'plugins': tenant.has_feature('can_use_plugins'),
|
||||
'can_create_plugins': tenant.has_feature('can_create_plugins'),
|
||||
'automations': tenant.has_feature('can_use_automations'),
|
||||
'can_create_automations': tenant.has_feature('can_create_automations'),
|
||||
'tasks': tenant.has_feature('can_use_tasks'),
|
||||
'export_data': tenant.has_feature('can_export_data'),
|
||||
'video_conferencing': tenant.has_feature('can_add_video_conferencing'),
|
||||
|
||||
@@ -297,8 +297,8 @@ class TestCurrentBusinessView:
|
||||
'custom_domain': False,
|
||||
'white_label': False,
|
||||
'can_manage_oauth': False,
|
||||
'can_use_plugins': True,
|
||||
'can_create_plugins': False,
|
||||
'can_use_automations': True,
|
||||
'can_create_automations': False,
|
||||
'can_use_tasks': True,
|
||||
'can_export_data': False,
|
||||
'can_add_video_conferencing': False,
|
||||
@@ -324,7 +324,7 @@ class TestCurrentBusinessView:
|
||||
assert response.data['subdomain'] == 'demo'
|
||||
assert response.data['plan'] == 'pro'
|
||||
assert response.data['plan_permissions']['sms_reminders'] is True
|
||||
assert response.data['plan_permissions']['plugins'] is True
|
||||
assert response.data['plan_permissions']['automations'] is True
|
||||
|
||||
|
||||
class TestUpdateBusinessView:
|
||||
@@ -393,8 +393,8 @@ class TestUpdateBusinessView:
|
||||
mock_tenant.can_use_custom_domain = False
|
||||
mock_tenant.can_white_label = False
|
||||
mock_tenant.can_manage_oauth_credentials = False
|
||||
mock_tenant.can_use_plugins = False
|
||||
mock_tenant.can_create_plugins = False
|
||||
mock_tenant.can_use_automations = False
|
||||
mock_tenant.can_create_automations = False
|
||||
mock_tenant.can_use_tasks = False
|
||||
mock_tenant.can_export_data = False
|
||||
mock_tenant.can_add_video_conferencing = False
|
||||
@@ -456,8 +456,8 @@ class TestUpdateBusinessView:
|
||||
mock_tenant.can_use_custom_domain = False
|
||||
mock_tenant.can_white_label = False
|
||||
mock_tenant.can_manage_oauth_credentials = False
|
||||
mock_tenant.can_use_plugins = False
|
||||
mock_tenant.can_create_plugins = False
|
||||
mock_tenant.can_use_automations = False
|
||||
mock_tenant.can_create_automations = False
|
||||
mock_tenant.can_use_tasks = False
|
||||
mock_tenant.can_export_data = False
|
||||
mock_tenant.can_add_video_conferencing = False
|
||||
|
||||
@@ -921,7 +921,7 @@ class ScheduledTaskViewSet(TaskFeatureRequiredMixin, TenantFilteredQuerySetMixin
|
||||
- Must be authenticated
|
||||
- Only owners/managers can create/update/delete
|
||||
- Subject to MAX_AUTOMATED_TASKS quota (hard block on creation)
|
||||
- Requires can_use_plugins AND can_use_tasks features
|
||||
- Requires can_use_automations AND can_use_tasks features
|
||||
|
||||
Features:
|
||||
- List all scheduled tasks
|
||||
@@ -1117,9 +1117,9 @@ class PluginTemplateViewSet(viewsets.ModelViewSet):
|
||||
|
||||
Permissions:
|
||||
- Marketplace view: Always accessible (for discovery)
|
||||
- My Plugins view: Requires can_use_plugins feature
|
||||
- Install action: Requires can_use_plugins feature
|
||||
- Create: Requires can_use_plugins AND can_create_plugins features
|
||||
- My Plugins view: Requires can_use_automations feature
|
||||
- Install action: Requires can_use_automations feature
|
||||
- Create: Requires can_use_automations AND can_create_automations features
|
||||
"""
|
||||
queryset = PluginTemplate.objects.all()
|
||||
serializer_class = PluginTemplateSerializer
|
||||
@@ -1132,7 +1132,7 @@ class PluginTemplateViewSet(viewsets.ModelViewSet):
|
||||
"""Check if tenant has permission to use plugins."""
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant:
|
||||
return tenant.has_feature('can_use_plugins')
|
||||
return tenant.has_feature('can_use_automations')
|
||||
return True # Allow if no tenant context
|
||||
|
||||
def get_queryset(self):
|
||||
@@ -1140,7 +1140,7 @@ class PluginTemplateViewSet(viewsets.ModelViewSet):
|
||||
Filter templates based on user permissions.
|
||||
|
||||
- Marketplace view: Only approved PUBLIC templates (always accessible)
|
||||
- My Plugins: User's own templates (requires can_use_plugins)
|
||||
- My Plugins: User's own templates (requires can_use_automations)
|
||||
- Platform admins: All templates
|
||||
"""
|
||||
queryset = super().get_queryset()
|
||||
@@ -1198,14 +1198,14 @@ class PluginTemplateViewSet(viewsets.ModelViewSet):
|
||||
|
||||
# Check permission to use plugins first
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not tenant.has_feature('can_use_plugins'):
|
||||
if tenant and not tenant.has_feature('can_use_automations'):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Plugin access. "
|
||||
"Please upgrade your subscription to use plugins."
|
||||
)
|
||||
|
||||
# Check permission to create plugins (requires can_use_plugins)
|
||||
if tenant and not tenant.has_feature('can_create_plugins'):
|
||||
# Check permission to create plugins (requires can_use_automations)
|
||||
if tenant and not tenant.has_feature('can_create_automations'):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Plugin Creation. "
|
||||
"Please upgrade your subscription to create custom plugins."
|
||||
@@ -1284,7 +1284,7 @@ class PluginTemplateViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
# Check permission to use plugins
|
||||
tenant = getattr(request, 'tenant', None)
|
||||
if tenant and not tenant.has_feature('can_use_plugins'):
|
||||
if tenant and not tenant.has_feature('can_use_automations'):
|
||||
return Response(
|
||||
{'error': 'Your current plan does not include Plugin access. Please upgrade your subscription to install plugins.'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
@@ -1476,7 +1476,7 @@ class PluginInstallationViewSet(viewsets.ModelViewSet):
|
||||
- Rate and review plugin
|
||||
|
||||
Permissions:
|
||||
- Requires can_use_plugins feature for all operations
|
||||
- Requires can_use_automations feature for all operations
|
||||
"""
|
||||
queryset = PluginInstallation.objects.select_related('template', 'scheduled_task').all()
|
||||
serializer_class = PluginInstallationSerializer
|
||||
@@ -1488,7 +1488,7 @@ class PluginInstallationViewSet(viewsets.ModelViewSet):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not tenant.has_feature('can_use_plugins'):
|
||||
if tenant and not tenant.has_feature('can_use_automations'):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Plugin access. "
|
||||
"Please upgrade your subscription to use plugins."
|
||||
@@ -1520,7 +1520,7 @@ class PluginInstallationViewSet(viewsets.ModelViewSet):
|
||||
|
||||
# Check permission to use plugins
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not tenant.has_feature('can_use_plugins'):
|
||||
if tenant and not tenant.has_feature('can_use_automations'):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Plugin access. "
|
||||
"Please upgrade your subscription to use plugins."
|
||||
@@ -1640,7 +1640,7 @@ class EventPluginViewSet(viewsets.ModelViewSet):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not tenant.has_feature('can_use_plugins'):
|
||||
if tenant and not tenant.has_feature('can_use_automations'):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Plugin access. "
|
||||
"Please upgrade your subscription to use plugins."
|
||||
@@ -1767,7 +1767,7 @@ class GlobalEventPluginViewSet(viewsets.ModelViewSet):
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
tenant = getattr(self.request, 'tenant', None)
|
||||
if tenant and not tenant.has_feature('can_use_plugins'):
|
||||
if tenant and not tenant.has_feature('can_use_automations'):
|
||||
raise PermissionDenied(
|
||||
"Your current plan does not include Plugin access. "
|
||||
"Please upgrade your subscription to use plugins."
|
||||
|
||||
Reference in New Issue
Block a user