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:
poduck
2025-12-16 17:42:59 -05:00
parent 79b76bf2dc
commit 0a4a8c7687
16 changed files with 94 additions and 87 deletions

View File

@@ -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,17 +283,29 @@ 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}>
<SidebarItem
to="/dashboard/automations/my-automations"
icon={Plug}
label={t('nav.automations', 'Automations')}
isCollapsed={isCollapsed}
locked={!canUse('automations')}
badgeElement={<UnfinishedBadge />}
/>
{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}
label={t('nav.automations', 'Automations')}
isCollapsed={isCollapsed}
locked={!canUse('automations')}
badgeElement={<UnfinishedBadge />}
/>
)}
</SidebarSection>
)}

View File

@@ -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');

View File

@@ -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,

View File

@@ -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',

View File

@@ -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;

View File

@@ -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,

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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")

View File

@@ -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,

View File

@@ -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."

View File

@@ -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'),

View File

@@ -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

View File

@@ -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."