# Anforderungen – AufmaßWeb v2.35 ## Projektübersicht Flask-basierte Webanwendung zur Verwaltung von Aufmaßen, Projekten und Positionen. --- ## 1. Projektübersicht (`/projekt/`) ### 1.1 Sortierung - Projekte werden nach **Bezeichnung A-Z** aufsteigend sortiert angezeigt - NULL-Werte werden als leerer String behandelt (sortieren ans Ende) ### 1.2 Projektsuche - Suchleiste oben auf der Seite - Clientseitige Filterung nach Projektname (Bezeichnung / SM-Nr) - Echtzeit-Filterung bei Eingabe, Clear-Button vorhanden ### 1.3 Baumansicht - Jedes Projekt als aufklappbarer `
`-Block - Zeigt: Bezeichnung, Summe (€), Anzahl Aufmaße, Anzahl Positionen, Status - Doppelklick auf Projekt-Header navigiert zur Listenansicht (`/projekt/`) ### 1.4 Aufmaß-Suche - Suchleiste filtert Aufmaß-Namen innerhalb der Baumansicht ### 1.5 Inline-Bearbeitung Aufmaß-Name - ✏️-Button pro Aufmaß → Inline-Input für Name - Speichern: Blur oder Enter - Abbrechen: Escape - Kein OK/Abbrechen-Button, kein setTimeout ### 1.6 Inline-Bearbeitung Projekt-Name - ✏️-Button im Projekt-Header → Inline-Input für Name - Speichern: Blur oder Enter - Abbrechen: Escape - POST an `/projekt//update-name` ### 1.7 Aufmaß löschen - ✕-Button pro Aufmaß, keine Bestätigung (ohne `confirm()`) - Redirect per `request.referrer`: in Baumansicht → bleibt in `index`, in Detailansicht → bleibt in `list` ### 1.8 Aufmaß duplizieren - Duplizieren-Button (📋) pro Aufmaß, keine Bestätigung - Erstellt Kopie mit allen Positionen, hängt ` (Kopie)` an den Namen - Bei Namenskonflikt: automatisch ` (1)`, ` (2)` etc. - Redirect per `request.referrer` (wie Löschen) ### 1.9 Baum-Status persistent - `
` open/close-Zustand in `localStorage` - Schlüssel: `tree_open_{projectId}` - Überlebt Seitenneuladungen nach Aktionen --- ## 2. Neues Aufmaß erstellen ### 2.1 Button - **"+ Neues Aufmaß"**-Button unter jedem Projekt in der Baumansicht - Klick klappt ein vollständiges Formular auf (slide-down) ### 2.2 Formular-Felder (Reihenfolge) 1. **Vertrag** (Dropdown, optional) 2. **LV-Name** (Text, optional) 3. **Typ** (Dropdown, optional) 4. **Bezeichnung (Baustelle)** (Text, leer) 5. **Bauabschnitt** (Text, pre-filled aus Projekt) 6. **SM-Nr** (Text, pre-filled aus Projekt) 7. **Abruf-Nr** (Text, pre-filled aus Projekt) 8. **Kopfdaten EV holen** (Button, bedingt sichtbar – siehe Abschnitt 11) 9. **Ansprechpartner** (Trennlinie) - Vorname - Name - Tel - Email 10. **Startdatum** (Date) 11. **Endedatum** (Date) 12. **Aufmaß-Name** (readonly, automatisch generiert) ### 2.3 Automatische Aufmaß-Namensgenerierung - Zusammensetzung: `Bezeichnung + " - " + Bauabschnitt + " - " + SM-Nr + " - " + Abruf-Nr` - Nur nicht-leere Felder werden berücksichtigt - Wird live bei Eingabe aktualisiert (readonly-Feld) - Nur OS/Nextcloud-sichere Zeichen: `< > : " / \ | ? * & # % { } ~ [ ]` werden entfernt, Mehrfachleerzeichen → eines ### 2.4 Speichern - Aktualisiert Projekt-Metadaten (SM-Nr, Vertrag, LV-Name, Abruf-Nr, Daten, Ansprechpartner) - `project.bezeichnung` wird NICHT überschrieben - Erstellt neues Aufmaß mit generiertem Namen und gewähltem Typ - Redirect zur Projektübersicht --- ## 3. Neues Projekt erstellen (`/projekt/neu`) ### 3.1 Formular (vereinfacht) - **Projektname / Bezeichnung** (Pflichtfeld) - **Vertrag** (Dropdown, optional) - **LV-Name** (Dropdown, optional, abhängig von Vertrag) - Kein Standard-Aufmaß wird automatisch erstellt --- ## 4. Projekt-Listenansicht (`/projekt/`) ### 4.1 Aufmaß-Liste - Alle Aufmaße eines Projekts als Tabelle - Inline-Editing für Name, Typ, Status - Sofortige visuelle Aktualisierung vor async-fetch - Kein `resp.status`-Check (Server-Response enthält Aufmaß-Status, nicht 'ok') ### 4.2 Projekt-Name inline bearbeiten - ✏️-Button in der Projektkopfzeile - Gleiche Funktionalität wie in Baumansicht (POST `/projekt//update-name`) ### 4.3 Aufmaß erstellen (in Listenansicht) - Formular mit Name + Typ + Sortierung ### 4.4 TXT-Import - 📥 Import-Button → Lädt TXT-Datei - Parst `[Kopfdaten]` + `[Aufmaßdaten]` - Erstellt Aufmaß mit Positionen - Bei doppeltem Namen: automatisch ` (1)`, ` (2)` etc. anfügen --- ## 5. Aufmaß-Editor (`/projekt//`) ### 5.1 Positionstabelle - Inline-Editing für alle Positionsfelder - Speichern: Blur oder Enter - Sofortige visuelle Aktualisierung vor async-fetch - Kein `resp.status`-Check (Server-Response enthält Aufmaß-Status, nicht 'ok') ### 5.2 Zahlenformat - Alle Zahleneingaben: `type="text" inputmode="decimal"` - **Deutsches Format**: Komma = Dezimaltrenner, Punkt = Tausendertrenner - `germanNum()`: Zahl → deutsche Anzeige (z.B. `1.234,56`) - `parseNum()`: Deutsche Eingabe → Zahl (z.B. `1.234,56` → `1234.56`) - `sanitizeNum()`: Entfernt ungültige Zeichen live bei Eingabe - `validateNumField()`: Rote Border bei ungültigem Format - **Validierung**: Erlaubt sind `123`, `1.234`, `1,5`, `1.234,56`, `-1,5` - Ungültig: Buchstaben, `1,2,3`, `1.2.3`, etc. ### 5.3 ST/LE/STD/h/Psch – Menge-Verhalten - Faktor-Feld ist immer `1,0` (kein Highlight) - Bei ST/LE/STD/h/Psch wird **Menge** hervorgehoben (amber `#fff3cd`, fett) und ist **leer** – die Menge ist hier das relevante Feld, muss aber vom Benutzer über die Berechnung (Faktor-Eingabe) bestimmt werden - Bei M/M2/M3 werden die Dimensionsfelder (Länge/Breite/Tiefe) hervorgehoben - Menge ist readonly und wird automatisch berechnet: ST/LE/STD/h/Psch → `Faktor × 1`, M → Länge, M2 → L×B, M3 → L×B×T ### 5.4 Formel-Auswahl - **Standard**: Länge × Breite × Tiefe (abhängig von Einheit) - **Frei**: Eigenes Formelfeld - Typabhängige Markierung der Pflichtfelder (Länge, Breite, Tiefe) mit amber ### 5.5 Einfüge-Position ("Am Ende anfügen") - Checkbox "Am Ende anfügen" (default: checked) - **checked**: Neue Positionen werden am Ende der Tabelle angefügt - **unchecked**: Neue Positionen werden unterhalb der zuletzt markierten Position eingefügt - Klick auf eine Position in der rechten Tabelle setzt die Einfügemarke, ohne die LV-Auswahl zu beeinflussen ### 5.6 LV-Tabelle (linke Seite) - Spalten: Pos-Nr, Kurztext, EP (deutsch formatiert via `|german_number`), Einheit - Einfachklick: LV-Zeile markieren, Formular füllen - **Multi-Select**: Strg+Klick markiert mehrere Zeilen - **Drag & Drop**: Ziehen einer LV-Zeile überträgt alle markierten IDs (nicht nur die gezogene) - **Standard-Menge bei Drag**: Nach LV-Drag bleibt menge=0 (keine automatische `berechne_menge()`) - `fillForm()` setzt `currentLVRow` / `currentLVId` pro Zeile - Klick auf Positionstabelle löscht NICHT `currentLVRow` oder die LV-Auswahl ### 5.7 Hinzufügen-Button - **"Hinzufügen"** und **"Auswahl hinzufügen"**: Beide verwenden `selectedSet` (alle markierten LV-Zeilen) + Formularwerte (Overrides) - Bei Mehrfachauswahl (>1 LV-Zeile) werden Text-Overrides (`pos_nr`, `kurztext`, `abschnitt`, `bemerkung`) **nicht** gesendet – jede Position behält ihre LV-eigenen Textwerte - Dimensions-Overrides (Faktor, Länge, Breite, Tiefe, Formel) werden immer für alle markierten Positionen verwendet - Beide nutzen `getInsertIdxFromCheckbox()` für die Einfügeposition ### 5.8 Positionen importieren (TXT) - 📥 Button "Import TXT" im Tabellen-Footer - Lädt nur `[Aufmaßdaten]` aus einer TXT-Datei - Fügt Positionen in das aktuelle Aufmaß ein - Bestehende Kopfdaten bleiben unverändert ### 5.9 Kontext-Menü (Rechtsklick) - Rechtsklick auf eine Position in der Tabelle öffnet ein Kontext-Menü - **Markierte löschen** (✕) – löscht alle markierten Positionen (wie Button unten) - **Markierte kopieren** (📋) – kopiert alle markierten Positionen (wie Button unten) - **Leere Zeile einfügen** (📄) – fügt eine leere Zeile unterhalb der angeklickten Position ein - Beim Rechtsklick ohne Ctrl/Shift wird die angeklickte Zeile zuerst selektiert (Single-Select) - Rechtsklick auf Trenner/Leerzeilen ist ebenfalls möglich (setzt Einfügepunkt) - Menü schließt bei Klick außerhalb oder Escape ### 5.10 Spalten-Filter (Excel-ähnlich) - Jede Spaltenüberschrift (außer Z-Art und ✕) hat einen ▾-Filterbutton - Klick öffnet ein Dropdown-Menü mit: - **↑ Aufsteigend / ↓ Absteigend sortieren** (Sortierung aufheben mit ✕) - **Suchen…** (Livesuche filtert Werte-Liste + Tabelle) - **Checkbox-Liste** aller vorkommenden Werte mit **Alles**/**Nichts** - Mehrere Spalten können gleichzeitig gefiltert werden (Und-Verknüpfung) - Aktive Filter werden durch blaue ▾-Buttons angezeigt - **↺ Filter zurücksetzen**-Button lädt die Tabelle neu und entfernt alle Filter - Nur Datenzeilen (mit `data-id`) werden gefiltert – Trenner bleiben sichtbar - Sortieren + Filtern sind kombinierbar und wirken nur clientseitig (Ansichtsfilter) - Einfügen, Löschen und Bearbeiten sind auch im gefilterten Zustand möglich ### 5.11 Export (Excel, PDF, TXT) mit Ansichtsfilter - Buttons: **📊 Excel**, **📄 PDF**, **📄 TXT** in der Kopfzeile - Checkbox **"Aktuelle Ansicht"** – wenn aktiv, werden nur sichtbare (gefilterte) Positionen exportiert - TXT-Export erzeugt UTF-8-Datei im Format `[Kopfdaten]` + `[Aufmaßdaten]` (pipe-separiert) - Export-Route akzeptiert `?visible_ids=1,2,3` zum Filtern ### 5.12 LV-Tabelle Filter - Gleiches Filter-UI wie Positionstabelle (▾-Buttons in Spaltenüberschriften) - Spalten: Pos-Nr, Kurztext, EH, EP - **Sortieren** (↑/↓) und **Suchen** (Text-Livesuche) - Keine Checkbox-Liste (LV hat zu viele unique values) - Aktive Filter werden durch blaue ▾-Buttons angezeigt ### 5.13 Spaltenbreite anpassen (ziehen) - Jede Spaltenüberschrift hat rechts einen unsichtbaren Resize-Handle (5px breit) - **Ziehen**: Breite ändert sich live, bei Loslassen in `localStorage` gespeichert - **Doppelklick** auf Handle: automatische optimale Breite (maximale Inhaltsbreite) - **Rechtsklick** auf Spaltenkopf → Kontext-Menü mit **"💾 Ansicht speichern"** - Fixiert die aktuellen Spaltenbreiten als `saved_lv_col_widths` / `saved_pos_col_widths` - Nach Seitenneuladung werden gespeicherte Breiten wiederhergestellt - Funktion `saveColumnWidths()` speichert aktuelle Breiten separat als "gespeicherte Ansicht" - Gespeicherte Breiten überleben Seitenneuladungen (werden sofort beim Laden angewandt) ### 5.14 Positionstabelle Farben - `has-background-warning-light` (gelb) nur wenn Wert=0 UND passende Einheit (z.B. Menge=0 bei ST) ### 5.10 LV-Ergebnis EP - EP-Werte in der LV-Tabelle werden mit deutschem Zahlenformat angezeigt (`|german_number`) --- ## 6. Datenbank ### 6.1 Projekt-Modell (`projekte`) - `id`, `company_id`, `contract_id` - `sm_nr`, `bezeichnung`, `vertrag`, `abruf_nr`, `lv_name` - `datum_start`, `datum_ende` - `ansprechpartner_vorname`, `ansprechpartner_nachname`, `ansprechpartner_tel`, `ansprechpartner_email` - `bauabschnitt`, `status` - `erstellt_von`, `erstellt_am`, `geaendert_am` ### 6.2 Lizenz-Modell (`lizenzen`) - `uid` (auto-generiert SHA256) - `max_mitarbeiter`, `max_module_slots` - `unlimited_users`, `unlimited_modules` - Properties: `used_users`, `used_module_slots`, `user_slots_display()`, `module_slots_display()` ### 6.3 Datenbank-Engine - Lokal: SQLite (`data/aufmass.db`) - Produktion: PostgreSQL via `DATABASE_URL` Umgebungsvariable - Docker-Setup mit PostgreSQL-Service vorhanden --- ## 7. Docker ### 7.1 Services - `db`: PostgreSQL 16-alpine mit Healthcheck - `web`: Flask-App mit Gunicorn (4 Worker) ### 7.2 Konfiguration - `DATABASE_URL` wird via Environment Variable gesetzt - `SECRET_KEY` muss vor Produktion geändert werden - Volume `pgdata` für persistente Daten --- ## 8. Rollen & Berechtigungen ### 8.1 Superadmin - Zugriff auf alle Projekte, Firmen, Lizenzen - Firmenverwaltung & Lizenzverwaltung im Menü - Dashboard: Lizenz-Statistiken, Firmen-Übersicht ### 8.2 Firmadmin - Zugriff auf Projekte der eigenen Firma - Mitarbeiterverwaltung, Rechteverwaltung - Lizenz-Übersicht ### 8.3 Mitarbeiter - Zugriff nur auf zugewiesene Projekte (ProjectAccess) - Berechtigungen: lesen, schreiben, aufmass_verwalten, projekte_anlegen, darf_preise_sehen, darf_lv_verwalten --- ## 9. Login-Daten | Rolle | Email | Passwort | |---|---|---| | Superadmin | super@admin.de | admin123 | | Firmadmin | firmadmin@kpt.de | firma123 | --- ## 10. Wichtige technische Regeln ### 10.1 Inline-Edit - Blur/Enter speichert, Escape bricht ab - Keine OK/X-Buttons, kein setTimeout ### 10.2 Visuelle Aktualisierung - SOFORT vor async-fetch, nicht im Callback ### 10.3 Server-Response (resp.status) - **`resp.status`** ist das Aufmaß-Statusfeld ('aktiv'), NICHT ein Erfolgsindikator - **Kein `resp.status !== 'ok'`-Check** ### 10.4 Projekt-Name schützen - `project.bezeichnung` wird in `aufmass_neu_voll` und `aufmass_import` NICHT überschrieben - Nur explizit via `POST /projekt//update-name` änderbar ### 10.5 Redirect-Verhalten - Löschen, Duplizieren, Import: `request.referrer` prüfen - Baumansicht (`/projekt/`) → Redirect zu `aufmass.index` - Detailansicht (`/projekt/`) → Redirect zu `aufmass.aufmass_list` - Erkennung: `referrer.rstrip('/').endswith('/projekt')` ### 10.6 Namensvalidierung - Aufmaß-Name: nur OS/Nextcloud-sichere Zeichen - Entfernt: `< > : " / \ | ? * & # % { } ~ [ ]` - Mehrfachleerzeichen → eines --- ## 11. E-Vergabe Addon ### 11.1 Firmeneinstellungen (Firmadmin) - Neuer Bereich **"E-Vergabe"** in den Firmeneinstellungen (`/admin/firma`) - Sichtbar nur wenn E-Vergabe-Addon für die Firma freigeschaltet ist (durch Superadmin) - Felder: - **Benutzer** (aus `_legacy/daten/conf.ini` → `[EVergabe] Benutzer`) - **Passwort** (aus `_legacy/daten/conf.ini` → `[EVergabe] Passwort`) - **Name** (aus `_legacy/daten/conf.ini` → `[EVergabe] Name`) ### 11.2 Superadmin – E-Vergabe Addon freigeben - Superadmin kann E-Vergabe-Addon pro Firma freischalten/deaktivieren - Dashboard: Spalte "E-Vergabe" mit Status und Toggle-Button (EV +/EV ✕) - Firma-Detail: E-Vergabe-Bereich mit Status und Freischalten/Deaktivieren-Button ### 11.3 Berechtigungen (User-Ebene) Drei neue Berechtigungen für Mitarbeiter: - **E-Vergabe Addon nutzen** (`darf_evergabe_nutzen`) – Grundvoraussetzung für alle E-Vergabe-Funktionen - **Kopfdaten holen erlauben** (`darf_kopfdaten_holen`) – Erlaubt das Abrufen von Kopfdaten aus der E-Vergabe - **Aufmaße in E-Vergabe übertragen** (`darf_aufmass_uebertragen`) – Erlaubt das Übertragen von Aufmaßen ### 11.4 "Kopfdaten EV holen" Button - Sichtbar nur wenn: - User hat `darf_evergabe_nutzen` UND `darf_kopfdaten_holen` - Firma hat `evergabe_aktiviert = True` - Anklickbar nur wenn: - Firma hat `evergabe_benutzer` UND `evergabe_passwort` hinterlegt - Mouseover-Hinweis wenn nicht anklickbar: *"Es sind keine E-Vergabe Logindaten hinterlegt."* - Funktion: Ruft Kopfdaten von der E-Vergabe-API ab und füllt die Formularfelder (Bezeichnung, Bauabschnitt, SM-Nr, Abruf-Nr, Daten, Ansprechpartner) ### 11.5 Company-Modell (neue Felder) - `evergabe_aktiviert` (Boolean, default=False) –Addon freigeschaltet - `evergabe_benutzer` (String) – Login-Benutzername - `evergabe_passwort` (String) – Login-Passwort - `evergabe_name` (String) – Name/Kennung ### 11.6 E-Vergabe Service (`evergabe_service.py`) Python-Port der AutoIt `EVvergabeWebobj.au3` Funktionen: - **`EVergabeClient`** Klasse mit `requests.Session()` für Cookie-Handling - **`login()`** – Login via POST `/public/login` mit CSRF-Token-Extraktion - **`search_sm(sm_nr)`** – SM-Nr-Suche via GET `/framework-agreement-call`, extrahiert Title, Bedarfsnr, Belegnr, DetailsID, RV, Daten, Status - **`get_aspa(details_id)`** – Ansprechpartner-Daten (Name, Email, Tel) aus Details-Seite - **`hole_kopfdaten(sm_nr)`** – Kombiniert Login + Suche + ASPA → Rückgabe aller Kopfdaten - **`aufmass_uebertragen(sm_nr, positionen, ...)`** – Überträgt Positionen in E-Vergabe: - Erstellt Sheet via `/sheet/create-sheet` - Für jede Position: Erstellt Position, dann INSERT je nach Einheit (ST, M, M2, M3, STD, LE) - Richtige Headers: User-Agent, Referer, Content-Type, CSRF-Token - URL-Encoding aller Parameter ### 11.7 Aufmaß in E-Vergabe übertragen - Route: `POST /projekt//aufmass//evergabe-uebertragen` - Berechtigung: `darf_evergabe_nutzen` + `darf_aufmass_uebertragen` - Überträgt alle Positionen des Aufmaßes in die E-Vergabe-Plattform - Rückgabe: JSON mit Erfolg/Fehler pro Position - `evergabe_sub` (String) – Sub-Kennung ### 11.8 User-Modell (neue Felder) - `darf_evergabe_nutzen` (Boolean, default=False) - `darf_kopfdaten_holen` (Boolean, default=False) - `darf_aufmass_uebertragen` (Boolean, default=False)