177 lines
6.1 KiB
PHP
177 lines
6.1 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace MintyPHP\Module\Helpdesk\Service;
|
||
|
|
|
||
|
|
use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
|
||
|
|
use MintyPHP\Module\Helpdesk\Repository\HandoverRevisionRepository;
|
||
|
|
|
||
|
|
/** @api Called from pages/helpdesk/handovers action files */
|
||
|
|
class HandoverRevisionService
|
||
|
|
{
|
||
|
|
public const CHANGE_TYPE_INITIAL = 'initial';
|
||
|
|
public const CHANGE_TYPE_FIELDS = 'fields';
|
||
|
|
public const CHANGE_TYPE_STATUS = 'status';
|
||
|
|
public const CHANGE_TYPE_BOTH = 'both';
|
||
|
|
public const CHANGE_TYPE_RESTORE = 'restore';
|
||
|
|
|
||
|
|
public function __construct(
|
||
|
|
private readonly HandoverRevisionRepository $revisionRepository,
|
||
|
|
private readonly HandoverRepository $handoverRepository,
|
||
|
|
) {
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create a new revision snapshot.
|
||
|
|
*
|
||
|
|
* @param array<string, mixed> $fieldValues
|
||
|
|
*/
|
||
|
|
public function createRevision(
|
||
|
|
int $tenantId,
|
||
|
|
int $handoverId,
|
||
|
|
array $fieldValues,
|
||
|
|
string $status,
|
||
|
|
int $schemaVersion,
|
||
|
|
int $userId,
|
||
|
|
string $changeType = self::CHANGE_TYPE_FIELDS,
|
||
|
|
): ?int {
|
||
|
|
$nextRevision = $this->revisionRepository->getLatestRevisionNumber($tenantId, $handoverId) + 1;
|
||
|
|
|
||
|
|
$fieldValuesJson = json_encode($fieldValues, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||
|
|
|
||
|
|
return $this->revisionRepository->insert([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'handover_id' => $handoverId,
|
||
|
|
'revision' => $nextRevision,
|
||
|
|
'field_values' => $fieldValuesJson,
|
||
|
|
'status' => $status,
|
||
|
|
'schema_version' => $schemaVersion,
|
||
|
|
'change_type' => $changeType,
|
||
|
|
'changed_by' => $userId,
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* List all revisions for a handover, newest first.
|
||
|
|
*
|
||
|
|
* @return list<array<string, mixed>>
|
||
|
|
*/
|
||
|
|
public function listByHandover(int $tenantId, int $handoverId): array
|
||
|
|
{
|
||
|
|
return $this->revisionRepository->listByHandover($tenantId, $handoverId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Find a specific revision.
|
||
|
|
*
|
||
|
|
* @return array<string, mixed>|null
|
||
|
|
*/
|
||
|
|
public function findRevision(int $tenantId, int $handoverId, int $revision): ?array
|
||
|
|
{
|
||
|
|
return $this->revisionRepository->findByRevision($tenantId, $handoverId, $revision);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Compute the diff between two sets of field values.
|
||
|
|
*
|
||
|
|
* @param array<string, mixed> $oldValues
|
||
|
|
* @param array<string, mixed> $newValues
|
||
|
|
* @return array<string, array{type: string, old: mixed, new: mixed}>
|
||
|
|
*/
|
||
|
|
public function computeDiff(array $oldValues, array $newValues): array
|
||
|
|
{
|
||
|
|
$diff = [];
|
||
|
|
$allKeys = array_unique(array_merge(array_keys($oldValues), array_keys($newValues)));
|
||
|
|
|
||
|
|
foreach ($allKeys as $key) {
|
||
|
|
$oldVal = $oldValues[$key] ?? null;
|
||
|
|
$newVal = $newValues[$key] ?? null;
|
||
|
|
|
||
|
|
if ($oldVal === null && $newVal !== null) {
|
||
|
|
$diff[$key] = ['type' => 'added', 'old' => null, 'new' => $newVal];
|
||
|
|
} elseif ($oldVal !== null && $newVal === null) {
|
||
|
|
$diff[$key] = ['type' => 'removed', 'old' => $oldVal, 'new' => null];
|
||
|
|
} elseif ((string) $oldVal !== (string) $newVal) {
|
||
|
|
$diff[$key] = ['type' => 'changed', 'old' => $oldVal, 'new' => $newVal];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return $diff;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Restore a handover to a previous revision's values.
|
||
|
|
* Creates a new revision with the restored values.
|
||
|
|
*
|
||
|
|
* @return array{ok: bool, errors: array<string, string>}
|
||
|
|
*/
|
||
|
|
public function restoreRevision(
|
||
|
|
int $tenantId,
|
||
|
|
int $handoverId,
|
||
|
|
int $revisionNumber,
|
||
|
|
int $userId,
|
||
|
|
string $permissionLevel,
|
||
|
|
): array {
|
||
|
|
if ($permissionLevel !== HandoverService::PERMISSION_MANAGE) {
|
||
|
|
return ['ok' => false, 'errors' => ['general' => t('Only managers can restore revisions')]];
|
||
|
|
}
|
||
|
|
|
||
|
|
$revision = $this->revisionRepository->findByRevision($tenantId, $handoverId, $revisionNumber);
|
||
|
|
if ($revision === null) {
|
||
|
|
return ['ok' => false, 'errors' => ['general' => t('Revision not found')]];
|
||
|
|
}
|
||
|
|
|
||
|
|
$handover = $this->handoverRepository->findById($tenantId, $handoverId);
|
||
|
|
if ($handover === null) {
|
||
|
|
return ['ok' => false, 'errors' => ['general' => t('Handover not found')]];
|
||
|
|
}
|
||
|
|
|
||
|
|
$restoredValues = json_decode((string) ($revision['field_values'] ?? '{}'), true);
|
||
|
|
if (!is_array($restoredValues)) {
|
||
|
|
$restoredValues = [];
|
||
|
|
}
|
||
|
|
|
||
|
|
$restoredStatus = (string) ($revision['status'] ?? $handover['status']);
|
||
|
|
|
||
|
|
// Update the handover with restored values
|
||
|
|
$fieldValuesJson = json_encode($restoredValues, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||
|
|
$this->handoverRepository->updateFieldValues($tenantId, $handoverId, $fieldValuesJson, $userId);
|
||
|
|
|
||
|
|
$currentStatus = (string) ($handover['status'] ?? '');
|
||
|
|
if ($restoredStatus !== $currentStatus) {
|
||
|
|
$this->handoverRepository->updateStatus($tenantId, $handoverId, $restoredStatus, $userId);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Determine schema version from current handover
|
||
|
|
$schema = json_decode((string) ($handover['schema_snapshot'] ?? '{}'), true);
|
||
|
|
$schemaVersion = is_array($schema) ? (int) ($schema['version'] ?? 1) : 1;
|
||
|
|
|
||
|
|
// Create a new revision marking the restore
|
||
|
|
$this->createRevision(
|
||
|
|
$tenantId,
|
||
|
|
$handoverId,
|
||
|
|
$restoredValues,
|
||
|
|
$restoredStatus,
|
||
|
|
$schemaVersion,
|
||
|
|
$userId,
|
||
|
|
self::CHANGE_TYPE_RESTORE
|
||
|
|
);
|
||
|
|
|
||
|
|
return ['ok' => true, 'errors' => []];
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get a human-readable label for a change type.
|
||
|
|
*/
|
||
|
|
public static function changeTypeLabel(string $changeType): string
|
||
|
|
{
|
||
|
|
return match ($changeType) {
|
||
|
|
self::CHANGE_TYPE_INITIAL => t('Created'),
|
||
|
|
self::CHANGE_TYPE_FIELDS => t('Fields changed'),
|
||
|
|
self::CHANGE_TYPE_STATUS => t('Status changed'),
|
||
|
|
self::CHANGE_TYPE_BOTH => t('Fields and status changed'),
|
||
|
|
self::CHANGE_TYPE_RESTORE => t('Restored'),
|
||
|
|
default => $changeType,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|