feat(import): Mailable ImportCompletedNotification
Task 8 — email-уведомление пользователю по завершении CSV-импорта исторических лидов (ТЗ §6.6). Два исхода: done (счётчики строк) / failed (сообщение об ошибке). Blade-шаблон markdown. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\ImportLog;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
/**
|
||||
* Уведомление о завершении CSV-импорта исторических лидов (ТЗ §6.6).
|
||||
*/
|
||||
class ImportCompletedNotification extends Mailable
|
||||
{
|
||||
use Queueable;
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* @param string $outcome 'done' | 'failed'
|
||||
*/
|
||||
public function __construct(
|
||||
public ImportLog $log,
|
||||
public string $outcome,
|
||||
) {}
|
||||
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
$subject = $this->outcome === 'done'
|
||||
? 'Импорт данных завершён — Лидерра'
|
||||
: 'Импорт данных не удался — Лидерра';
|
||||
|
||||
return new Envelope(subject: $subject);
|
||||
}
|
||||
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'mail.import-completed',
|
||||
with: [
|
||||
'log' => $this->log,
|
||||
'outcome' => $this->outcome,
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1026,6 +1026,18 @@ parameters:
|
||||
count: 18
|
||||
path: tests/Feature/Import/HistoricalImportServiceTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#'
|
||||
identifier: property.notFound
|
||||
count: 3
|
||||
path: tests/Feature/Import/ImportCompletedNotificationTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$user\.$#'
|
||||
identifier: property.notFound
|
||||
count: 3
|
||||
path: tests/Feature/Import/ImportCompletedNotificationTest.php
|
||||
|
||||
-
|
||||
message: '#^Access to an undefined property Pest\\PendingCalls\\TestCall\:\:\$tenant\.$#'
|
||||
identifier: property.notFound
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<x-mail::message>
|
||||
@if ($outcome === 'done')
|
||||
# Импорт завершён
|
||||
|
||||
Импорт файла **{{ $log->filename }}** успешно завершён.
|
||||
|
||||
| Показатель | Значение |
|
||||
|:-----------|---------:|
|
||||
| Добавлено сделок | {{ $log->rows_added }} |
|
||||
| Обновлено сделок | {{ $log->rows_updated }} |
|
||||
| Пропущено строк | {{ $log->rows_skipped }} |
|
||||
| Неизвестных статусов | {{ $log->unknown_statuses_count }} |
|
||||
|
||||
@if ($log->unknown_statuses_count > 0)
|
||||
Обнаружены неизвестные статусы воронки — замапьте их вручную на экране «Импорт данных».
|
||||
@endif
|
||||
@else
|
||||
# Импорт не удался
|
||||
|
||||
Импорт файла **{{ $log->filename }}** завершился ошибкой:
|
||||
|
||||
> {{ $log->error_message }}
|
||||
|
||||
Проверьте формат файла и повторите загрузку на экране «Импорт данных».
|
||||
@endif
|
||||
|
||||
<x-mail::button :url="config('app.url').'/import'">
|
||||
Открыть «Импорт данных»
|
||||
</x-mail::button>
|
||||
|
||||
С уважением,<br>
|
||||
Лидерра
|
||||
</x-mail::message>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Mail\ImportCompletedNotification;
|
||||
use App\Models\ImportLog;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
uses(DatabaseTransactions::class);
|
||||
|
||||
beforeEach(function (): void {
|
||||
$this->tenant = Tenant::factory()->create();
|
||||
$this->user = User::factory()->for($this->tenant)->create();
|
||||
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
|
||||
});
|
||||
|
||||
test('письмо об успешном импорте содержит счётчики', function (): void {
|
||||
$log = ImportLog::factory()->create([
|
||||
'tenant_id' => $this->tenant->id,
|
||||
'user_id' => $this->user->id,
|
||||
'status' => 'done',
|
||||
'rows_added' => 120,
|
||||
'rows_updated' => 8,
|
||||
'rows_skipped' => 2,
|
||||
]);
|
||||
|
||||
$rendered = (new ImportCompletedNotification($log, 'done'))->render();
|
||||
|
||||
expect($rendered)->toContain('120')
|
||||
->and($rendered)->toContain('Импорт завершён');
|
||||
});
|
||||
|
||||
test('письмо о неуспешном импорте сообщает об ошибке', function (): void {
|
||||
$log = ImportLog::factory()->create([
|
||||
'tenant_id' => $this->tenant->id,
|
||||
'user_id' => $this->user->id,
|
||||
'status' => 'failed',
|
||||
'error_message' => 'Файл повреждён',
|
||||
]);
|
||||
|
||||
$mailable = new ImportCompletedNotification($log, 'failed');
|
||||
$rendered = $mailable->render();
|
||||
|
||||
expect($rendered)->toContain('Файл повреждён')
|
||||
->and($mailable->envelope()->subject)->toContain('не удался');
|
||||
});
|
||||
Reference in New Issue
Block a user