import os from flask import Blueprint, render_template, redirect, url_for, request, flash from flask_login import login_required, current_user from app.extensions import db from app.models.company import Company from app.models.user import User from app.models.license import License, LicenseModule from app.models.module import Module from app.models.company_module import CompanyModule from app.models.user_module import UserModulePermission from app.services.license_service import get_aktive_module from app.models.project import Project from app.models.contract import Contract from app.models.lv import LVPosition from app.models.project_access import ProjectAccess admin_bp = Blueprint('admin', __name__) def _get_sichtbare_projekte(): if current_user.is_firmadmin(): return Project.query.filter_by(company_id=current_user.company_id) zugriff_ids = db.session.query(ProjectAccess.project_id).filter_by( user_id=current_user.id ).subquery() return Project.query.filter(Project.id.in_(zugriff_ids)) @admin_bp.route('/') @login_required def dashboard(): if current_user.is_superadmin(): return redirect(url_for('superadmin.dashboard')) modules = get_aktive_module(current_user.company_id, user=current_user) projekte_query = _get_sichtbare_projekte() projekte_anzahl = projekte_query.filter(Project.status == 'aktiv').count() letztes_projekt = projekte_query.order_by(Project.erstellt_am.desc()).first() contracts = Contract.query.filter_by( company_id=current_user.company_id ).order_by(Contract.name).all() lv_names = [r[0] for r in db.session.query(LVPosition.lv_name).filter_by( company_id=current_user.company_id ).distinct().order_by(LVPosition.lv_name).all()] # Project sums projekte = projekte_query.order_by(Project.sm_nr).all() from app.models.position import Position from app.models.aufmass import Aufmass projekte_mit_summe = [] gesamt_summe = 0.0 for p in projekte: summe = db.session.query(db.func.coalesce(db.func.sum(Position.gesamtpreis), 0)).join( Aufmass, Position.aufmass_id == Aufmass.id ).filter(Aufmass.project_id == p.id).scalar() or 0.0 projekte_mit_summe.append((p, summe)) gesamt_summe += summe # Employee count mitarbeiter_anzahl = User.query.filter_by(company_id=current_user.company_id).count() # License info licenses = License.query.filter_by(company_id=current_user.company_id, aktiv=True).all() license_max_ma = sum(l.max_mitarbeiter for l in licenses) license_module_count = 0 license_module_used = 0 for lic in licenses: lms = LicenseModule.query.filter_by(license_id=lic.id, aktiv=True).all() license_module_count += len(lms) license_count = len(licenses) # Profiles from app.models.view_profile import ViewProfile profile_list = ViewProfile.query.filter_by( user_id=current_user.id, view_type='dashboard' ).order_by(ViewProfile.is_default.desc(), ViewProfile.name).all() active_profile = next((p for p in profile_list if p.is_default), profile_list[0] if profile_list else None) return render_template('admin/dashboard.html', modules=modules, titel='Dashboard', projekte_anzahl=projekte_anzahl, letztes_projekt=letztes_projekt, contracts=contracts, lv_names=lv_names, projekte_mit_summe=projekte_mit_summe, gesamt_summe=gesamt_summe, mitarbeiter_anzahl=mitarbeiter_anzahl, license_count=license_count, license_max_ma=license_max_ma, license_module_count=license_module_count, license_module_used=license_module_used, profile_list=profile_list, active_profile=active_profile) @admin_bp.route('/profil', methods=['GET', 'POST']) @login_required def profil(): if request.method == 'POST': current_user.vorname = request.form.get('vorname', '').strip() current_user.nachname = request.form.get('nachname', '').strip() if request.form.get('password'): current_user.set_password(request.form['password']) db.session.commit() flash('Profil aktualisiert.', 'success') return render_template('admin/profil.html', titel='Profil') @admin_bp.route('/firma') @login_required def firma(): if not current_user.is_firmadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.dashboard')) company = Company.query.get(current_user.company_id) users = User.query.filter_by(company_id=current_user.company_id).all() licenses = License.query.filter_by(company_id=current_user.company_id).all() modules = get_aktive_module(company.id, user=current_user) return render_template('admin/firma.html', company=company, users=users, licenses=licenses, modules=modules, titel='Firma') @admin_bp.route('/mitarbeiter/neu', methods=['POST']) @login_required def mitarbeiter_neu(): if not current_user.is_firmadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) email = request.form.get('email', '').strip() if User.query.filter_by(email=email).first(): flash('E-Mail existiert bereits.', 'danger') return redirect(url_for('admin.firma')) rolle = request.form.get('rolle', 'mitarbeiter') if rolle not in ('mitarbeiter', 'firmadmin'): rolle = 'mitarbeiter' user = User( company_id=current_user.company_id, email=email, vorname=request.form.get('vorname', '').strip(), nachname=request.form.get('nachname', '').strip(), rolle=rolle, darf_projekte_anlegen=bool(request.form.get('darf_projekte_anlegen')), darf_lv_verwalten=bool(request.form.get('darf_lv_verwalten')), darf_preise_sehen=bool(request.form.get('darf_preise_sehen')), darf_aufmass_verwalten=bool(request.form.get('darf_aufmass_verwalten')), darf_evergabe_nutzen=bool(request.form.get('darf_evergabe_nutzen')), darf_kopfdaten_holen=bool(request.form.get('darf_kopfdaten_holen')), darf_aufmass_uebertragen=bool(request.form.get('darf_aufmass_uebertragen')), ) user.set_password(request.form.get('password', '123456')) db.session.add(user) db.session.commit() flash(f'Mitarbeiter {user.full_name} angelegt.', 'success') return redirect(url_for('admin.firma')) @admin_bp.route('/projekt/schnellanlage', methods=['POST']) @login_required def projekt_schnellanlage(): if not current_user.is_firmadmin() and not current_user.is_superadmin() and not current_user.darf_projekte_anlegen: flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.dashboard')) contract_id = request.form.get('contract_id', type=int) contract = Contract.query.get(contract_id) if contract_id else None from datetime import datetime as dt project = Project( company_id=current_user.company_id, contract_id=contract_id, sm_nr=request.form.get('sm_nr', '').strip(), bezeichnung=request.form.get('bezeichnung', '').strip(), vertrag=contract.name if contract else '', lv_name=request.form.get('lv_name', '').strip(), status='aktiv', erstellt_von=current_user.id, ) db.session.add(project) db.session.commit() flash(f'Projekt {project.sm_nr} angelegt.', 'success') return redirect(url_for('aufmass.aufmass_list', project_id=project.id)) @admin_bp.route('/mitarbeiter//toggle') @login_required def mitarbeiter_toggle(user_id): if not current_user.is_firmadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) user = User.query.get_or_404(user_id) if user.company_id != current_user.company_id: flash('Zugriff verweigert.', 'danger') return redirect(url_for('admin.firma')) user.aktiv = not user.aktiv db.session.commit() flash(f'Benutzer {"aktiviert" if user.aktiv else "deaktiviert"}.', 'success') return redirect(url_for('admin.firma')) @admin_bp.route('/mitarbeiter//rechte', methods=['GET', 'POST']) @login_required def mitarbeiter_rechte(user_id): if not current_user.is_firmadmin() and not current_user.is_superadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.dashboard')) user = User.query.get_or_404(user_id) if user.company_id != current_user.company_id and not current_user.is_superadmin(): flash('Zugriff verweigert.', 'danger') return redirect(url_for('admin.firma')) if request.method == 'POST': user.darf_projekte_anlegen = bool(request.form.get('darf_projekte_anlegen')) user.darf_lv_verwalten = bool(request.form.get('darf_lv_verwalten')) user.darf_preise_sehen = bool(request.form.get('darf_preise_sehen')) user.darf_aufmass_verwalten = bool(request.form.get('darf_aufmass_verwalten')) user.darf_evergabe_nutzen = bool(request.form.get('darf_evergabe_nutzen')) user.darf_kopfdaten_holen = bool(request.form.get('darf_kopfdaten_holen')) user.darf_aufmass_uebertragen = bool(request.form.get('darf_aufmass_uebertragen')) db.session.commit() flash('Rechte aktualisiert.', 'success') return redirect(url_for('admin.firma')) projekte = Project.query.filter_by(company_id=current_user.company_id).order_by(Project.sm_nr).all() zugriffe = {pa.project_id: pa for pa in ProjectAccess.query.filter_by(user_id=user.id).all()} modules = get_aktive_module(current_user.company_id, user=current_user) user_modules = {um.module_id for um in UserModulePermission.query.filter_by(user_id=user.id, aktiv=True).all()} return render_template('admin/rechte.html', user=user, projekte=projekte, zugriffe=zugriffe, modules=modules, user_modules=user_modules, titel='Rechte') @admin_bp.route('/mitarbeiter//projekt-zugriff', methods=['POST']) @login_required def mitarbeiter_projekt_zugriff(user_id): if not current_user.is_firmadmin(): return 'Keine Berechtigung', 403 user = User.query.get_or_404(user_id) if user.company_id != current_user.company_id: return 'Zugriff verweigert', 403 project_id = request.form.get('project_id', type=int) zugriff = request.form.get('zugriff', '') if not project_id or zugriff not in ('lesen', 'schreiben', ''): return 'Ungültige Daten', 400 existing = ProjectAccess.query.filter_by(user_id=user.id, project_id=project_id).first() if zugriff: if existing: existing.zugriff = zugriff else: pa = ProjectAccess(user_id=user.id, project_id=project_id, zugriff=zugriff) db.session.add(pa) elif existing: db.session.delete(existing) db.session.commit() return redirect(url_for('admin.mitarbeiter_rechte', user_id=user.id)) @admin_bp.route('/mitarbeiter//bearbeiten', methods=['GET', 'POST']) @login_required def mitarbeiter_bearbeiten(user_id): if not current_user.is_firmadmin() and not current_user.is_superadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) user = User.query.get_or_404(user_id) if user.company_id != current_user.company_id and not current_user.is_superadmin(): flash('Zugriff verweigert.', 'danger') return redirect(url_for('admin.firma')) if request.method == 'POST': email = request.form.get('email', '').strip() if email and email != user.email: if User.query.filter_by(email=email).first(): flash('E-Mail existiert bereits.', 'danger') return redirect(url_for('admin.mitarbeiter_bearbeiten', user_id=user.id)) user.email = email user.vorname = request.form.get('vorname', '').strip() user.nachname = request.form.get('nachname', '').strip() rolle = request.form.get('rolle', '') if rolle in ('mitarbeiter', 'firmadmin'): user.rolle = rolle if request.form.get('password'): user.set_password(request.form['password']) user.darf_projekte_anlegen = bool(request.form.get('darf_projekte_anlegen')) user.darf_lv_verwalten = bool(request.form.get('darf_lv_verwalten')) user.darf_preise_sehen = bool(request.form.get('darf_preise_sehen')) user.darf_aufmass_verwalten = bool(request.form.get('darf_aufmass_verwalten')) user.darf_evergabe_nutzen = bool(request.form.get('darf_evergabe_nutzen')) user.darf_kopfdaten_holen = bool(request.form.get('darf_kopfdaten_holen')) user.darf_aufmass_uebertragen = bool(request.form.get('darf_aufmass_uebertragen')) db.session.commit() flash(f'Benutzer {user.full_name} aktualisiert.', 'success') if current_user.is_superadmin(): return redirect(url_for('superadmin.firma_detail', company_id=user.company_id)) return redirect(url_for('admin.firma')) company = Company.query.get(user.company_id) return render_template('admin/mitarbeiter_bearbeiten.html', user=user, company=company, titel='Benutzer bearbeiten') @admin_bp.route('/mitarbeiter//loeschen', methods=['POST']) @login_required def mitarbeiter_loeschen(user_id): if not current_user.is_firmadmin() and not current_user.is_superadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) user = User.query.get_or_404(user_id) if user.company_id != current_user.company_id and not current_user.is_superadmin(): flash('Zugriff verweigert.', 'danger') return redirect(url_for('admin.firma')) if user.id == current_user.id: flash('Sie können sich nicht selbst löschen.', 'danger') return redirect(url_for('admin.firma')) email = user.email ProjectAccess.query.filter_by(user_id=user.id).delete() UserModulePermission.query.filter_by(user_id=user.id).delete() db.session.delete(user) db.session.commit() flash(f'Benutzer {email} gelöscht.', 'success') return redirect(url_for('admin.firma')) @admin_bp.route('/mitarbeiter//module//toggle') @login_required def mitarbeiter_module_toggle(user_id, module_id): if not current_user.is_firmadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) user = User.query.get_or_404(user_id) if user.company_id != current_user.company_id: flash('Zugriff verweigert.', 'danger') return redirect(url_for('admin.firma')) if user.is_firmadmin(): flash('Firmadmin hat automatisch alle Module.', 'info') return redirect(url_for('admin.mitarbeiter_rechte', user_id=user.id)) um = UserModulePermission.query.filter_by(user_id=user.id, module_id=module_id).first() if um: um.aktiv = not um.aktiv else: um = UserModulePermission(user_id=user.id, module_id=module_id, aktiv=True) db.session.add(um) db.session.commit() return redirect(url_for('admin.mitarbeiter_rechte', user_id=user.id)) @admin_bp.route('/avatar/upload', methods=['POST']) @login_required def avatar_upload(): file = request.files.get('avatar') if not file: flash('Keine Datei ausgewählt.', 'danger') return redirect(url_for('admin.profil')) import os from flask import current_app ext = os.path.splitext(file.filename)[1].lower() if ext not in ('.png', '.jpg', '.jpeg', '.gif'): flash('Nur PNG, JPG, GIF erlaubt.', 'danger') return redirect(url_for('admin.profil')) upload_dir = os.path.join(current_app.root_path, 'static', 'avatars') os.makedirs(upload_dir, exist_ok=True) filename = f'avatar_{current_user.id}{ext}' file.save(os.path.join(upload_dir, filename)) for old_ext in ('.png', '.jpg', '.jpeg', '.gif'): old_path = os.path.join(upload_dir, f'avatar_{current_user.id}{old_ext}') if old_ext != ext and os.path.exists(old_path): os.remove(old_path) current_user.profile_image = filename db.session.commit() flash('Profilbild aktualisiert.', 'success') return redirect(url_for('admin.profil')) @admin_bp.route('/avatar/entfernen', methods=['POST']) @login_required def avatar_entfernen(): import os from flask import current_app if current_user.profile_image: path = os.path.join(current_app.root_path, 'static', 'avatars', current_user.profile_image) if os.path.exists(path): os.remove(path) current_user.profile_image = None db.session.commit() flash('Profilbild entfernt.', 'success') return redirect(url_for('admin.profil')) @admin_bp.route('/firma/logo') @login_required def firma_logo(): if not current_user.is_firmadmin(): return '', 403 company = Company.query.get(current_user.company_id) if not company or not company.logo or not os.path.exists(company.logo): return '', 404 from flask import send_file return send_file(company.logo) @admin_bp.route('/firma/logo-upload', methods=['POST']) @login_required def firma_logo_upload(): if not current_user.is_firmadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) company = Company.query.get(current_user.company_id) if request.form.get('delete'): if company.logo and os.path.exists(company.logo): os.remove(company.logo) company.logo = None db.session.commit() flash('Firmenlogo entfernt.', 'success') return redirect(url_for('admin.firma')) file = request.files.get('logo') if not file or file.filename == '': flash('Keine Datei ausgewählt.', 'danger') return redirect(url_for('admin.firma')) ext = os.path.splitext(file.filename)[1].lower() if ext not in ('.png', '.jpg', '.jpeg', '.gif', '.webp'): flash('Nur Bildformate: PNG, JPG, GIF, WebP.', 'danger') return redirect(url_for('admin.firma')) from flask import current_app upload_dir = os.path.join(current_app.root_path, '..', 'data', 'uploads', 'logos') os.makedirs(upload_dir, exist_ok=True) filename = f'logo_{company.id}{ext}' filepath = os.path.join(upload_dir, filename) file.save(filepath) company.logo = filepath db.session.commit() flash('Firmenlogo erfolgreich hochgeladen.', 'success') return redirect(url_for('admin.firma')) @admin_bp.route('/firma/evergabe', methods=['POST']) @login_required def firma_evergabe_save(): if not current_user.is_firmadmin(): flash('Keine Berechtigung.', 'danger') return redirect(url_for('admin.firma')) company = Company.query.get(current_user.company_id) if not company.evergabe_aktiviert: flash('E-Vergabe ist für Ihre Firma nicht freigeschaltet.', 'danger') return redirect(url_for('admin.firma')) company.evergabe_benutzer = request.form.get('evergabe_benutzer', '').strip() company.evergabe_passwort = request.form.get('evergabe_passwort', '').strip() company.evergabe_name = request.form.get('evergabe_name', '').strip() db.session.commit() flash('E-Vergabe Logindaten gespeichert.', 'success') return redirect(url_for('admin.firma'))