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:
@@ -3,6 +3,7 @@
|
||||
namespace MintyPHP\Tests\Module\Helpdesk\Service;
|
||||
|
||||
use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
|
||||
use MintyPHP\Module\Helpdesk\Service\HandoverRevisionService;
|
||||
use MintyPHP\Module\Helpdesk\Service\HandoverService;
|
||||
use MintyPHP\Module\Helpdesk\Service\SoftwareProductService;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -17,11 +18,12 @@ class HandoverServiceTest extends TestCase
|
||||
private function createService(
|
||||
?HandoverRepository $repository = null,
|
||||
?SoftwareProductService $productService = null,
|
||||
?HandoverRevisionService $revisionService = null,
|
||||
): HandoverService {
|
||||
$repository = $repository ?? $this->createMock(HandoverRepository::class);
|
||||
$productService = $productService ?? $this->createMock(SoftwareProductService::class);
|
||||
|
||||
return new HandoverService($repository, $productService);
|
||||
return new HandoverService($repository, $productService, $revisionService);
|
||||
}
|
||||
|
||||
private function mockProductService(bool $exists = true, bool $hasSchema = true): SoftwareProductService
|
||||
@@ -193,4 +195,99 @@ class HandoverServiceTest extends TestCase
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
|
||||
// ── Revision integration tests ──────────────────────────────────
|
||||
|
||||
public function testCreateCreatesInitialRevision(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
$repo->expects($this->once())->method('insert')->willReturn(99);
|
||||
|
||||
$revisionService = $this->createMock(HandoverRevisionService::class);
|
||||
$revisionService->expects($this->once())
|
||||
->method('createRevision')
|
||||
->with(
|
||||
self::TENANT_ID,
|
||||
99,
|
||||
[],
|
||||
HandoverService::STATUS_DRAFT,
|
||||
1,
|
||||
self::USER_ID,
|
||||
HandoverRevisionService::CHANGE_TYPE_INITIAL
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, $this->mockProductService(), $revisionService);
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', self::USER_ID);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
|
||||
public function testCreateRevisionAfterSaveDeterminesChangeType(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
|
||||
$revisionService = $this->createMock(HandoverRevisionService::class);
|
||||
$revisionService->expects($this->once())
|
||||
->method('createRevision')
|
||||
->with(
|
||||
self::TENANT_ID,
|
||||
1,
|
||||
['name' => 'Test'],
|
||||
'in_progress',
|
||||
1,
|
||||
self::USER_ID,
|
||||
HandoverRevisionService::CHANGE_TYPE_BOTH
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'in_progress', 'draft', 1, self::USER_ID);
|
||||
}
|
||||
|
||||
public function testCreateRevisionAfterSaveFieldsOnlyChangeType(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
|
||||
$revisionService = $this->createMock(HandoverRevisionService::class);
|
||||
$revisionService->expects($this->once())
|
||||
->method('createRevision')
|
||||
->with(
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
$this->anything(),
|
||||
HandoverRevisionService::CHANGE_TYPE_FIELDS
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'draft', 'draft', 1, self::USER_ID);
|
||||
}
|
||||
|
||||
public function testCreateRevisionForStatusChange(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
$repo->method('findById')->willReturn([
|
||||
'id' => 1,
|
||||
'status' => 'in_progress',
|
||||
'field_values' => '{"name":"Test"}',
|
||||
'schema_snapshot' => self::VALID_SCHEMA_JSON,
|
||||
]);
|
||||
|
||||
$revisionService = $this->createMock(HandoverRevisionService::class);
|
||||
$revisionService->expects($this->once())
|
||||
->method('createRevision')
|
||||
->with(
|
||||
self::TENANT_ID,
|
||||
1,
|
||||
['name' => 'Test'],
|
||||
'completed',
|
||||
1,
|
||||
self::USER_ID,
|
||||
HandoverRevisionService::CHANGE_TYPE_STATUS
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionForStatusChange(self::TENANT_ID, 1, 'completed', 1, self::USER_ID);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user