fc78ee1ece
Прод-инцидент 14.07.2026. Пять писем (заморозка, напоминание, финальное, разморозка, «проект остановлен — нет денег») уезжали в очередь с Eloquent-моделью Tenant. SerializesModels заменяет модель на id, а воркер грузит её заново — под ролью crm_app_user, где RLS-policy tenants_self_isolation без app.current_tenant_id отдаёт 0 строк → ModelNotFoundException. Клиент №7 заморожен с 12.07 и не получил ни одного письма; на проде это ломало письма о заморозке для ВСЕХ клиентов. Письма больше не ходят в БД при отправке: несут снимок данных (без SerializesModels). Заодно: сторож incidents:watch-failures плодил копию persistent-инцидента каждый час (строка в failed_jobs живёт вечно, а дедуп был окном в 60 мин) — 2 залипшие ошибки дали 31 запись за сутки и красную лампу «Очереди/джобы». Дедуп persistent теперь по факту незакрытого инцидента, а не по возрасту последней копии. Регрессия: BalanceMailsQueueRestoreTest (6 кейсов) + 2 теста сторожа. Прогон: 165/165 billing+incidents, phpstan 0, pint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
47 lines
1.7 KiB
PHP
47 lines
1.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Mail;
|
|
|
|
use App\Models\Tenant;
|
|
use App\Services\Billing\PreflightResult;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Mail\Mailable;
|
|
use Illuminate\Mail\Mailables\Content;
|
|
use Illuminate\Mail\Mailables\Envelope;
|
|
|
|
/**
|
|
* Письмо клиенту: приём лидов приостановлен из-за нехватки баланса (Billing v2 Spec C §3.7).
|
|
*
|
|
* Триггер: BalancePreflightSweepJob при переходе тенанта active → frozen (cut-off 18:00 MSK).
|
|
*
|
|
* 🔴 БЕЗ SerializesModels — НЕ возвращать (прод-инцидент 14.07.2026). С этим трейтом
|
|
* в очередь уезжает только id тенанта, а воркер грузит модель заново — под ролью
|
|
* crm_app_user, где RLS-policy tenants_self_isolation без app.current_tenant_id отдаёт
|
|
* 0 строк → ModelNotFoundException, письмо не уходит НИКОГДА. Без трейта письмо несёт
|
|
* снимок данных и отправляется без похода в БД. Регрессия: BalanceMailsQueueRestoreTest.
|
|
*/
|
|
final class BalanceFrozenMail extends Mailable
|
|
{
|
|
use Queueable;
|
|
|
|
public function __construct(
|
|
public readonly Tenant $tenant,
|
|
public readonly PreflightResult $result,
|
|
) {}
|
|
|
|
public function envelope(): Envelope
|
|
{
|
|
return new Envelope(
|
|
subject: 'Приём лидов приостановлен — недостаточно баланса',
|
|
to: [$this->tenant->contact_email],
|
|
);
|
|
}
|
|
|
|
public function content(): Content
|
|
{
|
|
return new Content(view: 'emails.balance_frozen');
|
|
}
|
|
}
|