# CLAUDE.md – spawntree Timetracker ## Projekt-Überblick Internes Timetracking-Tool (Orientierung an mite.de), zunächst Single-User, geplant als SaaS. Multi-Tenant-Architektur: jeder Account bekommt eine Subdomain und eigene Tenant-DB. ## Tech Stack - **Backend**: Symfony 7.4, PHP 8.2+ (DDEV nutzt 8.4), Doctrine ORM, MariaDB 10.11 - **Frontend**: Twig, SCSS (Webpack Encore), Vanilla JS (kein Framework, kein jQuery) - **Export**: PhpSpreadsheet (Excel), Dompdf (PDF), natives PHP (CSV) - **Dev-Umgebung**: DDEV, Projektname `timetracking`, HTTPS-Port 8459 - **Keine** Symfony Forms – eigene HTML-Formulare mit `fetch()`-API ## Verzeichnisstruktur Der Symfony-Code liegt unter `httpdocs/`. Das ist das Projekt-Root für Symfony. ``` httpdocs/ ├── src/ │ ├── Controller/ # Symfony Controller (Routes via PHP Attributes) │ ├── Entity/ │ │ ├── Central/ # User, Account, AccountUser, Tokens (Central-DB) │ │ └── Tenant/ # Client, Project, Service, TimeEntry (Tenant-DB) │ ├── Repository/ │ │ ├── Central/ │ │ └── Tenant/ │ ├── Service/ # TenantContext, RegistrationService, etc. │ ├── Doctrine/ # TenantConnectionMiddleware │ ├── EventSubscriber/ # TenantRequestSubscriber, ArchivedUserSubscriber │ ├── Security/ # ArchivedUserChecker, AccessDeniedHandler │ └── Twig/ # AppExtension + Runtime ├── config/ │ ├── packages/ │ │ ├── doctrine.yaml # Zwei Connections: central + tenant │ │ └── security.yaml # Firewall, Access Control, form_login │ ├── services.yaml # DI: Tenant-EM explizit an Controller gebunden │ └── routes.yaml ├── templates/ # Twig-Templates (Atomic Design: atoms/components/sections) ├── assets/ │ ├── app.js # Webpack-Entry für Timetracking │ ├── styles/ # SCSS (main.scss als Entry) │ └── scripts/ # JS-Module (calendar, entries, crud, team, account, report) ├── migrations/ │ ├── central/ # Doctrine-Migrations für Central-DB │ └── tenant/ # Doctrine-Migrations für Tenant-DB ├── translations/ # messages.de.yaml └── public/ # Webroot (index.php, build/) ``` ## Multi-Tenant-Architektur - **Central-DB** (`db`): User, Account, AccountUser, Tokens - **Tenant-DB** (`db_{slug}`): Client, Project, Service, TimeEntry - `TenantRequestSubscriber` (Prio 20) liest Subdomain → setzt `TenantContext` - `TenantConnectionMiddleware` schaltet die DB-Connection auf `db_{slug}` um - Zwei Doctrine Entity Manager: `central` und `tenant` ## Wichtige Befehle ```bash # Dev-Umgebung starten ddev start # Datenbank Reset + Seed (innerhalb DDEV) ddev exec bash 1-reset-and-seed.sh ddev exec bash 2-update-tenant-db.sh # Migrations ddev exec php bin/console doctrine:migrations:migrate --em=central --no-interaction ddev exec php bin/console doctrine:migrations:migrate --em=tenant --no-interaction # Frontend Build ddev exec npm run dev # Development ddev exec npm run watch # Watch-Mode ddev exec npm run build # Production # Cache leeren ddev exec php bin/console cache:clear # Deploy bash httpdocs/deploy.sh ``` ## Webpack Encore Entries | Entry | Datei | Seite | |----------------|----------------------------------|--------------------------| | `app` | `assets/app.js` | Timetracking-Woche | | `crud` | `assets/scripts/crud.js` | Kunden/Projekte/Services | | `registration` | `assets/scripts/registration.js` | Registrierung | | `team` | `assets/scripts/team.js` | Team-Verwaltung | | `account` | `assets/scripts/account.js` | Account-Einstellungen | | `report` | `assets/scripts/report.js` | Report-Seite | ## Konventionen - **Durations**: Integer (Minuten) in der DB, Eingabeformate: `1:30`, `8 12` (von-bis), `1,75` (Dezimal) - **Rounding**: Konfigurierbar per `Account.trackingInterval` (1/15/30/60 Min) - **API-Pattern**: `/api/...` Routen, JSON Request/Response, kein CSRF auf API-Endpunkten - **Rollen**: `admin` (alles), `member` (eigene + fremde Einträge sehen), `tracker` (nur eigene) - **Translations**: `messages.de.yaml`, JS-Strings via `window.TT.i18n` / `window.Report.i18n`. Auch Backend-Services (z.B. `ReportExportService`) nutzen `TranslatorInterface` — keine hardcoded Strings. - **SCSS**: BEM-ähnlich, Atoms → Components → Sections → Themes - **CSS Custom Properties**: Brand-Farben via `:root`-Variablen (`--color-primary`, etc.) ## Rollen-System | Rolle | Rechte | |-----------|------------------------------------------------------------------| | `admin` | Alles: Team, alle Einträge, Account-Settings | | `member` | Eigene Einträge + alle fremden sehen (kein Team-Zugriff) | | `tracker` | Nur eigene Einträge | Superadmin = Account-Ersteller, kann Kontoinhaber übertragen und `primaryColor` setzen. ## Services-Injection (services.yaml) Controller die Tenant-Entities nutzen brauchen den `tenant_entity_manager` explizit: - `$tenantEm: '@doctrine.orm.tenant_entity_manager'` (TimeTrackingController, ReportController) - `$em: '@doctrine.orm.tenant_entity_manager'` (ClientController, ProjectController, ServiceController) ## Report-Exporte - **Excel** (`/reports/export/excel`): PhpSpreadsheet, Autofilter, Frozen Header, Zebra-Stripes, Summenzeile - **CSV** (`/reports/export/csv`): Semikolon-Trennzeichen, UTF-8 BOM, deutsche Zahlenformatierung - **PDF** (`/reports/export/pdf`): Dompdf, A4 Querformat, professioneller Header + Footer Alle drei nutzen die gleichen Filter-Parameter wie die Report-Seite, exportieren ohne Limit. `ReportExportService` bereitet Daten zentral in `prepareData()` auf, formatspezifische Methoden erzeugen die Ausgabe. Tracker sehen nur eigene Einträge. ## TenantConnectionMiddleware Registriert via Service-Tag in `services.yaml` (nicht via `doctrine.yaml` — DoctrineBundle 3.x unterstützt `middlewares`-Config-Key nicht): ```yaml App\Doctrine\TenantConnectionMiddleware: tags: - { name: doctrine.middleware, connection: tenant } ```