Initial commit – AufmaßCreater v2.35

This commit is contained in:
2026-06-10 11:03:43 +02:00
commit 84c933ea9c
2823 changed files with 490495 additions and 0 deletions
+414
View File
@@ -0,0 +1,414 @@
from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from flask_login import login_required, current_user
from app.extensions import db
from app.models.custom_module import CustomModule, CustomModuleAssignment
from app.models.project import Project
from app.models.aufmass import Aufmass
from app.models.position import Position
from app.models.user import User
from app.models.company import Company
from app.services.custom_module_renderer import render_form
from app.services.custom_module_executor import execute_rules
import json
custom_modules_bp = Blueprint('custom_modules', __name__, url_prefix='/custom-modules')
def _can_manage(user, module=None):
if user.is_superadmin():
return True
if user.is_firmadmin():
if module is None:
return True
if module.is_template:
return False
return module.company_id == user.company_id
return False
def _can_use(user, module):
if user.is_superadmin():
return True
if user.is_firmadmin() and module.company_id == user.company_id:
return True
if not module.is_active:
return False
return CustomModuleAssignment.query.filter_by(
module_id=module.id, user_id=user.id
).first() is not None
@custom_modules_bp.route('/')
@login_required
def index():
if not _can_manage(current_user):
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('admin.dashboard'))
if current_user.is_superadmin():
templates = CustomModule.query.filter_by(is_template=True).order_by(CustomModule.sort_index).all()
company_modules = CustomModule.query.filter_by(is_template=False).order_by(CustomModule.company_id, CustomModule.sort_index).all()
companies = Company.query.order_by(Company.name).all()
else:
templates = CustomModule.query.filter_by(is_template=True, is_active=True).order_by(CustomModule.sort_index).all()
company_modules = CustomModule.query.filter_by(
company_id=current_user.company_id, is_template=False
).order_by(CustomModule.sort_index).all()
companies = []
return render_template('custom_modules/index.html',
templates=templates,
company_modules=company_modules,
companies=companies)
@custom_modules_bp.route('/neu', methods=['GET', 'POST'])
@login_required
def neu():
if not _can_manage(current_user):
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('admin.dashboard'))
if request.method == 'POST':
name = request.form.get('name', '').strip()
if not name:
flash('Name ist erforderlich.', 'danger')
return render_template('custom_modules/edit.html', module=None)
module = CustomModule(
company_id=current_user.company_id if not current_user.is_superadmin() else None,
name=name,
description=request.form.get('description', ''),
kategorie=request.form.get('kategorie', 'allgemein'),
icon=request.form.get('icon', '🔧'),
is_template=bool(request.form.get('is_template')) if current_user.is_superadmin() else False,
form_json='[]',
rules_json='[]',
created_by=current_user.id,
)
db.session.add(module)
db.session.commit()
flash(f'Modul "{name}" erstellt.', 'success')
return redirect(url_for('custom_modules.edit', module_id=module.id))
return render_template('custom_modules/edit.html', module=None,
is_superadmin=current_user.is_superadmin())
@custom_modules_bp.route('/<int:module_id>/bearbeiten', methods=['GET', 'POST'])
@login_required
def edit(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('admin.dashboard'))
if request.method == 'POST':
module.name = request.form.get('name', module.name).strip()
module.description = request.form.get('description', '')
module.kategorie = request.form.get('kategorie', 'allgemein')
module.icon = request.form.get('icon', '🔧')
module.is_active = bool(request.form.get('is_active', True))
if current_user.is_superadmin():
module.is_template = bool(request.form.get('is_template', module.is_template))
db.session.commit()
flash('Modul aktualisiert.', 'success')
return redirect(url_for('custom_modules.index'))
users = []
if module.company_id:
users = User.query.filter_by(company_id=module.company_id, aktiv=True).order_by(User.vorname).all()
assignments = {a.user_id: a for a in module.assignments}
return render_template('custom_modules/edit.html', module=module,
users=users, assignments=assignments,
is_superadmin=current_user.is_superadmin())
@custom_modules_bp.route('/<int:module_id>/builder')
@login_required
def builder(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('admin.dashboard'))
return render_template('custom_modules/builder.html', module=module)
@custom_modules_bp.route('/<int:module_id>/loeschen', methods=['POST'])
@login_required
def loeschen(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('admin.dashboard'))
name = module.name
db.session.delete(module)
db.session.commit()
flash(f'Modul "{name}" gelöscht.', 'success')
return redirect(url_for('custom_modules.index'))
@custom_modules_bp.route('/<int:module_id>/importieren', methods=['POST'])
@login_required
def importieren(module_id):
template = CustomModule.query.get_or_404(module_id)
if not template.is_template:
flash('Nur Vorlagen können importiert werden.', 'danger')
return redirect(url_for('custom_modules.index'))
if not current_user.is_firmadmin():
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('custom_modules.index'))
existing = CustomModule.query.filter_by(
company_id=current_user.company_id,
original_template_id=template.id
).first()
if existing:
flash(f'Vorlage "{template.name}" bereits als "{existing.name}" importiert.', 'warning')
return redirect(url_for('custom_modules.index'))
copy = CustomModule(
company_id=current_user.company_id,
original_template_id=template.id,
name=template.name,
description=template.description,
kategorie=template.kategorie,
icon=template.icon,
form_json=template.form_json,
rules_json=template.rules_json,
is_template=False,
created_by=current_user.id,
)
db.session.add(copy)
db.session.commit()
flash(f'Vorlage "{template.name}" importiert jetzt kannst du sie anpassen.', 'success')
return redirect(url_for('custom_modules.edit', module_id=copy.id))
@custom_modules_bp.route('/<int:module_id>/als-vorlage', methods=['POST'])
@login_required
def als_vorlage(module_id):
module = CustomModule.query.get_or_404(module_id)
if not current_user.is_superadmin():
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('admin.dashboard'))
template = CustomModule(
company_id=None,
original_template_id=None,
name=module.name,
description=module.description,
kategorie=module.kategorie,
icon=module.icon,
form_json=module.form_json,
rules_json=module.rules_json,
is_template=True,
created_by=current_user.id,
)
db.session.add(template)
db.session.commit()
flash(f'Modul "{module.name}" als globale Vorlage gespeichert.', 'success')
return redirect(url_for('custom_modules.index'))
@custom_modules_bp.route('/<int:module_id>/user/<int:user_id>/toggle', methods=['POST'])
@login_required
def user_toggle(module_id, user_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
return jsonify({'error': 'Keine Berechtigung'}), 403
user = User.query.get_or_404(user_id)
assignment = CustomModuleAssignment.query.filter_by(
module_id=module.id, user_id=user.id
).first()
data = request.get_json(silent=True) or {}
active = data.get('active', False)
can_edit = data.get('can_edit', False)
if active:
if not assignment:
assignment = CustomModuleAssignment(
module_id=module.id, user_id=user.id, can_edit=can_edit
)
db.session.add(assignment)
db.session.commit()
return jsonify({'message': f'{user.full_name} hat jetzt Zugriff.', 'active': True})
return jsonify({'active': True})
else:
if assignment:
db.session.delete(assignment)
db.session.commit()
return jsonify({'message': f'Zugriff für {user.full_name} entzogen.', 'active': False})
return jsonify({'active': False})
@custom_modules_bp.route('/<int:module_id>/api/form-json', methods=['GET', 'PUT'])
@login_required
def api_form_json(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
return jsonify({'error': 'Keine Berechtigung'}), 403
if request.method == 'PUT':
try:
data = request.get_json(force=True)
module.form_json = json.dumps(data, ensure_ascii=False)
db.session.commit()
return jsonify({'ok': True})
except Exception as e:
return jsonify({'error': str(e)}), 400
return jsonify(json.loads(module.form_json or '[]'))
@custom_modules_bp.route('/<int:module_id>/api/rules-json', methods=['GET', 'PUT'])
@login_required
def api_rules_json(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
return jsonify({'error': 'Keine Berechtigung'}), 403
if request.method == 'PUT':
try:
data = request.get_json(force=True)
module.rules_json = json.dumps(data, ensure_ascii=False)
db.session.commit()
return jsonify({'ok': True})
except Exception as e:
return jsonify({'error': str(e)}), 400
return jsonify(json.loads(module.rules_json or '[]'))
@custom_modules_bp.route('/<int:module_id>/api/preview')
@login_required
def api_preview(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
return '<div class="notification is-danger">Keine Berechtigung.</div>'
html = render_form(module.form_json, module_id)
return html
@custom_modules_bp.route('/api/available')
@login_required
def api_available():
modules = CustomModule.query.filter(
CustomModule.is_active == True,
CustomModule.is_template == False,
CustomModule.company_id == current_user.company_id
).order_by(CustomModule.sort_index).all()
result = []
for m in modules:
if current_user.is_firmadmin() or current_user.is_superadmin():
result.append({'id': m.id, 'name': m.name, 'icon': m.icon, 'kategorie': m.kategorie})
elif _can_use(current_user, m):
result.append({'id': m.id, 'name': m.name, 'icon': m.icon, 'kategorie': m.kategorie})
return jsonify(result)
@custom_modules_bp.route('/<int:module_id>/formular')
@login_required
def formular(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_use(current_user, module):
return '<div class="notification is-danger">Keine Berechtigung für dieses Modul.</div>'
aufmass_id = request.args.get('aufmass_id')
html = render_form(module.form_json, module_id, aufmass_id=aufmass_id)
return html
@custom_modules_bp.route('/<int:module_id>/berechnen', methods=['POST'])
@login_required
def berechnen(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_use(current_user, module):
return '<div class="notification is-danger">Keine Berechtigung.</div>'
aufmass_id = request.args.get('aufmass_id')
if not aufmass_id:
return '<div class="notification is-danger">Kein Aufmaß ausgewählt.<br><button class="button is-small is-light mt-2" onclick="closeModulModal()">Schließen</button></div>'
aufmass = Aufmass.query.get_or_404(aufmass_id)
project = Project.query.get(aufmass.project_id)
if not project:
return '<div class="notification is-danger">Projekt nicht gefunden.<br><button class="button is-small is-light mt-2" onclick="closeModulModal()">Schließen</button></div>'
project_id = project.id
company_id = project.company_id
form_data = {k: v for k, v in request.form.items()}
rules = json.loads(module.rules_json) if isinstance(module.rules_json, str) else module.rules_json
positions_data = execute_rules(form_data, module.rules_json, company_id)
if not positions_data:
return '''
<div class="notification is-warning" style="margin-top:10px">
Keine Bedingungen erfüllt es wurden keine Positionen erzeugt.
<button class="delete" onclick="closeModulModal()"></button>
</div>
<button class="button is-small is-light mt-2" onclick="closeModulModal()">Schließen</button>'''
created = 0
for pd in positions_data:
pos = Position(
aufmass_id=aufmass.id,
project_id=project_id,
pos_nr=pd.get('pos_nr', ''),
kurztext=pd.get('kurztext', ''),
langtext=pd.get('langtext', ''),
einheit=pd.get('einheit', 'ST'),
menge=pd.get('menge', 0),
faktor=pd.get('faktor', 1),
laenge=pd.get('laenge', 0),
breite=pd.get('breite', 0),
tiefe=pd.get('tiefe', 0),
einzelpreis=pd.get('einzelpreis', 0),
bemerkung=pd.get('bemerkung', ''),
sort_index=pd.get('sort_index', 0),
)
db.session.add(pos)
created += 1
db.session.commit()
return f'''<div class="notification is-success">
<strong>{created} Positionen</strong> ins Aufmaß übernommen.
<span class="is-pulled-right tag is-light is-info">Seite wird neu geladen...</span>
</div>
<script>
setTimeout(function() {{
var m = document.getElementById('modul-modal');
if (m) m.classList.remove('is-active');
location.reload();
}}, 1200);
</script>'''
@custom_modules_bp.route('/api/sort-batch', methods=['POST'])
@login_required
def sort_batch():
if not _can_manage(current_user):
return jsonify({'error': 'Keine Berechtigung'}), 403
data = request.get_json(force=True)
if not isinstance(data, list):
return jsonify({'error': 'Erwarte Liste von {id, sort_index}'}), 400
for item in data:
module = CustomModule.query.get(item.get('id'))
if not module:
continue
if not _can_manage(current_user, module):
continue
module.sort_index = item.get('sort_index', 0)
db.session.commit()
return jsonify({'ok': True, 'updated': len(data)})
@custom_modules_bp.route('/<int:module_id>/sort', methods=['POST'])
@login_required
def sort(module_id):
module = CustomModule.query.get_or_404(module_id)
if not _can_manage(current_user, module):
return jsonify({'error': 'Keine Berechtigung'}), 403
data = request.get_json(force=True)
sort_index = data.get('sort_index', 0)
module.sort_index = sort_index
db.session.commit()
return jsonify({'ok': True})