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>
This commit is contained in:
@@ -45,6 +45,7 @@ class HandoverService
|
||||
public function __construct(
|
||||
private readonly HandoverRepository $repository,
|
||||
private readonly SoftwareProductService $softwareProductService,
|
||||
private readonly ?HandoverRevisionService $revisionService = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -122,6 +123,17 @@ class HandoverService
|
||||
return ['ok' => false, 'id' => null, 'errors' => ['general' => t('Failed to create handover')]];
|
||||
}
|
||||
|
||||
// Create initial revision (revision 1)
|
||||
$this->revisionService?->createRevision(
|
||||
$tenantId,
|
||||
$id,
|
||||
$fieldValues,
|
||||
self::STATUS_DRAFT,
|
||||
(int) ($schema['version'] ?? 1),
|
||||
$userId,
|
||||
HandoverRevisionService::CHANGE_TYPE_INITIAL
|
||||
);
|
||||
|
||||
return ['ok' => true, 'id' => $id, 'errors' => []];
|
||||
}
|
||||
|
||||
@@ -199,6 +211,76 @@ class HandoverService
|
||||
return ['ok' => true, 'errors' => []];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a revision snapshot after a combined save (fields + optional status change).
|
||||
* Call this from the action after both updateFields and changeStatus have succeeded.
|
||||
*
|
||||
* @param array<string, mixed> $fieldValues The final field values
|
||||
*/
|
||||
public function createRevisionAfterSave(
|
||||
int $tenantId,
|
||||
int $handoverId,
|
||||
array $fieldValues,
|
||||
string $status,
|
||||
string $previousStatus,
|
||||
int $schemaVersion,
|
||||
int $userId,
|
||||
): void {
|
||||
if ($this->revisionService === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$statusChanged = $status !== $previousStatus;
|
||||
$changeType = $statusChanged
|
||||
? HandoverRevisionService::CHANGE_TYPE_BOTH
|
||||
: HandoverRevisionService::CHANGE_TYPE_FIELDS;
|
||||
|
||||
$this->revisionService->createRevision(
|
||||
$tenantId,
|
||||
$handoverId,
|
||||
$fieldValues,
|
||||
$status,
|
||||
$schemaVersion,
|
||||
$userId,
|
||||
$changeType
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a revision for a status-only change.
|
||||
*/
|
||||
public function createRevisionForStatusChange(
|
||||
int $tenantId,
|
||||
int $handoverId,
|
||||
string $newStatus,
|
||||
int $schemaVersion,
|
||||
int $userId,
|
||||
): void {
|
||||
if ($this->revisionService === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$handover = $this->repository->findById($tenantId, $handoverId);
|
||||
if ($handover === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fieldValues = json_decode((string) ($handover['field_values'] ?? '{}'), true);
|
||||
if (!is_array($fieldValues)) {
|
||||
$fieldValues = [];
|
||||
}
|
||||
|
||||
$this->revisionService->createRevision(
|
||||
$tenantId,
|
||||
$handoverId,
|
||||
$fieldValues,
|
||||
$newStatus,
|
||||
$schemaVersion,
|
||||
$userId,
|
||||
HandoverRevisionService::CHANGE_TYPE_STATUS
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{total: int, rows: list<array<string, mixed>>}
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user