@@ -0,0 +1,179 @@
# CLAUDE.md — Helpdesk Module
Self-contained MintyPHP module. Namespace: `MintyPHP\Module\Helpdesk\*` . All changes stay inside `modules/helpdesk/` — never touch core templates or global layout.
After any manifest change (new route, permission, slot): `docker compose exec php php bin/console module:sync`
---
## Architecture
```
lib/Module/Helpdesk/
HelpdeskAuthorizationPolicy.php — all ability/permission constants + authorize()
HelpdeskContainerRegistrar.php — all DI bindings (add new services here)
Handler/ — scheduled job handlers
Providers/HelpdeskLayoutProvider.php — resolves can_* flags for sidebar nav
Repository/ — SQL only, no HTTP
Service/ — business logic, no HTML
pages/helpdesk/ — actions (*.php) + views (*.phtml)
templates/
aside-helpdesk-panel.phtml — sidebar navigation
helpdesk-ticket-detail.phtml — shared ticket detail body
i18n/default_de.json + default_en.json — 551 keys each, always add both
migrations/ — 011 SQL files applied via module:migrate
tests/Module/Helpdesk/Service/ — 20 test files, happy path + edge case required
web/css/helpdesk.css
web/js/ — 16 JS files, components + page scripts
```
---
## Page File Naming
| URL | Action file | View file |
|-----|-------------|-----------|
| `helpdesk/team` | `pages/helpdesk/team/index().php` | `team/index(default).phtml` |
| `helpdesk/handovers/edit/{id}` | `pages/helpdesk/handovers/edit($id).php` | `handovers/edit(default).phtml` |
| `helpdesk/dashboard` | `pages/helpdesk/dashboard/index().php` | `dashboard/index(default).phtml` |
| data endpoint | `pages/helpdesk/foo-data().php` | — |
| drawer fragment | `pages/helpdesk/foo-fragment($id).php` | `foo-fragment(none).phtml` |
**View structure pattern ** (always):
``` php
< div class = " app-details-container " >
< section >
< ? php
$titlebar = [ 'title' => t ( 'Page title' )];
require templatePath ( 'partials/app-details-titlebar.phtml' );
?>
<!-- content -->
</section>
</div>
```
---
## Permissions (10 total)
All constants in `HelpdeskAuthorizationPolicy` :
| Constant | Permission key |
|----------|---------------|
| `ABILITY_ACCESS` | `helpdesk.access` — base access, customer search + detail views |
| `ABILITY_SETTINGS_MANAGE` | `helpdesk.settings.manage` |
| `ABILITY_TEAM_WORKLOAD` | `helpdesk.team-workload.view` |
| `ABILITY_RISK_RADAR` | `helpdesk.risk-radar.view` |
| `ABILITY_SOFTWARE_PRODUCTS_MANAGE` | `helpdesk.software-products.manage` |
| `ABILITY_HANDOVERS_VIEW` | `helpdesk.handovers.view` |
| `ABILITY_HANDOVERS_CREATE` | `helpdesk.handovers.create` |
| `ABILITY_HANDOVERS_MANAGE` | `helpdesk.handovers.manage` |
| `ABILITY_UPDATES_VIEW` | `helpdesk.updates.view` |
| `ABILITY_UPDATES_MANAGE` | `helpdesk.updates.manage` |
New pages reuse existing permissions — no new permission keys without product decision.
**Action file boilerplate: **
``` php
Guard :: requireLogin ();
Guard :: requireAbilityOrForbidden ( HelpdeskAuthorizationPolicy :: ABILITY_ACCESS );
$session = app ( SessionStoreInterface :: class ) -> all ();
$tenantId = ( int ) ( $session [ 'current_tenant' ][ 'id' ] ? ? 0 );
```
---
## Services & Repositories
**Gateways (external BC API): **
- `BcODataGateway` — OData V4 queries (customers, domains, tickets, contacts)
- `BcSoapGateway` — SOAP (ticket communication feed, file downloads)
**Core services: **
- `EffectiveHelpdeskSettingsService` — resolves BC config per tenant (global + override)
- `HelpdeskOAuthTokenService` — OAuth2 token acquisition + cache
- `DebitorSearchService` — debtor search by name/number
- `DebitorDetailService` — debtor master data + contacts + tickets
- `DomainDetailService` — domain/contract data + handover/update enrichment
- `DomainListService` — domain list with security level enrichment
- `HandoverService` — handover protocol CRUD + status transitions
- `HandoverRevisionService` — revision snapshots for audit trail
- `UpdateService` — software update tracking + assignment
- `SoftwareProductService` — product catalog + handover schema
- `SoftwareProductSyncService` — BC sync (scheduled daily 02:00 CET)
- `RiskRadarService` — risk scoring engine (5 dimensions, 0– 100 score)
- `DomainSecurityLevelService` — security level get/set (niedrig/normal/hoch/kritisch)
- `SystemRecommendationEngine` — rule-based recommendation engine (no constructor)
**Repositories: **
- `HandoverRepository` — insert, findById, listPaged, updateFieldValues, updateStatus, deleteByIds, findByDomainNo, transactions
- `UpdateRepository` — insert, findByTicketNo, findByDomainNo, findAllByTenant, updateAssignment
- `SoftwareProductRepository` — upsertByCode, listPaged, updateHandoverSchema, softDeleteNotInCodes
- `HandoverRevisionRepository` — insert, getLatestRevisionNumber, listByHandover
- `DomainSecurityLevelRepository` — findByTenantAndDomainNo, listLevelsByDomainNos, upsert
- `HelpdeskTenantSettingsRepository` — findByTenantId, upsert, deleteByTenantId
- `HelpdeskTokenRepository` — findValidToken, storeToken, deleteByTenantId (encrypted)
New service → register in `HelpdeskContainerRegistrar::register()` + add `@api` annotation if called from pages.
---
## Sidebar Navigation
* * `HelpdeskLayoutProvider` ** resolves these flags → passed as `$layoutNav['helpdesk.nav']` :
`can_view_dashboard` , `can_manage_settings` , `can_view_team` , `can_view_risk_radar` ,
`can_manage_software_products` , `can_view_handovers` , `can_create_handovers` ,
`can_manage_handovers` , `can_view_updates`
* * `aside-helpdesk-panel.phtml` ** — 5 collapsible groups:
1. **Overview ** (`bi-speedometer2` ) — Dashboard
2. **Lookup ** (`bi-search` ) — Customers, Domains
3. **Monitoring ** (`bi-bar-chart-line` ) — Team workload, Risk radar
4. **Operations ** (`bi-clipboard-check` ) — Handovers, Updates
5. **Administration ** (`bi-sliders` ) — Software products, Settings
Adding a nav item = 3 steps: (1) add `can_*` flag to `HelpdeskLayoutProvider` , (2) add `$dashboardActive = navActive(...)` + item to group in `aside-helpdesk-panel.phtml` , (3) add condition to `$customersActive` override if needed.
---
## DB Schema (relevant tables)
| Table | Key columns |
|-------|-------------|
| `helpdesk_handovers` | id, tenant_id, status (draft/in_progress/completed/archived), debitor_no, domain_no |
| `helpdesk_handover_revisions` | handover_id, revision_number, snapshot_json |
| `helpdesk_updates` | id, tenant_id, status (open/assigned/done/resolved/closed), ticket_no, domain_no |
| `helpdesk_software_products` | code, name, handover_schema_json |
| `helpdesk_domain_security_levels` | tenant_id, domain_no, level, note |
| `helpdesk_tenant_settings` | tenant_id, encrypted BC connection overrides |
| `helpdesk_oauth_token_cache` | tenant_id, token (encrypted), expires_at |
Tenant scope enforced on every query — always pass `$tenantId` to repository methods.
---
## Tests
Location: `tests/Module/Helpdesk/Service/`
Pattern: `->method('foo')->willReturn([...])` — no `->with()` (PHPUnit 12 incompatible with PHPStan).
Every new/changed Service or Gateway needs happy path + at least one edge case test.
Run: `docker compose exec php vendor/bin/phpunit modules/helpdesk/tests/`
---
## JS Components
Registered via `data-app-component="<name>"` on the container div:
- `helpdesk-detail` — customer search detail
- `helpdesk-domain-detail` — domain detail
- `helpdesk-ticket` — ticket view
- `helpdesk-team` — team workload
- `helpdesk-risk-radar` — risk radar
- `helpdesk-settings` — settings config
- `helpdesk-communication` — ticket communication feed
- `helpdesk-handover-domain-select` — domain picker in handover wizard
- `helpdesk-handover-schema-editor` — handover schema JSON editor
CSS class prefix: `helpdesk-` for module-specific styles in `web/css/helpdesk.css` .