Commit Graph

335 Commits

Author SHA1 Message Date
fs
312d43d9d9 feat(scheduler): delete orphaned jobs and block editing of unregistered jobs
Adds deleteOrphanedJobs() to clean up stale job_keys during ensureSystemJobs, and prevents editing jobs that are no longer registered in the runtime registry.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 14:13:09 +02:00
fs
1c784efd5c feat(auth): add domain-based SSO discovery for unprovisioned users
Users who exist in Microsoft Entra ID or LDAP but have no local account
were blocked at the email-first login step because the system required a
local user record before showing SSO options. This adds a domain-based
fallback: when the email is unknown locally, the system checks tenant SSO
configurations for matching allowed_domains and presents SSO-only login
options, enabling the existing JIT provisioning to trigger on callback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 14:35:57 +02:00
fs
cacd1fb6d1 feat(helpdesk): refactor multi-tenant BC settings and risk radar improvements
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>
2026-04-06 22:22:17 +02:00
fs
1582844b01 fix(ui): prevent dialog close button from shifting down on long titles
Replace float-based header layout with flexbox (align-items: flex-start)
so the close button stays pinned top-right regardless of title length.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 22:22:08 +02:00
fs
a3d05baffa feat(ui): add outline/fill icon-swap pattern for aside icon bar
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>
2026-04-06 22:22:00 +02:00
fs
ae509c607b refactor(ui): consistent outline/fill icons in topbar, remove dropdown chevrons, truncate breadcrumbs
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>
2026-04-06 22:21:50 +02:00
fs
a3f270408b feat(layout): migrate to full-width topbar with mobile drawer navigation
Move topbar outside the grid container as a standalone sticky header
(Shopify/Stripe pattern). Sidebar and icon bar use position:sticky
relative to topbar height. Mobile uses drawer pattern with backdrop,
focus trap, ESC close, and scroll lock. Tenant branding moved from
sidebar header into the topbar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 22:21:39 +02:00
fs
fe88e748a3 fix(notifications): use createFromMessage directly for CLI-safe scheduler notifications
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>
2026-04-06 12:38:47 +02:00
fs
34892aff6f fix(notifications): use per-type dedup window, 24h for scheduler failures
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>
2026-04-06 12:29:04 +02:00
fs
97ff3ce135 feat(scheduler): dispatch event on job failure, notify admins via notifications module
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>
2026-04-06 12:26:54 +02:00
fs
576a0c51cd feat(guards): add GR-SEC-010 — output escaping enforcement in views
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>
2026-04-06 12:08:25 +02:00
fs
9a4d59eb4c feat: add reusable app-file-upload component with drag-and-drop and preview
Replace bare <input type="file"> across all upload locations with a
card-based dropzone component via shared partial. Three visual states:
current server file (thumbnail + Replace/Delete), empty dropzone, and
pending file preview (local FileReader thumbnail + metadata). Delete
actions use data-confirm-message for confirmation dialog.

Centralized as templates/partials/app-file-upload.phtml to prevent
markup drift — documented as deliberate exception to plain-HTML inputs
convention in CLAUDE.md and developer checklist.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:58:14 +02:00
fs
ea786f5341 feat(helpdesk): add multi-tenant BC connection settings with risk radar refinements
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>
2026-04-06 10:29:59 +02:00
fs
97dbc7f57b fix(helpdesk): reset card margin-bottom and tighten risk level thresholds
- 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>
2026-04-06 00:55:41 +02:00
fs
4dfe98f40a fix(helpdesk): replace bulky score circle with clean colored number
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>
2026-04-06 00:54:13 +02:00
fs
4d23feb555 feat(helpdesk): redesign risk cards with dominant score and clear hierarchy
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>
2026-04-06 00:52:23 +02:00
fs
1128197022 fix(helpdesk): show risk score as points/budget per dimension in dialog
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>
2026-04-06 00:49:34 +02:00
fs
28cb60efb3 refactor(helpdesk): remove resolution time dimension from risk score
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>
2026-04-06 00:47:58 +02:00
fs
d5d1e22e87 fix(helpdesk): consolidate card content into compact fact pills
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>
2026-04-06 00:43:35 +02:00
fs
caa1e430a3 fix(helpdesk): remove double bullets, replace dialog bars with score table
- 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>
2026-04-06 00:39:35 +02:00
fs
e279e469da feat(helpdesk): replace abstract driver bars with concrete reason texts
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>
2026-04-06 00:36:49 +02:00
fs
53db166916 fix(helpdesk): replace cryptic net flow label with arrow indicator
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>
2026-04-06 00:34:01 +02:00
fs
201b00c8f5 fix(helpdesk): stack search above period selector vertically
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:32:05 +02:00
fs
e684d20be0 fix(helpdesk): move search above period selector in risk radar toolbar
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>
2026-04-06 00:31:24 +02:00
fs
23503049d1 fix(helpdesk): fix card coloring, dialog structure, search alignment
- Remove role=button from cards (caused Core button styling). Keep
  tabindex + keydown for keyboard access.
