forked from fa/breadcrumb-the-shire
feat(helpdesk): add domain linking, bulk actions, and revision polish to handovers
Adds domain selection (cascaded from debitor) to handover creation and edit, bulk delete with confirmation dialog, and various UI improvements to the handover wizard and list page. Includes migration for domain columns, domain-select AJAX endpoint, and updated tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,7 +53,7 @@ class HandoverServiceTest extends TestCase
|
||||
$repo->expects($this->once())->method('insert')->willReturn(99);
|
||||
|
||||
$service = $this->createService($repo, $this->mockProductService());
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', self::USER_ID);
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', 'DNS00001', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertSame(99, $result['id']);
|
||||
@@ -63,7 +63,7 @@ class HandoverServiceTest extends TestCase
|
||||
public function testCreateWithInvalidProductCode(): void
|
||||
{
|
||||
$service = $this->createService(productService: $this->mockProductService(exists: false));
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'INVALID', self::USER_ID);
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'INVALID', 'DNS00001', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
$this->assertNull($result['id']);
|
||||
@@ -73,7 +73,7 @@ class HandoverServiceTest extends TestCase
|
||||
public function testCreateWithProductWithoutSchema(): void
|
||||
{
|
||||
$service = $this->createService(productService: $this->mockProductService(hasSchema: false));
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', self::USER_ID);
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', 'DNS00001', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
$this->assertNull($result['id']);
|
||||
@@ -83,7 +83,7 @@ class HandoverServiceTest extends TestCase
|
||||
public function testCreateWithEmptyDebitorNo(): void
|
||||
{
|
||||
$service = $this->createService();
|
||||
$result = $service->create(self::TENANT_ID, '', 'Acme Corp', 'PROD-A', self::USER_ID);
|
||||
$result = $service->create(self::TENANT_ID, '', 'Acme Corp', 'PROD-A', 'DNS00001', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
$this->assertArrayHasKey('debitor_no', $result['errors']);
|
||||
@@ -217,12 +217,38 @@ class HandoverServiceTest extends TestCase
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, $this->mockProductService(), $revisionService);
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', self::USER_ID);
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', 'DNS00001', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
|
||||
public function testCreateRevisionAfterSaveDeterminesChangeType(): void
|
||||
public function testCreateWithEmptyDomainNo(): void
|
||||
{
|
||||
$service = $this->createService(productService: $this->mockProductService());
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', '', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
$this->assertArrayHasKey('domain_no', $result['errors']);
|
||||
}
|
||||
|
||||
public function testCreatePassesDomainToRepository(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
$repo->expects($this->once())
|
||||
->method('insert')
|
||||
->with($this->callback(function (array $data): bool {
|
||||
return ($data['domain_no'] ?? '') === 'DNS00001'
|
||||
&& ($data['domain_url'] ?? '') === 'example.com';
|
||||
}))
|
||||
->willReturn(99);
|
||||
|
||||
$service = $this->createService($repo, $this->mockProductService());
|
||||
$result = $service->create(self::TENANT_ID, 'D10001', 'Acme Corp', 'PROD-A', 'DNS00001', 'example.com', self::USER_ID);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
}
|
||||
|
||||
public function testCreateRevisionAfterSaveSetsBothWhenFieldsAndStatusChanged(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
|
||||
@@ -240,10 +266,10 @@ class HandoverServiceTest extends TestCase
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'in_progress', 'draft', 1, self::USER_ID);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'in_progress', true, true, 1, self::USER_ID);
|
||||
}
|
||||
|
||||
public function testCreateRevisionAfterSaveFieldsOnlyChangeType(): void
|
||||
public function testCreateRevisionAfterSaveSetsFieldsWhenOnlyFieldsChanged(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
|
||||
@@ -261,18 +287,12 @@ class HandoverServiceTest extends TestCase
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'draft', 'draft', 1, self::USER_ID);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'draft', true, false, 1, self::USER_ID);
|
||||
}
|
||||
|
||||
public function testCreateRevisionForStatusChange(): void
|
||||
public function testCreateRevisionAfterSaveSetsStatusWhenOnlyStatusChanged(): 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())
|
||||
@@ -288,6 +308,67 @@ class HandoverServiceTest extends TestCase
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionForStatusChange(self::TENANT_ID, 1, 'completed', 1, self::USER_ID);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'completed', false, true, 1, self::USER_ID);
|
||||
}
|
||||
|
||||
public function testCreateRevisionAfterSaveDefaultsToFieldsWhenNoContentChanged(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
|
||||
$revisionService = $this->createMock(HandoverRevisionService::class);
|
||||
$revisionService->expects($this->once())
|
||||
->method('createRevision')
|
||||
->with(
|
||||
self::TENANT_ID,
|
||||
1,
|
||||
['name' => 'Test'],
|
||||
'draft',
|
||||
1,
|
||||
self::USER_ID,
|
||||
HandoverRevisionService::CHANGE_TYPE_FIELDS
|
||||
);
|
||||
|
||||
$service = $this->createService($repo, null, $revisionService);
|
||||
$service->createRevisionAfterSave(self::TENANT_ID, 1, ['name' => 'Test'], 'draft', false, false, 1, self::USER_ID);
|
||||
}
|
||||
|
||||
// ── Delete tests ─────────────────────────────────────────────────
|
||||
|
||||
public function testDeleteByIdsHappyPath(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
$repo->expects($this->once())
|
||||
->method('deleteByIds')
|
||||
->with(self::TENANT_ID, [1, 2, 3])
|
||||
->willReturn(3);
|
||||
|
||||
$service = $this->createService($repo);
|
||||
$result = $service->deleteByIds(self::TENANT_ID, [1, 2, 3], HandoverService::PERMISSION_MANAGE);
|
||||
|
||||
$this->assertTrue($result['ok']);
|
||||
$this->assertSame(3, $result['count']);
|
||||
}
|
||||
|
||||
public function testDeleteByIdsDeniedForCreatePermission(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
$repo->expects($this->never())->method('deleteByIds');
|
||||
|
||||
$service = $this->createService($repo);
|
||||
$result = $service->deleteByIds(self::TENANT_ID, [1], HandoverService::PERMISSION_CREATE);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
$this->assertSame(0, $result['count']);
|
||||
}
|
||||
|
||||
public function testDeleteByIdsWithEmptyArray(): void
|
||||
{
|
||||
$repo = $this->createMock(HandoverRepository::class);
|
||||
$repo->expects($this->never())->method('deleteByIds');
|
||||
|
||||
$service = $this->createService($repo);
|
||||
$result = $service->deleteByIds(self::TENANT_ID, [], HandoverService::PERMISSION_MANAGE);
|
||||
|
||||
$this->assertFalse($result['ok']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user