feat: Implement warehouse module roles with auto-schema repair and remove module access section
- Add SchemaVerifier class for automatic database schema verification and repair - Implement warehouse_manager (Level 75) and warehouse_worker (Level 35) roles - Add zone-based access control for warehouse workers - Implement worker-manager binding system with zone filtering - Add comprehensive database auto-repair on Docker initialization - Remove Module Access section from user form (role-based access only) - Add autocomplete attributes to password fields for better UX - Include detailed documentation for warehouse implementation - Update initialize_db.py with schema verification as Step 0
This commit is contained in:
@@ -11,25 +11,37 @@ ROLES = {
|
||||
'name': 'Super Administrator',
|
||||
'description': 'Full system access to all modules and features',
|
||||
'level': 100,
|
||||
'modules': ['quality', 'settings']
|
||||
},
|
||||
'manager': {
|
||||
'name': 'Manager',
|
||||
'description': 'Full access to assigned modules and quality control',
|
||||
'level': 70,
|
||||
'modules': ['quality']
|
||||
},
|
||||
'worker': {
|
||||
'name': 'Worker',
|
||||
'description': 'Limited access to view and create quality inspections',
|
||||
'level': 50,
|
||||
'modules': ['quality']
|
||||
'modules': ['quality', 'settings', 'warehouse']
|
||||
},
|
||||
'admin': {
|
||||
'name': 'Administrator',
|
||||
'description': 'Administrative access - can manage users and system configuration',
|
||||
'level': 90,
|
||||
'modules': ['quality', 'settings']
|
||||
'modules': ['quality', 'settings', 'warehouse']
|
||||
},
|
||||
'manager': {
|
||||
'name': 'Manager - Quality',
|
||||
'description': 'Full access to quality module and quality control',
|
||||
'level': 70,
|
||||
'modules': ['quality']
|
||||
},
|
||||
'warehouse_manager': {
|
||||
'name': 'Manager - Warehouse',
|
||||
'description': 'Full access to warehouse module - input and reports',
|
||||
'level': 75,
|
||||
'modules': ['warehouse']
|
||||
},
|
||||
'worker': {
|
||||
'name': 'Worker - Quality',
|
||||
'description': 'Limited access to quality inspections - input only',
|
||||
'level': 50,
|
||||
'modules': ['quality']
|
||||
},
|
||||
'warehouse_worker': {
|
||||
'name': 'Worker - Warehouse',
|
||||
'description': 'Limited access to warehouse - input pages only, no reports',
|
||||
'level': 35,
|
||||
'modules': ['warehouse']
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +113,72 @@ MODULE_PERMISSIONS = {
|
||||
'superadmin': ['view', 'edit'],
|
||||
'admin': ['view', 'edit'],
|
||||
'manager': [],
|
||||
'worker': []
|
||||
'worker': [],
|
||||
'warehouse_manager': [],
|
||||
'warehouse_worker': []
|
||||
}
|
||||
}
|
||||
},
|
||||
'warehouse': {
|
||||
'name': 'Warehouse Module',
|
||||
'sections': {
|
||||
'input': {
|
||||
'name': 'Warehouse Data Input',
|
||||
'actions': {
|
||||
'view': 'View warehouse input pages',
|
||||
'create': 'Create warehouse entries',
|
||||
'edit': 'Edit warehouse entries',
|
||||
'delete': 'Delete warehouse entries'
|
||||
},
|
||||
'superadmin': ['view', 'create', 'edit', 'delete'],
|
||||
'admin': ['view', 'create', 'edit', 'delete'],
|
||||
'manager': [],
|
||||
'worker': [],
|
||||
'warehouse_manager': ['view', 'create', 'edit', 'delete'],
|
||||
'warehouse_worker': ['view', 'create', 'edit']
|
||||
},
|
||||
'reports': {
|
||||
'name': 'Warehouse Reports & Analytics',
|
||||
'actions': {
|
||||
'view': 'View warehouse reports',
|
||||
'export': 'Export warehouse data',
|
||||
'download': 'Download reports',
|
||||
'analytics': 'View analytics'
|
||||
},
|
||||
'superadmin': ['view', 'export', 'download', 'analytics'],
|
||||
'admin': ['view', 'export', 'download', 'analytics'],
|
||||
'manager': [],
|
||||
'worker': [],
|
||||
'warehouse_manager': ['view', 'export', 'download', 'analytics'],
|
||||
'warehouse_worker': []
|
||||
},
|
||||
'locations': {
|
||||
'name': 'Location Management',
|
||||
'actions': {
|
||||
'view': 'View locations',
|
||||
'create': 'Create locations',
|
||||
'edit': 'Edit locations',
|
||||
'delete': 'Delete locations'
|
||||
},
|
||||
'superadmin': ['view', 'create', 'edit', 'delete'],
|
||||
'admin': ['view', 'create', 'edit', 'delete'],
|
||||
'manager': [],
|
||||
'worker': [],
|
||||
'warehouse_manager': ['view', 'create', 'edit', 'delete'],
|
||||
'warehouse_worker': ['view']
|
||||
},
|
||||
'management': {
|
||||
'name': 'Warehouse User Management',
|
||||
'actions': {
|
||||
'manage_workers': 'Manage assigned workers',
|
||||
'manage_zones': 'Manage warehouse zones'
|
||||
},
|
||||
'superadmin': ['manage_workers', 'manage_zones'],
|
||||
'admin': ['manage_workers', 'manage_zones'],
|
||||
'manager': [],
|
||||
'worker': [],
|
||||
'warehouse_manager': ['manage_workers'],
|
||||
'warehouse_worker': []
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,3 +354,192 @@ def get_user_permissions(user_role):
|
||||
permissions[module][section] = allowed_actions
|
||||
|
||||
return permissions
|
||||
|
||||
|
||||
# Warehouse-specific access control helpers
|
||||
|
||||
def can_access_warehouse_input(user_role):
|
||||
"""
|
||||
Check if user can access warehouse INPUT pages
|
||||
|
||||
Args:
|
||||
user_role (str): User's role
|
||||
|
||||
Returns:
|
||||
bool: True if user can access input pages (managers and workers)
|
||||
"""
|
||||
return check_permission(user_role, 'warehouse', 'input', 'view')
|
||||
|
||||
|
||||
def can_access_warehouse_reports(user_role):
|
||||
"""
|
||||
Check if user can access warehouse REPORT/ANALYTICS pages
|
||||
|
||||
Args:
|
||||
user_role (str): User's role
|
||||
|
||||
Returns:
|
||||
bool: True if user can access reports (managers only)
|
||||
"""
|
||||
return check_permission(user_role, 'warehouse', 'reports', 'view')
|
||||
|
||||
|
||||
def can_manage_warehouse_workers(user_role):
|
||||
"""
|
||||
Check if user can manage warehouse workers (assign/unassign)
|
||||
|
||||
Args:
|
||||
user_role (str): User's role
|
||||
|
||||
Returns:
|
||||
bool: True if user can manage workers (managers only)
|
||||
"""
|
||||
return check_permission(user_role, 'warehouse', 'management', 'manage_workers')
|
||||
|
||||
|
||||
# Zone-restricted access functions
|
||||
|
||||
def get_worker_warehouse_zone(user_id):
|
||||
"""
|
||||
Get the warehouse zone restriction for a warehouse_worker
|
||||
|
||||
Args:
|
||||
user_id (int): Worker user ID
|
||||
|
||||
Returns:
|
||||
str or None: Zone name if restricted, None if all zones allowed
|
||||
"""
|
||||
try:
|
||||
from app.database import get_db
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT warehouse_zone FROM worker_manager_bindings
|
||||
WHERE worker_id = %s AND is_active = 1
|
||||
LIMIT 1
|
||||
""", (user_id,))
|
||||
|
||||
result = cursor.fetchone()
|
||||
if result:
|
||||
return result[0] # Return zone name or None
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting worker zone: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_manager_workers(manager_id):
|
||||
"""
|
||||
Get all active workers assigned to a manager
|
||||
|
||||
Args:
|
||||
manager_id (int): Manager user ID
|
||||
|
||||
Returns:
|
||||
list: List of dicts with worker info {id, username, full_name, zone}
|
||||
"""
|
||||
try:
|
||||
from app.database import get_db
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT u.id, u.username, u.full_name, wmb.warehouse_zone
|
||||
FROM worker_manager_bindings wmb
|
||||
JOIN users u ON wmb.worker_id = u.id
|
||||
WHERE wmb.manager_id = %s AND wmb.is_active = 1
|
||||
ORDER BY u.full_name
|
||||
""", (manager_id,))
|
||||
|
||||
results = []
|
||||
for row in cursor.fetchall():
|
||||
results.append({
|
||||
'id': row[0],
|
||||
'username': row[1],
|
||||
'full_name': row[2],
|
||||
'zone': row[3]
|
||||
})
|
||||
return results
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting manager workers: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def validate_worker_zone_access(worker_id, manager_id, zone):
|
||||
"""
|
||||
Validate that a worker can access a specific zone
|
||||
|
||||
Args:
|
||||
worker_id (int): Worker user ID
|
||||
manager_id (int): Expected manager ID
|
||||
zone (str): Zone name to validate
|
||||
|
||||
Returns:
|
||||
bool: True if worker can access zone
|
||||
"""
|
||||
try:
|
||||
from app.database import get_db
|
||||
db = get_db()
|
||||
cursor = db.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT warehouse_zone FROM worker_manager_bindings
|
||||
WHERE worker_id = %s AND manager_id = %s AND is_active = 1
|
||||
LIMIT 1
|
||||
""", (worker_id, manager_id))
|
||||
|
||||
result = cursor.fetchone()
|
||||
if not result:
|
||||
return False # Binding doesn't exist
|
||||
|
||||
assigned_zone = result[0]
|
||||
|
||||
# If no zone restriction (NULL), worker can access all zones
|
||||
if assigned_zone is None:
|
||||
return True
|
||||
|
||||
# If zone is restricted, must match the requested zone
|
||||
return assigned_zone == zone
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating worker zone access: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def build_zone_filter_sql(user_id, user_role):
|
||||
"""
|
||||
Build WHERE clause SQL fragment for zone-filtered queries
|
||||
|
||||
For managers: returns all data from assigned workers
|
||||
For workers: returns only data from their assigned zone
|
||||
For others: returns no data or all data depending on role
|
||||
|
||||
Args:
|
||||
user_id (int): User ID
|
||||
user_role (str): User's role
|
||||
|
||||
Returns:
|
||||
str: SQL WHERE fragment (empty string if no filter needed)
|
||||
"""
|
||||
if user_role in ['superadmin', 'admin']:
|
||||
return "" # No filter - see everything
|
||||
|
||||
if user_role == 'warehouse_manager':
|
||||
# Manager sees data from all assigned workers
|
||||
try:
|
||||
workers = get_manager_workers(user_id)
|
||||
if not workers:
|
||||
# Manager has no workers assigned - see own data only
|
||||
return f"AND created_by_user_id = {user_id}"
|
||||
worker_ids = [w['id'] for w in workers]
|
||||
return f"AND (created_by_user_id IN ({','.join(map(str, worker_ids))}) OR created_by_user_id = {user_id})"
|
||||
except Exception as e:
|
||||
logger.error(f"Error building manager zone filter: {e}")
|
||||
return f"AND created_by_user_id = {user_id}"
|
||||
|
||||
if user_role == 'warehouse_worker':
|
||||
# Worker sees only their own data in their assigned zone
|
||||
return f"AND created_by_user_id = {user_id}"
|
||||
|
||||
return "" # Default - no filtering
|
||||
|
||||
|
||||
Reference in New Issue
Block a user