- Restructure dialog to Core pattern: dialog > article.app-dialog-lg
  > header > .close (uses Core close icon and positioning).
- Stack search input below segment control, left-aligned, full width
  up to 20rem.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:30:13 +02:00
fs
ba356d2d61 fix(helpdesk): address code review findings for Risk Radar
- CR-001/002: Replace hardcoded 'critical', 'Ticket', 'Status' with
  i18n data-label attributes
- CR-004: Add tabindex, role=button, keydown handler to risk cards
  for keyboard accessibility
- CR-005: Add aria-labelledby on detail dialog

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:28:09 +02:00
fs
5a4974909b feat(helpdesk): add Risk Radar dashboard with customer risk scoring
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>
2026-04-06 00:24:48 +02:00
fs
c6c5d06936 feat(audit): harden API audit log redaction and schema
- 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>
2026-04-05 23:20:55 +02:00
fs
c2a7d6b789 fix(helpdesk): remove redundant margin-left from file preview and link
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>
2026-04-05 23:09:31 +02:00
fs
2d016afb1f fix(helpdesk): add data-type=image hint for FsLightbox cache reuse
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>
2026-04-05 23:05:47 +02:00
fs
8187c9b4dd feat(helpdesk): add skeleton loader and lightbox for image previews
- 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>
2026-04-05 23:03:13 +02:00
fs
5730a9a7b7 feat(helpdesk): add inline image preview for ticket file attachments
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>
2026-04-05 23:00:06 +02:00
fs
f40b23d2b0 fix(helpdesk): detect file extension from magic bytes when filename has none
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>
2026-04-05 22:58:12 +02:00
fs
f1c9c414a6 fix(helpdesk): resolve contactNo from ticket for SOAP file authorization
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>
2026-04-05 22:56:11 +02:00
fs
2b2f41ebab fix(helpdesk): extract SOAP fault detail from GetTicketFile error responses
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>
2026-04-05 22:55:12 +02:00
fs
c2eba3e671 fix(helpdesk): add empty ticketNo field to GetTicketFile SOAP envelope
BC expects all four parameters in the envelope even if ticketNo is
empty. The entry number alone should be sufficient to identify the file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:54:22 +02:00
fs
f974ca4fd4 fix(helpdesk): add security headers and size limit to file proxy
- X-Content-Type-Options: nosniff prevents MIME sniffing
- Content-Security-Policy: sandbox prevents script execution
- 20 MB size limit on decoded file data (413 on exceed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:53:46 +02:00
fs
2c2392b398 fix(helpdesk): address code review findings for ticket files
- Use Router::download() instead of die() to avoid Analyzer rejection
- Add 5 PHPUnit tests for BcSoapGateway::getTicketFile() (F-001)
- Add test for file metadata on communication entries (F-002)
- Add rel=noopener noreferrer on target=_blank links (F-003)
- Add aria-hidden on download icon, aria-label on image link (F-003)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:53:17 +02:00
fs
6ce5f9a547 fix(helpdesk): use die() instead of echo for file proxy output
MintyPHP Analyzer rejects echo in action files. Use die() which is
the same pattern as Router::download() internally.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:50:28 +02:00
fs
792f706d9e feat(helpdesk): add ticket file download and inline image preview
New SOAP method getTicketFile() on BcSoapGateway calls BC's
GetTicketFile to retrieve file attachments as Base64, decodes in
memory and returns binary data without writing to disk.

New proxy endpoint helpdesk/ticket-file-data streams files directly
to the browser with correct MIME type. Images served inline, other
files as download attachment.

TicketCommunicationService now attaches file_entry_no and file_name
to file-type events. Frontend renders download links with bi-download
icon and inline image previews for jpg/png/gif attachments.

Works in both ticket detail and debitor detail communication feeds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:49:01 +02:00
fs
659a6d71c5 fix(helpdesk): only resolve most recent support code on reassigned tickets
When a ticket was reassigned (multiple support codes), all codes were
mapped to the current Support_User_Name — replacing the previous
agent's name with the new one on older entries.

Now: if multiple support codes exist, only the code from the most
recent entry is mapped to the current name. Older codes remain as
abbreviations, preserving the actual history of who worked on what.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:26:25 +02:00
fs
fbe8140937 fix(helpdesk): drop redundant SOAP message entries when OData has them
When the SOAP message map is applied to OData entries, the message text
is already embedded in the OData entries. The separate SOAP message
entries then cause duplicates with different actor/timestamp metadata.

Now: when soapMessageMap was used, filter out SOAP entries with
type_variant 'message' since they are already represented in OData.
Non-message SOAP entries (status, activity) are kept.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:21:28 +02:00
fs
640da8245d feat(helpdesk): add refresh button to ticket detail titlebar
Adds 'Refresh data' button that re-fetches the communication feed
with ?refresh=1 to bypass the session cache. Shows loading skeleton
during reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:19:15 +02:00
fs
66be390fa9 fix(helpdesk): remove actor from dedup signature to prevent duplicate entries
SOAP entries often have an empty actor while OData entries have a code
like 'NKS'. After name resolution, one becomes 'Nikita Soldatov' and
the other stays empty — producing different signatures and causing
duplicates. The actor is not a reliable dedup field since the same
event can have different actor representations across sources.

Dedup now uses: ticket_no + timestamp_iso + type + message_hash.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:13:04 +02:00
fs
be4d89b6fe fix(helpdesk): remove 'Ticket communication' section title entirely
Remove the heading and the CSS override — communication feed starts
directly without a title in the ticket detail view.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:07:16 +02:00
fs
2bb01e1eab fix(helpdesk): hide section title divider line in ticket detail communication
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:06:28 +02:00
fs
c958f68738 fix(helpdesk): replace ticket detail KPI tiles with unified label/value list
Remove the Status and Age KPI metric tiles from the ticket aside.
Merge all information into a single definition list: Status, Description,
Company, Contact, Category, Support, Created, Last activity, Age.
Consistent with the label/value pattern used across all detail views.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:05:20 +02:00
fs
a3dedde7bf fix(helpdesk): use single continuous timeline line instead of per-item segments
The timeline line between communication entries had gaps because each
item drew its own ::before segment with fixed overlap that didn't match
the feed gap. Replace with a single continuous ::before line on the
feed container. Item dots (::after) remain per-item. Works robustly
regardless of gap size or item height.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:04:31 +02:00
fs
d6835d3b7b fix(helpdesk): remove contracts KPI section from support dashboard
The Contracts & products KPI tiles (active contracts, monthly volume,
support hours, next invoicing) distract from the ticket-focused support
view. Contract data remains available in the dedicated Sales tab.

Removes: section HTML, skeleton placeholder, JS render function + call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:01:50 +02:00
fs
bdeb2b9970 fix(helpdesk): clicking sidebar icon opens nav panel instead of navigating
Set href to empty string so the Helpdesk sidebar icon toggles the
navigation panel open/closed without navigating to the customers page.
Same pattern as the bookmarks module.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 21:49:24 +02:00