Initial commit – AufmaßCreater v2.35
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
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))
|
||||
Reference in New Issue
Block a user