Rename default staff roles: Manager, Support Staff, Staff
- Renamed "Full Access Staff" to "Manager" - Renamed "Front Desk" to "Support Staff" - Renamed "Limited Staff" to "Staff" - Updated permissions: Support Staff now has access to messages and payments - Updated all references in code, tests, help docs, and translations - Added migration 0016 to rename existing roles in database 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1979,7 +1979,7 @@
|
||||
"deleteRole": "Delete Role",
|
||||
"roleName": "Role Name",
|
||||
"roleDescription": "Description",
|
||||
"roleNamePlaceholder": "e.g., Front Desk",
|
||||
"roleNamePlaceholder": "e.g., Support Staff",
|
||||
"roleDescriptionPlaceholder": "Brief description of this role's responsibilities",
|
||||
"permissions": "Permissions",
|
||||
"menuAccess": "Menu Access",
|
||||
|
||||
@@ -84,28 +84,28 @@ const HelpSettingsStaffRoles: React.FC = () => {
|
||||
<div className="flex flex-col gap-2 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={20} className="text-green-500" />
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Full Access Staff</h4>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Manager</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Access to all menu items and all dangerous permissions. Best for managers and supervisors.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={20} className="text-blue-500" />
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Limited Staff</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Access to Scheduler, Customers, and Messages only. No dangerous permissions. Best for general staff.
|
||||
Full access to all features and settings. Best for supervisors and team leads.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={20} className="text-purple-500" />
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Front Desk</h4>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Support Staff</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Access to Scheduler, Services, Customers, and Messages. No delete permissions. Best for reception.
|
||||
Customer-facing operations including scheduling, customers, tickets, messages, and payments.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
<Shield size={20} className="text-blue-500" />
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Staff</h4>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Basic access to own schedule and availability. Best for general staff members.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -406,7 +406,7 @@ const HelpSettingsStaffRoles: React.FC = () => {
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900 dark:text-white">Start with Default Roles</h4>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Use the built-in roles (Full Access, Limited, Front Desk) as templates when creating custom roles
|
||||
Use the built-in roles (Manager, Support Staff, Staff) as templates when creating custom roles
|
||||
</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Migration to rename default staff roles to more intuitive names.
|
||||
|
||||
Renames:
|
||||
- "Full Access Staff" -> "Manager"
|
||||
- "Front Desk" -> "Support Staff"
|
||||
- "Limited Staff" -> "Staff"
|
||||
"""
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
ROLE_RENAMES = {
|
||||
'Full Access Staff': 'Manager',
|
||||
'Front Desk': 'Support Staff',
|
||||
'Limited Staff': 'Staff',
|
||||
}
|
||||
|
||||
ROLE_DESCRIPTIONS = {
|
||||
'Manager': 'Full access to all features and settings',
|
||||
'Support Staff': 'Customer-facing operations and scheduling',
|
||||
'Staff': 'Basic access to own schedule and availability',
|
||||
}
|
||||
|
||||
|
||||
def rename_staff_roles(apps, schema_editor):
|
||||
"""Rename default staff roles to new names."""
|
||||
StaffRole = apps.get_model('users', 'StaffRole')
|
||||
|
||||
for old_name, new_name in ROLE_RENAMES.items():
|
||||
StaffRole.objects.filter(
|
||||
name=old_name,
|
||||
is_default=True
|
||||
).update(
|
||||
name=new_name,
|
||||
description=ROLE_DESCRIPTIONS.get(new_name, '')
|
||||
)
|
||||
|
||||
|
||||
def reverse_migration(apps, schema_editor):
|
||||
"""Reverse: rename roles back to original names."""
|
||||
StaffRole = apps.get_model('users', 'StaffRole')
|
||||
|
||||
reverse_renames = {v: k for k, v in ROLE_RENAMES.items()}
|
||||
original_descriptions = {
|
||||
'Full Access Staff': 'Complete access to all features (similar to manager)',
|
||||
'Front Desk': 'Access to scheduling, customers, and basic operations',
|
||||
'Limited Staff': 'Basic access to own schedule only',
|
||||
}
|
||||
|
||||
for new_name, old_name in reverse_renames.items():
|
||||
StaffRole.objects.filter(
|
||||
name=new_name,
|
||||
is_default=True
|
||||
).update(
|
||||
name=old_name,
|
||||
description=original_descriptions.get(old_name, '')
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('users', '0015_update_staff_invitation_role_choices'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
rename_staff_roles,
|
||||
reverse_code=reverse_migration,
|
||||
),
|
||||
]
|
||||
@@ -26,7 +26,7 @@ class User(AbstractUser):
|
||||
|
||||
# Tenant-level roles (access within single tenant)
|
||||
TENANT_OWNER = 'TENANT_OWNER', _('Tenant Owner')
|
||||
# TENANT_MANAGER removed - use TENANT_STAFF with "Full Access Staff" role instead
|
||||
# TENANT_MANAGER removed - use TENANT_STAFF with "Manager" role instead
|
||||
TENANT_STAFF = 'TENANT_STAFF', _('Tenant Staff')
|
||||
|
||||
# Customer role (end users of the tenant)
|
||||
|
||||
@@ -261,12 +261,12 @@ def get_default_permissions_for_role(role_name: str) -> dict:
|
||||
# Default role configurations
|
||||
# These are created for each tenant during migration and on new tenant creation
|
||||
DEFAULT_ROLES = {
|
||||
'Full Access Staff': {
|
||||
'description': 'Complete access to all features (similar to manager)',
|
||||
'Manager': {
|
||||
'description': 'Full access to all features and settings',
|
||||
'permissions': {k: True for k in ALL_PERMISSIONS.keys()},
|
||||
},
|
||||
'Front Desk': {
|
||||
'description': 'Access to scheduling, customers, and basic operations',
|
||||
'Support Staff': {
|
||||
'description': 'Customer-facing operations and scheduling',
|
||||
'permissions': {
|
||||
'can_access_dashboard': True,
|
||||
'can_access_scheduler': True,
|
||||
@@ -274,11 +274,13 @@ DEFAULT_ROLES = {
|
||||
'can_access_my_availability': True,
|
||||
'can_access_customers': True,
|
||||
'can_access_tickets': True,
|
||||
'can_access_messages': True,
|
||||
'can_access_payments': True,
|
||||
'can_cancel_appointments': True,
|
||||
},
|
||||
},
|
||||
'Limited Staff': {
|
||||
'description': 'Basic access to own schedule only',
|
||||
'Staff': {
|
||||
'description': 'Basic access to own schedule and availability',
|
||||
'permissions': {
|
||||
'can_access_dashboard': True,
|
||||
'can_access_my_schedule': True,
|
||||
|
||||
@@ -285,27 +285,27 @@ class TestDefaultRoles:
|
||||
"""Default roles are defined"""
|
||||
from smoothschedule.identity.users.staff_permissions import DEFAULT_ROLES
|
||||
|
||||
assert 'Full Access Staff' in DEFAULT_ROLES
|
||||
assert 'Front Desk' in DEFAULT_ROLES
|
||||
assert 'Limited Staff' in DEFAULT_ROLES
|
||||
assert 'Manager' in DEFAULT_ROLES
|
||||
assert 'Support Staff' in DEFAULT_ROLES
|
||||
assert 'Staff' in DEFAULT_ROLES
|
||||
|
||||
def test_full_access_has_all_permissions(self):
|
||||
"""Full Access Staff has all permissions enabled"""
|
||||
def test_manager_has_all_permissions(self):
|
||||
"""Manager has all permissions enabled"""
|
||||
from smoothschedule.identity.users.staff_permissions import DEFAULT_ROLES, ALL_PERMISSIONS
|
||||
|
||||
full_access = DEFAULT_ROLES['Full Access Staff']
|
||||
permissions = full_access['permissions']
|
||||
manager = DEFAULT_ROLES['Manager']
|
||||
permissions = manager['permissions']
|
||||
|
||||
for key in ALL_PERMISSIONS.keys():
|
||||
assert key in permissions, f"Missing permission: {key}"
|
||||
assert permissions[key] is True, f"Permission not enabled: {key}"
|
||||
|
||||
def test_limited_staff_has_basic_permissions(self):
|
||||
"""Limited Staff has only basic permissions"""
|
||||
def test_staff_has_basic_permissions(self):
|
||||
"""Staff has only basic permissions"""
|
||||
from smoothschedule.identity.users.staff_permissions import DEFAULT_ROLES
|
||||
|
||||
limited = DEFAULT_ROLES['Limited Staff']
|
||||
permissions = limited['permissions']
|
||||
staff = DEFAULT_ROLES['Staff']
|
||||
permissions = staff['permissions']
|
||||
|
||||
# Should have basic permissions
|
||||
assert permissions.get('can_access_dashboard') is True
|
||||
|
||||
@@ -274,18 +274,18 @@ class Command(BaseCommand):
|
||||
if created:
|
||||
manager.set_password("test123")
|
||||
manager.save()
|
||||
# Assign Full Access Staff role
|
||||
full_access_role = StaffRole.objects.filter(
|
||||
# Assign Manager role
|
||||
manager_role = StaffRole.objects.filter(
|
||||
tenant=tenant,
|
||||
name="Full Access Staff"
|
||||
name="Manager"
|
||||
).first()
|
||||
if full_access_role and manager.staff_role != full_access_role:
|
||||
manager.staff_role = full_access_role
|
||||
if manager_role and manager.staff_role != manager_role:
|
||||
manager.staff_role = manager_role
|
||||
manager.save(update_fields=['staff_role'])
|
||||
users["manager"] = manager
|
||||
if not self.quiet:
|
||||
status = self.style.SUCCESS("CREATED") if created else self.style.WARNING("EXISTS")
|
||||
self.stdout.write(f" {status} {manager.email} (Full Access Staff)")
|
||||
self.stdout.write(f" {status} {manager.email} (Manager)")
|
||||
|
||||
# Staff members (stylists and spa therapists)
|
||||
staff_data = [
|
||||
@@ -716,17 +716,17 @@ class Command(BaseCommand):
|
||||
"""Assign staff roles to demo staff members."""
|
||||
staff_users = tenant_users.get("staff", [])
|
||||
|
||||
# Role assignments: first gets Full Access, some get Front Desk, rest get Limited
|
||||
# Role assignments: mix of Manager, Support Staff, and Staff roles
|
||||
role_assignments = {
|
||||
0: "Full Access Staff", # Sophia
|
||||
1: "Front Desk", # Emma
|
||||
2: "Limited Staff", # Olivia
|
||||
3: "Front Desk", # Isabella
|
||||
4: "Limited Staff", # Mia
|
||||
0: "Manager", # Sophia - Senior Stylist gets manager role
|
||||
1: "Support Staff", # Emma - handles front desk duties
|
||||
2: "Staff", # Olivia - basic access
|
||||
3: "Support Staff", # Isabella - handles customers
|
||||
4: "Staff", # Mia - basic access
|
||||
}
|
||||
|
||||
for i, user in enumerate(staff_users):
|
||||
role_name = role_assignments.get(i, "Limited Staff")
|
||||
role_name = role_assignments.get(i, "Staff")
|
||||
try:
|
||||
# Get tenant from user
|
||||
if user.tenant:
|
||||
|
||||
@@ -259,7 +259,7 @@ class Command(BaseCommand):
|
||||
"last_name": "Manager",
|
||||
"tenant": tenant,
|
||||
"phone": "555-100-0002",
|
||||
"_assign_full_access": True, # Flag to assign Full Access Staff role
|
||||
"_assign_manager_role": True, # Flag to assign Manager role
|
||||
},
|
||||
{
|
||||
"username": "staff@demo.com",
|
||||
@@ -277,7 +277,7 @@ class Command(BaseCommand):
|
||||
manager_user = None # Track manager user separately
|
||||
for user_data in tenant_users:
|
||||
password = user_data.pop("password")
|
||||
assign_full_access = user_data.pop("_assign_full_access", False)
|
||||
assign_manager_role = user_data.pop("_assign_manager_role", False)
|
||||
user, created = User.objects.get_or_create(
|
||||
username=user_data["username"],
|
||||
defaults=user_data,
|
||||
@@ -289,14 +289,14 @@ class Command(BaseCommand):
|
||||
else:
|
||||
status = self.style.WARNING("EXISTS")
|
||||
|
||||
# Assign Full Access Staff role if flagged
|
||||
if assign_full_access:
|
||||
full_access_role = StaffRole.objects.filter(
|
||||
# Assign Manager role if flagged
|
||||
if assign_manager_role:
|
||||
manager_role = StaffRole.objects.filter(
|
||||
tenant=tenant,
|
||||
name="Full Access Staff"
|
||||
name="Manager"
|
||||
).first()
|
||||
if full_access_role and user.staff_role != full_access_role:
|
||||
user.staff_role = full_access_role
|
||||
if manager_role and user.staff_role != manager_role:
|
||||
user.staff_role = manager_role
|
||||
user.save(update_fields=['staff_role'])
|
||||
manager_user = user # Track for resource creation
|
||||
|
||||
|
||||
Reference in New Issue
Block a user