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.
timetracking, HTTPS-Port 8459fetch()-APIDer 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/)
db): User, Account, AccountUser, Tokensdb_{slug}): Client, Project, Service, TimeEntryTenantRequestSubscriber (Prio 20) liest Subdomain → setzt TenantContextTenantConnectionMiddleware schaltet die DB-Connection auf db_{slug} umcentral und tenant# 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
| 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 |
1:30, 8 12 (von-bis), 1,75 (Dezimal)Account.trackingInterval (1/15/30/60 Min)/api/... Routen, JSON Request/Response, kein CSRF auf API-Endpunktenadmin (alles), member (eigene + fremde Einträge sehen), tracker (nur eigene)messages.de.yaml, JS-Strings via window.TT.i18n / window.Report.i18n. Auch Backend-Services (z.B. ReportExportService) nutzen TranslatorInterface — keine hardcoded Strings.:root-Variablen (--color-primary, etc.)| 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.
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)/reports/export/excel): PhpSpreadsheet, Autofilter, Frozen Header, Zebra-Stripes, Summenzeile/reports/export/csv): Semikolon-Trennzeichen, UTF-8 BOM, deutsche Zahlenformatierung/reports/export/pdf): Dompdf, A4 Querformat, professioneller Header + FooterAlle 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.
Registriert via Service-Tag in services.yaml (nicht via doctrine.yaml — DoctrineBundle 3.x unterstützt middlewares-Config-Key nicht):
App\Doctrine\TenantConnectionMiddleware:
tags:
- { name: doctrine.middleware, connection: tenant }