Files
aufmass-web/_aufmass_web/app/routes/lv.py
T

237 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for
from flask_login import login_required, current_user
from app.extensions import db
from app.models.lv import LVPosition
from app.models.contract import Contract
from app.models.view_profile import ViewProfile
import json
lv_bp = Blueprint('lv', __name__)
def _lv_berechtigt():
if current_user.is_superadmin():
return True
if current_user.is_firmadmin() or current_user.darf_lv_verwalten:
return True
return False
@lv_bp.route('/')
@login_required
def index():
if not _lv_berechtigt():
flash('Keine Berechtigung für LV-Verwaltung.', 'danger')
return redirect(url_for('admin.dashboard'))
lv_names = db.session.query(LVPosition.lv_name).filter_by(
company_id=current_user.company_id
).distinct().order_by(LVPosition.lv_name).all()
lv_names = [r[0] for r in lv_names]
selected_lv = request.args.get('lv', lv_names[0] if lv_names else '')
search = request.args.get('q', '').strip()
sort_col = request.args.get('sort', '')
sort_dir = request.args.get('dir', 'asc')
base = LVPosition.query.filter_by(
company_id=current_user.company_id, lv_name=selected_lv
)
if search:
like = f'%{search}%'
base = base.filter(
db.or_(LVPosition.pos_nr.like(like), LVPosition.kurztext.like(like))
)
sort_map = {
'pos_nr': LVPosition.pos_nr,
'text': LVPosition.kurztext,
'einheit': LVPosition.einheit,
'ep': LVPosition.einzelpreis,
}
if sort_col in sort_map:
col = sort_map[sort_col]
order = col.asc() if sort_dir == 'asc' else col.desc()
positionen = base.order_by(LVPosition.favorite.desc(), order).all()
else:
positionen = base.order_by(LVPosition.favorite.desc(), LVPosition.order_index).all()
contracts = Contract.query.filter_by(company_id=current_user.company_id).order_by(Contract.name).all()
view_id = request.args.get('view_id', type=int)
view_config = ViewProfile.get_default_config()
if view_id:
vp = ViewProfile.query.get(view_id)
if vp and vp.user_id == current_user.id:
view_config = vp.get_config()
else:
default_vp = ViewProfile.query.filter_by(
user_id=current_user.id, view_type='lv', is_default=True
).first()
if default_vp:
view_config = default_vp.get_config()
preise_sichtbar = current_user.is_firmadmin() or current_user.darf_preise_sehen
return render_template('lv/index.html',
lv_names=lv_names, selected_lv=selected_lv,
positionen=positionen, search=search,
contracts=contracts,
view_config=view_config,
view_config_json=json.dumps(view_config),
preise_sichtbar=preise_sichtbar,
titel='Leistungsverzeichnis')
@lv_bp.route('/neu', methods=['POST'])
@login_required
def neu_lv():
if not _lv_berechtigt():
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('lv.index'))
lv_name = request.form.get('lv_name', '').strip()
if not lv_name:
flash('LV-Name erforderlich.', 'danger')
return redirect(url_for('lv.index'))
return redirect(url_for('lv.index', lv=lv_name))
@lv_bp.route('/position/neu', methods=['POST'])
@login_required
def position_neu():
if not _lv_berechtigt():
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('lv.index'))
lv_name = request.form.get('lv_name', '')
if not lv_name:
flash('Bitte zuerst ein LV auswählen.', 'danger')
return redirect(url_for('lv.index'))
max_order = db.session.query(db.func.max(LVPosition.order_index)).filter_by(
company_id=current_user.company_id, lv_name=lv_name
).scalar() or 0
einzelpreis = float(request.form.get('einzelpreis', 0))
pos = LVPosition(
company_id=current_user.company_id, lv_name=lv_name,
pos_nr=request.form.get('pos_nr', ''), order_index=max_order + 1,
kurztext=request.form.get('kurztext', ''),
langtext=request.form.get('langtext', ''),
einheit=request.form.get('einheit', 'ST'),
einzelpreis=einzelpreis if current_user.is_firmadmin() or current_user.darf_preise_sehen else 0,
gruppe=request.form.get('gruppe', ''),
)
db.session.add(pos)
db.session.commit()
return redirect(url_for('lv.index', lv=lv_name))
@lv_bp.route('/position/<int:pos_id>/bearbeiten', methods=['POST'])
@login_required
def position_bearbeiten(pos_id):
if not _lv_berechtigt():
return 'Keine Berechtigung', 403
pos = LVPosition.query.get_or_404(pos_id)
if pos.company_id != current_user.company_id:
return 'Zugriff verweigert', 403
pos.pos_nr = request.form.get('pos_nr', pos.pos_nr)
pos.kurztext = request.form.get('kurztext', pos.kurztext)
pos.langtext = request.form.get('langtext', pos.langtext)
pos.einheit = request.form.get('einheit', pos.einheit)
if current_user.is_firmadmin() or current_user.darf_preise_sehen:
pos.einzelpreis = float(request.form.get('einzelpreis', pos.einzelpreis))
pos.gruppe = request.form.get('gruppe', pos.gruppe)
db.session.commit()
return redirect(url_for('lv.index', lv=pos.lv_name))
@lv_bp.route('/position/<int:pos_id>/loeschen', methods=['POST'])
@login_required
def position_loeschen(pos_id):
if not _lv_berechtigt():
return 'Keine Berechtigung', 403
pos = LVPosition.query.get_or_404(pos_id)
if pos.company_id != current_user.company_id:
return 'Zugriff verweigert', 403
lv_name = pos.lv_name
db.session.delete(pos)
db.session.commit()
return redirect(url_for('lv.index', lv=lv_name))
@lv_bp.route('/positionen/reihenfolge', methods=['POST'])
@login_required
def positionen_reihenfolge():
if not _lv_berechtigt():
return jsonify({'error': 'Keine Berechtigung'}), 403
data = request.get_json()
if not data or 'reihenfolge' not in data:
return jsonify({'error': 'Keine Daten'}), 400
for item in data['reihenfolge']:
pos = LVPosition.query.get(item['id'])
if pos and pos.company_id == current_user.company_id:
pos.order_index = item['order_index']
db.session.commit()
return jsonify({'status': 'ok'})
@lv_bp.route('/position/<int:pos_id>/favorite', methods=['POST'])
@login_required
def position_favorite(pos_id):
pos = LVPosition.query.get_or_404(pos_id)
if pos.company_id != current_user.company_id:
return 'Zugriff verweigert', 403
data = request.get_json() or {}
pos.favorite = data.get('favorite', not pos.favorite)
db.session.commit()
return jsonify({'favorite': pos.favorite})
@lv_bp.route('/position/<int:pos_id>/langtext')
@login_required
def position_langtext(pos_id):
pos = LVPosition.query.get_or_404(pos_id)
if pos.company_id != current_user.company_id:
return 'Zugriff verweigert', 403
preise_sichtbar = current_user.is_firmadmin() or current_user.darf_preise_sehen
preis_text = f'{pos.einzelpreis:.2f}' if preise_sichtbar else 'versteckt'
return f'''<div class="box">
<p class="has-text-weight-bold mb-2">{pos.pos_nr} {pos.kurztext or ''}</p>
<p class="is-size-6"><strong>Einheit:</strong> {pos.einheit} | <strong>EP:</strong> {preis_text} | <strong>Favorit:</strong> {'' if pos.favorite else ''}</p>
<hr>
<div style="white-space:pre-wrap;font-size:0.9rem">{pos.langtext or 'Kein Langtext vorhanden.'}</div>
</div>'''
@lv_bp.route('/import/txt', methods=['POST'])
@login_required
def import_txt():
if not _lv_berechtigt():
flash('Keine Berechtigung.', 'danger')
return redirect(url_for('lv.index'))
lv_name = request.form.get('lv_name', '').strip()
if not lv_name:
flash('LV-Name erforderlich.', 'danger')
return redirect(url_for('lv.index'))
file = request.files.get('datei')
if not file:
flash('Keine Datei ausgewählt.', 'danger')
return redirect(url_for('lv.index'))
content = file.read().decode('utf-8', errors='ignore')
max_order = db.session.query(db.func.max(LVPosition.order_index)).filter_by(
company_id=current_user.company_id, lv_name=lv_name
).scalar() or 0
zeilen = content.split('\n')
count = 0
preise_sichtbar = current_user.is_firmadmin() or current_user.darf_preise_sehen
for zeile in zeilen:
zeile = zeile.strip()
if not zeile or zeile.startswith('#'):
continue
parts = [p.strip() for p in zeile.split('|')]
if len(parts) >= 2:
pos_nr = parts[0]
if not LVPosition.query.filter_by(
company_id=current_user.company_id, lv_name=lv_name, pos_nr=pos_nr
).first():
max_order += 1
einzelpreis = float(parts[4]) if len(parts) > 4 and parts[4] else 0
pos = LVPosition(
company_id=current_user.company_id, lv_name=lv_name,
pos_nr=pos_nr, order_index=max_order,
kurztext=parts[1] if len(parts) > 1 else '',
langtext=parts[2] if len(parts) > 2 else '',
einheit=parts[3] if len(parts) > 3 else 'ST',
einzelpreis=einzelpreis if preise_sichtbar else 0,
)
db.session.add(pos)
count += 1
db.session.commit()
flash(f'{count} Positionen importiert.', 'success')
return redirect(url_for('lv.index', lv=lv_name))