feat: Add company role dropdown to admin users panel
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions

Adds independent company_role management (NONE/VIEWER/EMPLOYEE/MANAGER)
visible next to company column. Decouples company_role from system role
so admins can control portal permissions for company profiles separately.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-02-18 12:47:19 +01:00
parent 30d9cdb698
commit c2a6d5e286
2 changed files with 103 additions and 17 deletions

View File

@ -341,23 +341,7 @@ def admin_users_change_role():
old_role = user.role
user.set_role(SystemRole[new_role])
# Update company_role based on new role
if new_role in ['MANAGER']:
user.company_role = 'MANAGER'
elif new_role in ['EMPLOYEE']:
user.company_role = 'EMPLOYEE'
elif new_role in ['UNAFFILIATED', 'MEMBER']:
user.company_role = 'NONE'
# OFFICE_MANAGER and ADMIN keep their company_role unchanged
# Sync role to user_companies table (primary company)
if user.company_id and new_role in ['MANAGER', 'EMPLOYEE']:
uc = db.query(UserCompany).filter_by(
user_id=user.id, company_id=user.company_id
).first()
if uc:
uc.role = new_role
uc.updated_at = datetime.now()
# Note: company_role is now managed independently via change-company-role endpoint
# Create default permissions for EMPLOYEE if they have a company
if new_role == 'EMPLOYEE' and user.company_id:
@ -411,6 +395,59 @@ def admin_users_get_roles():
return jsonify({'success': True, 'roles': roles})
@bp.route('/users-api/change-company-role', methods=['POST'])
@login_required
@role_required(SystemRole.OFFICE_MANAGER)
def admin_users_change_company_role():
"""Change user's company role (portal permissions for company profile)."""
db = SessionLocal()
try:
data = request.get_json() or {}
user_id = data.get('user_id')
new_role = data.get('company_role')
if not user_id or not new_role:
return jsonify({'success': False, 'error': 'Brak wymaganych danych'}), 400
valid_roles = ['NONE', 'VIEWER', 'EMPLOYEE', 'MANAGER']
if new_role not in valid_roles:
return jsonify({'success': False, 'error': f'Nieprawidłowa rola: {new_role}'}), 400
user = db.query(User).filter(User.id == user_id).first()
if not user:
return jsonify({'success': False, 'error': 'Użytkownik nie znaleziony'}), 404
old_role = user.company_role
user.company_role = new_role
# Sync to user_companies table
if user.company_id:
uc = db.query(UserCompany).filter_by(
user_id=user.id, company_id=user.company_id
).first()
if uc:
uc.role = new_role
uc.updated_at = datetime.now()
db.commit()
logger.info(f"Admin {current_user.email} changed company_role for user {user.email}: {old_role} -> {new_role}")
return jsonify({
'success': True,
'message': f'Uprawnienia firmowe zmienione na {new_role}',
'user_id': user.id,
'old_role': old_role,
'new_role': new_role
})
except Exception as e:
db.rollback()
logger.error(f"Error changing company role: {e}")
return jsonify({'success': False, 'error': f'Błąd: {str(e)}'}), 500
finally:
db.close()
# ============================================================
# USER-COMPANY ASSOCIATIONS (Multi-company support)
# ============================================================

View File

@ -1140,6 +1140,7 @@
<th>ID</th>
<th>Użytkownik</th>
<th>Firma</th>
<th>Upr. firmowe</th>
<th>Rola</th>
<th>Utworzono</th>
<th>Ostatnie logowanie</th>
@ -1169,6 +1170,20 @@
<span style="color: var(--text-secondary);">-</span>
{% endif %}
</td>
<td>
{% if user.company %}
<select class="role-select" style="font-size: var(--font-size-sm);"
data-user-id="{{ user.id }}"
onchange="changeCompanyRole({{ user.id }}, this.value)">
<option value="NONE" {% if user.company_role == 'NONE' %}selected{% endif %}>Brak</option>
<option value="VIEWER" {% if user.company_role == 'VIEWER' %}selected{% endif %}>Podgląd</option>
<option value="EMPLOYEE" {% if user.company_role == 'EMPLOYEE' %}selected{% endif %}>Pracownik</option>
<option value="MANAGER" {% if user.company_role == 'MANAGER' %}selected{% endif %}>Zarządzający</option>
</select>
{% else %}
<span style="color: var(--text-secondary);">-</span>
{% endif %}
</td>
<td>
<select class="role-select"
data-user-id="{{ user.id }}"
@ -1884,6 +1899,40 @@ Lub format CSV, Excel, lista emaili..."></textarea>
}
}
async function changeCompanyRole(userId, newRole) {
const selects = document.querySelectorAll(`select[data-user-id="${userId}"]`);
const select = Array.from(selects).find(s => s.onchange?.toString().includes('changeCompanyRole'));
if (!select) return;
const originalValue = select.dataset.originalValue || select.value;
try {
const response = await fetch('/admin/users-api/change-company-role', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify({
user_id: userId,
company_role: newRole
})
});
const data = await response.json();
if (data.success) {
select.dataset.originalValue = newRole;
const labels = {'NONE': 'Brak', 'VIEWER': 'Podgląd', 'EMPLOYEE': 'Pracownik', 'MANAGER': 'Zarządzający'};
showMessage(`Uprawnienia firmowe zmienione na: ${labels[newRole] || newRole}`, 'success');
} else {
select.value = originalValue;
showMessage(data.error || 'Wystąpił błąd', 'error');
}
} catch (error) {
select.value = originalValue;
showMessage('Błąd połączenia', 'error');
}
}
function getRoleLabel(role) {
const labels = {
'UNAFFILIATED': 'Niezrzeszony',