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