2026-04-02 17:48:27 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace MintyPHP\Module\Helpdesk;
|
|
|
|
|
|
|
|
|
|
use MintyPHP\App\AppContainer;
|
|
|
|
|
use MintyPHP\App\Container\ContainerRegistrar;
|
2026-04-02 21:46:01 +02:00
|
|
|
use MintyPHP\Http\SessionStoreInterface;
|
2026-04-02 17:48:27 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\BcODataGateway;
|
2026-04-03 16:45:41 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\BcSoapGateway;
|
2026-04-02 17:48:27 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorDetailService;
|
|
|
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorSearchService;
|
2026-04-16 13:21:40 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\DomainDetailService;
|
feat(core): generic CSV export primitive + helpdesk domains consumer
Add a reusable CSV-export building block to the core so any Grid.js list
page can gain a filter/sort-aware download with two clicks of glue.
Core primitive:
- core/Service/Export/CsvExportService: flavor-aware writer (plain CSV
or Excel-compatible UTF-8+BOM+semicolon), with OWASP-aligned formula-
injection escape (=, @, +, -, TAB, CR) applied to both rows *and*
headers. Value objects for columns (ExportColumn) carry an extractor
closure and an allowSignedNumeric flag for phone-number-shaped cells.
- core/Support/helpers/export.php: thin HTTP layer (exportSendCsv,
exportCapLimit, exportResolveFlavor, exportRequireGetRequest) reusing
the requestInput() contract for GR-CORE-003 consistency. Filename is
sanitized and length-clamped; callers must still enforce auth.
- templates/partials/app-list-export-dropdown.phtml: zero-config
<details class="dropdown"> with CSV + Excel triggers.
- web/js/core/app-list-export.js: initListExport({ gridConfig, exportUrl })
mirrors the current grid filters + sort onto the export URL and
navigates, preserving session cookies for download.
First consumer — Helpdesk domains:
- DomainListService extracts the shared filter/enrich/sort logic from
domains-data so the grid endpoint and the new domains/export endpoint
cannot drift.
- domains/export endpoint delegates to DomainListService, declares
ExportColumns (with translated level labels), and exits via
exportSendCsv.
- domains-data now ~20 lines, delegating to DomainListService.
Tests:
- CsvExportServiceTest (10 cases): both flavors, BOM/no-BOM, formula
escapes incl. TAB/CR, header escape, signed-numeric allowlist,
quoting, multiline, empty columns, generator-compatible iterable.
- ExportHelpersTest (5 cases): exportCapLimit bounds.
- DomainListServiceTest (8 cases): filter, search, "all" sentinel,
sort, paging, enrichment, BC failure, tenant-scoped security filter.
Gates: PHPUnit green, PHPStan clean on touched files, module:sync ok.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:57:19 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\DomainListService;
|
2026-04-06 10:29:59 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\EffectiveHelpdeskSettingsService;
|
2026-04-02 17:48:27 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\HelpdeskOAuthTokenService;
|
|
|
|
|
use MintyPHP\Module\Helpdesk\Service\HelpdeskSettingsGateway;
|
2026-04-06 10:29:59 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\HelpdeskTenantSettingsGateway;
|
2026-04-13 19:28:07 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Repository\HelpdeskTenantSettingsRepository;
|
|
|
|
|
use MintyPHP\Module\Helpdesk\Repository\HelpdeskTokenRepository;
|
2026-04-14 22:16:21 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\SoftwareProductService;
|
|
|
|
|
use MintyPHP\Module\Helpdesk\Service\SoftwareProductSyncService;
|
2026-04-04 18:34:03 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\SystemRecommendationEngine;
|
2026-04-03 16:45:41 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\TicketCommunicationService;
|
2026-04-14 22:16:21 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Handler\SoftwareProductSyncJobHandler;
|
2026-04-21 13:43:16 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Repository\DomainSecurityLevelRepository;
|
2026-04-15 20:42:41 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
|
feat(helpdesk): add handover revision history with timeline, diff, and restore
Every save creates an immutable revision snapshot. The aside shows a
commit-graph-style timeline (newest first) with vertical line, circle
nodes, version numbers, change type, user, and date. Clicking a past
revision renders it readonly with inline git-diff-style highlights
(changed/added/removed with tinted backgrounds). Compare mode allows
diffing any two arbitrary revisions. MANAGE users can restore old
versions, which creates a new revision preserving full history.
New: HandoverRevisionService, HandoverRevisionRepository,
migration 006, 14 PHPUnit tests, DE/EN translations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:14:40 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Repository\HandoverRevisionRepository;
|
2026-04-14 22:16:21 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Repository\SoftwareProductRepository;
|
2026-04-16 13:21:40 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Repository\UpdateRepository;
|
2026-04-21 13:43:16 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\DomainSecurityLevelService;
|
2026-06-01 14:35:03 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\HandoverNotificationService;
|
feat(helpdesk): add handover revision history with timeline, diff, and restore
Every save creates an immutable revision snapshot. The aside shows a
commit-graph-style timeline (newest first) with vertical line, circle
nodes, version numbers, change type, user, and date. Clicking a past
revision renders it readonly with inline git-diff-style highlights
(changed/added/removed with tinted backgrounds). Compare mode allows
diffing any two arbitrary revisions. MANAGE users can restore old
versions, which creates a new revision preserving full history.
New: HandoverRevisionService, HandoverRevisionRepository,
migration 006, 14 PHPUnit tests, DE/EN translations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:14:40 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\HandoverRevisionService;
|
2026-04-15 20:42:41 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\HandoverService;
|
2026-06-01 14:35:03 +02:00
|
|
|
use MintyPHP\Service\Mail\MailService;
|
2026-04-16 13:21:40 +02:00
|
|
|
use MintyPHP\Module\Helpdesk\Service\UpdateService;
|
2026-04-02 17:48:27 +02:00
|
|
|
use MintyPHP\Service\Access\PermissionService;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingServicesFactory;
|
|
|
|
|
use MintyPHP\Service\Settings\SettingsMetadataGateway;
|
|
|
|
|
|
|
|
|
|
final class HelpdeskContainerRegistrar implements ContainerRegistrar
|
|
|
|
|
{
|
|
|
|
|
public function register(AppContainer $container): void
|
|
|
|
|
{
|
|
|
|
|
$container->set(HelpdeskAuthorizationPolicy::class, static fn (AppContainer $c): HelpdeskAuthorizationPolicy => new HelpdeskAuthorizationPolicy(
|
|
|
|
|
$c->get(PermissionService::class)
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(HelpdeskSettingsGateway::class, static fn (AppContainer $c): HelpdeskSettingsGateway => new HelpdeskSettingsGateway(
|
|
|
|
|
$c->get(SettingsMetadataGateway::class),
|
|
|
|
|
$c->get(SettingServicesFactory::class)->createSettingsCryptoGateway()
|
|
|
|
|
));
|
|
|
|
|
|
2026-04-06 10:29:59 +02:00
|
|
|
$container->set(HelpdeskTenantSettingsRepository::class, static fn (): HelpdeskTenantSettingsRepository => new HelpdeskTenantSettingsRepository());
|
|
|
|
|
|
|
|
|
|
$container->set(HelpdeskTenantSettingsGateway::class, static fn (AppContainer $c): HelpdeskTenantSettingsGateway => new HelpdeskTenantSettingsGateway(
|
|
|
|
|
$c->get(HelpdeskTenantSettingsRepository::class),
|
|
|
|
|
$c->get(SettingServicesFactory::class)->createSettingsCryptoGateway()
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(EffectiveHelpdeskSettingsService::class, static fn (AppContainer $c): EffectiveHelpdeskSettingsService => new EffectiveHelpdeskSettingsService(
|
2026-04-02 21:46:01 +02:00
|
|
|
$c->get(HelpdeskSettingsGateway::class),
|
2026-04-06 10:29:59 +02:00
|
|
|
$c->get(HelpdeskTenantSettingsGateway::class),
|
|
|
|
|
$c->get(SessionStoreInterface::class)
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(BcODataGateway::class, static fn (AppContainer $c): BcODataGateway => new BcODataGateway(
|
|
|
|
|
$c->get(EffectiveHelpdeskSettingsService::class),
|
2026-04-02 21:46:01 +02:00
|
|
|
$c->get(HelpdeskOAuthTokenService::class),
|
|
|
|
|
$c->get(SessionStoreInterface::class)
|
2026-04-02 17:48:27 +02:00
|
|
|
));
|
|
|
|
|
|
2026-04-03 16:45:41 +02:00
|
|
|
$container->set(BcSoapGateway::class, static fn (AppContainer $c): BcSoapGateway => new BcSoapGateway(
|
2026-04-06 10:29:59 +02:00
|
|
|
$c->get(EffectiveHelpdeskSettingsService::class),
|
2026-04-03 16:45:41 +02:00
|
|
|
$c->get(HelpdeskOAuthTokenService::class),
|
|
|
|
|
$c->get(SessionStoreInterface::class)
|
|
|
|
|
));
|
|
|
|
|
|
2026-04-02 17:48:27 +02:00
|
|
|
$container->set(HelpdeskTokenRepository::class, static fn (AppContainer $c): HelpdeskTokenRepository => new HelpdeskTokenRepository(
|
|
|
|
|
$c->get(SettingServicesFactory::class)->createSettingsCryptoGateway()
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(HelpdeskOAuthTokenService::class, static fn (AppContainer $c): HelpdeskOAuthTokenService => new HelpdeskOAuthTokenService(
|
2026-04-06 10:29:59 +02:00
|
|
|
$c->get(EffectiveHelpdeskSettingsService::class),
|
2026-04-02 17:48:27 +02:00
|
|
|
$c->get(HelpdeskTokenRepository::class)
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(DebitorSearchService::class, static fn (AppContainer $c): DebitorSearchService => new DebitorSearchService(
|
|
|
|
|
$c->get(BcODataGateway::class),
|
2026-04-06 10:29:59 +02:00
|
|
|
$c->get(EffectiveHelpdeskSettingsService::class)
|
2026-04-02 17:48:27 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(DebitorDetailService::class, static fn (AppContainer $c): DebitorDetailService => new DebitorDetailService(
|
|
|
|
|
$c->get(BcODataGateway::class),
|
2026-04-06 10:29:59 +02:00
|
|
|
$c->get(EffectiveHelpdeskSettingsService::class)
|
2026-04-02 17:48:27 +02:00
|
|
|
));
|
2026-04-03 16:45:41 +02:00
|
|
|
|
2026-04-16 13:21:40 +02:00
|
|
|
$container->set(DomainDetailService::class, static fn (AppContainer $c): DomainDetailService => new DomainDetailService(
|
|
|
|
|
$c->get(BcODataGateway::class),
|
|
|
|
|
$c->get(EffectiveHelpdeskSettingsService::class),
|
|
|
|
|
$c->get(HandoverRepository::class),
|
|
|
|
|
$c->get(UpdateRepository::class)
|
|
|
|
|
));
|
|
|
|
|
|
2026-04-03 16:45:41 +02:00
|
|
|
$container->set(TicketCommunicationService::class, static fn (AppContainer $c): TicketCommunicationService => new TicketCommunicationService(
|
|
|
|
|
$c->get(BcSoapGateway::class),
|
|
|
|
|
$c->get(BcODataGateway::class)
|
|
|
|
|
));
|
2026-04-04 18:34:03 +02:00
|
|
|
|
|
|
|
|
$container->set(SystemRecommendationEngine::class, static fn (): SystemRecommendationEngine => new SystemRecommendationEngine());
|
2026-04-14 22:16:21 +02:00
|
|
|
|
|
|
|
|
$container->set(SoftwareProductRepository::class, static fn (): SoftwareProductRepository => new SoftwareProductRepository());
|
|
|
|
|
|
|
|
|
|
$container->set(SoftwareProductSyncService::class, static fn (AppContainer $c): SoftwareProductSyncService => new SoftwareProductSyncService(
|
|
|
|
|
$c->get(BcODataGateway::class),
|
|
|
|
|
$c->get(SoftwareProductRepository::class),
|
|
|
|
|
$c->get(HelpdeskSettingsGateway::class)
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(SoftwareProductService::class, static fn (AppContainer $c): SoftwareProductService => new SoftwareProductService(
|
|
|
|
|
$c->get(SoftwareProductRepository::class)
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
$container->set(SoftwareProductSyncJobHandler::class, static fn (AppContainer $c): SoftwareProductSyncJobHandler => new SoftwareProductSyncJobHandler(
|
|
|
|
|
$c->get(SoftwareProductSyncService::class)
|
|
|
|
|
));
|
2026-04-15 20:42:41 +02:00
|
|
|
|
2026-04-21 13:43:16 +02:00
|
|
|
$container->set(DomainSecurityLevelRepository::class, static fn (): DomainSecurityLevelRepository => new DomainSecurityLevelRepository());
|
|
|
|
|
|
|
|
|
|
$container->set(DomainSecurityLevelService::class, static fn (AppContainer $c): DomainSecurityLevelService => new DomainSecurityLevelService(
|
|
|
|
|
$c->get(DomainSecurityLevelRepository::class)
|
|
|
|
|
));
|
|
|
|
|
|
feat(core): generic CSV export primitive + helpdesk domains consumer
Add a reusable CSV-export building block to the core so any Grid.js list
page can gain a filter/sort-aware download with two clicks of glue.
Core primitive:
- core/Service/Export/CsvExportService: flavor-aware writer (plain CSV
or Excel-compatible UTF-8+BOM+semicolon), with OWASP-aligned formula-
injection escape (=, @, +, -, TAB, CR) applied to both rows *and*
headers. Value objects for columns (ExportColumn) carry an extractor
closure and an allowSignedNumeric flag for phone-number-shaped cells.
- core/Support/helpers/export.php: thin HTTP layer (exportSendCsv,
exportCapLimit, exportResolveFlavor, exportRequireGetRequest) reusing
the requestInput() contract for GR-CORE-003 consistency. Filename is
sanitized and length-clamped; callers must still enforce auth.
- templates/partials/app-list-export-dropdown.phtml: zero-config
<details class="dropdown"> with CSV + Excel triggers.
- web/js/core/app-list-export.js: initListExport({ gridConfig, exportUrl })
mirrors the current grid filters + sort onto the export URL and
navigates, preserving session cookies for download.
First consumer — Helpdesk domains:
- DomainListService extracts the shared filter/enrich/sort logic from
domains-data so the grid endpoint and the new domains/export endpoint
cannot drift.
- domains/export endpoint delegates to DomainListService, declares
ExportColumns (with translated level labels), and exits via
exportSendCsv.
- domains-data now ~20 lines, delegating to DomainListService.
Tests:
- CsvExportServiceTest (10 cases): both flavors, BOM/no-BOM, formula
escapes incl. TAB/CR, header escape, signed-numeric allowlist,
quoting, multiline, empty columns, generator-compatible iterable.
- ExportHelpersTest (5 cases): exportCapLimit bounds.
- DomainListServiceTest (8 cases): filter, search, "all" sentinel,
sort, paging, enrichment, BC failure, tenant-scoped security filter.
Gates: PHPUnit green, PHPStan clean on touched files, module:sync ok.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 21:57:19 +02:00
|
|
|
$container->set(DomainListService::class, static fn (AppContainer $c): DomainListService => new DomainListService(
|
|
|
|
|
$c->get(BcODataGateway::class),
|
|
|
|
|
$c->get(DomainSecurityLevelService::class)
|
|
|
|
|
));
|
|
|
|
|
|
2026-04-15 20:42:41 +02:00
|
|
|
$container->set(HandoverRepository::class, static fn (): HandoverRepository => new HandoverRepository());
|
|
|
|
|
|
feat(helpdesk): add handover revision history with timeline, diff, and restore
Every save creates an immutable revision snapshot. The aside shows a
commit-graph-style timeline (newest first) with vertical line, circle
nodes, version numbers, change type, user, and date. Clicking a past
revision renders it readonly with inline git-diff-style highlights
(changed/added/removed with tinted backgrounds). Compare mode allows
diffing any two arbitrary revisions. MANAGE users can restore old
versions, which creates a new revision preserving full history.
New: HandoverRevisionService, HandoverRevisionRepository,
migration 006, 14 PHPUnit tests, DE/EN translations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:14:40 +02:00
|
|
|
$container->set(HandoverRevisionRepository::class, static fn (): HandoverRevisionRepository => new HandoverRevisionRepository());
|
|
|
|
|
|
|
|
|
|
$container->set(HandoverRevisionService::class, static fn (AppContainer $c): HandoverRevisionService => new HandoverRevisionService(
|
|
|
|
|
$c->get(HandoverRevisionRepository::class),
|
|
|
|
|
$c->get(HandoverRepository::class)
|
|
|
|
|
));
|
|
|
|
|
|
2026-06-01 14:35:03 +02:00
|
|
|
$container->set(HandoverNotificationService::class, static fn (AppContainer $c): HandoverNotificationService => new HandoverNotificationService(
|
|
|
|
|
$c->get(MailService::class)
|
|
|
|
|
));
|
|
|
|
|
|
2026-04-15 20:42:41 +02:00
|
|
|
$container->set(HandoverService::class, static fn (AppContainer $c): HandoverService => new HandoverService(
|
|
|
|
|
$c->get(HandoverRepository::class),
|
feat(helpdesk): add handover revision history with timeline, diff, and restore
Every save creates an immutable revision snapshot. The aside shows a
commit-graph-style timeline (newest first) with vertical line, circle
nodes, version numbers, change type, user, and date. Clicking a past
revision renders it readonly with inline git-diff-style highlights
(changed/added/removed with tinted backgrounds). Compare mode allows
diffing any two arbitrary revisions. MANAGE users can restore old
versions, which creates a new revision preserving full history.
New: HandoverRevisionService, HandoverRevisionRepository,
migration 006, 14 PHPUnit tests, DE/EN translations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:14:40 +02:00
|
|
|
$c->get(SoftwareProductService::class),
|
2026-06-01 14:35:03 +02:00
|
|
|
$c->get(HandoverRevisionService::class),
|
|
|
|
|
$c->get(HandoverNotificationService::class)
|
2026-04-15 20:42:41 +02:00
|
|
|
));
|
2026-04-16 13:21:40 +02:00
|
|
|
|
|
|
|
|
$container->set(UpdateRepository::class, static fn (): UpdateRepository => new UpdateRepository());
|
|
|
|
|
|
|
|
|
|
$container->set(UpdateService::class, static fn (AppContainer $c): UpdateService => new UpdateService(
|
|
|
|
|
$c->get(UpdateRepository::class),
|
|
|
|
|
$c->get(BcODataGateway::class)
|
|
|
|
|
));
|
2026-04-02 17:48:27 +02:00
|
|
|
}
|
|
|
|
|
}
|