Initial commit – AufmaßCreater v2.35
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for
|
||||
from config import Config
|
||||
from app.extensions import db, login_manager, migrate
|
||||
|
||||
def create_app(config_class=Config):
|
||||
app = Flask(__name__)
|
||||
app.config.from_object(config_class)
|
||||
|
||||
db.init_app(app)
|
||||
login_manager.init_app(app)
|
||||
migrate.init_app(app, db)
|
||||
|
||||
login_manager.login_view = 'auth.login'
|
||||
login_manager.login_message = 'Bitte melden Sie sich an.'
|
||||
|
||||
from app.models.user import User
|
||||
from app.models.company import Company
|
||||
from app.models.license import License
|
||||
from app.models.module import Module
|
||||
from app.models.lv import LVPosition
|
||||
from app.models.project import Project
|
||||
from app.models.aufmass import Aufmass
|
||||
from app.models.project_access import ProjectAccess
|
||||
from app.models.position import Position
|
||||
from app.models.contract import Contract
|
||||
from app.models.view_profile import ViewProfile
|
||||
from app.models.company_module import CompanyModule
|
||||
from app.models.user_module import UserModulePermission
|
||||
from app.models.aufmass_typ import AufmassTyp
|
||||
from app.models.aufmass_history import AufmassHistory
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
user = User.query.get(int(user_id))
|
||||
if user:
|
||||
from flask import session
|
||||
session.setdefault('font_size', user.font_size or '1')
|
||||
return user
|
||||
|
||||
from app.routes.auth import auth_bp
|
||||
from app.routes.admin import admin_bp
|
||||
from app.routes.lv import lv_bp
|
||||
from app.routes.aufmass import aufmass_bp
|
||||
from app.routes.export import export_bp
|
||||
from app.routes.modules import modules_bp
|
||||
from app.routes.contracts import contracts_bp
|
||||
from app.routes.views import views_bp
|
||||
from app.routes.superadmin import superadmin_bp
|
||||
from app.routes.custom_modules import custom_modules_bp
|
||||
|
||||
app.register_blueprint(auth_bp, url_prefix='/auth')
|
||||
app.register_blueprint(admin_bp, url_prefix='/admin')
|
||||
app.register_blueprint(lv_bp, url_prefix='/lv')
|
||||
app.register_blueprint(aufmass_bp, url_prefix='/projekt')
|
||||
app.register_blueprint(export_bp, url_prefix='/export')
|
||||
app.register_blueprint(modules_bp, url_prefix='/modules')
|
||||
app.register_blueprint(contracts_bp, url_prefix='/contracts')
|
||||
app.register_blueprint(views_bp)
|
||||
app.register_blueprint(superadmin_bp, url_prefix='/superadmin')
|
||||
app.register_blueprint(custom_modules_bp, url_prefix='/custom-modules')
|
||||
|
||||
@app.route('/aufmass/')
|
||||
@app.route('/aufmass/<path:subpath>')
|
||||
def _aufmass_redirect(subpath=''):
|
||||
return redirect('/projekt/' + subpath), 301
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
from flask_login import current_user
|
||||
if current_user.is_authenticated:
|
||||
if current_user.is_superadmin():
|
||||
return redirect(url_for('superadmin.dashboard'))
|
||||
return redirect(url_for('admin.dashboard'))
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
@app.route('/font-size/<size>')
|
||||
def set_font_size(size):
|
||||
from flask import session
|
||||
from flask_login import current_user
|
||||
if size in ('0.8', '0.9', '1', '1.1', '1.25', '1.5'):
|
||||
session['font_size'] = size
|
||||
if current_user.is_authenticated:
|
||||
current_user.font_size = size
|
||||
try:
|
||||
db.session.commit()
|
||||
except:
|
||||
db.session.rollback()
|
||||
return redirect(request.referrer or url_for('admin.dashboard'))
|
||||
|
||||
@app.template_filter('german_number')
|
||||
def german_number_filter(value, precision=2, zero_dash=False):
|
||||
if value is None or (zero_dash and value == 0):
|
||||
return '\u2013'
|
||||
try:
|
||||
s = f'{float(value):.{precision}f}'
|
||||
parts = s.split('.')
|
||||
int_part = parts[0]
|
||||
dec_part = parts[1] if len(parts) > 1 else '0'*precision
|
||||
int_part = '{:,}'.format(int(int_part)).replace(',', '.')
|
||||
return f'{int_part},{dec_part}'
|
||||
except (ValueError, TypeError):
|
||||
return '\u2013' if zero_dash else '0,00'
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found(e):
|
||||
return render_template('errors/404.html'), 404
|
||||
|
||||
@app.errorhandler(500)
|
||||
def server_error(e):
|
||||
return render_template('errors/500.html'), 500
|
||||
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
_seed_defaults()
|
||||
|
||||
return app
|
||||
|
||||
def _seed_defaults():
|
||||
from app.extensions import db
|
||||
from app.models.module import Module
|
||||
|
||||
def upsert(name, titel, kategorie, icon, standard, sortierung=0):
|
||||
m = Module.query.filter_by(name=name).first()
|
||||
if m:
|
||||
m.titel = titel
|
||||
m.kategorie = kategorie
|
||||
m.icon = icon
|
||||
m.standard = standard
|
||||
m.sortierung = sortierung
|
||||
else:
|
||||
db.session.add(Module(name=name, titel=titel, kategorie=kategorie, icon=icon, standard=standard, sortierung=sortierung))
|
||||
|
||||
# 1. Tote Module entfernen (inkl. FK-Referenzen)
|
||||
for dead_name in ('mfg', 'stoersammler'):
|
||||
old = Module.query.filter_by(name=dead_name).first()
|
||||
if old:
|
||||
from app.models.license import LicenseModule
|
||||
from app.models.company_module import CompanyModule
|
||||
LicenseModule.query.filter_by(module_id=old.id).delete()
|
||||
CompanyModule.query.filter_by(module_id=old.id).delete()
|
||||
db.session.delete(old)
|
||||
db.session.commit()
|
||||
|
||||
# 2. Alle Module upserten
|
||||
upsert('graben', 'Graben', 'Tiefbau', '🔲', standard=True)
|
||||
upsert('gruben', 'Gruben', 'Tiefbau', '🕳️', standard=True)
|
||||
upsert('gf_montage', 'GF-Montage', 'Glasfaser', '🔗', standard=True)
|
||||
upsert('ftth', 'FTTH', 'Glasfaser', '🏠', standard=True)
|
||||
upsert('kabelzug', 'Kabelzug', 'Tiefbau', '🔌', standard=True)
|
||||
upsert('absperrung', 'Absperrung', 'Tiefbau', '🚧', standard=True)
|
||||
upsert('sas_mecka', 'SAS Meckenbeuren', 'Spezial', '📍', standard=False)
|
||||
upsert('neff_achberg', 'Neff-Achberg', 'Spezial', '🏗️', standard=False)
|
||||
upsert('cu', 'CU', 'Kupfer', '📞', standard=True)
|
||||
upsert('stoerung', 'Störung', 'Service', '🔧', standard=True)
|
||||
upsert('tvum', 'TV/UM', 'Glasfaser', '📺', standard=True)
|
||||
upsert('planung', 'Planung', 'Planung', '📐', standard=True)
|
||||
upsert('zw_rv', 'ZW/RV', 'Tiefbau', '🔩', standard=True)
|
||||
upsert('doku', 'Dokumentation', 'Planung', '📄', standard=True)
|
||||
upsert('sto_sammler', 'Störungs-Sammler', 'Service', '📋', standard=True)
|
||||
db.session.commit()
|
||||
from app.models.aufmass_typ import AufmassTyp
|
||||
if AufmassTyp.query.count() == 0:
|
||||
db.session.add(AufmassTyp(name='Teilaufmaß/AZ', sortierung=1))
|
||||
db.session.add(AufmassTyp(name='Schlussaufmaß', sortierung=2))
|
||||
db.session.commit()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_login import LoginManager
|
||||
from flask_migrate import Migrate
|
||||
|
||||
db = SQLAlchemy()
|
||||
login_manager = LoginManager()
|
||||
migrate = Migrate()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
LOCK_TIMEOUT = timedelta(minutes=2)
|
||||
|
||||
class Aufmass(db.Model):
|
||||
__tablename__ = 'aufmass'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
project_id = db.Column(db.Integer, db.ForeignKey('projekte.id'), nullable=False)
|
||||
name = db.Column(db.String(200), nullable=False, default='Standard')
|
||||
typ = db.Column(db.String(50), default='')
|
||||
status = db.Column(db.String(20), default='aktiv')
|
||||
sortierung = db.Column(db.Integer, default=0)
|
||||
bemerkung = db.Column(db.Text)
|
||||
erstellt_von = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
geaendert_am = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
locked_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
||||
locked_at = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
positionen = db.relationship('Position', backref='aufmass_ref', lazy='dynamic',
|
||||
cascade='all, delete-orphan', order_by='Position.sortierung')
|
||||
|
||||
def is_locked(self):
|
||||
if not self.locked_by or not self.locked_at:
|
||||
return False, None
|
||||
if datetime.utcnow() - self.locked_at > LOCK_TIMEOUT:
|
||||
return False, None
|
||||
return True, self.locked_by
|
||||
|
||||
def try_lock(self, user_id):
|
||||
locked, holder = self.is_locked()
|
||||
if locked and holder != user_id:
|
||||
return False
|
||||
self.locked_by = user_id
|
||||
self.locked_at = datetime.utcnow()
|
||||
return True
|
||||
|
||||
def unlock(self):
|
||||
self.locked_by = None
|
||||
self.locked_at = None
|
||||
|
||||
def refresh_lock(self, user_id):
|
||||
if self.locked_by == user_id:
|
||||
self.locked_at = datetime.utcnow()
|
||||
return True
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Aufmass {self.name} @ {self.project_id}>'
|
||||
@@ -0,0 +1,17 @@
|
||||
from datetime import datetime
|
||||
from app.extensions import db
|
||||
|
||||
class AufmassHistory(db.Model):
|
||||
__tablename__ = 'aufmass_history'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
aufmass_id = db.Column(db.Integer, db.ForeignKey('aufmass.id'), nullable=False, index=True)
|
||||
position_id = db.Column(db.Integer, db.ForeignKey('positionen.id', ondelete='SET NULL'), nullable=True)
|
||||
changed_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
changed_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
|
||||
action = db.Column(db.String(10), nullable=False)
|
||||
description = db.Column(db.String(500), nullable=True)
|
||||
diff = db.Column(db.Text, nullable=False)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<AufmassHistory {self.id} {self.action} @ {self.changed_at}>'
|
||||
@@ -0,0 +1,12 @@
|
||||
from app.extensions import db
|
||||
|
||||
class AufmassTyp(db.Model):
|
||||
__tablename__ = 'aufmass_typen'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=True)
|
||||
sortierung = db.Column(db.Integer, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<AufmassTyp {self.name}>'
|
||||
@@ -0,0 +1,29 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class Company(db.Model):
|
||||
__tablename__ = 'companies'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(200), nullable=False)
|
||||
slug = db.Column(db.String(100), unique=True, nullable=False)
|
||||
strasse = db.Column(db.String(200))
|
||||
house_number = db.Column(db.String(20))
|
||||
plz = db.Column(db.String(10))
|
||||
ort = db.Column(db.String(100))
|
||||
telefon = db.Column(db.String(50))
|
||||
email = db.Column(db.String(200))
|
||||
logo = db.Column(db.String(500))
|
||||
aktiv = db.Column(db.Boolean, default=True)
|
||||
evergabe_aktiviert = db.Column(db.Boolean, default=False)
|
||||
evergabe_benutzer = db.Column(db.String(200))
|
||||
evergabe_passwort = db.Column(db.String(300))
|
||||
evergabe_name = db.Column(db.String(200))
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
users = db.relationship('User', backref='company', lazy='dynamic')
|
||||
licenses = db.relationship('License', backref='company', lazy='dynamic')
|
||||
projekte = db.relationship('Project', backref='company', lazy='dynamic')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Company {self.name}>'
|
||||
@@ -0,0 +1,14 @@
|
||||
from app.extensions import db
|
||||
|
||||
class CompanyModule(db.Model):
|
||||
__tablename__ = 'company_modules'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=False)
|
||||
module_id = db.Column(db.Integer, db.ForeignKey('modules.id'), nullable=False)
|
||||
aktiv = db.Column(db.Boolean, default=True)
|
||||
|
||||
company = db.relationship('Company', backref='company_module_list')
|
||||
module = db.relationship('Module', backref='company_assignments')
|
||||
|
||||
__table_args__ = (db.UniqueConstraint('company_id', 'module_id'),)
|
||||
@@ -0,0 +1,20 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class Contract(db.Model):
|
||||
__tablename__ = 'contracts'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=False)
|
||||
name = db.Column(db.String(300), nullable=False)
|
||||
belegnummer = db.Column(db.String(100))
|
||||
beleg_datum = db.Column(db.Date)
|
||||
laufzeit_start = db.Column(db.Date)
|
||||
laufzeit_ende = db.Column(db.Date)
|
||||
status = db.Column(db.String(50), default='NEU') # NEU, Zur Prüfung, Angenommen
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
projekte = db.relationship('Project', backref='contract', lazy='dynamic')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Contract {self.name}>'
|
||||
@@ -0,0 +1,62 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class CustomModule(db.Model):
|
||||
__tablename__ = 'custom_modules'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=True)
|
||||
original_template_id = db.Column(db.Integer, db.ForeignKey('custom_modules.id'), nullable=True)
|
||||
name = db.Column(db.String(200), nullable=False)
|
||||
description = db.Column(db.Text, default='')
|
||||
kategorie = db.Column(db.String(50), default='allgemein')
|
||||
icon = db.Column(db.String(50), default='🔧')
|
||||
form_json = db.Column(db.Text, default='[]')
|
||||
rules_json = db.Column(db.Text, default='[]')
|
||||
is_template = db.Column(db.Boolean, default=False)
|
||||
sort_index = db.Column(db.Integer, default=0)
|
||||
is_active = db.Column(db.Boolean, default=True)
|
||||
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
company = db.relationship('Company', backref='custom_modules', foreign_keys=[company_id])
|
||||
template = db.relationship('CustomModule', backref='copies', remote_side=[id])
|
||||
creator = db.relationship('User', backref='created_custom_modules', foreign_keys=[created_by])
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'company_id': self.company_id,
|
||||
'original_template_id': self.original_template_id,
|
||||
'name': self.name,
|
||||
'description': self.description,
|
||||
'kategorie': self.kategorie,
|
||||
'icon': self.icon,
|
||||
'is_template': self.is_template,
|
||||
'sort_index': self.sort_index,
|
||||
'is_active': self.is_active,
|
||||
'created_by': self.created_by,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<CustomModule {self.name} ({"template" if self.is_template else "company"})>'
|
||||
|
||||
|
||||
class CustomModuleAssignment(db.Model):
|
||||
__tablename__ = 'custom_module_assignments'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
module_id = db.Column(db.Integer, db.ForeignKey('custom_modules.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
can_edit = db.Column(db.Boolean, default=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
module = db.relationship('CustomModule', backref='assignments')
|
||||
user = db.relationship('User', backref='custom_module_assignments')
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('module_id', 'user_id', name='uq_module_user'),
|
||||
)
|
||||
@@ -0,0 +1,59 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
import hashlib, secrets
|
||||
|
||||
def _generate_uid(company_name):
|
||||
raw = f"{company_name}-{secrets.token_hex(6)}"
|
||||
return hashlib.sha256(raw.encode()).hexdigest()[:12]
|
||||
|
||||
class License(db.Model):
|
||||
__tablename__ = 'licenses'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=False)
|
||||
uid = db.Column(db.String(64), unique=True, nullable=False)
|
||||
max_mitarbeiter = db.Column(db.Integer, default=5)
|
||||
max_module_slots = db.Column(db.Integer, default=5)
|
||||
unlimited_users = db.Column(db.Boolean, default=False)
|
||||
unlimited_modules = db.Column(db.Boolean, default=False)
|
||||
aktiv = db.Column(db.Boolean, default=True)
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
modules = db.relationship('LicenseModule', backref='license', lazy='dynamic', cascade='all, delete-orphan')
|
||||
|
||||
@property
|
||||
def used_users(self):
|
||||
from app.models.user import User
|
||||
return User.query.filter_by(company_id=self.company_id).count()
|
||||
|
||||
@property
|
||||
def used_module_slots(self):
|
||||
from app.models.user_module import UserModulePermission
|
||||
from app.models.user import User
|
||||
return db.session.query(UserModulePermission.id).join(User, UserModulePermission.user_id==User.id)\
|
||||
.filter(User.company_id==self.company_id, UserModulePermission.aktiv==True).count()
|
||||
|
||||
def user_slots_display(self):
|
||||
if self.unlimited_users: return '\u221e'
|
||||
return f'{self.used_users} / {self.max_mitarbeiter}'
|
||||
|
||||
def module_slots_display(self):
|
||||
if self.unlimited_modules: return '\u221e'
|
||||
return f'{self.used_module_slots} / {self.max_module_slots}'
|
||||
|
||||
@staticmethod
|
||||
def generate_uid(company_name):
|
||||
return _generate_uid(company_name)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<License {self.uid}>'
|
||||
|
||||
class LicenseModule(db.Model):
|
||||
__tablename__ = 'license_modules'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
license_id = db.Column(db.Integer, db.ForeignKey('licenses.id'), nullable=False)
|
||||
module_id = db.Column(db.Integer, db.ForeignKey('modules.id'), nullable=False)
|
||||
aktiv = db.Column(db.Boolean, default=True)
|
||||
|
||||
module = db.relationship('Module', backref='license_assignments')
|
||||
@@ -0,0 +1,44 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class LVPosition(db.Model):
|
||||
__tablename__ = 'lv_positionen'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=False)
|
||||
contract_id = db.Column(db.Integer, db.ForeignKey('contracts.id'), nullable=True)
|
||||
lv_name = db.Column(db.String(200), nullable=False)
|
||||
pos_nr = db.Column(db.String(50), nullable=False)
|
||||
order_index = db.Column(db.Integer, default=0)
|
||||
kurztext = db.Column(db.String(300))
|
||||
langtext = db.Column(db.Text)
|
||||
einheit = db.Column(db.String(10), default='ST')
|
||||
einzelpreis = db.Column(db.Float, default=0.0)
|
||||
gruppe = db.Column(db.String(100))
|
||||
rsa = db.Column(db.String(20))
|
||||
abschnitt = db.Column(db.String(100))
|
||||
notiz = db.Column(db.Text)
|
||||
favorite = db.Column(db.Boolean, default=False)
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('company_id', 'lv_name', 'pos_nr', name='uq_lv_position'),
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'lv_name': self.lv_name,
|
||||
'pos_nr': self.pos_nr,
|
||||
'kurztext': self.kurztext,
|
||||
'langtext': self.langtext,
|
||||
'einheit': self.einheit,
|
||||
'einzelpreis': self.einzelpreis,
|
||||
'gruppe': self.gruppe,
|
||||
'rsa': self.rsa,
|
||||
'abschnitt': self.abschnitt,
|
||||
'favorite': self.favorite,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<LV {self.lv_name} | {self.pos_nr}>'
|
||||
@@ -0,0 +1,16 @@
|
||||
from app.extensions import db
|
||||
|
||||
class Module(db.Model):
|
||||
__tablename__ = 'modules'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(100), unique=True, nullable=False)
|
||||
titel = db.Column(db.String(200), nullable=False)
|
||||
kategorie = db.Column(db.String(100))
|
||||
icon = db.Column(db.String(20), default='📦')
|
||||
beschreibung = db.Column(db.Text)
|
||||
standard = db.Column(db.Boolean, default=False) # immer verfügbar
|
||||
sortierung = db.Column(db.Integer, default=0)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Module {self.name}>'
|
||||
@@ -0,0 +1,78 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class Position(db.Model):
|
||||
__tablename__ = 'positionen'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
project_id = db.Column(db.Integer, db.ForeignKey('projekte.id'), nullable=False)
|
||||
aufmass_id = db.Column(db.Integer, db.ForeignKey('aufmass.id'), nullable=True)
|
||||
lv_position_id = db.Column(db.Integer, db.ForeignKey('lv_positionen.id'), nullable=True)
|
||||
pos_nr = db.Column(db.String(50), nullable=False)
|
||||
sortierung = db.Column(db.Integer, default=0)
|
||||
rsa = db.Column(db.String(20))
|
||||
abschnitt = db.Column(db.String(100))
|
||||
kurztext = db.Column(db.String(300))
|
||||
langtext = db.Column(db.Text)
|
||||
einheit = db.Column(db.String(10), default='ST')
|
||||
einzelpreis = db.Column(db.Float, default=0.0)
|
||||
menge = db.Column(db.Float, default=0.0)
|
||||
gesamtpreis = db.Column(db.Float, default=0.0)
|
||||
faktor = db.Column(db.Float, default=1.0)
|
||||
laenge = db.Column(db.Float, default=0.0)
|
||||
breite = db.Column(db.Float, default=0.0)
|
||||
tiefe = db.Column(db.Float, default=0.0)
|
||||
formel_typ = db.Column(db.String(10), default='standard')
|
||||
formel = db.Column(db.String(300))
|
||||
bemerkung = db.Column(db.Text)
|
||||
menge_hinten = db.Column(db.Float, default=0.0)
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
def berechne_menge(self, recalc_hinten=True, skip_menge_recalc=False):
|
||||
if not skip_menge_recalc:
|
||||
if self.formel_typ == 'frei':
|
||||
if self.formel:
|
||||
from app.services.formel_rechner import berechne_formel
|
||||
try:
|
||||
self.menge = berechne_formel(self.formel)
|
||||
except Exception:
|
||||
self.menge = 0
|
||||
else:
|
||||
self.menge = 0
|
||||
elif self.einheit == 'ST':
|
||||
self.menge = self.faktor * 1
|
||||
elif self.einheit == 'M':
|
||||
self.menge = self.laenge
|
||||
elif self.einheit == 'M2':
|
||||
self.menge = self.laenge * self.breite
|
||||
elif self.einheit == 'M3':
|
||||
self.menge = self.laenge * self.breite * self.tiefe
|
||||
else:
|
||||
self.menge = self.laenge
|
||||
if recalc_hinten:
|
||||
self.menge_hinten = self.faktor * self.menge
|
||||
self.gesamtpreis = self.menge_hinten * self.einzelpreis
|
||||
return self.menge
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'pos_nr': self.pos_nr,
|
||||
'sortierung': self.sortierung,
|
||||
'rsa': self.rsa,
|
||||
'kurztext': self.kurztext,
|
||||
'langtext': self.langtext,
|
||||
'einheit': self.einheit,
|
||||
'einzelpreis': self.einzelpreis,
|
||||
'menge': self.menge,
|
||||
'gesamtpreis': self.gesamtpreis,
|
||||
'faktor': self.faktor,
|
||||
'laenge': self.laenge,
|
||||
'breite': self.breite,
|
||||
'tiefe': self.tiefe,
|
||||
'bemerkung': self.bemerkung,
|
||||
'menge_hinten': self.menge_hinten,
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Position {self.pos_nr} @ {self.project_id}>'
|
||||
@@ -0,0 +1,37 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class Project(db.Model):
|
||||
__tablename__ = 'projekte'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=False)
|
||||
contract_id = db.Column(db.Integer, db.ForeignKey('contracts.id'), nullable=True)
|
||||
sm_nr = db.Column(db.String(100), nullable=False, default='')
|
||||
bezeichnung = db.Column(db.String(300))
|
||||
baustelle = db.Column(db.String(300))
|
||||
vertrag = db.Column(db.String(200))
|
||||
abruf_nr = db.Column(db.String(100))
|
||||
lv_name = db.Column(db.String(200))
|
||||
datum_start = db.Column(db.Date)
|
||||
datum_ende = db.Column(db.Date)
|
||||
ansprechpartner_vorname = db.Column(db.String(100))
|
||||
ansprechpartner_nachname = db.Column(db.String(100))
|
||||
ansprechpartner_tel = db.Column(db.String(50))
|
||||
ansprechpartner_email = db.Column(db.String(200))
|
||||
bauabschnitt = db.Column(db.String(200))
|
||||
datum = db.Column(db.Date)
|
||||
ev_details_id = db.Column(db.String(50))
|
||||
status = db.Column(db.String(20), default='aktiv')
|
||||
erstellt_von = db.Column(db.Integer, db.ForeignKey('users.id'))
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
geaendert_am = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
positionen = db.relationship('Position', backref='project', lazy='dynamic',
|
||||
cascade='all, delete-orphan', order_by='Position.sortierung')
|
||||
aufmass_liste = db.relationship('Aufmass', backref='aufmass_project',
|
||||
cascade='all, delete-orphan',
|
||||
order_by='Aufmass.sortierung')
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Project {self.sm_nr}>'
|
||||
@@ -0,0 +1,17 @@
|
||||
from app.extensions import db
|
||||
|
||||
class ProjectAccess(db.Model):
|
||||
__tablename__ = 'project_access'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
project_id = db.Column(db.Integer, db.ForeignKey('projekte.id'), nullable=False)
|
||||
zugriff = db.Column(db.String(20), default='lesen')
|
||||
|
||||
user = db.relationship('User', backref='project_access_list')
|
||||
project = db.relationship('Project', backref='user_access_list')
|
||||
|
||||
__table_args__ = (db.UniqueConstraint('user_id', 'project_id'),)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ProjectAccess u={self.user_id} p={self.project_id} {self.zugriff}>'
|
||||
@@ -0,0 +1,24 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
|
||||
class Settings(db.Model):
|
||||
__tablename__ = 'settings'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
key = db.Column(db.String(100), unique=True, nullable=False)
|
||||
value = db.Column(db.Text)
|
||||
|
||||
@classmethod
|
||||
def get(cls, key, default=None):
|
||||
s = cls.query.filter_by(key=key).first()
|
||||
return s.value if s else default
|
||||
|
||||
@classmethod
|
||||
def set(cls, key, value):
|
||||
s = cls.query.filter_by(key=key).first()
|
||||
if s:
|
||||
s.value = value
|
||||
else:
|
||||
s = cls(key=key, value=value)
|
||||
db.session.add(s)
|
||||
db.session.commit()
|
||||
@@ -0,0 +1,80 @@
|
||||
from app.extensions import db, login_manager
|
||||
from flask_login import UserMixin
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from datetime import datetime
|
||||
|
||||
class User(UserMixin, db.Model):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey('companies.id'), nullable=True)
|
||||
email = db.Column(db.String(200), unique=True, nullable=False)
|
||||
password_hash = db.Column(db.String(300), nullable=False)
|
||||
vorname = db.Column(db.String(100))
|
||||
nachname = db.Column(db.String(100))
|
||||
rolle = db.Column(db.String(20), default='mitarbeiter')
|
||||
aktiv = db.Column(db.Boolean, default=True)
|
||||
font_size = db.Column(db.String(10), default='1')
|
||||
profile_image = db.Column(db.String(255), nullable=True)
|
||||
letzter_login = db.Column(db.DateTime)
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
darf_projekte_anlegen = db.Column(db.Boolean, default=False)
|
||||
darf_lv_verwalten = db.Column(db.Boolean, default=False)
|
||||
darf_preise_sehen = db.Column(db.Boolean, default=False)
|
||||
darf_aufmass_verwalten = db.Column(db.Boolean, default=False)
|
||||
darf_evergabe_nutzen = db.Column(db.Boolean, default=False)
|
||||
darf_kopfdaten_holen = db.Column(db.Boolean, default=False)
|
||||
darf_aufmass_uebertragen = db.Column(db.Boolean, default=False)
|
||||
hidden_modules = db.Column(db.Text, default='[]')
|
||||
|
||||
def get_hidden_modules(self):
|
||||
import json
|
||||
try:
|
||||
return json.loads(self.hidden_modules or '[]')
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return []
|
||||
|
||||
def set_hidden_modules(self, val):
|
||||
import json
|
||||
self.hidden_modules = json.dumps(val, ensure_ascii=False)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return f"{self.vorname or ''} {self.nachname or ''}".strip() or self.email
|
||||
|
||||
def set_password(self, password):
|
||||
self.password_hash = generate_password_hash(password)
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
def is_superadmin(self):
|
||||
return self.rolle == 'superadmin'
|
||||
|
||||
def is_firmadmin(self):
|
||||
return self.rolle == 'firmadmin'
|
||||
|
||||
def is_admin(self):
|
||||
return self.rolle in ('firmadmin', 'superadmin')
|
||||
|
||||
def hat_zugriff(self, project, required='lesen'):
|
||||
if self.is_superadmin():
|
||||
return True
|
||||
if self.is_firmadmin():
|
||||
from app.models.project import Project
|
||||
return Project.query.get(project.id).company_id == self.company_id
|
||||
from app.models.project_access import ProjectAccess
|
||||
access = ProjectAccess.query.filter_by(
|
||||
user_id=self.id, project_id=project.id
|
||||
).first()
|
||||
if not access:
|
||||
return False
|
||||
if required == 'lesen':
|
||||
return True
|
||||
if required == 'schreiben':
|
||||
return access.zugriff in ('lesen', 'schreiben')
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.email} ({self.rolle})>'
|
||||
@@ -0,0 +1,14 @@
|
||||
from app.extensions import db
|
||||
|
||||
class UserModulePermission(db.Model):
|
||||
__tablename__ = 'user_module_permissions'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
module_id = db.Column(db.Integer, db.ForeignKey('modules.id'), nullable=False)
|
||||
aktiv = db.Column(db.Boolean, default=True)
|
||||
|
||||
user = db.relationship('User', backref='user_module_list')
|
||||
module = db.relationship('Module', backref='user_assignments')
|
||||
|
||||
__table_args__ = (db.UniqueConstraint('user_id', 'module_id'),)
|
||||
@@ -0,0 +1,34 @@
|
||||
from app.extensions import db
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
class ViewProfile(db.Model):
|
||||
__tablename__ = 'view_profiles'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
name = db.Column(db.String(100), nullable=False)
|
||||
view_type = db.Column(db.String(50), default='lv') # lv, aufmass, ...
|
||||
config_json = db.Column(db.Text, default='{}')
|
||||
is_default = db.Column(db.Boolean, default=False)
|
||||
erstellt_am = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
def get_config(self):
|
||||
try:
|
||||
return json.loads(self.config_json or '{}')
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return {}
|
||||
|
||||
def set_config(self, config):
|
||||
self.config_json = json.dumps(config)
|
||||
|
||||
@staticmethod
|
||||
def get_default_config():
|
||||
return {
|
||||
'column_order': ['fav', 'drag', 'pos_nr', 'text', 'einheit', 'ep', 'aktion'],
|
||||
'column_widths': {'fav': 32, 'drag': 28, 'pos_nr': 90, 'text': 400, 'einheit': 60, 'ep': 80, 'aktion': 70},
|
||||
'column_visible': {'fav': True, 'drag': True, 'pos_nr': True, 'text': True, 'einheit': True, 'ep': True, 'aktion': True},
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return f'<ViewProfile {self.name} ({self.view_type})>'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,42 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_absperrung.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
tage = _int(form_data.get('tage', 1))
|
||||
typ = form_data.get('absperr_typ', 'voll')
|
||||
|
||||
if typ == 'voll':
|
||||
positionen.append({
|
||||
'pos_nr': '10041001',
|
||||
'kurztext': 'Vollsperrung einrichten',
|
||||
'menge': tage,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
elif typ == 'teil':
|
||||
positionen.append({
|
||||
'pos_nr': '10041002',
|
||||
'kurztext': 'Teilsperrung einrichten',
|
||||
'menge': tage,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
|
||||
if form_data.get('ampel') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10041003',
|
||||
'kurztext': 'Baustellenampel',
|
||||
'menge': tage,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
|
||||
return positionen
|
||||
|
||||
def _int(val, default=0):
|
||||
try:
|
||||
return int(float(str(val).replace(',', '.')))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,14 @@
|
||||
from flask import render_template
|
||||
|
||||
class AufmassModul:
|
||||
name = ''
|
||||
titel = ''
|
||||
template = ''
|
||||
|
||||
@classmethod
|
||||
def get_formular_html(cls):
|
||||
return render_template(cls.template)
|
||||
|
||||
@classmethod
|
||||
def berechne(cls, form_data):
|
||||
raise NotImplementedError
|
||||
@@ -0,0 +1,50 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_cu.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
abschnitt = form_data.get('abschnitt', '')
|
||||
anz_verb = _int(form_data.get('anz_cu_verb', 0))
|
||||
anz_stk = _int(form_data.get('anz_stk', 0))
|
||||
|
||||
if form_data.get('muffe_bis10') == 'an':
|
||||
pos.append(dict(pos_nr='10037500', kurztext='Muffe bis 10 DA montieren', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('apl_bis10') == 'an':
|
||||
pos.append(dict(pos_nr='10037501', kurztext='APL bis 10 DA montieren', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('anschl_trenn') == 'an':
|
||||
pos.append(dict(pos_nr='10037502', kurztext='Anschluss-/Trennleisten einbauen', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('muffe_schrumpf') == 'an':
|
||||
pos.append(dict(pos_nr='10037503', kurztext='Schrumpfmuffe herstellen', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('muffe_klemm') == 'an':
|
||||
pos.append(dict(pos_nr='10037504', kurztext='Klemmmuffe herstellen', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('muffe_gel') == 'an':
|
||||
pos.append(dict(pos_nr='10037505', kurztext='Gel-Muffen herstellen', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('muffe_klemm_dlr') == 'an':
|
||||
pos.append(dict(pos_nr='10037506', kurztext='Klemmmuffen für DLR', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if anz_verb > 0:
|
||||
pos.append(dict(pos_nr='10037507', kurztext=f'CU verbinden ({anz_verb} Stk)', menge=anz_verb, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('cu_da_gr') == 'an':
|
||||
pos.append(dict(pos_nr='10037508', kurztext='CU-DA > 0,8 mm verbinden', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('cu_da_kl') == 'an':
|
||||
pos.append(dict(pos_nr='10037509', kurztext='CU-DA ≤ 0,8 mm verbinden', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('stopfstelle') == 'an':
|
||||
pos.append(dict(pos_nr='10037510', kurztext='Druckluftstutzen Stopfstelle einbauen', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('zulage_stopf') == 'an':
|
||||
pos.append(dict(pos_nr='10037511', kurztext='Zulage Stopfstelle DLR', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('evs_einbauen') == 'an' and anz_stk > 0:
|
||||
pos.append(dict(pos_nr='10037512', kurztext=f'EVs einbauen ({anz_stk} Stk)', menge=anz_stk, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('kabel_anlegen_ev') == 'an':
|
||||
pos.append(dict(pos_nr='10037513', kurztext='Kabel anlegen EVs/TrLe', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
@@ -0,0 +1,36 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_doku.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
laenge = _float(form_data.get('doku_laenge', 0))
|
||||
breite = _float(form_data.get('doku_breite', 0))
|
||||
hktr = _float(form_data.get('doku_hktr_m', 0))
|
||||
vzktr = _float(form_data.get('doku_vzktr_m', 0))
|
||||
gf_haus = _int(form_data.get('doku_gf_haus_anz', 0))
|
||||
|
||||
if laenge > 0 and breite > 0:
|
||||
pos.append(dict(pos_nr='10038000', kurztext=f'MP-Einarbeitung Gelände/Gebäude ({laenge}×{breite}m)', menge=1, einheit='ST'))
|
||||
elif laenge > 0:
|
||||
pos.append(dict(pos_nr='10038000', kurztext='MP-Einarbeitung Gelände/Gebäude', menge=laenge, einheit='M'))
|
||||
if hktr > 0:
|
||||
pos.append(dict(pos_nr='10038001', kurztext=f'Dokumentation HK-Trasse ({hktr}m)', menge=hktr, einheit='M'))
|
||||
if vzktr > 0:
|
||||
pos.append(dict(pos_nr='10038002', kurztext=f'Dokumentation VzK-Trasse ({vzktr}m)', menge=vzktr, einheit='M'))
|
||||
if gf_haus > 0:
|
||||
pos.append(dict(pos_nr='10038003', kurztext=f'Dokumentation GF-Hausanschluss ({gf_haus} Stk)', menge=gf_haus, einheit='ST'))
|
||||
if form_data.get('doku_geh') == 'an':
|
||||
pos.append(dict(pos_nr='10038004', kurztext='Dokumentation von Gehäusen', menge=1, einheit='ST'))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
@@ -0,0 +1,53 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_ftth.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
anzahl = _int(form_data.get('anzahl_ha', 0))
|
||||
|
||||
if anzahl <= 0:
|
||||
return positionen
|
||||
|
||||
# Hausanschluss GF-Montage
|
||||
positionen.append({
|
||||
'pos_nr': '10039001',
|
||||
'kurztext': 'GF-Hausanschluss herstellen',
|
||||
'menge': anzahl,
|
||||
'einheit': 'ST',
|
||||
'bemerkung': form_data.get('bemerkung', ''),
|
||||
})
|
||||
|
||||
if form_data.get('tiefbau') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10039002',
|
||||
'kurztext': 'Tiefbau für Hausanschluss',
|
||||
'menge': anzahl * _float(form_data.get('trassenlaenge', 5)),
|
||||
'einheit': 'M',
|
||||
'bemerkung': 'Trassenlänge pro HA',
|
||||
})
|
||||
|
||||
if form_data.get('muffe') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10038002',
|
||||
'kurztext': 'Muffe bauen (FTTH)',
|
||||
'menge': anzahl,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
|
||||
return positionen
|
||||
|
||||
def _int(val, default=0):
|
||||
try:
|
||||
return int(float(str(val).replace(',', '.')))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
def _float(val, default=0):
|
||||
try:
|
||||
return float(str(val).replace(',', '.'))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,50 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_gf.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
typ = form_data.get('typ', 'nvt')
|
||||
anzahl = _int(form_data.get('anzahl', 1))
|
||||
|
||||
if typ == 'nvt':
|
||||
positionen.append({
|
||||
'pos_nr': '10038001',
|
||||
'kurztext': 'NVT Verbinden',
|
||||
'menge': anzahl,
|
||||
'einheit': 'ST',
|
||||
'bemerkung': form_data.get('bemerkung', ''),
|
||||
})
|
||||
elif typ == 'muffe':
|
||||
positionen.append({
|
||||
'pos_nr': '10038002',
|
||||
'kurztext': 'Muffe bauen',
|
||||
'menge': anzahl,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
elif typ == 'pegel':
|
||||
positionen.append({
|
||||
'pos_nr': '10038003',
|
||||
'kurztext': 'Pegelmessung durchführen',
|
||||
'menge': anzahl,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
|
||||
if form_data.get('fasern_verbinden') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10038004',
|
||||
'kurztext': 'Fasern verbinden',
|
||||
'menge': anzahl,
|
||||
'einheit': 'ST',
|
||||
})
|
||||
|
||||
return positionen
|
||||
|
||||
def _int(val, default=0):
|
||||
try:
|
||||
return int(float(str(val).replace(',', '.')))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,113 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_graben.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
l = _float(form_data.get('laenge', 0))
|
||||
b = _float(form_data.get('breite', 0))
|
||||
t = _float(form_data.get('tiefe', 0))
|
||||
asphalt = _float(form_data.get('asphaltstaerke', 0))
|
||||
rest_l = _float(form_data.get('rest_laenge', 0))
|
||||
rest_b = _float(form_data.get('rest_breite', 0))
|
||||
anz_einz = _int(form_data.get('anz_einzeiler', 0))
|
||||
lm_bre = _float(form_data.get('lm_bre', 0))
|
||||
abschnitt = form_data.get('abschnitt', '')
|
||||
bemerkung = form_data.get('bemerkung', '')
|
||||
|
||||
if l <= 0 or b <= 0 or t <= 0:
|
||||
return positionen
|
||||
|
||||
vol = l * b * t / 100
|
||||
|
||||
# Mindertiefe
|
||||
mind_lt = form_data.get('mind_langstrasse') == 'an'
|
||||
mind_ftth = form_data.get('mind_ftth_ha') == 'an'
|
||||
if mind_lt:
|
||||
positionen.append({'pos_nr': '10037491', 'kurztext': 'Mindertiefe Längstrasse', 'menge': vol, 'einheit': 'M3', 'laenge': l, 'breite': b, 'tiefe': t, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
if mind_ftth:
|
||||
positionen.append({'pos_nr': '10037492', 'kurztext': 'Mindertiefe FTTH Hausanschluss', 'menge': vol, 'einheit': 'M3', 'laenge': l, 'breite': b, 'tiefe': t, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Oberfläche - mehrere Checkboxen möglich
|
||||
oberfl_checks = [
|
||||
('ob_wiese', '10037463', 'Graben in Wiese herstellen'),
|
||||
('ob_kies', '10037464', 'Graben in Kies herstellen'),
|
||||
('ob_asphalt', '10037465', 'Graben in Asphalt herstellen'),
|
||||
('ob_pflaster', '10037466', 'Graben in Pflaster herstellen'),
|
||||
('ob_mosaik', '10037467', 'Graben in Mosaik herstellen'),
|
||||
('ob_bodentausch', '10037468', 'Graben mit Bodentausch herstellen'),
|
||||
('ob_fels', '10037469', 'Graben in Fels herstellen'),
|
||||
('ob_winterbau', '10037470', 'Graben im Winterbau herstellen'),
|
||||
('ob_gr_natur_pfl', '10037471', 'Graben in Groß/Natursteinpflaster herstellen'),
|
||||
('ob_in_beton', '10037472', 'Graben in Beton Pflaster/Mosaik herstellen'),
|
||||
]
|
||||
for key, pnr, txt in oberfl_checks:
|
||||
if form_data.get(key) == 'an':
|
||||
positionen.append({'pos_nr': pnr, 'kurztext': txt, 'menge': vol, 'einheit': 'M3', 'laenge': l, 'breite': b, 'tiefe': t, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Asphalt durchtrennen
|
||||
if asphalt > 0:
|
||||
positionen.append({'pos_nr': '10037473', 'kurztext': 'Asphaltdecke durchtrennen', 'menge': l, 'einheit': 'M', 'laenge': l, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Reststreifen
|
||||
if rest_l > 0 and rest_b > 0:
|
||||
positionen.append({'pos_nr': '10037480', 'kurztext': 'Reststreifen herstellen', 'menge': rest_l * rest_b / 100, 'einheit': 'M3', 'laenge': rest_l, 'breite': rest_b, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Bord/Rinne/Einzeiler
|
||||
bord = form_data.get('bord') == 'an'
|
||||
kante = form_data.get('kante') == 'an'
|
||||
rinne = form_data.get('rinne') == 'an'
|
||||
liefern = form_data.get('liefern') == 'an'
|
||||
if bord:
|
||||
positionen.append({'pos_nr': '10037481', 'kurztext': 'Bordstein setzen', 'menge': l, 'einheit': 'M', 'laenge': l, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
if kante:
|
||||
positionen.append({'pos_nr': '10037482', 'kurztext': 'Kantenstein setzen', 'menge': l, 'einheit': 'M', 'laenge': l, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
if rinne:
|
||||
positionen.append({'pos_nr': '10037483', 'kurztext': 'Einzeiler/Rinne setzen', 'menge': l, 'einheit': 'M', 'laenge': l, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
if liefern and (bord or kante or rinne):
|
||||
positionen.append({'pos_nr': '10037484', 'kurztext': 'Bord/Rinne/Einzeiler liefern', 'menge': anz_einz if anz_einz > 0 else 1, 'einheit': 'ST', 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
if anz_einz > 0:
|
||||
positionen.append({'pos_nr': '10037485', 'kurztext': f'Einzeiler ({anz_einz} Stk)', 'menge': anz_einz, 'einheit': 'ST', 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Medien
|
||||
medien_map = [
|
||||
('cu_kabel', '10037450', f'6-100 DA CU-Kabel ({_int(form_data.get("anz_cu",0))} DA)', _int(form_data.get('anz_cu',0)), 'DA'),
|
||||
('cu_kabel_gr', '10037451', f'> 100 DA CU-Kabel ({_int(form_data.get("anz_cu",0))} DA)', _int(form_data.get('anz_cu',0)), 'DA'),
|
||||
('dn110', '10037452', f'DN110 ({_int(form_data.get("anz_dn110",0))} Stk)', _int(form_data.get('anz_dn110',0)), 'ST'),
|
||||
('dn50_1', '10037453', f'1xDN50 ({_int(form_data.get("anz_dn50_1",0))} Stk)', _int(form_data.get('anz_dn50_1',0)), 'ST'),
|
||||
('dn50_2', '10037454', f'2xDN50 ({_int(form_data.get("anz_dn50_2",0))} Stk)', _int(form_data.get('anz_dn50_2',0)), 'ST'),
|
||||
('dn50_3', '10037455', f'3xDN50 ({_int(form_data.get("anz_dn50_3",0))} Stk)', _int(form_data.get('anz_dn50_3',0)), 'ST'),
|
||||
('snrve_7x12', '10037456', f'SNRVe 7x12 ({_int(form_data.get("anz_snrve_7x12",0))} Stk)', _int(form_data.get('anz_snrve_7x12',0)), 'ST'),
|
||||
('snrve_22x7', '10037457', f'SNRVe 22x7 ({_int(form_data.get("anz_snrve_22x7",0))} Stk)', _int(form_data.get('anz_snrve_22x7',0)), 'ST'),
|
||||
('snrve_8x7', '10037458', f'SNRVe 8x7 ({_int(form_data.get("anz_snrve_8x7",0))} Stk)', _int(form_data.get('anz_snrve_8x7',0)), 'ST'),
|
||||
('snrve_1x7', '10037459', f'SNRVe 1x7 ({_int(form_data.get("anz_snrve_1x7",0))} Stk)', _int(form_data.get('anz_snrve_1x7',0)), 'ST'),
|
||||
]
|
||||
for key, pnr, txt, menge, eh in medien_map:
|
||||
if form_data.get(key) == 'an' and menge > 0:
|
||||
positionen.append({'pos_nr': pnr, 'kurztext': txt, 'menge': menge, 'einheit': eh, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Trasse einmessen
|
||||
if form_data.get('trasseeinmessen') == 'an':
|
||||
positionen.append({'pos_nr': '10037486', 'kurztext': 'Trasse einmessen', 'menge': l, 'einheit': 'M', 'laenge': l, 'bemerkung': bemerkung, 'abschnitt': abschnitt})
|
||||
|
||||
# Stahlplatte
|
||||
if form_data.get('stahlplatte') == 'an':
|
||||
stahl_bem = form_data.get('stahlplatte_bemerk', '')
|
||||
positionen.append({'pos_nr': '10037487', 'kurztext': 'Stahlplatte', 'menge': 1, 'einheit': 'ST', 'bemerkung': stahl_bem, 'abschnitt': abschnitt})
|
||||
|
||||
return positionen
|
||||
|
||||
def _float(val, default=0):
|
||||
try:
|
||||
return float(str(val).replace(',', '.'))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try:
|
||||
return int(float(str(val).replace(',', '.')))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,39 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_gruben.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
l = _float(form_data.get('laenge', 0))
|
||||
b = _float(form_data.get('breite', 0))
|
||||
t = _float(form_data.get('tiefe', 0))
|
||||
|
||||
if l <= 0 or b <= 0 or t <= 0:
|
||||
return positionen
|
||||
|
||||
oberflaeche = form_data.get('oberflaeche', 'wiese')
|
||||
volumen = l * b * t / 100
|
||||
|
||||
oberfl_map = {
|
||||
'wiese': ('10037463', 'Grube in Wiese herstellen'),
|
||||
'kies': ('10037464', 'Grube in Kies herstellen'),
|
||||
'asphalt': ('10037465', 'Grube in Asphalt herstellen'),
|
||||
'pflaster': ('10037466', 'Grube in Pflaster herstellen'),
|
||||
}
|
||||
if oberflaeche in oberfl_map:
|
||||
pnr, txt = oberfl_map[oberflaeche]
|
||||
positionen.append({
|
||||
'pos_nr': pnr, 'kurztext': txt, 'menge': volumen,
|
||||
'einheit': 'M3', 'laenge': l, 'breite': b, 'tiefe': t,
|
||||
})
|
||||
|
||||
return positionen
|
||||
|
||||
def _float(val, default=0):
|
||||
try:
|
||||
return float(str(val).replace(',', '.'))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,43 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_kabelzug.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
laenge = _float(form_data.get('laenge', 0))
|
||||
typ = form_data.get('kabel_typ', 'dn50')
|
||||
|
||||
if laenge <= 0:
|
||||
return positionen
|
||||
|
||||
kabel_map = {
|
||||
'dn50': ('10040001', 'Kabel DN50 einziehen', laenge),
|
||||
'dn110': ('10040002', 'Kabel DN110 einziehen', laenge),
|
||||
'cu': ('10040003', 'Cu-Kabel einziehen', laenge),
|
||||
}
|
||||
if typ in kabel_map:
|
||||
pnr, txt, menge = kabel_map[typ]
|
||||
positionen.append({
|
||||
'pos_nr': pnr, 'kurztext': txt, 'menge': menge, 'einheit': 'M',
|
||||
'laenge': laenge,
|
||||
})
|
||||
|
||||
if form_data.get('einblasen') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10040004',
|
||||
'kurztext': 'Einblasen von Kabel',
|
||||
'menge': laenge,
|
||||
'einheit': 'M',
|
||||
'laenge': laenge,
|
||||
})
|
||||
|
||||
return positionen
|
||||
|
||||
def _float(val, default=0):
|
||||
try:
|
||||
return float(str(val).replace(',', '.'))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,73 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_neff_achberg.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
l = _float(form_data.get('laenge', 0))
|
||||
typ = form_data.get('typ', 'haus')
|
||||
|
||||
if l <= 0:
|
||||
return positionen
|
||||
|
||||
if typ == 'haus':
|
||||
positionen.append({
|
||||
'pos_nr': '10043001',
|
||||
'kurztext': 'Leitungsgraben HA Neff-Achberg',
|
||||
'menge': l,
|
||||
'einheit': 'M',
|
||||
'laenge': l,
|
||||
'bemerkung': form_data.get('bemerkung', ''),
|
||||
})
|
||||
elif typ == 'tb':
|
||||
positionen.append({
|
||||
'pos_nr': '10043002',
|
||||
'kurztext': 'Leitungsgraben TB Neff-Achberg',
|
||||
'menge': l,
|
||||
'einheit': 'M',
|
||||
'laenge': l,
|
||||
'bemerkung': form_data.get('bemerkung', ''),
|
||||
})
|
||||
|
||||
if form_data.get('rohr_4x20') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10043003',
|
||||
'kurztext': 'Rohr 4x20mm (Neff-Achberg)',
|
||||
'menge': l,
|
||||
'einheit': 'M',
|
||||
'laenge': l,
|
||||
})
|
||||
|
||||
if form_data.get('trassenband') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10043004',
|
||||
'kurztext': 'Trassenband einlegen',
|
||||
'menge': l,
|
||||
'einheit': 'M',
|
||||
'laenge': l,
|
||||
})
|
||||
|
||||
if form_data.get('kopfloch') == 'an':
|
||||
positionen.append({
|
||||
'pos_nr': '10043005',
|
||||
'kurztext': 'Kopfloch herstellen',
|
||||
'menge': _int(form_data.get('kopfloch_anzahl', 1)),
|
||||
'einheit': 'ST',
|
||||
})
|
||||
|
||||
return positionen
|
||||
|
||||
def _float(val, default=0):
|
||||
try:
|
||||
return float(str(val).replace(',', '.'))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try:
|
||||
return int(float(str(val).replace(',', '.')))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,66 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_planung.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
zul_mtb = _float(form_data.get('zulage_struk_mtb', 0))
|
||||
zul_otb = _float(form_data.get('zulage_struk_otb', 0))
|
||||
mpp_zn = _float(form_data.get('mpp_gfk_zn', 0))
|
||||
mpp_kr = _float(form_data.get('mpp_kr', 0))
|
||||
mpp_snrv = _float(form_data.get('mpp_snrv', 0))
|
||||
gf_minik = _float(form_data.get('gf_minik', 0))
|
||||
ivk = _float(form_data.get('ivk_m', 0))
|
||||
zul_kl = _float(form_data.get('zulage_kl_baum', 0))
|
||||
s_liste = form_data.get('s_liste', '').strip()
|
||||
|
||||
if form_data.get('proj_struk_mtb') == 'an':
|
||||
txt = 'Projektierung n. Strukturplanung mit TB bis 100m'
|
||||
if zul_mtb > 0:
|
||||
txt += f', Zulage {zul_mtb}m ab 101m'
|
||||
pos.append(dict(pos_nr='10037800', kurztext=txt, menge=1, einheit='ST'))
|
||||
if form_data.get('proj_struk_otb') == 'an':
|
||||
txt = 'Projektierung n. Strukturplanung ohne TB bis 100m'
|
||||
if zul_otb > 0:
|
||||
txt += f', Zulage {zul_otb}m ab 101m'
|
||||
pos.append(dict(pos_nr='10037801', kurztext=txt, menge=1, einheit='ST'))
|
||||
if form_data.get('mpp_gfk_nvt_ap') == 'an':
|
||||
txt = 'MP-Proj. FTTH-Gf-Kabel v. NVT → GF-AP'
|
||||
if mpp_zn > 0:
|
||||
txt += f' ({mpp_zn}m im ZN-Netz)'
|
||||
pos.append(dict(pos_nr='10037802', kurztext=txt, menge=1, einheit='ST'))
|
||||
if form_data.get('mpp_gfap') == 'an':
|
||||
pos.append(dict(pos_nr='10037803', kurztext='MP-Proj. GF-AP', menge=1, einheit='ST'))
|
||||
if mpp_kr > 0:
|
||||
pos.append(dict(pos_nr='10037804', kurztext=f'MP-Proj. KR-Anlagen ({mpp_kr}m)', menge=mpp_kr, einheit='M'))
|
||||
if mpp_snrv > 0:
|
||||
pos.append(dict(pos_nr='10037805', kurztext=f'MP-Proj. ({mpp_snrv}m)', menge=mpp_snrv, einheit='M'))
|
||||
if form_data.get('ap_sgs') == 'an':
|
||||
pos.append(dict(pos_nr='10037806', kurztext='GF-AP + Patchfeld', menge=1, einheit='ST'))
|
||||
if gf_minik > 0:
|
||||
pos.append(dict(pos_nr='10037807', kurztext=f'GF-Kabel einbl./verlegen ({gf_minik}m)', menge=gf_minik, einheit='M'))
|
||||
if ivk > 0:
|
||||
pos.append(dict(pos_nr='10037808', kurztext=f'IVK ({ivk}m)', menge=ivk, einheit='M'))
|
||||
if form_data.get('proj_kl_baum') == 'an':
|
||||
txt = 'Proj. kl. Baumaßnahme'
|
||||
if zul_kl > 0:
|
||||
txt += f' ({zul_kl}m)'
|
||||
pos.append(dict(pos_nr='10037809', kurztext=txt, menge=1, einheit='ST'))
|
||||
if form_data.get('s_planung_05') == 'an':
|
||||
pos.append(dict(pos_nr='10037810', kurztext='S-Planung 0,5', menge=1, einheit='ST'))
|
||||
if s_liste:
|
||||
lines = [l.strip() for l in s_liste.split('\n') if l.strip()]
|
||||
for i, line in enumerate(lines):
|
||||
pos.append(dict(pos_nr='10037811', kurztext=f'S-Liste: {line}', menge=1, einheit='ST'))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
@@ -0,0 +1,185 @@
|
||||
from flask import render_template
|
||||
from flask_login import current_user
|
||||
from app.extensions import db
|
||||
from app.models.lv import LVPosition
|
||||
|
||||
TEMPLATE = 'components/modul_sas_mecka.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
|
||||
def _lookup_pos(pos_nr):
|
||||
lv_pos = LVPosition.query.filter_by(
|
||||
company_id=current_user.company_id,
|
||||
pos_nr=pos_nr
|
||||
).first()
|
||||
if lv_pos:
|
||||
return {'pos_nr': lv_pos.pos_nr, 'kurztext': lv_pos.kurztext,
|
||||
'einheit': lv_pos.einheit, 'einzelpreis': lv_pos.einzelpreis}
|
||||
return {'pos_nr': pos_nr, 'kurztext': f'Position {pos_nr}',
|
||||
'einheit': 'ST', 'einzelpreis': 0}
|
||||
|
||||
|
||||
def _make_pos(pos_nr, faktor=1.0, laenge=0, breite=0, tiefe=0,
|
||||
menge=None, bemerkung='', abschnitt=''):
|
||||
lv = _lookup_pos(pos_nr)
|
||||
pos = {'pos_nr': pos_nr, 'kurztext': lv['kurztext'],
|
||||
'einheit': lv['einheit'], 'einzelpreis': lv['einzelpreis'],
|
||||
'faktor': faktor, 'laenge': laenge, 'breite': breite, 'tiefe': tiefe,
|
||||
'bemerkung': bemerkung, 'abschnitt': abschnitt}
|
||||
if menge is not None:
|
||||
pos['menge'] = menge
|
||||
return pos
|
||||
|
||||
|
||||
def berechne(form_data):
|
||||
positionen = []
|
||||
abschnitt_ha = form_data.get('scan_name_ha', '')
|
||||
|
||||
# ── HA (Hausanschluss) ──
|
||||
if form_data.get('ha_herstellen') == 'an':
|
||||
for pnr in ['01.06.0001', '01.06.0003', '01.06.0007']:
|
||||
positionen.append(_make_pos(pnr, faktor=1.0, menge=1,
|
||||
abschnitt=abschnitt_ha))
|
||||
|
||||
anz_qkr = _float(form_data.get('anz_qkr', 0))
|
||||
anz_qst = _float(form_data.get('anz_qst', 0))
|
||||
if anz_qkr > 0:
|
||||
bem = "Siehe Bild: "
|
||||
positionen.append(_make_pos('01.03.0019',
|
||||
faktor=anz_qst if anz_qst > 0 else 1,
|
||||
laenge=1.0, menge=1.0, bemerkung=bem,
|
||||
abschnitt=abschnitt_ha))
|
||||
positionen.append(_make_pos('01.03.0020',
|
||||
faktor=anz_qkr, laenge=0.5, menge=0.5,
|
||||
bemerkung=bem, abschnitt=abschnitt_ha))
|
||||
|
||||
trmeter = _float(form_data.get('trassenmeter', 0))
|
||||
strqm_ha = _float(form_data.get('strqm', 0))
|
||||
if trmeter > 0:
|
||||
positionen.append(_make_pos('01.06.0004',
|
||||
faktor=1.0, laenge=trmeter,
|
||||
abschnitt=abschnitt_ha))
|
||||
|
||||
einzug10 = form_data.get('einzug_10er') == 'an'
|
||||
if einzug10:
|
||||
positionen.append(_make_pos('01.04.0003',
|
||||
faktor=1.0, laenge=trmeter, menge=trmeter,
|
||||
abschnitt=abschnitt_ha))
|
||||
positionen.append(_make_pos('01.06.0006',
|
||||
faktor=1.0, laenge=trmeter, menge=trmeter,
|
||||
abschnitt=abschnitt_ha))
|
||||
else:
|
||||
leange = trmeter + 1 + strqm_ha
|
||||
positionen.append(_make_pos('01.06.0006',
|
||||
faktor=1.0, laenge=leange,
|
||||
abschnitt=abschnitt_ha))
|
||||
|
||||
kabelm_ha = _float(form_data.get('kabelmeter_ha', 0))
|
||||
if kabelm_ha > 0:
|
||||
bem = "Siehe Bild: "
|
||||
positionen.append(_make_pos('01.03.0019',
|
||||
faktor=1.0, laenge=kabelm_ha, menge=kabelm_ha,
|
||||
bemerkung=bem, abschnitt=abschnitt_ha))
|
||||
positionen.append(_make_pos('01.03.0020',
|
||||
faktor=1.0, laenge=kabelm_ha, menge=kabelm_ha,
|
||||
bemerkung=bem, abschnitt=abschnitt_ha))
|
||||
|
||||
if strqm_ha > 0:
|
||||
positionen.append(_make_pos('01.03.0008',
|
||||
faktor=1.0, laenge=strqm_ha, menge=strqm_ha,
|
||||
bemerkung="Öffentlicherbereich ", abschnitt=abschnitt_ha))
|
||||
|
||||
anz_sh = _float(form_data.get('anz_suchgrube_ha', 0))
|
||||
if anz_sh > 0:
|
||||
positionen.append(_make_pos('01.03.0018',
|
||||
faktor=1.0, menge=anz_sh,
|
||||
bemerkung="Öffentlicherbereich ", abschnitt=abschnitt_ha))
|
||||
|
||||
# ── TB (Tiefbau) ──
|
||||
tb_laenge = _float(form_data.get('tb_laenge', 0))
|
||||
tb_tiefe = _float(form_data.get('tb_tiefe', 0))
|
||||
abschnitt_tb = form_data.get('scan_name_tb', '')
|
||||
|
||||
if tb_laenge > 0 and tb_tiefe > 0:
|
||||
if tb_tiefe <= 0.65:
|
||||
tief_key = '0.6'
|
||||
elif tb_tiefe <= 0.9:
|
||||
tief_key = '0.8'
|
||||
else:
|
||||
tief_key = '1.2'
|
||||
|
||||
tiefe_map = {
|
||||
'0.6': {'unbe': '01.03.0001', 'be_kg2': '01.03.0004',
|
||||
'be_kg4': '01.03.0005', 'be_kg6': '01.03.0006'},
|
||||
'0.8': {'unbe': '01.03.0002', 'be_kg2': '01.03.0008',
|
||||
'be_kg4': '01.03.0009', 'be_kg6': '01.03.0010'},
|
||||
'1.2': {'unbe': '01.03.0003', 'be_kg2': '01.03.0012',
|
||||
'be_kg4': '01.03.0013', 'be_kg6': '01.03.0014'},
|
||||
}
|
||||
|
||||
tm = tiefe_map[tief_key]
|
||||
if form_data.get('tb_unbefestigt') == 'an':
|
||||
positionen.append(_make_pos(tm['unbe'],
|
||||
faktor=1.0, laenge=tb_laenge,
|
||||
abschnitt=abschnitt_tb))
|
||||
|
||||
if form_data.get('tb_befestigt') == 'an':
|
||||
for kg_key, map_key in [('tb_kg2', 'be_kg2'), ('tb_kg4', 'be_kg4'),
|
||||
('tb_kg6', 'be_kg6')]:
|
||||
if form_data.get(kg_key) == 'an':
|
||||
positionen.append(_make_pos(tm[map_key],
|
||||
faktor=1.0, laenge=tb_laenge,
|
||||
abschnitt=abschnitt_tb))
|
||||
|
||||
anz_4x20 = _float(form_data.get('tb_anz_4x12', 0))
|
||||
if anz_4x20 > 0:
|
||||
positionen.append(_make_pos('01.04.0001',
|
||||
faktor=anz_4x20, laenge=tb_laenge, menge=tb_laenge,
|
||||
bemerkung=f"{int(anz_4x20)}x 4x20 Rohre",
|
||||
abschnitt=abschnitt_tb))
|
||||
|
||||
anz_12x10 = _float(form_data.get('tb_anz_12x10', 0))
|
||||
if anz_12x10 > 0:
|
||||
positionen.append(_make_pos('01.04.0002',
|
||||
faktor=anz_12x10, laenge=tb_laenge, menge=tb_laenge,
|
||||
bemerkung=f"{int(anz_12x10)}x 12x10 Rohre",
|
||||
abschnitt=abschnitt_tb))
|
||||
|
||||
tb_anz_qs = _float(form_data.get('tb_anz_qs', 0))
|
||||
if tb_anz_qs > 0:
|
||||
bem = "Siehe Bild: "
|
||||
tb_anz_qk = _float(form_data.get('tb_anz_qk', 0))
|
||||
positionen.append(_make_pos('01.03.0019',
|
||||
faktor=tb_anz_qs, laenge=1.0, menge=1.0,
|
||||
bemerkung=bem, abschnitt=abschnitt_tb))
|
||||
positionen.append(_make_pos('01.03.0020',
|
||||
faktor=tb_anz_qk if tb_anz_qk > 0 else 1,
|
||||
laenge=0.5, menge=0.5, bemerkung=bem,
|
||||
abschnitt=abschnitt_tb))
|
||||
|
||||
tb_kabelm = _float(form_data.get('tb_kabelmeter', 0))
|
||||
if tb_kabelm > 0:
|
||||
bem = "Siehe Bild: "
|
||||
positionen.append(_make_pos('01.03.0019',
|
||||
faktor=1.0, laenge=tb_kabelm, menge=tb_kabelm,
|
||||
bemerkung=bem, abschnitt=abschnitt_tb))
|
||||
positionen.append(_make_pos('01.03.0020',
|
||||
faktor=1.0, laenge=tb_kabelm, menge=tb_kabelm,
|
||||
bemerkung=bem, abschnitt=abschnitt_tb))
|
||||
|
||||
anz_tb_sg = _float(form_data.get('tb_anz_suchgrube', 0))
|
||||
if anz_tb_sg > 0:
|
||||
positionen.append(_make_pos('01.03.0018',
|
||||
faktor=1.0, menge=anz_tb_sg,
|
||||
bemerkung="Öffentlicherbereich ", abschnitt=abschnitt_tb))
|
||||
|
||||
return positionen
|
||||
|
||||
|
||||
def _float(val, default=0):
|
||||
try:
|
||||
return float(str(val).replace(',', '.'))
|
||||
except (ValueError, TypeError):
|
||||
return default
|
||||
@@ -0,0 +1,23 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_sto_sammler.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
liste = form_data.get('sto_liste', '').strip()
|
||||
if liste:
|
||||
lines = [l.strip() for l in liste.split('\n') if l.strip()]
|
||||
for i, line in enumerate(lines):
|
||||
pos.append(dict(pos_nr='10038100', kurztext=f'Störung: {line}', menge=1, einheit='ST'))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
@@ -0,0 +1,71 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_stoerung.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
cu_gr = _int(form_data.get('cu_da_gr_anz', 0))
|
||||
cu_kl = _int(form_data.get('cu_da_kl_anz', 0))
|
||||
kabel_kl30 = _float(form_data.get('kabel_kl30', 0))
|
||||
kabel_gr30 = _float(form_data.get('kabel_gr30', 0))
|
||||
gf_ausbl = _float(form_data.get('gf_ausbl_m', 0))
|
||||
gf_einbl = _float(form_data.get('gf_einbl_m', 0))
|
||||
gf_unge = _float(form_data.get('gf_unge_kas', 0))
|
||||
gf_indoor = _int(form_data.get('gf_verb_indoor', 0))
|
||||
gf_outdoor = _int(form_data.get('gf_verb_outdoor', 0))
|
||||
vao = _float(form_data.get('vao_preis', 0))
|
||||
|
||||
if form_data.get('cu_fehlerortung') == 'an':
|
||||
pos.append(dict(pos_nr='10037600', kurztext='Fehlerortung von Kabelfehlern an Cu-Kabel', menge=1, einheit='ST'))
|
||||
if form_data.get('cu_zul_instan') == 'an':
|
||||
pos.append(dict(pos_nr='10037601', kurztext='Zulage Instandsetzung v. Kabelfehlern', menge=1, einheit='ST'))
|
||||
if form_data.get('cu_schaden_beweiss') == 'an':
|
||||
pos.append(dict(pos_nr='10037602', kurztext='Schadens-/Beweissicherung an TK-Anlagen', menge=1, einheit='ST'))
|
||||
if form_data.get('cu_instan_beweis') == 'an':
|
||||
pos.append(dict(pos_nr='10037603', kurztext='Instands. nach Schadensbeweis', menge=1, einheit='ST'))
|
||||
if cu_gr > 0:
|
||||
pos.append(dict(pos_nr='10037604', kurztext=f'Cu-DA > 0,8 mm verbinden ({cu_gr} Stk)', menge=cu_gr, einheit='ST'))
|
||||
if cu_kl > 0:
|
||||
pos.append(dict(pos_nr='10037605', kurztext=f'Cu-DA ≤ 0,8 mm verbinden ({cu_kl} Stk)', menge=cu_kl, einheit='ST'))
|
||||
if kabel_kl30 > 0:
|
||||
pos.append(dict(pos_nr='10037606', kurztext='Kabel bis 30mm auslegen', menge=kabel_kl30, einheit='M'))
|
||||
if kabel_gr30 > 0:
|
||||
pos.append(dict(pos_nr='10037607', kurztext='Kabel größer 30mm auslegen', menge=kabel_gr30, einheit='M'))
|
||||
if form_data.get('gf_fehlerortung') == 'an':
|
||||
pos.append(dict(pos_nr='10037608', kurztext='Fehlerortung v. Kabelfehlern an GF-Kabel', menge=1, einheit='ST'))
|
||||
if form_data.get('gf_inst_n_fehl') == 'an':
|
||||
pos.append(dict(pos_nr='10037609', kurztext='Instands. v. GF-Kabel nach Fehlerortung', menge=1, einheit='ST'))
|
||||
if form_data.get('gf_beweis') == 'an':
|
||||
pos.append(dict(pos_nr='10037610', kurztext='Schadens-/Beweissicherung an TK-Anlagen (GF)', menge=1, einheit='ST'))
|
||||
if form_data.get('gf_instand_ohne') == 'an':
|
||||
pos.append(dict(pos_nr='10037611', kurztext='Instands. v. GF-Kabel ohne Fehlerortung', menge=1, einheit='ST'))
|
||||
if gf_ausbl > 0:
|
||||
pos.append(dict(pos_nr='10037612', kurztext='GF-Kabel ausblasen', menge=gf_ausbl, einheit='M'))
|
||||
if gf_einbl > 0:
|
||||
pos.append(dict(pos_nr='10037613', kurztext='GF-Kabel einblasen', menge=gf_einbl, einheit='M'))
|
||||
if gf_unge > 0:
|
||||
pos.append(dict(pos_nr='10037614', kurztext='GF ungeschweißt in Kassetten ablegen', menge=gf_unge, einheit='M'))
|
||||
if gf_indoor > 0:
|
||||
pos.append(dict(pos_nr='10037615', kurztext=f'Glasfasern verbinden Indoor ({gf_indoor} Stk)', menge=gf_indoor, einheit='ST'))
|
||||
if gf_outdoor > 0:
|
||||
pos.append(dict(pos_nr='10037616', kurztext=f'Glasfasern verbinden Outdoor ({gf_outdoor} Stk)', menge=gf_outdoor, einheit='ST'))
|
||||
if form_data.get('gf_muffe_neu') == 'an':
|
||||
pos.append(dict(pos_nr='10037617', kurztext='Neue GF-Muffe öffnen', menge=1, einheit='ST'))
|
||||
if form_data.get('gf_muffe_bestand') == 'an':
|
||||
pos.append(dict(pos_nr='10037618', kurztext='Bestandsmuffe öffnen', menge=1, einheit='ST'))
|
||||
if form_data.get('anfahrt_montage') == 'an':
|
||||
pos.append(dict(pos_nr='10037619', kurztext='Anfahrt Montagestelle', menge=1, einheit='ST'))
|
||||
if vao > 0:
|
||||
pos.append(dict(pos_nr='10037620', kurztext='VAO', menge=1, einheit='ST', einzelpreis=vao))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
@@ -0,0 +1,45 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_tvum.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
inst_snr = _int(form_data.get('inst_snr', 0))
|
||||
gf_innen = _int(form_data.get('gf_innen_bef', 0))
|
||||
tvum_spl = _int(form_data.get('tvum_anz_spl', 0))
|
||||
tvum_mess = _int(form_data.get('tvum_anz_mess', 0))
|
||||
ftth_spl = _int(form_data.get('ftth_anz_spl', 0))
|
||||
nvt_spl = _int(form_data.get('nvt_anz_spl', 0))
|
||||
ausbl = _float(form_data.get('ausbl_m', 0))
|
||||
einbl = _float(form_data.get('einbl_m', 0))
|
||||
|
||||
if form_data.get('tvum_ap_mont') == 'an':
|
||||
pos.append(dict(pos_nr='10037700', kurztext='TVUM-AP montieren Wand', menge=1, einheit='ST'))
|
||||
if inst_snr > 0:
|
||||
pos.append(dict(pos_nr='10037701', kurztext='Kanäle/SNR befestigen', menge=inst_snr, einheit='ST'))
|
||||
if gf_innen > 0:
|
||||
pos.append(dict(pos_nr='10037702', kurztext='GF-Innenkabel befestigen/einziehen', menge=gf_innen, einheit='M'))
|
||||
if tvum_spl > 0:
|
||||
pos.append(dict(pos_nr='10037703', kurztext=f'Spleiße TVUM-AP ({tvum_spl} Stk)', menge=tvum_spl, einheit='ST'))
|
||||
if tvum_mess > 0:
|
||||
pos.append(dict(pos_nr='10037704', kurztext=f'Messungen ({tvum_mess} Stk)', menge=tvum_mess, einheit='ST'))
|
||||
if ftth_spl > 0:
|
||||
pos.append(dict(pos_nr='10037705', kurztext=f'FTTH Spleiße ({ftth_spl} Stk)', menge=ftth_spl, einheit='ST'))
|
||||
if nvt_spl > 0:
|
||||
pos.append(dict(pos_nr='10037706', kurztext=f'NVT Spleiße ({nvt_spl} Stk)', menge=nvt_spl, einheit='ST'))
|
||||
if ausbl > 0:
|
||||
pos.append(dict(pos_nr='10037707', kurztext='Kabel ausblasen', menge=ausbl, einheit='M'))
|
||||
if einbl > 0:
|
||||
pos.append(dict(pos_nr='10037708', kurztext='Kabel einblasen', menge=einbl, einheit='M'))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
@@ -0,0 +1,39 @@
|
||||
from flask import render_template
|
||||
|
||||
TEMPLATE = 'components/modul_zw_rv.html'
|
||||
|
||||
def get_formular_html():
|
||||
return render_template(TEMPLATE)
|
||||
|
||||
def berechne(form_data):
|
||||
pos = []
|
||||
abschnitt = form_data.get('abschnitt', '')
|
||||
veg_m = _float(form_data.get('vegetation_m', 0))
|
||||
pfl_m = _float(form_data.get('pflaster_m', 0))
|
||||
asp_m = _float(form_data.get('asphalt_m', 0))
|
||||
|
||||
if veg_m > 0:
|
||||
pos.append(dict(pos_nr='10037900', kurztext='Vegetation (Hausanschluss)', menge=veg_m, einheit='M', abschnitt=abschnitt))
|
||||
if pfl_m > 0:
|
||||
pos.append(dict(pos_nr='10037901', kurztext='Pflaster (Hausanschluss)', menge=pfl_m, einheit='M', abschnitt=abschnitt))
|
||||
if asp_m > 0:
|
||||
pos.append(dict(pos_nr='10037902', kurztext='Asphalt (Hausanschluss)', menge=asp_m, einheit='M', abschnitt=abschnitt))
|
||||
if form_data.get('kopfloch_geb') == 'an':
|
||||
pos.append(dict(pos_nr='10037903', kurztext='Kopfloch Gebäude', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('kopfloch_tr') == 'an':
|
||||
pos.append(dict(pos_nr='10037904', kurztext='Kopfloch Haupttrasse', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('koordinieren_ha') == 'an':
|
||||
pos.append(dict(pos_nr='10037905', kurztext='Koordinieren Hausanschluss', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('md1_ftth') == 'an':
|
||||
pos.append(dict(pos_nr='10037906', kurztext='MD1-FttH', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
if form_data.get('kernbohrung') == 'an':
|
||||
pos.append(dict(pos_nr='10037907', kurztext='Kernbohrung', menge=1, einheit='ST', abschnitt=abschnitt))
|
||||
return pos
|
||||
|
||||
def _float(val, default=0):
|
||||
try: return float(str(val).replace(',', '.'))
|
||||
except: return default
|
||||
|
||||
def _int(val, default=0):
|
||||
try: return int(float(str(val).replace(',', '.')))
|
||||
except: return default
|
||||
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user