Files
minicrm/tests/Feature/TransferFlowTest.php
phuongtc 887765bbd7 feat: AfterSales CRM - SOP compliant real-estate customer care system
- Departments + cross-department transfer workflow
- Role-based closing (staff→resolved, manager→closed) with proof_of_resolution
- Private file storage with collection system + temporary signed URLs
- Dashboard: resolved tickets table, stats, handler performance bar chart
- Spatie RBAC (admin/manager/staff)
- Notifications: TicketTransferred, TicketClosed (database + mail)
- Pest tests: 8 tests, 21 assertions
- Docker production-ready (Dockerfile + compose + entrypoint)
2026-04-27 05:29:48 +00:00

109 lines
3.9 KiB
PHP

<?php
use App\Models\Customer;
use App\Models\Department;
use App\Models\Feedback;
use App\Models\FeedbackChannel;
use App\Models\TicketTransferLog;
use App\Models\User;
use App\Services\TransferService;
uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);
// ─── Scenario 3: Transfer Flow ──────────────────────────────────────
test('transfer creates log, updates department, and notifies manager', function () {
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
\Illuminate\Support\Facades\Notification::fake();
$sender = User::factory()->create();
$sender->assignRole('manager');
$deptManager = User::factory()->create(['name' => 'Tech Manager']);
$deptManager->assignRole('manager');
$fromDept = Department::create(['name' => 'CSKH', 'manager_id' => null]);
$toDept = Department::create(['name' => 'Ky Thuat', 'manager_id' => $deptManager->id]);
$channel = FeedbackChannel::create(['name' => 'Email', 'slug' => 'email-transfer']);
$customer = Customer::create(['name' => 'Test Transfer', 'email' => 'transfer@test.com']);
$feedback = Feedback::create([
'customer_id' => $customer->id,
'feedback_channel_id' => $channel->id,
'title' => 'Transfer Test Ticket',
'content' => 'Test content',
'status' => 'pending',
'current_department_id' => $fromDept->id,
'assigned_to' => $sender->id,
]);
$this->actingAs($sender);
$transferService = app(TransferService::class);
$log = $transferService->transfer(
$feedback,
$toDept,
'Can chuyen sang phong ky thuat de kiem tra',
$sender,
);
// Assert log created
expect($log)->toBeInstanceOf(TicketTransferLog::class);
expect($log->from_department_id)->toBe($fromDept->id);
expect($log->to_department_id)->toBe($toDept->id);
expect($log->sender_id)->toBe($sender->id);
expect($log->reason)->toBe('Can chuyen sang phong ky thuat de kiem tra');
// Assert department updated
$feedback->refresh();
expect($feedback->current_department_id)->toBe($toDept->id);
expect($feedback->assignedTo->id)->toBe($deptManager->id);
// Assert notification sent to target department manager
\Illuminate\Support\Facades\Notification::assertSentTo(
$deptManager,
\App\Notifications\TicketTransferred::class,
function ($notification) use ($feedback, $sender) {
return $notification->feedback->id === $feedback->id
&& $notification->sender->id === $sender->id;
}
);
});
// ─── Transfer without reason should fail ────────────────────────────
test('transfer without reason throws validation error', function () {
\Spatie\Permission\Models\Role::create(['name' => 'manager']);
$sender = User::factory()->create();
$sender->assignRole('manager');
$fromDept = Department::create(['name' => 'CSKH']);
$toDept = Department::create(['name' => 'Ky Thuat']);
$channel = FeedbackChannel::create(['name' => 'Zalo', 'slug' => 'zalo-transfer']);
$customer = Customer::create(['name' => 'Test', 'email' => 'fail@test.com']);
$feedback = Feedback::create([
'customer_id' => $customer->id,
'feedback_channel_id' => $channel->id,
'title' => 'Fail Transfer',
'content' => 'Test',
'status' => 'pending',
'current_department_id' => $fromDept->id,
]);
$this->actingAs($sender);
$transferService = app(TransferService::class);
try {
$transferService->transfer($feedback, $toDept, ' ', $sender);
$this->fail('Expected ValidationException but none thrown.');
} catch (\Illuminate\Validation\ValidationException $e) {
expect($e->errors()['reason'][0])->toContain('Vui lòng nhập lý do chuyển tiếp');
}
expect(TicketTransferLog::count())->toBe(0);
});