From aa9d920612acb3bca363e2c6b7c60e7b293afa5e Mon Sep 17 00:00:00 2001 From: poduck Date: Fri, 12 Dec 2025 21:48:09 -0500 Subject: [PATCH] Fix plan permissions using correct billing feature codes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /api/business/current/ endpoint was using legacy permission key names instead of the actual feature codes from the billing catalog. This caused tenants on paid plans to be incorrectly locked out of features. - Updated current_business_view to use correct feature codes (e.g., 'can_use_plugins' instead of 'plugins', 'sms_enabled' instead of 'sms_reminders') - Updated test to mock billing subscription and has_feature correctly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../scheduling/schedule/api_views.py | 27 +++++----- .../schedule/tests/test_api_views.py | 54 ++++++++++++------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/smoothschedule/smoothschedule/scheduling/schedule/api_views.py b/smoothschedule/smoothschedule/scheduling/schedule/api_views.py index 1cea769..0cba3f9 100644 --- a/smoothschedule/smoothschedule/scheduling/schedule/api_views.py +++ b/smoothschedule/smoothschedule/scheduling/schedule/api_views.py @@ -163,23 +163,24 @@ def current_business_view(request): subdomain = domain_parts[0] # Get plan permissions from billing system entitlements + # Use the actual feature codes from the billing catalog permissions = { - 'sms_reminders': tenant.has_feature('sms_reminders'), - 'webhooks': tenant.has_feature('webhooks'), + 'sms_reminders': tenant.has_feature('sms_enabled'), + 'webhooks': tenant.has_feature('integrations_enabled'), 'api_access': tenant.has_feature('api_access'), 'custom_domain': tenant.has_feature('custom_domain'), 'white_label': tenant.has_feature('white_label'), - 'custom_oauth': tenant.has_feature('custom_oauth'), - 'plugins': tenant.has_feature('plugins'), + 'custom_oauth': tenant.has_feature('can_manage_oauth'), + 'plugins': tenant.has_feature('can_use_plugins'), 'can_create_plugins': tenant.has_feature('can_create_plugins'), - 'tasks': tenant.has_feature('tasks'), - 'export_data': tenant.has_feature('export_data'), - 'video_conferencing': tenant.has_feature('video_conferencing'), - 'two_factor_auth': tenant.has_feature('two_factor_auth'), - 'masked_calling': tenant.has_feature('masked_calling'), - 'pos_system': tenant.has_feature('pos_system'), - 'mobile_app': tenant.has_feature('mobile_app'), - 'contracts': tenant.has_feature('contracts'), + 'tasks': tenant.has_feature('can_use_tasks'), + 'export_data': tenant.has_feature('can_export_data'), + 'video_conferencing': tenant.has_feature('can_add_video_conferencing'), + 'two_factor_auth': tenant.has_feature('team_permissions'), + 'masked_calling': tenant.has_feature('masked_calling_enabled'), + 'pos_system': tenant.has_feature('can_use_pos'), + 'mobile_app': tenant.has_feature('mobile_app_access'), + 'contracts': tenant.has_feature('can_use_contracts'), 'multi_location': tenant.has_feature('multi_location'), } @@ -219,7 +220,7 @@ def current_business_view(request): 'website_pages': {}, 'customer_dashboard_content': [], # Platform permissions - 'can_manage_oauth_credentials': tenant.has_feature('custom_oauth'), + 'can_manage_oauth_credentials': tenant.has_feature('can_manage_oauth'), 'payments_enabled': tenant.payment_mode != 'none', # Plan permissions (what features are available based on subscription) 'plan_permissions': permissions, diff --git a/smoothschedule/smoothschedule/scheduling/schedule/tests/test_api_views.py b/smoothschedule/smoothschedule/scheduling/schedule/tests/test_api_views.py index d0c9b39..6f87b10 100644 --- a/smoothschedule/smoothschedule/scheduling/schedule/tests/test_api_views.py +++ b/smoothschedule/smoothschedule/scheduling/schedule/tests/test_api_views.py @@ -263,7 +263,6 @@ class TestCurrentBusinessView: mock_tenant.id = 1 mock_tenant.name = 'Demo Business' mock_tenant.schema_name = 'demo' - mock_tenant.subscription_tier = 'PROFESSIONAL' mock_tenant.is_active = True mock_tenant.created_on = Mock() mock_tenant.created_on.isoformat.return_value = '2024-01-01T00:00:00' @@ -277,26 +276,43 @@ class TestCurrentBusinessView: mock_tenant.booking_return_url = '' mock_tenant.service_selection_heading = 'Choose' mock_tenant.service_selection_subheading = 'Select' - mock_tenant.can_use_sms_reminders = True - mock_tenant.can_use_webhooks = False - mock_tenant.can_api_access = False - 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 = True - mock_tenant.can_create_plugins = False - mock_tenant.can_use_tasks = True - mock_tenant.can_export_data = False - mock_tenant.can_add_video_conferencing = False - mock_tenant.can_require_2fa = False - mock_tenant.can_use_masked_phone_numbers = False - mock_tenant.can_use_pos = False - mock_tenant.can_use_mobile_app = True - mock_tenant.can_use_contracts = False - mock_tenant.subscription_plan = None mock_tenant.payment_mode = 'stripe' mock_tenant.domains.filter.return_value.first.return_value = mock_domain + # Mock billing subscription (plan is "pro") + mock_plan = Mock() + mock_plan.code = 'pro' + mock_plan_version = Mock() + mock_plan_version.plan = mock_plan + mock_subscription = Mock() + mock_subscription.plan_version = mock_plan_version + mock_tenant.billing_subscription = mock_subscription + + # Mock has_feature to return correct values for billing feature codes + def has_feature_impl(feature_code): + feature_map = { + 'sms_enabled': True, + 'integrations_enabled': False, + 'api_access': False, + 'custom_domain': False, + 'white_label': False, + 'can_manage_oauth': False, + 'can_use_plugins': True, + 'can_create_plugins': False, + 'can_use_tasks': True, + 'can_export_data': False, + 'can_add_video_conferencing': False, + 'team_permissions': False, + 'masked_calling_enabled': False, + 'can_use_pos': False, + 'mobile_app_access': True, + 'can_use_contracts': False, + 'multi_location': False, + } + return feature_map.get(feature_code, False) + + mock_tenant.has_feature = Mock(side_effect=has_feature_impl) + request.user = Mock() request.user.tenant = mock_tenant @@ -306,7 +322,7 @@ class TestCurrentBusinessView: assert response.data['id'] == 1 assert response.data['name'] == 'Demo Business' assert response.data['subdomain'] == 'demo' - assert response.data['tier'] == 'PROFESSIONAL' + assert response.data['plan'] == 'pro' assert response.data['plan_permissions']['sms_reminders'] is True assert response.data['plan_permissions']['plugins'] is True