| @@ -27,7 +27,7 @@ httpdocs/ | |||
| │ ├── Repository/ | |||
| │ │ ├── Central/ | |||
| │ │ └── Tenant/ | |||
| │ ├── Service/ # TenantContext, RegistrationService, etc. | |||
| │ ├── Service/ # TenantContext, RegistrationService, LexofficeService, etc. | |||
| │ ├── Doctrine/ # TenantConnectionMiddleware | |||
| │ ├── EventSubscriber/ # TenantRequestSubscriber, ArchivedUserSubscriber | |||
| │ ├── Security/ # ArchivedUserChecker, AccessDeniedHandler | |||
| @@ -42,7 +42,7 @@ httpdocs/ | |||
| ├── assets/ | |||
| │ ├── app.js # Webpack-Entry für Timetracking | |||
| │ ├── styles/ # SCSS (main.scss als Entry) | |||
| │ └── scripts/ # JS-Module (calendar, entries, crud, stopwatch, team, account, report) | |||
| │ └── scripts/ # JS-Module (calendar, entries, crud, stopwatch, searchable-select, team, account, report) | |||
| ├── migrations/ | |||
| │ ├── central/ # Doctrine-Migrations für Central-DB | |||
| │ └── tenant/ # Doctrine-Migrations für Tenant-DB | |||
| @@ -136,10 +136,13 @@ Gemeinsame Hilfsfunktionen in `assets/scripts/utils.js`: | |||
| - `removeWithAnimation(el, className)` / `animateIn(el, className)` — Animierte DOM-Operationen | |||
| - Konstanten: `ANIMATION_MS`, `FADE_MS`, `MINUTES_PER_DAY` | |||
| ### JS: searchable-select.js | |||
| Wiederverwendbares durchsuchbares Dropdown-Modul (`SearchableSelect`-Klasse) mit Gruppen, Keyboard-Navigation, Filter und `onSelect`-Callback. Wird von `stopwatch.js` (Projekt/Service-Auswahl) und `crud.js` (Lexoffice-Kontaktauswahl) importiert. | |||
| ### JS: stopwatch.js | |||
| Stoppuhr-Modul mit zwei Klassen: | |||
| - `SearchableSelect` — Wiederverwendbares durchsuchbares Dropdown mit Gruppen, Keyboard-Navigation und Filter | |||
| Stoppuhr-Modul: | |||
| - `StopwatchManager` — Timer-Steuerung: Start/Stop/Resume, Tick-Display (Sekunden-Auflösung), DOM-Integration mit `window.entryManager`, Tab-Title-Update | |||
| ### Globale `[hidden]`-Regel | |||
| @@ -162,7 +165,7 @@ Komplexe Logik die in mehreren Templates vorkommt wird in `templates/_macros/hel | |||
| | `member` | Eigene Einträge + alle fremden sehen (kein Team-Zugriff) | | |||
| | `tracker` | Nur eigene Einträge | | |||
| Superadmin = Account-Ersteller, kann Kontoinhaber übertragen und `primaryColor` setzen. | |||
| Superadmin = Account-Ersteller, kann Kontoinhaber übertragen, `primaryColor` und `lexofficeApiKey` setzen. | |||
| ## Services-Injection (services.yaml) | |||
| @@ -185,7 +188,7 @@ Live-Timer zum Tracken von Zeiteinträgen. UI in der Navigation (Desktop + Hambu | |||
| ### Architektur | |||
| - **Backend**: Timer-API im `TimeTrackingController` (`/api/timer/*`) | |||
| - **Frontend**: `assets/scripts/stopwatch.js` (importiert in `app.js`), enthält `SearchableSelect`-Klasse (durchsuchbare Dropdowns) und `StopwatchManager` | |||
| - **Frontend**: `assets/scripts/stopwatch.js` (importiert in `app.js`), nutzt `SearchableSelect` aus `searchable-select.js` | |||
| - **Styles**: `assets/styles/components/_stopwatch.scss` | |||
| - **Template**: Popover in `_sections/nav.html.twig`, Play-Button in `timetracking/_entry_row.html.twig` | |||
| - **Icon**: `_atoms/icon-stopwatch.html.twig` | |||
| @@ -209,6 +212,38 @@ Live-Timer zum Tracken von Zeiteinträgen. UI in der Navigation (Desktop + Hambu | |||
| - LocalStorage (`tt_timer_state`): persistiert Timer-State über Page-Reloads, wird mit Server-State abgeglichen | |||
| - LocalStorage (`tt_last_project_id`, `tt_last_service_id`): merkt letzte Auswahl für Quick-Start | |||
| ## Lexoffice-Integration (Lexware Office) | |||
| Optionale Verknüpfung von Kunden mit Lexware Office Kontakten. Nur aktiv wenn `Account.lexofficeApiKey` gesetzt ist. | |||
| ### Datenmodell | |||
| - `Account.lexofficeApiKey` (Central-DB): API-Key für Lexware Office, gespeichert pro Account | |||
| - `Client.lexofficeContactId` (Tenant-DB): UUID des verknüpften Lexware-Kontakts (nullable) | |||
| ### Architektur | |||
| - **Service**: `LexofficeService` — HTTP-Client für Lexware Office API (`https://api.lexware.io/v1`), paginierter Kontakt-Abruf mit Rate-Limit-Retry | |||
| - **Controller**: `LexofficeController` — API-Endpunkte für Kontaktlisten und Einzelkontakte | |||
| - **Client-Controller**: `lexoffice-refresh`-Endpunkt zum Aktualisieren des Kundennamens aus Lexware | |||
| - **Frontend**: Lexoffice-Logik in `crud.js`, nutzt `SearchableSelect` für Kontakt-Dropdown | |||
| - **Account-Settings**: API-Key-Verwaltung im Account-Tab (Eingabe/Ändern mit Maskierung) | |||
| ### API-Endpunkte | |||
| | Route | Method | Beschreibung | | |||
| |------------------------------------------|--------|-------------------------------------------| | |||
| | `/api/lexoffice/contacts` | GET | Alle Kunden-Kontakte aus Lexware Office | | |||
| | `/api/lexoffice/contacts/{contactId}` | GET | Einzelnen Kontakt abrufen | | |||
| | `/api/clients/{id}/lexoffice-refresh` | PATCH | Kundenname aus Lexware aktualisieren | | |||
| ### Verhalten | |||
| - Beim Verknüpfen wird der Kundenname aus Lexware übernommen, das Name-Feld wird disabled | |||
| - Bereits verknüpfte Kontakte werden in der Dropdown-Liste ausgeblendet (Duplikat-Schutz) | |||
| - Reload-Button pro Zeile zum Aktualisieren des Namens aus Lexware | |||
| - Kontakte werden clientseitig gecacht (einmal geladen pro Page-Session) | |||
| ## TenantConnectionMiddleware | |||
| Registriert via Service-Tag in `services.yaml` (nicht via `doctrine.yaml` — DoctrineBundle 3.x unterstützt `middlewares`-Config-Key nicht): | |||