- JOIN users table in findById for created/updated-by display names
- Fix row mapping to handle merged JOIN results
- Use product name instead of debitor name in page title
- Sidebar: collapsible customer/product sections, status dropdown,
audit partial with user attribution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Require ABILITY_HANDOVERS_CREATE instead of generic ABILITY_ACCESS.
Replace manual header + echo with Router::json() for consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Avoids the separate updateFields call after insert by accepting fieldValues
in HandoverService::create() and validating/encoding them before the insert.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement handover protocols as a new entity in the helpdesk module,
allowing users to create fillable protocol records from admin-defined
software product schemas.
Key additions:
- DB migration (helpdesk_handovers table) with tenant scope
- HandoverService with status workflow (draft/in_progress/completed/archived)
- Three-tier permissions (view/create/manage)
- Two-step creation wizard (Stripe-style assistant)
- Grid.js list page with search and status filter
- Edit/detail page with aside metadata and status controls
- Reusable core autocomplete lookup component (app-lookup-field)
- Debitor lookup data endpoint for autocomplete
- Dynamic form rendering from schema snapshots
- 11 PHPUnit tests for HandoverService
- DE+EN i18n translations (48 keys each)
Also includes: PHPStan fixes (dead code removal, stale baseline cleanup),
software product edit title improvement, fieldset simplification,
and Stripe-style hover for schema preview.
Workflow: HD-HANDOVERS-001 (.agents/runs/HD-HANDOVERS-001/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auto-generate field keys and option values from labels via slugify,
add live preview panel with hover-highlight, insert-between-fields
dividers, and various spacing/styling improvements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a JSON-based schema editor as a new tab on the software product edit
page. Admins can define protocol fields (heading, paragraph, text,
textarea, number, date, checkbox, select) via a structured UI with
add/remove/reorder controls. The schema is stored as a versioned JSON
column on the product row.
Includes DB migration, server-side validation (type whitelist, key
uniqueness, key format, max 50 fields, select option checks), i18n
(DE+EN), CSS, and 10 PHPUnit tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scheduler jobs run without an active session, causing SessionStore::all()
to fail. Wrap the call in try-catch to fall back to global config (tenant
ID 0) when no session is available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Code and BC description are already shown in the aside — no need to
duplicate them as readonly inputs in the form.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use badge-list wrapper, <p><small> pattern, <hr> separators, and dt()
for date formatting — matching the departments edit page convention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use fully qualified \MintyPHP\Session in _form.phtml and remove
duplicate CSRF token call from edit template (already in form partial).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a new Software-Produkte feature to the helpdesk module that syncs
contract types (Create_SaaS_License=true) from BC OData into a local
database table with a nightly scheduler job, providing a Grid.js list
page and detail/edit page for managing custom product names.
- DB migration 003: helpdesk_software_products table (code UNIQUE key)
- BcODataGateway: FS_Contract_Types entity with SaaS filter + fallback
- SoftwareProductRepository: upsert, listPaged, softDelete, updateName
- SoftwareProductSyncService: fetch → upsert → soft-delete lifecycle
- SoftwareProductSyncJobHandler: daily at 02:00 via scheduler platform
- SoftwareProductService: web UI business logic with validation
- Permission: helpdesk.software-products.manage (nav-gated)
- List page: Grid.js with Code, BC Description, Name, Status columns
- Detail page: Code/BC Description read-only, Name editable, PRG pattern
- i18n: de + en translation keys
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a new "Domains" page to the helpdesk module that fetches domain data
from the FS_Contract_Domains OData endpoint and enriches each domain with
its contract type (PI_Header_Type) by joining with FS_Contract_Lines_Test
where Type = 'Domain'.
New files:
- domains/index().php, index(default).phtml, filter-schema.php — list page
- domains-data().php — data endpoint with PHP-side filtering/sorting
- helpdesk-domains-index.js — Grid.js via initStandardListPage()
Gateway additions:
- listDomains() — fetch all contract domains
- listDomainContractLines() — fetch domain-type contract lines for type lookup
Sidebar restructured into three collapsible groups (Lookup, Monitoring,
Administration) with per-group icons and color coding, matching the core
admin panel pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move HelpdeskTenantSettingsRepository and HelpdeskTokenRepository from
Service/ to Repository/ directory (GR-SEC-003: SQL only in repositories)
- Make AccessControl constructor require IntendedUrlService explicitly
instead of falling back to `new IntendedUrlService()` (GR-TEST-002:
no service instantiation outside factories)
- Update all imports and tests accordingly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Core code (lib/) no longer imports any MintyPHP\Module\Audit\* classes directly.
New ApiAuditServiceInterface and ApiSystemAuditReporterInterface follow the
existing pattern (interface + null implementation in core, concrete binding
from module registrar). DoctorRunner and helpers/app.php now resolve through
DI interfaces, ensuring the audit module remains fully optional.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add missing i18n key "LDAP attribute map could not be encoded" (de+en)
- Replace raw font-weight/line-height with design tokens in app-topbar.css
- Fix import ordering in SchedulerRunService.php (php-cs-fixer)
- Fix PHPStan mock chain errors in DebitorDetailServiceTest and TenantSsoServiceTest (PHPUnit 12 API)
- Simplify redundant comparison in NotificationService dedupe window
- Add PHPStan type hint for mock callback array in SchedulerJobFailedNotificationListenerTest
- Fix undefined variable and add flow-analysis suppression in login().php
- Increase dev PHP memory_limit to 1G for PHPStan (prod stays 256M)
All 6 quality gates now pass: QG-001 (1618 tests), QG-002 (0 errors),
QG-003 (6630 assertions), QG-006 (0 fixable), QG-008, QG-009.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds 11 tests for BcODataGateway covering getMeetingsForCustomer (empty,
whitespace, not-configured), OData injection rejection for all customerNo
methods (meetings, contract lines, escalation tickets, controlling), and
valid input acceptance. Also adds meetingsKey format and inclusion tests
for DebitorCacheControl.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces escapeODataTrustedLiteral with escapeODataStrictUserInput for all
customerNo parameters that originate from HTTP request input. The trusted
literal escape (quote-only) remains for BC-sourced values like customerName.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a meetings section to the debitor detail page, fetching meeting data from BC OData API with cache control support. Includes timeline UI for upcoming and past meetings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restructure helpdesk tenant settings into per-connection config with
improved token handling. Update risk radar data endpoint and UI.
Clean up ImageUploadTrait and update architecture contract tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce data-icon-swap attribute with paired outline/fill <i> elements.
CSS toggles to filled icon on hover and active state. Module slots
auto-derive fill variant from icon name, with explicit icon_fill override
for icons without a fill counterpart (e.g. bi-headset). Add enforcement
script bin/icon-swap-check.sh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use Stripe pattern: outline icons by default, filled for active state
(bookmarks bookmarked, notifications unread). Remove chevron arrows
from tenant/user dropdowns. Add max-width with ellipsis to breadcrumb
items to prevent overflow.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
createForTenantAdminUsersFromMessage relies on listActiveUserIdsByTenantId
which returns empty results in CLI context (scheduler). Instead, iterate
admin users directly and call createFromMessage per user per tenant.
Verified end-to-end: notification created, dedup blocks duplicates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change DEDUPE_TYPES from bool to int|true — int value overrides the
default 30-min window. scheduler.job_failed uses 1440 min (24h) so
admins receive at most one notification per failed job per day.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ?ModuleEventDispatcher to SchedulerRunService (fail-open, nullable).
Dispatch scheduler.job_failed event when a job fails with payload:
job_id, job_key, label, error_code, error_message, trigger_type.
New SchedulerJobFailedNotificationListener in notifications module
creates in-app admin notifications for users with JOBS_MANAGE
permission, scoped per tenant, with 30-min dedup per job_key.
Also fixes audit module manifest: system_audit_purge job_key was
mismatched (audit_system_purge vs system_audit_purge in DB).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add architecture test ViewLayerOutputEscapingContractTest that flags
raw <?php echo in .phtml files. Legitimate uses (pre-built ARIA attrs
from navActive(), hardcoded role values, pre-rendered HTML fragments)
must carry an inline // raw-html-ok: reason marker.
Annotated all 11 existing raw echo sites with justification markers.
Added guard to catalog, enforcement map (automated), CLAUDE.md security
section, and never-do-this list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce per-tenant override for helpdesk BC connection config.
New table helpdesk_tenant_settings stores tenant-specific credentials
with encrypted secrets (AES-256-GCM). EffectiveHelpdeskSettingsService
resolves global vs tenant config per request. Settings UI extended with
tenant override tab, toggle, and dual editing mode.
All runtime consumers (OData, SOAP, OAuth) read through the new
resolver. Token cache flushed on any config change. Default behavior
unchanged — tenants use global config until override explicitly enabled.
Also includes risk radar UI refinements: Stripe-style card redesign
with accent borders and score pills, removal of redundant KPI tiles
and search, and filtering of zero-score entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- margin-bottom: 0 on cards to override article shell default
- Level thresholds stricter: high >= 55 (was 70), medium >= 25 (was 40)
so fewer customers appear as 'low risk' and issues surface earlier
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove background circle, just show the score as a large colored
number. Lighter, more Stripe-like.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stripe-style layout: large colored score circle on the left, customer
name + risk level label on the right. One-line key facts below
separated by middot (e.g. '5 open · 2 critical · 1 SLA · ↑3').
Removed border-left color accent, pill list, and metric row.
Three visual levels: score (eye-catching), name+level (context),
facts (detail). Clean scannable layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each dimension shows 'points / max' (e.g. '18 / 35') making the
score composition immediately understandable. Total row sums to
the final 'score / 100'. Replaces abstract percentage column.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Resolution time measures the past, not current risk. The remaining
4 dimensions (open pressure 35%, trend 30%, SLA overdue 25%,
inactivity 10%) are all current and actionable. Removes resolution
aggregation, median calculation, and weight redistribution logic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove duplicate metrics row and reason list. Replace with a single
row of compact pills: '5 open', '2 critical' (orange), '1 SLA' (orange),
'↑3' (orange) or '↓2'. No bullet points — pills as flex-wrap row.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove '• ' prefix from reason list items (CSS list-style handles it)
- Replace abstract dimension bars in dialog with a clear table showing
dimension name, score (0-100), and weighted contribution per row
- Total row shows final risk score
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cards now show plain-language reasons instead of abstract progress bars:
'8 open, 3 critical', '↑4 more than resolved', '2 SLA breaches',
'Oldest ticket 12d'. Instantly understandable without knowing the
scoring formula. Driver bars remain in the detail dialog for the
full dimension breakdown.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show ↑3 (growing) or ↓2 (shrinking) instead of '+3 net' / '-2 net'.
Same visual language as the team dashboard trend arrows.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search input now comes first (left), period segment control after.
Both in a horizontal flex row on the same line.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New portfolio view scoring customers 0-100 across five dimensions:
- Open pressure (30%): open + critical ticket count
- Trend (25%): net ticket flow (created - closed) in period
- SLA overdue (20%): tickets exceeding escalation targets
- Resolution time (15%): median hours to close (null = neutral)
- Inactivity (10%): age of oldest open ticket
Card grid with score badges (color-coded high/medium/low), metric
pills, driver bars. Click opens detail dialog with all dimensions
and open ticket list. Clientside search filter.
PBI_LV_Tickets with client-driven paging ($skip/$top, hardcap 5000).
Escalation definitions cached separately (30min TTL). Truncated
banner when data is capped.
New: RiskRadarService, getTicketsForRiskRadar(), 13 PHPUnit tests,
permission helpdesk.risk-radar.view, 2 routes, i18n de+en.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change query_json column from TEXT to JSON, ip/user_agent to CHAR(64)
for PII redaction compliance
- Update repository, service, and views for hardened schema
- Add architecture contract test for security logging redaction
- Add search resource provider test coverage
- Include DB migration script for existing installs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The timeline indent is handled by the parent activity container,
not individual file elements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FsLightbox uses data-type to skip content detection. Combined with
Cache-Control: max-age=3600 on the proxy, the browser should serve
the lightbox image from cache instead of making a second SOAP call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Skeleton shimmer placeholder shown while SOAP image loads
- onload hides skeleton, onerror hides entire preview
- data-fslightbox="ticket-files" on image link enables FsLightbox
overlay (auto-detected by global MutationObserver)
- cursor: zoom-in on preview images
- Gallery name "ticket-files" groups all images in the feed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proxy endpoint supports ?inline=1 parameter that serves images with
correct Content-Type and Content-Disposition: inline. Uses fpassthru
via php://memory stream to bypass MintyPHP Analyzer restrictions.
Frontend always attempts image preview via <img> tag. Non-image files
gracefully hide the preview via onerror handler. Download link shown
for all file types regardless.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OData TicketLog doesn't expose the original filename (DataText field).
When the derived filename has no extension, detect the file type from
magic bytes (PNG, JPEG, GIF, PDF, ZIP) and append the correct extension.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BC's GetTicketFile requires a valid contactNo for authorization.
The endpoint now accepts ticketNo, loads the ticket via OData to get
Current_Contact_Name, looks up the contact's No, and passes it to
the SOAP call. Frontend includes ticketNo in the file download URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show the actual BC error message instead of just 'HTTP 500' to help
diagnose authentication and parameter issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>