6d70664904
Перенос fc78ee1e из gitea/main в ветку стройки, чтобы выкат отсюда не вернул
сломанные письма обратно на боевой (класс ошибки 08.07 — «затёрло выкатом из
устаревшей ветки»).
Суть: 5 писем (заморозка / напоминание / финальное / разморозка / «проект
остановлен — нет денег») уезжали в очередь с моделью Tenant; воркер грузил её
заново под crm_app_user, где RLS без app.current_tenant_id отдаёт 0 строк →
ModelNotFoundException, письмо не уходило никогда. Теперь письмо несёт снимок
данных и в БД при отправке не ходит.
Плюс дедуп persistent-инцидентов сторожа — по факту незакрытого инцидента,
а не по окну 60 мин (иначе копия инцидента каждый час, бесконечно).
NB: LEFTHOOK_EXCLUDE=larastan — статанализ в рабочей копии сломан устаревшим
_ide_helper_models.php (239 ошибок в 104 ЧУЖИХ файлах, напр. фантом
«Tenant::requiredLeadsForTomorrow() не существует», хотя метод есть в
Tenant.php:93). По изменённым файлам статанализ прогнан отдельно: 0 ошибок.
Bypass согласован с владельцем. Follow-up: перегенерировать стаб (--write-mixin).
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');
|
|
}
|
|
}
|