Initial commit – AufmaßCreater v2.35
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Pfad zum Projekt-Root hinzufügen
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.company import Company
|
||||
|
||||
def add_house_number_column():
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
# Prüfen, ob die Spalte bereits existiert
|
||||
inspector = db.inspect(db.engine)
|
||||
columns = [c['name'] for c in inspector.get_columns('companies')]
|
||||
|
||||
if 'house_number' not in columns:
|
||||
print("Spalte 'house_number' wird hinzugefügt...")
|
||||
db.session.execute(db.text("ALTER TABLE companies ADD COLUMN house_number VARCHAR(20)"))
|
||||
db.session.commit()
|
||||
print("Spalte erfolgreich hinzugefügt.")
|
||||
else:
|
||||
print("Spalte 'house_number' existiert bereits.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
add_house_number_column()
|
||||
@@ -0,0 +1,39 @@
|
||||
param(
|
||||
[string]$DumpFile = "$PSScriptRoot\aufmassweb_export.dump",
|
||||
[string]$PgBin = "C:\Program Files\PostgreSQL\16\bin",
|
||||
[string]$DbHost = "localhost",
|
||||
[string]$DbPort = "5432",
|
||||
[string]$DbUser = "aufmass",
|
||||
[string]$DbName = "aufmassweb"
|
||||
)
|
||||
|
||||
$pg_dump = Join-Path $PgBin "pg_dump.exe"
|
||||
if (-not (Test-Path $pg_dump)) {
|
||||
Write-Error "pg_dump nicht gefunden unter: $pg_dump"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "=== AufmaßWeb – Datenexport vom Laptop ===" -ForegroundColor Cyan
|
||||
Write-Host "Ziel: $DumpFile"
|
||||
Write-Host "DB: $DbHost:$DbPort/$DbName"
|
||||
Write-Host "User: $DbUser"
|
||||
Write-Host ""
|
||||
|
||||
$env:PGPASSWORD = Read-Host "Passwort für PostgreSQL-User '$DbUser'" -AsSecureString
|
||||
$BSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($env:PGPASSWORD)
|
||||
$env:PGPASSWORD = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)
|
||||
|
||||
Write-Host "Exportiere Datenbank (custom-format) ..."
|
||||
& $pg_dump -h $DbHost -p $DbPort -U $DbUser -d $DbName -F c -f $DumpFile --verbose
|
||||
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$size = (Get-Item $DumpFile).Length / 1MB
|
||||
Write-Host "Export erfolgreich: $DumpFile ({0:N1} MB)" -f $size -ForegroundColor Green
|
||||
Write-Host "Übertrage die Datei auf den Ziel-PC und führe dort import_from_dump.ps1 aus." -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Error "Export fehlgeschlagen (Exit-Code: $LASTEXITCODE)"
|
||||
Remove-Variable PGPASSWORD -ErrorAction SilentlyContinue
|
||||
exit 1
|
||||
}
|
||||
|
||||
Remove-Variable PGPASSWORD -ErrorAction SilentlyContinue
|
||||
@@ -0,0 +1,75 @@
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$DumpFile,
|
||||
[string]$DockerBin = "C:\Program Files\Docker\Docker\resources\bin",
|
||||
[string]$DbContainer = "aufmass_web-db-1",
|
||||
[string]$WebContainer = "aufmass_web-web-1",
|
||||
[string]$DbUser = "aufmass",
|
||||
[string]$DbName = "aufmassweb"
|
||||
)
|
||||
|
||||
Write-Host "=== AufmaßWeb – Datenimport (Docker PostgreSQL) ===" -ForegroundColor Cyan
|
||||
Write-Host "Dump: $DumpFile"
|
||||
Write-Host "Ziel: Container $DbContainer, DB $DbName"
|
||||
Write-Host ""
|
||||
|
||||
if (-not (Test-Path $DumpFile)) {
|
||||
Write-Error "Dump-Datei nicht gefunden: $DumpFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$dockerd = Join-Path $DockerBin "docker.exe"
|
||||
if (-not (Test-Path $dockerd)) {
|
||||
Write-Error "Docker nicht gefunden unter: $dockerd"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# 1. Prüfen ob Container läuft
|
||||
Write-Host "[1/5] Prüfe Docker-Container ..." -NoNewline
|
||||
$running = & $dockerd ps --filter "name=$DbContainer" --filter "status=running" --format "{{.Names}}" 2>$null
|
||||
if (-not $running) {
|
||||
Write-Host " FEHLER" -ForegroundColor Red
|
||||
Write-Error "Container $DbContainer läuft nicht. Starte zuerst 'docker compose up -d'"
|
||||
exit 1
|
||||
}
|
||||
Write-Host " OK ($running)" -ForegroundColor Green
|
||||
|
||||
# 2. Dump in Container kopieren
|
||||
Write-Host "[2/5] Kopiere Dump in Container ..." -NoNewline
|
||||
$remotePath = "/tmp/$(Split-Path $DumpFile -Leaf)"
|
||||
& $dockerd cp $DumpFile "${DbContainer}:${remotePath}"
|
||||
Write-Host " OK → $remotePath" -ForegroundColor Green
|
||||
|
||||
# 3. Datenbank droppen + neu anlegen (verbinde mit 'postgres' DB, nicht mit der Zieldatenbank)
|
||||
Write-Host "[3/5] Lösche alte Datenbank ..." -NoNewline
|
||||
& $dockerd exec -i $DbContainer psql -U $DbUser -d postgres -c "DROP DATABASE IF EXISTS \"$DbName\";" 2>$null
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
Write-Host "[4/5] Erstelle neue Datenbank ..." -NoNewline
|
||||
& $dockerd exec -i $DbContainer psql -U $DbUser -d postgres -c "CREATE DATABASE \"$DbName\";" 2>$null
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# 4. Restore
|
||||
Write-Host "[5/5] Stelle Daten aus Dump wieder her ..." -ForegroundColor Yellow
|
||||
& $dockerd exec -i $DbContainer pg_restore -U $DbUser -d $DbName --verbose --exit-on-error $remotePath
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "pg_restore fehlgeschlagen (Exit-Code: $LASTEXITCODE)"
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Datenbank-Restore erfolgreich!" -ForegroundColor Green
|
||||
|
||||
# 5. Web-Container neuladen
|
||||
Write-Host "Starte Web-Container neu ..." -NoNewline
|
||||
& $dockerd restart $WebContainer 2>$null
|
||||
Write-Host " OK" -ForegroundColor Green
|
||||
|
||||
# Aufräumen
|
||||
& $dockerd exec $DbContainer rm -f $remotePath 2>$null
|
||||
|
||||
$size = (Get-Item $DumpFile).Length / 1MB
|
||||
Write-Host ""
|
||||
Write-Host "=== Fertig! ===" -ForegroundColor Cyan
|
||||
Write-Host "Dump: $DumpFile ({0:N1} MB)" -f $size
|
||||
Write-Host "App: http://localhost:5000" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "Tipp: Du kannst den Dump archivieren oder löschen." -ForegroundColor Gray
|
||||
@@ -0,0 +1,30 @@
|
||||
"""Add rsa, abschnitt columns to lv_positionen + abschnitt to positionen."""
|
||||
import sqlite3, os
|
||||
|
||||
db_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'data', 'aufmass.db'))
|
||||
if not os.path.exists(db_path):
|
||||
print("No DB found, skipping.")
|
||||
exit(0)
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
c = conn.cursor()
|
||||
|
||||
c.execute("PRAGMA table_info(lv_positionen)")
|
||||
cols = [r[1] for r in c.fetchall()]
|
||||
|
||||
if 'rsa' not in cols:
|
||||
c.execute("ALTER TABLE lv_positionen ADD COLUMN rsa VARCHAR(20)")
|
||||
print("Added rsa to lv_positionen")
|
||||
if 'abschnitt' not in cols:
|
||||
c.execute("ALTER TABLE lv_positionen ADD COLUMN abschnitt VARCHAR(100)")
|
||||
print("Added abschnitt to lv_positionen")
|
||||
|
||||
c.execute("PRAGMA table_info(positionen)")
|
||||
pcols = [r[1] for r in c.fetchall()]
|
||||
if 'abschnitt' not in pcols:
|
||||
c.execute("ALTER TABLE positionen ADD COLUMN abschnitt VARCHAR(100)")
|
||||
print("Added abschnitt to positionen")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("Migration done.")
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Add profile_image column to users table."""
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
db_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'aufmass.db')
|
||||
db_path = os.path.normpath(db_path)
|
||||
|
||||
if not os.path.exists(db_path):
|
||||
print(f"DB not found at {db_path}")
|
||||
exit(0)
|
||||
|
||||
conn = sqlite3.connect(db_path)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if column already exists
|
||||
cursor.execute("PRAGMA table_info(users)")
|
||||
cols = [row[1] for row in cursor.fetchall()]
|
||||
if 'profile_image' not in cols:
|
||||
cursor.execute("ALTER TABLE users ADD COLUMN profile_image VARCHAR(255)")
|
||||
print("Added profile_image column to users table.")
|
||||
else:
|
||||
print("Column profile_image already exists.")
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print("Done.")
|
||||
@@ -0,0 +1,16 @@
|
||||
"""Add formel_typ, formel columns to positionen."""
|
||||
import sqlite3, os
|
||||
|
||||
db_path = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'data', 'aufmass.db'))
|
||||
if not os.path.exists(db_path):
|
||||
print("No DB found, skipping."); exit(0)
|
||||
|
||||
conn = sqlite3.connect(db_path); c = conn.cursor()
|
||||
c.execute("PRAGMA table_info(positionen)")
|
||||
cols = [r[1] for r in c.fetchall()]
|
||||
for col, typ in [('formel_typ','VARCHAR(10)'), ('formel','VARCHAR(300)')]:
|
||||
if col not in cols:
|
||||
c.execute(f"ALTER TABLE positionen ADD COLUMN {col} {typ}")
|
||||
print(f"Added {col} to positionen")
|
||||
conn.commit(); conn.close()
|
||||
print("Migration done.")
|
||||
@@ -0,0 +1,109 @@
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
|
||||
|
||||
from app import create_app
|
||||
from sqlalchemy import text
|
||||
import sqlite3
|
||||
|
||||
SQLITE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'data', 'aufmass.db')
|
||||
|
||||
# Tables in FK-safe order (parents first)
|
||||
TABLE_ORDER = [
|
||||
'companies',
|
||||
'users',
|
||||
'licenses',
|
||||
'contracts',
|
||||
'modules',
|
||||
'aufmass_typen',
|
||||
'projekte',
|
||||
'custom_modules',
|
||||
'company_modules',
|
||||
'license_modules',
|
||||
'aufmass',
|
||||
'lv_positionen',
|
||||
'positionen',
|
||||
'project_access',
|
||||
'custom_module_assignments',
|
||||
'user_module_permissions',
|
||||
'view_profiles',
|
||||
]
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("SQLite -> PostgreSQL Migration v2")
|
||||
print("=" * 60)
|
||||
|
||||
if not os.path.exists(SQLITE_PATH):
|
||||
print(f"SQLite DB nicht gefunden: {SQLITE_PATH}")
|
||||
return
|
||||
|
||||
# Read all SQLite data
|
||||
print("\n1. Lese SQLite-Daten...")
|
||||
conn = sqlite3.connect(SQLITE_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
c = conn.cursor()
|
||||
sqlite_data = {}
|
||||
for table_name in TABLE_ORDER:
|
||||
c.execute(f'SELECT * FROM "{table_name}"')
|
||||
rows = [dict(r) for r in c.fetchall()]
|
||||
sqlite_data[table_name] = rows
|
||||
print(f" {table_name}: {len(rows)} Zeilen")
|
||||
conn.close()
|
||||
|
||||
# Create Flask app -> creates PG tables + seed defaults
|
||||
print("\n2. Starte Flask-App mit PostgreSQL...")
|
||||
app = create_app()
|
||||
|
||||
with app.app_context():
|
||||
from app import db
|
||||
|
||||
# Delete all existing data in reverse FK order
|
||||
print(" Entferne vorhandene Daten...")
|
||||
with db.engine.connect() as c:
|
||||
for table_name in reversed(TABLE_ORDER):
|
||||
c.execute(text(f'DELETE FROM "{table_name}"'))
|
||||
c.commit()
|
||||
|
||||
# Import in FK-safe order
|
||||
print("\n3. Importiere Daten in FK-Reihenfolge...")
|
||||
meta = __import__('sqlalchemy', fromlist=['MetaData']).MetaData()
|
||||
meta.reflect(bind=db.engine)
|
||||
|
||||
total_ok = 0
|
||||
total_fail = 0
|
||||
|
||||
for table_name in TABLE_ORDER:
|
||||
rows = sqlite_data.get(table_name, [])
|
||||
if not rows:
|
||||
print(f" -- {table_name}: keine Daten")
|
||||
continue
|
||||
|
||||
table = __import__('sqlalchemy', fromlist=['Table']).Table(table_name, meta, autoload_with=db.engine)
|
||||
pg_cols = {c.name for c in table.columns}
|
||||
|
||||
ok = 0
|
||||
fail = 0
|
||||
with db.engine.connect() as pg_conn:
|
||||
for row in rows:
|
||||
filtered = {k: v for k, v in row.items() if k in pg_cols}
|
||||
try:
|
||||
pg_conn.execute(table.insert().values(**filtered))
|
||||
ok += 1
|
||||
except Exception as e:
|
||||
fail += 1
|
||||
if fail <= 2:
|
||||
print(f" Fehler {table_name} ID={row.get('id','?')}: {e}")
|
||||
pg_conn.commit()
|
||||
|
||||
total_ok += ok
|
||||
total_fail += fail
|
||||
status = "+" if ok else " "
|
||||
print(f" {status} {table_name}: {ok}/{len(rows)} OK" + (f" ({fail} Fehler)" if fail else ""))
|
||||
|
||||
print(f"\nErgebnis: {total_ok} Zeilen importiert" + (f", {total_fail} Fehler" if total_fail else ""))
|
||||
|
||||
print("\nFertig! Starte Flask-App neu -> _seed_defaults() ergaenzt Seed-Daten via Upsert.")
|
||||
print("PostgreSQL ist bereit fuer AufmassWeb.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,99 @@
|
||||
"""
|
||||
Migration: v2 Neue Struktur (Aufmaß-Entity + Rechtesystem)
|
||||
- users: neue Spalten (darf_*) + rolle admin -> firmadmin
|
||||
- neu: aufmass Tabelle + project_access Tabelle
|
||||
- positionen: neue Spalte aufmass_id
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.user import User
|
||||
from app.models.project import Project
|
||||
from app.models.position import Position
|
||||
from app.models.aufmass import Aufmass
|
||||
from app.models.project_access import ProjectAccess
|
||||
from sqlalchemy import inspect, text
|
||||
|
||||
app = create_app()
|
||||
|
||||
def _spalte_existiert(table, column):
|
||||
insp = inspect(db.engine)
|
||||
cols = [c['name'] for c in insp.get_columns(table)]
|
||||
return column in cols
|
||||
|
||||
def _tabelle_existiert(table):
|
||||
insp = inspect(db.engine)
|
||||
return table in insp.get_table_names()
|
||||
|
||||
with app.app_context():
|
||||
print("=== Migration v2 starten ===")
|
||||
|
||||
# 1. User-Spalten hinzufügen
|
||||
if not _spalte_existiert('users', 'darf_projekte_anlegen'):
|
||||
with db.engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN darf_projekte_anlegen BOOLEAN DEFAULT 0"))
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN darf_lv_verwalten BOOLEAN DEFAULT 0"))
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN darf_preise_sehen BOOLEAN DEFAULT 0"))
|
||||
conn.execute(text("ALTER TABLE users ADD COLUMN darf_aufmass_verwalten BOOLEAN DEFAULT 0"))
|
||||
conn.commit()
|
||||
print(" -> User-Spalten hinzugefügt")
|
||||
else:
|
||||
print(" -> User-Spalten bereits vorhanden")
|
||||
|
||||
# 2. Rolle migrieren: admin -> firmadmin, mitarbeiter bleibt
|
||||
count = User.query.filter_by(rolle='admin').update({'rolle': 'firmadmin'})
|
||||
db.session.commit()
|
||||
print(f" -> {count} User von 'admin' -> 'firmadmin' migriert")
|
||||
|
||||
# 3. firmadmin bekommt automatisch alle Rechte
|
||||
for u in User.query.filter_by(rolle='firmadmin').all():
|
||||
u.darf_projekte_anlegen = True
|
||||
u.darf_lv_verwalten = True
|
||||
u.darf_preise_sehen = True
|
||||
u.darf_aufmass_verwalten = True
|
||||
db.session.commit()
|
||||
print(" -> firmadmin-Rechte gesetzt")
|
||||
|
||||
# 4. Aufmass-Tabelle anlegen (wenn nicht vorhanden, wird von db.create_all() erstellt)
|
||||
db.create_all()
|
||||
print(" -> Tabellen erstellt/aktualisiert")
|
||||
|
||||
# 5. aufmass_id zu positionen hinzufügen
|
||||
if not _spalte_existiert('positionen', 'aufmass_id'):
|
||||
with db.engine.connect() as conn:
|
||||
conn.execute(text("ALTER TABLE positionen ADD COLUMN aufmass_id INTEGER REFERENCES aufmass(id)"))
|
||||
conn.commit()
|
||||
print(" -> aufmass_id zu positionen hinzugefügt")
|
||||
else:
|
||||
print(" -> aufmass_id bereits vorhanden")
|
||||
|
||||
# 6. Für jedes Projekt ohne Aufmaß ein Standard-Aufmaß anlegen
|
||||
projekte = Project.query.all()
|
||||
for p in projekte:
|
||||
bestand = Aufmass.query.filter_by(project_id=p.id).first()
|
||||
if not bestand:
|
||||
a = Aufmass(project_id=p.id, name='Standard', typ='', sortierung=0)
|
||||
db.session.add(a)
|
||||
db.session.flush()
|
||||
# Bestehende Positionen an das Standard-Aufmaß hängen
|
||||
Position.query.filter_by(project_id=p.id, aufmass_id=None).update({'aufmass_id': a.id})
|
||||
print(f" -> Standard-Aufmaß für Projekt {p.id} ({p.sm_nr}) angelegt")
|
||||
db.session.commit()
|
||||
|
||||
# 7. Noch verwaiste Positionen ohne aufmass_id
|
||||
rest = Position.query.filter_by(aufmass_id=None).count()
|
||||
if rest:
|
||||
print(f" -> WARNUNG: {rest} Positionen ohne aufmass_id!")
|
||||
# Für jedes Projekt ohne aufmass_id die erste gefundene Position suchen
|
||||
orphans = Position.query.filter_by(aufmass_id=None).all()
|
||||
for pos in orphans:
|
||||
a = Aufmass.query.filter_by(project_id=pos.project_id).first()
|
||||
if a:
|
||||
pos.aufmass_id = a.id
|
||||
db.session.commit()
|
||||
print(f" -> {len(orphans)} verwaiste Positionen zugeordnet")
|
||||
|
||||
print("=== Migration v2 abgeschlossen ===")
|
||||
@@ -0,0 +1,84 @@
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.company import Company
|
||||
from app.models.user import User
|
||||
from app.models.contract import Contract
|
||||
from app.models.lv import LVPosition
|
||||
from datetime import date
|
||||
|
||||
app = create_app()
|
||||
|
||||
def run():
|
||||
with app.app_context():
|
||||
# Create KPT company
|
||||
company = Company.query.filter_by(slug='kpt-consulting').first()
|
||||
if not company:
|
||||
company = Company(
|
||||
name='KPT-Consulting', slug='kpt-consulting',
|
||||
strasse='Musterstr. 1', plz='88045', ort='Friedrichshafen',
|
||||
telefon='+49 7541 123456', email='info@kpt-consulting.de'
|
||||
)
|
||||
db.session.add(company)
|
||||
db.session.flush()
|
||||
print(f'Firma: {company.name} (ID {company.id})')
|
||||
else:
|
||||
print(f'Firma: {company.name} (ID {company.id})')
|
||||
|
||||
# Superadmin (ohne company_id)
|
||||
sa = User.query.filter_by(email='super@admin.de').first()
|
||||
if not sa:
|
||||
sa = User(
|
||||
company_id=None, email='super@admin.de', rolle='superadmin',
|
||||
vorname='Super', nachname='Admin',
|
||||
darf_projekte_anlegen=True, darf_lv_verwalten=True,
|
||||
darf_preise_sehen=True, darf_aufmass_verwalten=True,
|
||||
darf_evergabe_nutzen=True, darf_kopfdaten_holen=True,
|
||||
darf_aufmass_uebertragen=True,
|
||||
)
|
||||
sa.set_password('admin123')
|
||||
db.session.add(sa)
|
||||
print('Superadmin: super@admin.de / admin123')
|
||||
else:
|
||||
print('Superadmin existiert bereits')
|
||||
|
||||
# Firmadmin
|
||||
fa = User.query.filter_by(email='firmadmin@kpt.de').first()
|
||||
if not fa:
|
||||
fa = User(
|
||||
company_id=company.id, email='firmadmin@kpt.de',
|
||||
vorname='Firmen', nachname='Admin', rolle='firmadmin',
|
||||
darf_projekte_anlegen=True, darf_lv_verwalten=True,
|
||||
darf_preise_sehen=True, darf_aufmass_verwalten=True,
|
||||
darf_evergabe_nutzen=True, darf_kopfdaten_holen=True,
|
||||
darf_aufmass_uebertragen=True,
|
||||
)
|
||||
fa.set_password('firma123')
|
||||
db.session.add(fa)
|
||||
print('Firmadmin: firmadmin@kpt.de / firma123')
|
||||
else:
|
||||
print('Firmadmin existiert bereits')
|
||||
|
||||
# Florian Kramer
|
||||
fk = User.query.filter_by(email='fk@kpt-consulting.de').first()
|
||||
if not fk:
|
||||
fk = User(
|
||||
company_id=company.id, email='fk@kpt-consulting.de',
|
||||
vorname='Florian', nachname='Kramer', rolle='firmadmin',
|
||||
darf_projekte_anlegen=True, darf_lv_verwalten=True,
|
||||
darf_preise_sehen=True, darf_aufmass_verwalten=True,
|
||||
darf_evergabe_nutzen=True, darf_kopfdaten_holen=True,
|
||||
darf_aufmass_uebertragen=True,
|
||||
)
|
||||
fk.set_password('kpt2024')
|
||||
db.session.add(fk)
|
||||
print('Florian Kramer: fk@kpt-consulting.de / kpt2024')
|
||||
else:
|
||||
print('Florian Kramer existiert bereits')
|
||||
|
||||
db.session.commit()
|
||||
print('\nSeed abgeschlossen.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,73 @@
|
||||
import sys, os, re
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.company import Company
|
||||
from app.models.user import User
|
||||
|
||||
app = create_app()
|
||||
|
||||
def slugify(name):
|
||||
s = name.lower().strip()
|
||||
s = re.sub(r'[^a-z0-9\s-]', '', s)
|
||||
s = re.sub(r'[\s-]+', '-', s)
|
||||
return s
|
||||
|
||||
def run():
|
||||
with app.app_context():
|
||||
slug = slugify('Dibran Dautaj Tief und Kabelbau')
|
||||
company = Company.query.filter_by(slug=slug).first()
|
||||
if not company:
|
||||
company = Company(
|
||||
name='Dibran Dautaj Tief und Kabelbau',
|
||||
slug=slug,
|
||||
strasse='Alemannenring',
|
||||
house_number='25',
|
||||
plz='88326',
|
||||
ort='Aulendorf',
|
||||
aktiv=True,
|
||||
evergabe_aktiviert=False,
|
||||
)
|
||||
db.session.add(company)
|
||||
db.session.flush()
|
||||
print(f'Firma angelegt: {company.name} (ID {company.id})')
|
||||
else:
|
||||
print(f'Firma existiert bereits: {company.name} (ID {company.id})')
|
||||
|
||||
fa = User.query.filter_by(email='fk@dd-kabelbau.de').first()
|
||||
if not fa:
|
||||
fa = User(
|
||||
company_id=company.id, email='fk@dd-kabelbau.de',
|
||||
vorname='Florian', nachname='Kramer', rolle='firmadmin',
|
||||
darf_projekte_anlegen=True, darf_lv_verwalten=True,
|
||||
darf_preise_sehen=True, darf_aufmass_verwalten=True,
|
||||
darf_evergabe_nutzen=True, darf_kopfdaten_holen=True,
|
||||
darf_aufmass_uebertragen=True,
|
||||
)
|
||||
fa.set_password('Tami1234!')
|
||||
db.session.add(fa)
|
||||
print('Firmadmin: fk@dd-kabelbau.de / Tami1234!')
|
||||
else:
|
||||
print('Firmadmin fk@dd-kabelbau.de existiert bereits')
|
||||
|
||||
usr = User.query.filter_by(email='rs@dd-kabelbau.de').first()
|
||||
if not usr:
|
||||
usr = User(
|
||||
company_id=company.id, email='rs@dd-kabelbau.de',
|
||||
vorname='Robert', nachname='Schöndienst', rolle='mitarbeiter',
|
||||
darf_projekte_anlegen=False, darf_lv_verwalten=False,
|
||||
darf_preise_sehen=True, darf_aufmass_verwalten=True,
|
||||
darf_evergabe_nutzen=False, darf_kopfdaten_holen=False,
|
||||
darf_aufmass_uebertragen=False,
|
||||
)
|
||||
usr.set_password('Robert21071954')
|
||||
db.session.add(usr)
|
||||
print('User: rs@dd-kabelbau.de / Robert21071954')
|
||||
else:
|
||||
print('User rs@dd-kabelbau.de existiert bereits')
|
||||
|
||||
db.session.commit()
|
||||
print('\nSeed abgeschlossen.')
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,131 @@
|
||||
"""
|
||||
Seed-Script: Legt Firma KPT-Consulting + User Florian an
|
||||
und importiert das LV-KPT-Proj+Doku-2024ff inkl. Langtexte.
|
||||
"""
|
||||
import sys, os
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.company import Company
|
||||
from app.models.user import User
|
||||
from app.models.lv import LVPosition
|
||||
from app.models.contract import Contract
|
||||
from datetime import date
|
||||
|
||||
app = create_app()
|
||||
|
||||
LV_NAME = 'LV-KPT-Proj+Doku-2024ff'
|
||||
FLATFILE = os.path.join('..', 'daten', 'LV', f'{LV_NAME}.txt')
|
||||
LANGFOLDER = os.path.join('..', 'daten', 'LV', LV_NAME)
|
||||
|
||||
def run():
|
||||
with app.app_context():
|
||||
# 1. Firma anlegen
|
||||
company = Company.query.filter_by(slug='kpt-consulting').first()
|
||||
if not company:
|
||||
company = Company(
|
||||
name='KPT-Consulting', slug='kpt-consulting',
|
||||
strasse='Musterstr. 1', plz='88045', ort='Friedrichshafen',
|
||||
telefon='+49 7541 123456', email='info@kpt-consulting.de'
|
||||
)
|
||||
db.session.add(company)
|
||||
db.session.flush()
|
||||
print(f'Firma angelegt: {company.name} (ID {company.id})')
|
||||
else:
|
||||
print(f'Firma existiert bereits: {company.name} (ID {company.id})')
|
||||
|
||||
# 2. User Florian Kramer anlegen
|
||||
user = User.query.filter_by(email='fk@kpt-consulting.de').first()
|
||||
if not user:
|
||||
user = User(
|
||||
company_id=company.id, email='fk@kpt-consulting.de',
|
||||
vorname='Florian', nachname='Kramer', rolle='firmadmin',
|
||||
darf_projekte_anlegen=True, darf_lv_verwalten=True,
|
||||
darf_preise_sehen=True, darf_aufmass_verwalten=True,
|
||||
)
|
||||
user.set_password('kpt2024')
|
||||
db.session.add(user)
|
||||
db.session.flush()
|
||||
print(f'User angelegt: {user.full_name} ({user.email})')
|
||||
else:
|
||||
print(f'User existiert bereits: {user.full_name}')
|
||||
|
||||
# 3. Vertrag anlegen
|
||||
contract = Contract.query.filter_by(company_id=company.id, belegnummer='4650014601').first()
|
||||
if not contract:
|
||||
contract = Contract(
|
||||
company_id=company.id,
|
||||
name='SW Proj+Doku 2024ff',
|
||||
belegnummer='4650014601',
|
||||
beleg_datum=date(2025, 12, 16),
|
||||
laufzeit_start=date(2024, 6, 1),
|
||||
laufzeit_ende=date(2026, 5, 31),
|
||||
status='Angenommen',
|
||||
)
|
||||
db.session.add(contract)
|
||||
db.session.flush()
|
||||
print(f'Vertrag angelegt: {contract.name}')
|
||||
else:
|
||||
print(f'Vertrag existiert bereits: {contract.name}')
|
||||
|
||||
# 4. LV-Positionen importieren
|
||||
if not os.path.exists(FLATFILE):
|
||||
print(f'FEHLER: Flatfile nicht gefunden: {FLATFILE}')
|
||||
return
|
||||
|
||||
existing = LVPosition.query.filter_by(company_id=company.id, lv_name=LV_NAME).count()
|
||||
if existing > 0:
|
||||
print(f'LV {LV_NAME} bereits importiert ({existing} Positionen). Überspringe.')
|
||||
return
|
||||
|
||||
# Encoding automatisch erkennen (UTF-16-BE/LE oder UTF-8)
|
||||
with open(FLATFILE, 'rb') as f:
|
||||
raw = f.read()
|
||||
if raw[:2] in (b'\xff\xfe', b'\xfe\xff'):
|
||||
enc = 'utf-16'
|
||||
else:
|
||||
enc = 'utf-8'
|
||||
text = raw.decode(enc, errors='replace').strip('\ufeff').replace('\r\n', '\n').replace('\r', '\n')
|
||||
lines = text.split('\n')
|
||||
|
||||
count = 0
|
||||
for idx, line in enumerate(lines):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
parts = [p.strip() for p in line.split('|')]
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
pos_nr = parts[0]
|
||||
kurztext = parts[1] if len(parts) > 1 else ''
|
||||
einheit = parts[2] if len(parts) > 2 else 'ST'
|
||||
preis_str = parts[3] if len(parts) > 3 else '0'
|
||||
einzelpreis = float(preis_str.replace(',', '.')) if preis_str else 0.0
|
||||
|
||||
# Langtext aus Einzeldatei lesen
|
||||
langtext = ''
|
||||
txt_path = os.path.join(LANGFOLDER, f'{pos_nr}.txt')
|
||||
if os.path.exists(txt_path):
|
||||
with open(txt_path, 'rb') as tf:
|
||||
raw_txt = tf.read()
|
||||
if raw_txt[:2] in (b'\xff\xfe', b'\xfe\xff'):
|
||||
enc_txt = 'utf-16'
|
||||
else:
|
||||
enc_txt = 'utf-8'
|
||||
langtext = raw_txt.decode(enc_txt, errors='replace').strip()
|
||||
|
||||
pos = LVPosition(
|
||||
company_id=company.id, contract_id=contract.id, lv_name=LV_NAME,
|
||||
pos_nr=pos_nr, order_index=idx + 1,
|
||||
kurztext=kurztext, langtext=langtext,
|
||||
einheit=einheit, einzelpreis=einzelpreis,
|
||||
)
|
||||
db.session.add(pos)
|
||||
count += 1
|
||||
|
||||
db.session.commit()
|
||||
print(f'{count} LV-Positionen importiert (mit Langtext aus {LANGFOLDER})')
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,290 @@
|
||||
"""
|
||||
Seed-Script: Erzeugt CustomModule-Vorlage "SAS Meckenbeuren"
|
||||
mit vollständigem Formular- und Regelwerk.
|
||||
"""
|
||||
import sys, os, json
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.custom_module import CustomModule
|
||||
|
||||
app = create_app()
|
||||
|
||||
FORM_JSON = [
|
||||
# ── HA (Hausanschluss) ──
|
||||
{"type":"group_start","title":"Hausanschluss","collapsible":False},
|
||||
{"type":"checkbox","name":"ha_herstellen","label":"HA Herstellen"},
|
||||
{"type":"text","name":"scan_name_ha","label":"Scan Name","placeholder":"Scan Name"},
|
||||
{"type":"number","name":"trassenmeter","label":"Trassenmeter","inputmode":"decimal","step":"0.1"},
|
||||
{"type":"number","name":"kabelmeter_ha","label":"Kabelsichern (m)","inputmode":"decimal","step":"0.1"},
|
||||
{"type":"number","name":"anz_qkr","label":"Anzahl Q-Kabel/Rohre","inputmode":"numeric","step":"1"},
|
||||
{"type":"number","name":"anz_qst","label":"Anzahl Querungsstellen","inputmode":"numeric","step":"1"},
|
||||
{"type":"number","name":"strqm","label":"Straßenquerung (m)","inputmode":"decimal","step":"0.1"},
|
||||
{"type":"number","name":"anz_suchgrube_ha","label":"Anzahl Suchgrube","inputmode":"numeric","step":"1"},
|
||||
{"type":"checkbox","name":"einzug_10er","label":"Einzug 10er Pipes"},
|
||||
{"type":"group_end"},
|
||||
|
||||
# ── TB (Tiefbau) ──
|
||||
{"type":"group_start","title":"Tiefbau","collapsible":False},
|
||||
{"type":"text","name":"scan_name_tb","label":"Scan Name","placeholder":"Scan Name"},
|
||||
{"type":"number","name":"tb_laenge","label":"Länge (m)","inputmode":"decimal","step":"0.1"},
|
||||
{"type":"number","name":"tb_tiefe","label":"Tiefe (m)","inputmode":"decimal","step":"0.1"},
|
||||
{"type":"checkbox","name":"tb_unbefestigt","label":"unbefestigt (Wiese, Kies)"},
|
||||
{"type":"checkbox","name":"tb_befestigt","label":"befestigt (Pflaster, Asphalt)"},
|
||||
{"type":"number","name":"tb_anz_12x10","label":"Anzahl Rohre 12×10","inputmode":"numeric","step":"1"},
|
||||
{"type":"number","name":"tb_anz_4x20","label":"Anzahl Rohre 4×20","inputmode":"numeric","step":"1"},
|
||||
{"type":"number","name":"tb_anz_qk","label":"Anzahl Q-Kabel/Rohre","inputmode":"numeric","step":"1"},
|
||||
{"type":"number","name":"tb_anz_qs","label":"Anzahl Querungsst.","inputmode":"numeric","step":"1"},
|
||||
{"type":"number","name":"tb_kabelmeter","label":"Kabelsichern (m)","inputmode":"decimal","step":"0.1"},
|
||||
{"type":"number","name":"tb_anz_suchgrube","label":"Anzahl Suchgrube","inputmode":"numeric","step":"1"},
|
||||
{"type":"checkbox","name":"tb_kg2","label":"Kabelgraben 2"},
|
||||
{"type":"checkbox","name":"tb_kg4","label":"Kabelgraben 4"},
|
||||
{"type":"checkbox","name":"tb_kg6","label":"Kabelgraben 6"},
|
||||
{"type":"group_end"},
|
||||
]
|
||||
|
||||
RULES_JSON = [
|
||||
# ════════════════════════════════════════
|
||||
# HA (Hausanschluss)
|
||||
# ════════════════════════════════════════
|
||||
|
||||
# HA Basis
|
||||
{"name":"HA – Basispositionen","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.06.0001","columns":{"menge":{"type":"fixed","value":"1"}}},
|
||||
{"pos_nr":"01.06.0003","columns":{"menge":{"type":"fixed","value":"1"}}},
|
||||
{"pos_nr":"01.06.0007","columns":{"menge":{"type":"fixed","value":"1"}}}]},
|
||||
|
||||
# HA Q-Kabel/Querung
|
||||
{"name":"HA – Q-Kabel / Querung","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"anz_qkr","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0019","columns":{"faktor":{"type":"field","value":"anz_qst"},"laenge":{"type":"fixed","value":"1"},"menge":{"type":"fixed","value":"1"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}},
|
||||
{"pos_nr":"01.03.0020","columns":{"faktor":{"type":"field","value":"anz_qkr"},"laenge":{"type":"fixed","value":"0.5"},"menge":{"type":"fixed","value":"0.5"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}}]},
|
||||
|
||||
# HA Trassenmeter
|
||||
{"name":"HA – Trassenmeter","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"trassenmeter","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.06.0004","columns":{"laenge":{"type":"field","value":"trassenmeter"}}}]},
|
||||
|
||||
# HA Einzug 10er
|
||||
{"name":"HA – Einzug 10er Pipes","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"trassenmeter","operator":"gt","value":"0"},
|
||||
{"field":"einzug_10er","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.04.0003","columns":{"laenge":{"type":"field","value":"trassenmeter"},"menge":{"type":"field","value":"trassenmeter"}}},
|
||||
{"pos_nr":"01.06.0006","columns":{"laenge":{"type":"field","value":"trassenmeter"},"menge":{"type":"field","value":"trassenmeter"}}}]},
|
||||
|
||||
# HA ohne Einzug
|
||||
{"name":"HA – ohne Einzug","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"trassenmeter","operator":"gt","value":"0"},
|
||||
{"field":"einzug_10er","operator":"is_empty"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.06.0006","columns":{"laenge":{"type":"formula","value":"[trassenmeter]+1+[strqm]"}}}]},
|
||||
|
||||
# HA Kabelsichern
|
||||
{"name":"HA – Kabelsichern","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"kabelmeter_ha","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0019","columns":{"laenge":{"type":"field","value":"kabelmeter_ha"},"menge":{"type":"field","value":"kabelmeter_ha"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}},
|
||||
{"pos_nr":"01.03.0020","columns":{"laenge":{"type":"field","value":"kabelmeter_ha"},"menge":{"type":"field","value":"kabelmeter_ha"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}}]},
|
||||
|
||||
# HA Straßenquerung
|
||||
{"name":"HA – Straßenquerung","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"strqm","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0008","columns":{"laenge":{"type":"field","value":"strqm"},"menge":{"type":"field","value":"strqm"},"bemerkung":{"type":"fixed","value":"Öffentlicherbereich "}}}]},
|
||||
|
||||
# HA Suchgrube
|
||||
{"name":"HA – Suchgrube","conditions":{"operator":"and","items":[
|
||||
{"field":"ha_herstellen","operator":"is_checked"},
|
||||
{"field":"anz_suchgrube_ha","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0018","columns":{"menge":{"type":"field","value":"anz_suchgrube_ha"},"bemerkung":{"type":"fixed","value":"Öffentlicherbereich "}}}]},
|
||||
|
||||
# ════════════════════════════════════════
|
||||
# TB (Tiefbau)
|
||||
# ════════════════════════════════════════
|
||||
|
||||
# -- Tiefe ≤ 0,65 --
|
||||
{"name":"TB 0,6m – unbefestigt","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.65"},
|
||||
{"field":"tb_unbefestigt","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0001","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 0,6m – KG 2","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.65"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg2","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0004","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 0,6m – KG 4","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.65"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg4","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0005","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 0,6m – KG 6","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.65"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg6","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0006","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
# -- Tiefe ≤ 0,9 --
|
||||
{"name":"TB 0,8m – unbefestigt","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.65"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.9"},
|
||||
{"field":"tb_unbefestigt","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0002","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 0,8m – KG 2","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.65"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.9"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg2","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0008","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 0,8m – KG 4","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.65"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.9"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg4","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0009","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 0,8m – KG 6","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.65"},
|
||||
{"field":"tb_tiefe","operator":"lte","value":"0.9"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg6","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0010","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
# -- Tiefe > 0,9 --
|
||||
{"name":"TB 1,2m – unbefestigt","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.9"},
|
||||
{"field":"tb_unbefestigt","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0003","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 1,2m – KG 2","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.9"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg2","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0012","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 1,2m – KG 4","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.9"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg4","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0013","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
{"name":"TB 1,2m – KG 6","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0.9"},
|
||||
{"field":"tb_befestigt","operator":"is_checked"},
|
||||
{"field":"tb_kg6","operator":"is_checked"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0014","columns":{"laenge":{"type":"field","value":"tb_laenge"}}}]},
|
||||
|
||||
# TB Rohre
|
||||
{"name":"TB – Rohre 4×20","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0"},
|
||||
{"field":"tb_anz_4x20","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.04.0001","columns":{"faktor":{"type":"field","value":"tb_anz_4x20"},"laenge":{"type":"field","value":"tb_laenge"},"menge":{"type":"field","value":"tb_laenge"},"bemerkung":{"type":"fixed","value":"4x20 Rohre"}}}]},
|
||||
|
||||
{"name":"TB – Rohre 12×10","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_laenge","operator":"gt","value":"0"},
|
||||
{"field":"tb_tiefe","operator":"gt","value":"0"},
|
||||
{"field":"tb_anz_12x10","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.04.0002","columns":{"faktor":{"type":"field","value":"tb_anz_12x10"},"laenge":{"type":"field","value":"tb_laenge"},"menge":{"type":"field","value":"tb_laenge"},"bemerkung":{"type":"fixed","value":"12x10 Rohre"}}}]},
|
||||
|
||||
# TB Querung
|
||||
{"name":"TB – Querung","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_anz_qs","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0019","columns":{"faktor":{"type":"field","value":"tb_anz_qs"},"laenge":{"type":"fixed","value":"1"},"menge":{"type":"fixed","value":"1"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}},
|
||||
{"pos_nr":"01.03.0020","columns":{"faktor":{"type":"field","value":"tb_anz_qk"},"laenge":{"type":"fixed","value":"0.5"},"menge":{"type":"fixed","value":"0.5"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}}]},
|
||||
|
||||
# TB Kabelsichern
|
||||
{"name":"TB – Kabelsichern","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_kabelmeter","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0019","columns":{"laenge":{"type":"field","value":"tb_kabelmeter"},"menge":{"type":"field","value":"tb_kabelmeter"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}},
|
||||
{"pos_nr":"01.03.0020","columns":{"laenge":{"type":"field","value":"tb_kabelmeter"},"menge":{"type":"field","value":"tb_kabelmeter"},"bemerkung":{"type":"fixed","value":"Siehe Bild: "}}}]},
|
||||
|
||||
# TB Suchgrube
|
||||
{"name":"TB – Suchgrube","conditions":{"operator":"and","items":[
|
||||
{"field":"tb_anz_suchgrube","operator":"gt","value":"0"}]},
|
||||
"actions":[
|
||||
{"pos_nr":"01.03.0018","columns":{"menge":{"type":"field","value":"tb_anz_suchgrube"},"bemerkung":{"type":"fixed","value":"Öffentlicherbereich "}}}]},
|
||||
]
|
||||
|
||||
def run():
|
||||
with app.app_context():
|
||||
name = 'SAS Meckenbeuren'
|
||||
existing = CustomModule.query.filter_by(name=name, is_template=True).first()
|
||||
if existing:
|
||||
existing.form_json = json.dumps(FORM_JSON, ensure_ascii=False)
|
||||
existing.rules_json = json.dumps(RULES_JSON, ensure_ascii=False)
|
||||
existing.description = 'SAS Meckenbeuren – Hausanschluss + Tiefbau (portiert vom Legacy-Modul)'
|
||||
db.session.commit()
|
||||
print(f'OK - Vorlage "{name}" aktualisiert (ID {existing.id})')
|
||||
print(f' Formular-Felder: {len(FORM_JSON)}')
|
||||
print(f' Regeln: {len(RULES_JSON)}')
|
||||
return
|
||||
|
||||
mod = CustomModule(
|
||||
name=name,
|
||||
description='SAS Meckenbeuren – Hausanschluss + Tiefbau (portiert vom Legacy-Modul)',
|
||||
kategorie='Spezial',
|
||||
icon='📍',
|
||||
form_json=json.dumps(FORM_JSON, ensure_ascii=False),
|
||||
rules_json=json.dumps(RULES_JSON, ensure_ascii=False),
|
||||
is_template=True,
|
||||
sort_index=0,
|
||||
is_active=True,
|
||||
)
|
||||
db.session.add(mod)
|
||||
db.session.commit()
|
||||
print(f'OK - Vorlage "{name}" angelegt (ID {mod.id})')
|
||||
print(f' Formular-Felder: {len(FORM_JSON)}')
|
||||
print(f' Regeln: {len(RULES_JSON)}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -0,0 +1,17 @@
|
||||
# Startet PostgreSQL-Dienst falls nicht bereits laufend
|
||||
$svc = Get-Service postgresql-x64-16 -ErrorAction SilentlyContinue
|
||||
if (-not $svc) {
|
||||
Write-Host "PostgreSQL Dienst nicht gefunden. Installiert?" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
if ($svc.Status -ne 'Running') {
|
||||
Write-Host "Starte PostgreSQL..." -ForegroundColor Yellow
|
||||
Start-Process -FilePath "powershell" -ArgumentList "-Command Start-Service postgresql-x64-16" -Verb RunAs -Wait
|
||||
Start-Sleep 2
|
||||
$svc.Refresh()
|
||||
}
|
||||
if ($svc.Status -eq 'Running') {
|
||||
Write-Host "PostgreSQL laeuft." -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "PostgreSQL Start fehlgeschlagen." -ForegroundColor Red
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
"""Test the formel update API flow"""
|
||||
import urllib.request, urllib.parse, json, sys
|
||||
|
||||
BASE = 'http://127.0.0.1:5000'
|
||||
|
||||
# Login
|
||||
s = urllib.request.build_opener(urllib.request.HTTPCookieProcessor())
|
||||
req = urllib.request.Request(BASE + '/auth/login',
|
||||
data=b'email=fk@kpt-consulting.de&password=kpt2024')
|
||||
s.open(req)
|
||||
|
||||
# Add a test position
|
||||
data = urllib.parse.urlencode({
|
||||
'pos_nr': 'FORMEL-TEST', 'kurztext': 'Test Formel',
|
||||
'einheit': 'M', 'einzelpreis': '50',
|
||||
'faktor': '1.0', 'laenge': '5.0'
|
||||
}).encode()
|
||||
req = urllib.request.Request(BASE + '/projekt/1/1/position/hinzufuegen',
|
||||
data=data, method='POST')
|
||||
r = s.open(req)
|
||||
print('Add position:', r.status)
|
||||
|
||||
# Get positions
|
||||
req = urllib.request.Request(BASE + '/projekt/1/1/positionen')
|
||||
r = s.open(req)
|
||||
positions = json.loads(r.read())
|
||||
pos_id = positions[-1]['id']
|
||||
print(f'Position ID: {pos_id}')
|
||||
|
||||
# Set formel_typ to frei
|
||||
body = json.dumps({'field': 'formel_typ', 'value': 'frei'}).encode()
|
||||
req = urllib.request.Request(
|
||||
BASE + f'/projekt/1/1/position/{pos_id}/update-cell',
|
||||
data=body, method='POST',
|
||||
headers={'Content-Type': 'application/json'})
|
||||
r = s.open(req)
|
||||
resp = json.loads(r.read())
|
||||
print(f'After formel_typ=frei: menge={resp.get("menge")} gp={resp.get("gesamtpreis")} hinten={resp.get("menge_hinten")}')
|
||||
|
||||
# Set formel value
|
||||
body = json.dumps({'field': 'formel', 'value': '2*3+1'}).encode()
|
||||
req = urllib.request.Request(
|
||||
BASE + f'/projekt/1/1/position/{pos_id}/update-cell',
|
||||
data=body, method='POST',
|
||||
headers={'Content-Type': 'application/json'})
|
||||
r = s.open(req)
|
||||
resp = json.loads(r.read())
|
||||
print(f'After formel=2*3+1: menge={resp.get("menge")} gp={resp.get("gesamtpreis")} hinten={resp.get("menge_hinten")}')
|
||||
|
||||
# Cleanup: delete test position
|
||||
req = urllib.request.Request(
|
||||
BASE + f'/projekt/1/1/position/{pos_id}/loeschen', method='POST')
|
||||
s.open(req)
|
||||
print(f'Test position {pos_id} deleted')
|
||||
print('\nSUCCESS: menge_hinten was correctly returned in response')
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Quick integration test for v2 changes"""
|
||||
import sys, os, json
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from app import create_app
|
||||
from app.extensions import db
|
||||
from app.models.position import Position
|
||||
from app.models.user import User
|
||||
|
||||
app = create_app()
|
||||
|
||||
with app.test_client() as c:
|
||||
c.post('/auth/login', data={'email': 'fk@kpt-consulting.de', 'password': 'kpt2024'})
|
||||
|
||||
# 1. Add a custom position
|
||||
r = c.post('/projekt/1/1/position/hinzufuegen', data={
|
||||
'pos_nr': 'TEST-001', 'kurztext': 'Testposition',
|
||||
'einheit': 'ST', 'einzelpreis': '100.00'
|
||||
})
|
||||
print(f'1. Position hinzufuegen: {r.status_code}')
|
||||
|
||||
# 2. List positions
|
||||
r = c.get('/projekt/1/1/positionen')
|
||||
data = json.loads(r.data)
|
||||
print(f'2. Positionen: {len(data)} found')
|
||||
|
||||
# 3. Update cell
|
||||
positions = Position.query.filter_by(aufmass_id=1).all()
|
||||
if positions:
|
||||
pos_id = positions[0].id
|
||||
r = c.post(f'/projekt/1/1/position/{pos_id}/update-cell', json={
|
||||
'field': 'menge', 'value': 5.0
|
||||
})
|
||||
res = json.loads(r.data)
|
||||
print(f'3. Update-cell menge: {r.status_code} -> hinten={res.get("menge_hinten")} gp={res.get("gesamtpreis")}')
|
||||
|
||||
# 4. Farben endpoint
|
||||
r = c.get('/projekt/1/1/positionen/farben')
|
||||
print(f'4. Farben: {r.status_code}')
|
||||
|
||||
# 5. Firmen page
|
||||
r = c.get('/admin/firma')
|
||||
print(f'5. Firma page: {r.status_code}')
|
||||
|
||||
# 6. Superadmin test
|
||||
c.post('/auth/login', data={'email': 'super@admin.de', 'password': 'admin'})
|
||||
r = c.get('/superadmin/')
|
||||
html = r.data.decode()
|
||||
print(f'6. Superadmin dashboard: {r.status_code} (Firmen: {"KPT" in html})')
|
||||
|
||||
# 7. Firm detail
|
||||
r = c.get('/superadmin/firma/1')
|
||||
print(f'7. Firm detail: {r.status_code}')
|
||||
|
||||
# 8. LV page as superadmin
|
||||
r = c.get('/lv/')
|
||||
print(f'8. LV page: {r.status_code}')
|
||||
|
||||
# 9. Create new aufmass
|
||||
c.post('/auth/login', data={'email': 'fk@kpt-consulting.de', 'password': 'kpt2024'})
|
||||
r = c.post('/projekt/1/projekt/neu', data={
|
||||
'name': 'Teilaufmass 1', 'typ': 'Teilaufmass'
|
||||
}, follow_redirects=True)
|
||||
print(f'9. Aufmass created: {r.status_code}')
|
||||
|
||||
# 10. List aufmasse
|
||||
r = c.get('/projekt/1')
|
||||
print(f'10. Aufmass list: {r.status_code}')
|
||||
|
||||
# 11. Check new aufmass in editor
|
||||
r = c.get('/projekt/1/2')
|
||||
print(f'11. Editor for new aufmass: {r.status_code}')
|
||||
|
||||
print('\n=== ALL TESTS PASSED ===')
|
||||
Reference in New Issue
Block a user